React native 放大像素艺术,无需消除混叠

React native 放大像素艺术,无需消除混叠,react-native,React Native,我所有的图像都是像素艺术图像。我想在保持纵横比的同时,在不进行任何抗锯齿的情况下对其进行缩放。目前,我可以保持纵横比,但缩放是抗锯齿的,因此图像模糊 这是一张图片: 以下是我当前代码的外观: <Image key={attr_name} source={{uri:attr_value}} resizeMode="contain" style={{ flex:1 }} resizeMethod="resize" />; 编辑添加这是我在stackoverflow上发现的一个很棒的主

我所有的图像都是像素艺术图像。我想在保持纵横比的同时,在不进行任何抗锯齿的情况下对其进行缩放。目前,我可以保持纵横比,但缩放是抗锯齿的,因此图像模糊

这是一张图片:

以下是我当前代码的外观:

<Image key={attr_name} source={{uri:attr_value}} resizeMode="contain" style={{ flex:1 }} resizeMethod="resize" />;

编辑添加这是我在stackoverflow上发现的一个很棒的主题,但它是针对web的-

我为自己找到了一个解决方案,我不喜欢它,但现在它只是权宜之计

如果您有解决方案,请与我们分享

我要做的是使用
WebView
。我最初的目标是做canvas.toDataURL,但它似乎不起作用,似乎iOS safari上没有完全的支持。因此,我正在使用透明背景色调整
网络视图的大小。在html中,我使用原始帖子中的样式表:

import React, { Component } from 'react'
import { Image, Text, View, WebView } from 'react-native'

const html = `
    <html>
        <head>
            <style>
                body {
                    margin: 0;
                }
                img {
                    image-rendering: optimizeSpeed;
                    image-rendering: -moz-crisp-edges;
                    image-rendering: -o-crisp-edges;
                    image-rendering: -webkit-optimize-contrast;
                    image-rendering: optimize-contrast;
                    -ms-interpolation-mode: nearest-neighbor;
                    image-rendering: pixelated;
                }
            </style>
            <script>
                function whenRNPostMessageReady(cb) {
                    if (postMessage.length === 1) cb();
                    else setTimeout(function() { whenRNPostMessageReady(cb) }, 1000);
                }

                function resizePixelated() {
                    var url = '%%%URL%%%';

                    var img = document.createElement('img');
                    document.body.appendChild(img);
                    img.addEventListener('load', handleImageLoad, false);
                    img.addEventListener('error', handleImageError, false);
                    img.setAttribute('id', 'image');
                    img.setAttribute('src', url);
                }

                function handleImageLoad(e) {
                    if (this.naturalHeight + this.naturalWidth === 0) {
                        this.onerror();
                        return;
                    }

                    var WANTED_HEIGHT = %%%HEIGHT%%%;
                    var WANTED_WIDTH = %%%WIDTH%%%;

                    var naturalHeight = this.naturalHeight;
                    var naturalWidth = this.naturalWidth;

                    postMessage('LOG:' + 'naturalHeight: ' + naturalHeight + ' naturalWidth: ' + naturalWidth);
                    postMessage('LOG:' + 'WANTED_HEIGHT: ' + WANTED_HEIGHT + ' WANTED_WIDTH: ' + WANTED_WIDTH);

                    var factorHeight = WANTED_HEIGHT / naturalHeight;
                    var factorWidth = WANTED_WIDTH / naturalWidth;

                    postMessage('LOG:' + 'factorHeight: ' + factorHeight + ' factorWidth: ' + factorWidth);

                    var byWidthHeight = naturalHeight * factorWidth;
                    var byHeightWidth = naturalWidth * factorHeight;
                    postMessage('LOG:' + 'byWidthHeight: ' + byWidthHeight + ' byHeightWidth: ' + byHeightWidth);

                    var sortable = [
                        { sorter:byWidthHeight, variable:'height', height:byWidthHeight, width:WANTED_WIDTH },
                        { sorter:byHeightWidth, variable:'width',  height:WANTED_HEIGHT, width:byHeightWidth }
                    ];

                    sortable.sort(function byDescSorter(a, b) {
                        return b.sorter - a.sorter;
                    });

                    postMessage('LOG:' + JSON.stringify(sortable));

                    for (var i=0; i<sortable.length; i++) {
                        var variable = sortable[i].variable;
                        var sorter = sortable[i].sorter;
                        if (variable == 'height') {
                            if (sorter <= WANTED_HEIGHT) {
                                break;
                            }
                        } else if (variable == 'width') {
                            if (sorter <= WANTED_WIDTH) {
                                break;
                            }
                        }
                    }

                    if (i >= sortable.length) {
                        postMessage('LOG: THIS SHOULD NEVER HAPPEN');
                    }

                    postMessage('LOG:' + i);

                    var drawWidth = Math.round(sortable[i].width);
                    var drawHeight = Math.round(sortable[i].height);

                    postMessage('LOG:will draw now at width: ' + drawWidth + ' drawHeight: ' + drawHeight);

                    var img = document.getElementById('image');
                    img.setAttribute('width', drawWidth);
                    img.setAttribute('height', drawHeight);

                    var dataurl = '';

                    postMessage('OK:' + drawWidth + '$' + drawHeight + '$' + dataurl);
                }

                function handleImageError() {
                    postMessage('Image failed to load.');
                }

                window.addEventListener('DOMContentLoaded', function() {
                    whenRNPostMessageReady(resizePixelated);
                }, false);
            </script>
        </head>
        <body></body>
    </html>
`;

