HTML5 canvas: есть ли способ изменить размер изображения с помощью повторной выборки «ближайшего соседа»?

#html #canvas #resampling

#HTML #холст #повторная выборка

Вопрос:

У меня есть JS, который выполняет некоторые манипуляции с изображениями. Я хочу иметь графику, подобную pixelart, поэтому мне пришлось увеличить исходные изображения в графическом редакторе. Но я думаю, было бы неплохо проделать все манипуляции с маленьким изображением, а затем увеличить его с помощью функциональности html5. Это сэкономит кучу времени на обработку (потому что сейчас, например, моя демо-версия (предупреждение: доменное имя может вызвать некоторые проблемы на работе и т.д.) загружается очень долго в Firefox). Но когда я пытаюсь изменить размер изображения, оно пересчитывается бикубически. Как изменить размер изображения без повторной выборки? Есть ли какое-либо кроссбраузерное решение?

Комментарии:

1. У многих из нас есть работа — пожалуйста, предупреждайте пользователей здесь о любых ссылках на что-то NSFW вроде «анального рабства».

2. Там нет ничего NSFW. Это всего лишь мой тестовый сервер с таким weird доменным именем.

3. Проблема заключается в самой ссылке, и она чрезвычайно неудобна.

4. О боже, теперь автоматическое завершение моей адресной строки выглядит довольно неплохо с этим URL. спасибо ABTOMAT, вы знаете, что можете использовать jsfiddle. net или dropbox не подходит?

5. Из-за этого URL у нас не может быть хороших вещей.

Ответ №1:

 image-rendering: -webkit-optimize-contrast; /* webkit */
image-rendering: -moz-crisp-edges /* Firefox */
  

http://phrogz.net/tmp/canvas_image_zoom.html можно предоставить запасной вариант с использованием canvas и getImageData . Короче говоря:

 // Create an offscreen canvas, draw an image to it, and fetch the pixels
var offtx = document.createElement('canvas').getContext('2d');
offtx.drawImage(img1,0,0);
var imgData = offtx.getImageData(0,0,img1.width,img1.height).data;

// Draw the zoomed-up pixels to a different canvas context
for (var x=0;x<img1.width;  x){
  for (var y=0;y<img1.height;  y){
    // Find the starting index in the one-dimensional image data
    var i = (y*img1.width   x)*4;
    var r = imgData[i  ];
    var g = imgData[i 1];
    var b = imgData[i 2];
    var a = imgData[i 3];
    ctx2.fillStyle = "rgba(" r "," g "," b "," (a/255) ")";
    ctx2.fillRect(x*zoom,y*zoom,zoom,zoom);
  }
}
  

Подробнее: Документы MDN по рендерингу изображений

Комментарии:

1. Комментарий для будущих читателей: Оптимизация изображения в CSS будет округляться до ближайшего пикселя, что не всегда может быть идеальным для ваших изображений. Ваши space invaders могут стать комковатыми, а Mario может деформироваться. Лучше всего использовать кодовый метод, описанный выше, или увеличить ваши изображения в 3 раза, чтобы эффекты биномиальной визуализации изображений были незначительными.

Ответ №2:

Некоторое время назад я написал скрипт изменения размера NN, используя ImageData (около строки 1794)

https://github.com/arahaya/ImageFilters.js/blob/master/imagefilters.js

Вы можете посмотреть демонстрацию здесь

http://www.arahaya.com/imagefilters/

к сожалению, встроенное изменение размера должно быть немного быстрее.

Ответ №3:

Этот CSS для элемента canvas работает:

 image-rendering: pixelated;
  

Это работает в Chrome 93, начиная с сентября 2021 года.

Комментарии:

1. > пиксельный

Ответ №4:

Вы можете просто установить context.imageSmoothingEnabled на false . Это приведет к тому, что все, что нарисовано с помощью context.drawImage() изменения размера с использованием ближайшего соседа.

 // the canvas to resize
const canvas = document.createElement("canvas");

// the canvas to output to
const canvas2 = document.createElement("canvas");
const context2 = canvas2.getContext("2d");

// disable image smoothing
context2.imageSmoothingEnabled = false;

// draw image from the canvas
context2.drawImage(canvas, 0, 0, canvas2.width, canvas2.height);
  

Это имеет лучшую поддержку, чем использование image-rendering: pixelated .

Комментарии:

1. Есть некоторые несоответствия в браузере. Firefox игнорирует imageSmoothingEnabled = false уменьшение масштаба изображения.

Ответ №5:

Я повторю то, что говорили другие, и скажу вам, что это не встроенная функция. Столкнувшись с той же проблемой, я описал ее ниже.

Он использует fillRect() вместо того, чтобы перебирать каждый пиксель и рисовать его. Все прокомментировано, чтобы помочь вам лучше понять, как это работает.

 //img is the original image, scale is a multiplier. It returns the resized image.
