无需抗锯齿即可放大像素艺术

问题描述 投票:0回答:4

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

这是一张图片:

这是我当前代码的样子:

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

这是我的 iOS 模拟器中的屏幕截图:https://i.stack.imgur.com/7dnzy.png

在网络上,我们使用 CSS 来实现这一点:

.pixelated-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;
}

编辑添加这是我在 stackoverflow 上发现的一个很棒的主题,但它是针对网络的 - 缩放时禁用插值

react-native
4个回答
1
投票

有一个解决方案:https://github.com/react-native-community/react-native-svg 如果您使用此库提供的

<Image />
,它将被像素化(就像
image-rendering: pixelated
CSS 效果)。

您的情况:

<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>

我很好奇,你最后找到解决办法了吗?


0
投票

我自己找到了一个解决方案,我不喜欢它,但目前这是权宜之计。

如果您有解决方案,请分享您的解决方案。

我所做的是使用

WebView
。我最初的目标是做
canvas.toDataURL
但它似乎不起作用,似乎 iOS safari 上没有完全支持。所以我只是用
WebView
调整
transparent backgroundColor
的大小。在 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

用途:

<ImagePixelated url={entity.image} height={90} width={90} />

适用于 Android 和 iOS。


0
投票

很抱歉这个建议迟到了,但我是新来的。无论如何,我不确定您是在寻求帮助编写自己的应用程序来执行此操作,还是只是在寻找使用现有应用程序执行此操作的任何方法,但如果是后者,那么这里有一个想法。

对于任何桌面操作系统,只需使用一个好的图像编辑器(例如= Gimp)并使用“调整大小”放大图像并选择大多数图像编辑器称为“最近邻居”或“无”的选项(它正是您想要的).

对于 Android(我唯一的设备),我的主要图像编辑器是“照片编辑器”(由 dev.macgyver 开发)。使用它,使用“调整大小”放大图像,然后进入“效果”菜单,选择“马赛克”,并调整它,使其看起来像是使用“最近邻居/无”放大的。 (其他一些 Android 图像编辑器也应该有马赛克功能,但他们可能称之为 Pixelate。)我希望这会有所帮助。


0
投票

react-native-svg
上面的建议对我来说不起作用,它可以使图像平滑。

Image
来自
react-native-skia
不平滑像素艺术!缩放无锯齿。

https://shopify.github.io/react-native-skia/docs/images/

import { Canvas, Image, useImage } from "@shopify/react-native-skia";
 
const ImageDemo = () => {
  const image = useImage(require("./assets/pixelart.png"));
  return (
    <Canvas style={{ flex: 1 }}>
      <Image image={image} fit="contain" x={0} y={0} width={256} height={256} />
    </Canvas>
  );
};
© www.soinside.com 2019 - 2024. All rights reserved.