const STATUS = {
    INIT: 'INIT',
    FAIL: 'FAIL',
    SUCCESS: 'SUCCESS'
}

class ImagePixelated extends Component {
    /* props
    url: dataURL or web url
    height?: number or undefined - set either height or width or both, but one must be set
    width?: number or undefined
    */
    state = {
        status: STATUS.INIT,
        reason: null, // set on STATUS.FAIL
        dataurl: null, // set on STATUS.SUCCESS
        height: null, // set on STATUS.SUCCESS
        width: null // set on STATUS.SUCCESS
    }
    handleMessage = e => {
        const {nativeEvent:{ data }} = e;

        const [action, payload] = data.split(/\:(.+)/); // split on first instance of colon
        // console.log('action:', action, 'payload:', payload);

        switch (action) {
            case 'LOG': {
                    // console.log(payload);
                break;
            }
            case 'OK': {
                    let [ width, height, dataurl ] = data.substr('OK:'.length).split('$');
                    width = parseInt(width);
                    height = parseInt(height);
                    console.log('width:', width, 'height:', height, 'dataurl:', dataurl);
                    this.setState(()=>({status:STATUS.SUCCESS, dataurl, height, width}));
                break;
            }
            default:
                // FAILED // TODO:
                this.setState(()=>({status:STATUS.FAIL, reason:data}));
        }
    }
    getHtml() {
        const { height, width, url } = this.props;
        let html_propified = html.replace('%%%URL%%%', url);

        // because my scaling in WebView is to get max height while maintaining aspect ratio, if one (height or width) is not specificed, instead of setting to undefined, set the other to 1000

        if (isNaN(height) || height === undefined || height === null) html_propified = html_propified.replace('%%%HEIGHT%%%', '1000');
        else html_propified = html_propified.replace('%%%HEIGHT%%%', height);

        if (isNaN(width) || width === undefined || width === null) html_propified = html_propified.replace('%%%WIDTH%%%', '1000');
        else html_propified = html_propified.replace('%%%WIDTH%%%', width);

        return html_propified;
    }
    render() {
        const { status } = this.state;
        switch (status) {
            case STATUS.INIT: {
                const { height, width } = this.state;
                // android: transparent the background in webview here too, because when switch to success, where display is not none, we see a flash of white
                // android: the wrap of view is needed because WebView does not respect height as its a RN bug
                return (
                    <View style={{ display:'none' }}>
                        <WebView source={{ html:this.getHtml() }} style={{ display:'none', backgroundColor:'transparent' }} onMessage={this.handleMessage} />
                    </View>
                )
            }
            case STATUS.FAIL: {
                const { reason } = this.state;
                return (
                    <View>
                        <Text>{reason}</Text>
                    </View>
                )
            }
            case STATUS.SUCCESS: {
                // const { dataurl, height, width } = this.state;
                // return <Image source={{ uri:dataurl, height, width }}  />
                const { height, width } = this.state;
                return (
                    <View style={{ height, width }}>
                        <WebView source={{ html:this.getHtml() }} style={{ height, width, backgroundColor:'transparent' }} />
                    </View>
                )
            }
            // no-default
        }
    }
}