function Resize_Nearest_Neighbour( img, scale ){
    //make shortcuts for image width and height
    var w = img.width;
    var h = img.height;

    //---------------------------------------------------------------
    //draw the original image to a new canvas
    //---------------------------------------------------------------

    //set up the canvas
    var c = document.createElement("CANVAS");
    var ctx = c.getContext("2d");
    //disable antialiasing on the canvas
    ctx.imageSmoothingEnabled = false;
    //size the canvas to match the input image
    c.width = w;
    c.height = h;
    //draw the input image
    ctx.drawImage( img, 0, 0 );
    //get the input image as image data
    var inputImg = ctx.getImageData(0,0,w,h);
    //get the data array from the canvas image data
    var data = inputImg.data;

    //---------------------------------------------------------------
    //resize the canvas to our bigger output image
    //---------------------------------------------------------------
    c.width = w * scale;
    c.height = h * scale;
    //---------------------------------------------------------------
    //loop through all the data, painting each pixel larger
    //---------------------------------------------------------------
    for ( var i = 0; i < data.length; i =4 ){

        //find the colour of this particular pixel
        var colour = "#";

        //---------------------------------------------------------------
        //convert the RGB numbers into a hex string. i.e. [255, 10, 100]
        //into "FF0A64"
        //---------------------------------------------------------------
        function _Dex_To_Hex( number ){
            var out = number.toString(16);
            if ( out.length < 2 ){
                out = "0"   out;
            }
            return out;
        }
        for ( var colourIndex = 0; colourIndex < 3; colourIndex   ){
            colour  = _Dex_To_Hex( data[ i colourIndex ] );
        }
        //set the fill colour
        ctx.fillStyle = colour;

        //---------------------------------------------------------------
        //convert the index in the data array to x and y coordinates
        //---------------------------------------------------------------
        var index = i/4;
        var x = index % w;
        //~~ is a faster way to do 'Math.floor'
        var y = ~~( index / w );
        //---------------------------------------------------------------
        //draw an enlarged rectangle on the enlarged canvas
        //---------------------------------------------------------------
        ctx.fillRect( x*scale, y*scale, scale, scale );
    }

    //get the output image from the canvas
    var output = c.toDataURL("image/png");
    //returns image data that can be plugged into an img tag's src
    return output;
}
  

Ниже приведен пример его использования.

Ваше изображение будет отображаться в HTML следующим образом:

 <img id="pixel-image" src="" data-src="pixel-image.png"/>
  

data-src Тег содержит URL-адрес изображения, которое вы хотите увеличить. Это пользовательский тег данных. Приведенный ниже код возьмет URL-адрес изображения из тега data и пропустит его через функцию изменения размера, возвращая увеличенное изображение (в 30 раз от исходного размера), которое затем вводится в src атрибут img тега.

Не забудьте поместить функцию Resize_Nearest_Neighbour (выше) в <script> тег, прежде чем включать следующее.

 function Load_Image( element ){
    var source = element.getAttribute("data-src");
    var img = new Image();

    img.addEventListener("load", function(){

        var bigImage = Resize_Nearest_Neighbour( this, 30 );
        element.src = bigImage;

    });

    img.src = source;
}

Load_Image( document.getElementById("pixel-image") );
  

Ответ №6:

Встроенного способа нет. Вы должны сделать это самостоятельно с getImageData .

Комментарии:

1. Думал об этом, но не будет ли это очень медленно?

2. Короче говоря, да. Это будет очень медленно. Я могу подумать о другом способе использования холста в памяти и большого количества вызовов drawImage, но он может быть не согласован в разных браузерах из-за различий в сглаживании и может быть ненамного быстрее.

Ответ №7:

Основано на комментарии Пола Айриша:

 function resizeBase64(base64, zoom) {
    return new Promise(function(resolve, reject) {
        var img = document.createElement("img");

        // once image loaded, resize it
        img.onload = function() {
            // get image size
            var imageWidth = img.width;
            var imageHeight = img.height;

            // create and draw image to our first offscreen canvas
            var canvas1 = document.createElement("canvas");
            canvas1.width = imageWidth;
            canvas1.height = imageHeight;
            var ctx1 = canvas1.getContext("2d");
            ctx1.drawImage(this, 0, 0, imageWidth, imageHeight);

            // get pixel data from first canvas
            var imgData = ctx1.getImageData(0, 0, imageWidth, imageHeight).data;

            // create second offscreen canvas at the zoomed size
            var canvas2 = document.createElement("canvas");
            canvas2.width = imageWidth * zoom;
            canvas2.height = imageHeight * zoom;
            var ctx2 = canvas2.getContext("2d");

            // draw the zoomed-up pixels to a the second canvas
            for (var x = 0; x < imageWidth;   x) {
                for (var y = 0; y < imageHeight;   y) {
                    // find the starting index in the one-dimensional image data
                    var i = (y * imageWidth   x) * 4;
                    var r = imgData[i];
                    var g = imgData[i   1];
                    var b = imgData[i   2];
                    var a = imgData[i   3];
                    ctx2.fillStyle = "rgba("   r   ","   g   ","   b   ","   a / 255   ")";
                    ctx2.fillRect(x * zoom, y * zoom, zoom, zoom);
                }
            }

            // resolve promise with the zoomed base64 image data
            var dataURI = canvas2.toDataURL();
            resolve(dataURI);
        };
        img.onerror = function(error) {
            reject(error);
        };
        // set the img soruce
        img.src = base64;
    });
}

resizeBase64(src, 4).then(function(zoomedSrc) {
    console.log(zoomedSrc);
});
  

https://jsfiddle.net/djhyquon/69/