Изменение размера изображения с помощью холста javascript (плавно)

Я пытаюсь изменить размер некоторых изображений с помощью canvas, но я не знаю, как их сгладить. В фотошопе, браузерах и т.д. Есть несколько алгоритмов, которые они используют (например, бикубический, билинейный), но я не знаю, встроены они в холст или нет.

Вот моя скрипка: http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

Первый тег - это изображение с нормальным изменением размера, а второй - холст. Обратите внимание, что холст один не так гладко. Как мне достичь "гладкости"?

Ответ 1

Вы можете использовать понижающий шаг для достижения лучших результатов. Большинство браузеров, по-видимому, используют линейную интерполяцию, а не бикубическую при изменении размеров изображений.

(Обновление В спецификации добавлено свойство качества imageSmoothingQuality которое в настоящее время доступно только в Chrome.)

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

Билинейный использует 2x2 пикселей для интерполяции, в то время как би-кубический использует 4x4, поэтому, делая это поэтапно, вы можете приблизиться к би-кубическому результату, используя билинейную интерполяцию, как видно на полученных изображениях.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

Ответ 2

Я создал многоразовый сервис Angular для высококачественного изменения размера изображений/полотен для всех, кто заинтересован: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

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

Пример использования:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})

Ответ 3

Поскольку скрипка Trung Le Nguyen Nhat вообще не верна (на последнем шаге используется только оригинальное изображение)
Я написал свою общую скрипку со сравнением производительности:

FIDDLE

В основном это:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}

Ответ 4

Я создал библиотеку, которая позволяет вам опускать любой процент, сохраняя все данные цвета.

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

Этот файл можно включить в браузер. Результаты будут выглядеть как фотошоп или магия изображения, сохраняя все цветовые данные, усредняя пиксели, вместо того, чтобы брать близлежащие и бросать других. Он не использует формулу для угадывания средних значений, она принимает точное среднее значение.

Ответ 5

Основываясь на ответе K3N, я переписываю код вообще для тех, кто хочет

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

ОБНОВЛЕНИЕ JSFIDDLE DEMO

Вот мой ОНЛАЙН-ДЕМО

Ответ 6

Хотя некоторые из этих фрагментов кода короткие и работают, они не так просты для понимания и понимания.

Поскольку я не являюсь поклонником "копирования-вставки" из, я хотел бы, чтобы разработчики поняли код, который они внедряют в свое программное обеспечение, надеюсь, что вы найдете следующее полезным.

ДЕМО: изменение размера изображений с помощью JS и HTML Canvas Demo fiddler.

Вы можете найти 3 различных метода для изменения размера, которые помогут вам понять, как работает код и почему.

https://jsfiddle.net/1b68eLdr/93089/

Полный код демонстрационного и метода TypeScript, который вы можете использовать в своем коде, можно найти в проекте GitHub.

https://github.com/eyalc4/ts-image-resizer

Это окончательный код:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the dize by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}

Ответ 7

Я написал небольшую утилиту js для обрезки и изменения размера изображения на интерфейсе. Вот ссылка в проекте GitHub. Также вы можете получить blob из окончательного изображения, чтобы отправить его.

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;