export default ImagePixelated
import React,{Component}来自“React”
从“react native”导入{Image,Text,View,WebView}
常量html=`
身体{
保证金:0;
}
img{
图像渲染:优化速度;
图像渲染:-moz清晰的边缘;
图像渲染:-o-脆边;
图像渲染:-webkit优化对比度;
图像渲染:优化对比度;
-ms插值方式:最近邻;
图像渲染:像素化;
}
NpPostMessageReady(cb)时的功能{
if(postMessage.length==1)cb();
else setTimeout(函数(){whennpostmessagerady(cb)},1000);
}
函数resizePixelated(){
变量url='%%%url%%';
var img=document.createElement('img');
文件.正文.附件(img);
img.addEventListener('load',handleImageLoad,false);
img.addEventListener('error',handleImageError,false);
img.setAttribute('id','image');
setAttribute('src',url);
}
函数handleImageLoad(e){
如果(this.naturalHeight+this.naturalWidth==0){
这个.onerror();
返回;
}
var\u高度=%%高度%%;
变量宽度=%%%WIDTH%%;
var naturalHeight=this.naturalHeight;
var naturalWidth=this.naturalWidth;
postMessage('LOG:'+'自然高度:'+naturalHeight+'naturalWidth:'+naturalWidth');
postMessage('LOG:'+'通缉高度:'+通缉高度+'通缉宽度:'+通缉宽度]);
var factorHeight=所需高度/自然高度;
var factorWidth=所需宽度/自然宽度;
postMessage('LOG:'+'factorHeight:'+factorHeight+'factorWidth:'+factorWidth);
var byWidthHeight=自然高度*系数宽度;
var byHeightWidth=自然宽度*因子高度;
postMessage('LOG:'+'byWidthHeight:'+byWidthHeight+'byHeightWidth:'+byHeightWidth));
变量可排序=[
{分类器:byWidthHeight,变量:'height',height:byWidthHeight,width:WANTED\u width},
{分类器:byHeightWidth,变量:'width',height:WANTED\u height,width:byHeightWidth}
];
sortable.sort(功能由Descsorter(a,b)实现){
返回b.分拣机-a.分拣机;
});
postMessage('LOG:'+JSON.stringify(可排序));
for(var i=0;i({status:status.SUCCESS,dataurl,height,width}));
打破
}
违约:
//失败//待办事项:
this.setState(()=>({status:status.FAIL,reason:data}));
}
}
getHtml(){
const{height,width,url}=this.props;
让html_propified=html.replace('%%URL%%',URL);
//因为我在WebView中的缩放是为了在保持纵横比的同时获得最大高度,所以如果其中一个(高度或宽度)未指定,而不是设置为未定义,则将另一个设置为1000
如果(isNaN(高度)| |高度===未定义的| |高度===空)html|profited=html|profited.replace('%%height%%','1000');
else html_-propified=html_-propified.replace('%%HEIGHT%%',HEIGHT);
如果(isNaN(宽度)| |宽度===未定义的| |宽度===空)html|profited=html|profited.replace('%%width%%','1000');
else html_-propified=html_-propified.replace('%%WIDTH%%',WIDTH);
返回已批准的html;
}
render(){
const{status}=this.state;
开关(状态){
case STATUS.INIT:{
const{height,width}=this.state;
//android:这里的webview背景也是透明的,因为当切换到success时,我们看到一道白色的闪光
//android:需要视图的包装,因为WebView不将高度视为一个错误
返回(
)
}
案例状态。失败:{
const{reason}=this.state;
返回(
{原因}
)
}
案例状态。成功:{
//const{dataurl,height,width}=this.state;
//返回
const{height,width}=this.state;
返回(
import React, { Component } from 'react'
import { Image, Text, View, WebView } from 'react-native'

const html = `
    <html>
        <head>
            <style>
                body {
                    margin: 0;
                }
                img {
                    image-rendering: optimizeSpeed;
                    image-rendering: -moz-crisp-edges;
                    image-rendering: -o-crisp-edges;
                    image-rendering: -webkit-optimize-contrast;
                    image-rendering: optimize-contrast;
                    -ms-interpolation-mode: nearest-neighbor;
                    image-rendering: pixelated;
                }
            </style>
            <script>
                function whenRNPostMessageReady(cb) {
                    if (postMessage.length === 1) cb();
                    else setTimeout(function() { whenRNPostMessageReady(cb) }, 1000);
                }

                function resizePixelated() {
                    var url = '%%%URL%%%';

                    var img = document.createElement('img');
                    document.body.appendChild(img);
                    img.addEventListener('load', handleImageLoad, false);
                    img.addEventListener('error', handleImageError, false);
                    img.setAttribute('id', 'image');
                    img.setAttribute('src', url);
                }

                function handleImageLoad(e) {
                    if (this.naturalHeight + this.naturalWidth === 0) {
                        this.onerror();
                        return;
                    }

                    var WANTED_HEIGHT = %%%HEIGHT%%%;
                    var WANTED_WIDTH = %%%WIDTH%%%;

                    var naturalHeight = this.naturalHeight;
                    var naturalWidth = this.naturalWidth;

                    postMessage('LOG:' + 'naturalHeight: ' + naturalHeight + ' naturalWidth: ' + naturalWidth);
                    postMessage('LOG:' + 'WANTED_HEIGHT: ' + WANTED_HEIGHT + ' WANTED_WIDTH: ' + WANTED_WIDTH);

                    var factorHeight = WANTED_HEIGHT / naturalHeight;
                    var factorWidth = WANTED_WIDTH / naturalWidth;

                    postMessage('LOG:' + 'factorHeight: ' + factorHeight + ' factorWidth: ' + factorWidth);

                    var byWidthHeight = naturalHeight * factorWidth;
                    var byHeightWidth = naturalWidth * factorHeight;
                    postMessage('LOG:' + 'byWidthHeight: ' + byWidthHeight + ' byHeightWidth: ' + byHeightWidth);

                    var sortable = [
                        { sorter:byWidthHeight, variable:'height', height:byWidthHeight, width:WANTED_WIDTH },
                        { sorter:byHeightWidth, variable:'width',  height:WANTED_HEIGHT, width:byHeightWidth }
                    ];

                    sortable.sort(function byDescSorter(a, b) {
                        return b.sorter - a.sorter;
                    });

                    postMessage('LOG:' + JSON.stringify(sortable));

                    for (var i=0; i<sortable.length; i++) {
                        var variable = sortable[i].variable;
                        var sorter = sortable[i].sorter;
                        if (variable == 'height') {
                            if (sorter <= WANTED_HEIGHT) {
                                break;
                            }
                        } else if (variable == 'width') {
                            if (sorter <= WANTED_WIDTH) {
                                break;
                            }
                        }
                    }

                    if (i >= sortable.length) {
                        postMessage('LOG: THIS SHOULD NEVER HAPPEN');
                    }

                    postMessage('LOG:' + i);

                    var drawWidth = Math.round(sortable[i].width);
                    var drawHeight = Math.round(sortable[i].height);

                    postMessage('LOG:will draw now at width: ' + drawWidth + ' drawHeight: ' + drawHeight);

                    var img = document.getElementById('image');
                    img.setAttribute('width', drawWidth);
                    img.setAttribute('height', drawHeight);

                    var dataurl = '';

                    postMessage('OK:' + drawWidth + '$' + drawHeight + '$' + dataurl);
                }

                function handleImageError() {
                    postMessage('Image failed to load.');
                }

                window.addEventListener('DOMContentLoaded', function() {
                    whenRNPostMessageReady(resizePixelated);
                }, false);
            </script>
        </head>
        <body></body>
    </html>
`;

const STATUS = {
    INIT: 'INIT',
    FAIL: 'FAIL',
    SUCCESS: 'SUCCESS'
}

class ImagePixelated extends Component {
    /* props
    url: dataURL or web url
    height?: number or undefined - set either height or width or both, but one must be set
    width?: number or undefined
    */
    state = {
        status: STATUS.INIT,
        reason: null, // set on STATUS.FAIL
        dataurl: null, // set on STATUS.SUCCESS
        height: null, // set on STATUS.SUCCESS
        width: null // set on STATUS.SUCCESS
    }
    handleMessage = e => {
        const {nativeEvent:{ data }} = e;

        const [action, payload] = data.split(/\:(.+)/); // split on first instance of colon
        // console.log('action:', action, 'payload:', payload);

        switch (action) {
            case 'LOG': {
                    // console.log(payload);
                break;
            }
            case 'OK': {
                    let [ width, height, dataurl ] = data.substr('OK:'.length).split('$');
                    width = parseInt(width);
                    height = parseInt(height);
                    console.log('width:', width, 'height:', height, 'dataurl:', dataurl);
                    this.setState(()=>({status:STATUS.SUCCESS, dataurl, height, width}));
                break;
            }
            default:
                // FAILED // TODO:
                this.setState(()=>({status:STATUS.FAIL, reason:data}));
        }
    }
    getHtml() {
        const { height, width, url } = this.props;
        let html_propified = html.replace('%%%URL%%%', url);

        // because my scaling in WebView is to get max height while maintaining aspect ratio, if one (height or width) is not specificed, instead of setting to undefined, set the other to 1000

        if (isNaN(height) || height === undefined || height === null) html_propified = html_propified.replace('%%%HEIGHT%%%', '1000');
        else html_propified = html_propified.replace('%%%HEIGHT%%%', height);

        if (isNaN(width) || width === undefined || width === null) html_propified = html_propified.replace('%%%WIDTH%%%', '1000');
        else html_propified = html_propified.replace('%%%WIDTH%%%', width);

        return html_propified;
    }
    render() {
        const { status } = this.state;
        switch (status) {
            case STATUS.INIT: {
                const { height, width } = this.state;
                // android: transparent the background in webview here too, because when switch to success, where display is not none, we see a flash of white
                // android: the wrap of view is needed because WebView does not respect height as its a RN bug
                return (
                    <View style={{ display:'none' }}>
                        <WebView source={{ html:this.getHtml() }} style={{ display:'none', backgroundColor:'transparent' }} onMessage={this.handleMessage} />
                    </View>
                )
            }
            case STATUS.FAIL: {
                const { reason } = this.state;
                return (
                    <View>
                        <Text>{reason}</Text>
                    </View>
                )
            }
            case STATUS.SUCCESS: {
                // const { dataurl, height, width } = this.state;
                // return <Image source={{ uri:dataurl, height, width }}  />
                const { height, width } = this.state;
                return (
                    <View style={{ height, width }}>
                        <WebView source={{ html:this.getHtml() }} style={{ height, width, backgroundColor:'transparent' }} />
                    </View>
                )
            }
            // no-default
        }
    }
}

export default ImagePixelated
<ImagePixelated url={entity.image} height={90} width={90} />
<Image key={attr_name} source={{uri:attr_value}} resizeMode="contain" style={{ flex:1 }} resizeMethod="resize" />;
<Svg><Image key={attr_name} href={{uri:attr_value}} height={...} width={...} /></Svg>