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

90

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

Вот моя скрипка: 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);

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

Стив
источник

Ответы:

136

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

( Обновление. В спецификации добавлено свойство качества, 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, если разница меньше.

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


источник
1
@steve heh, иногда такие вещи случаются :) Для изображений вы обычно можете переопределить это, установив css BTW.
Кен, первый результат сработал отлично, но когда я меняю изображения, вы видите, что они слишком размытые jsfiddle.net/kcHLG Что можно сделать в этом и других случаях?
Стив
@steve вы можете уменьшить количество шагов до 1 или ни одного (для некоторых изображений это работает нормально). См. Также этот ответ, который похож на этот, но здесь я добавил к нему свертку резкости, чтобы вы могли сделать получившееся изображение более резким после его уменьшения.
1
@steve - это модифицированная скрипка с Биллом, использующая только один дополнительный шаг: jsfiddle.net/AbdiasSoftware/kcHLG/1
1
@neaumusic код является продолжением кода OPs. Если вы откроете скрипт, вы увидите, что ctx определяется. Я вставил его сюда, чтобы избежать недоразумений.
27

Поскольку скрипка 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);
}
Себастьян Отт
источник
Самый недооцененный ответ, который когда-либо видел.
Амсаканна 02
17

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

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

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

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's 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
    })
})
fisch2
источник
Извините за это - я изменил свое имя пользователя на github. Только что обновил ссылку gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2
3
Я увидел слово угловой, у меня
возникло
8

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

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

ДЕМО : изменение размера изображений с помощью 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
        }
    };
}}
Эял с
источник
4

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

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

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

Funkodebat
источник
1
Я бы, наверное, сейчас использовал webgl для изменения размера
Funkodebat
4

Основываясь на ответе 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

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

Чунг Ле Нгуен Нхат
источник
2
Это не сработает: каждый раз, когда вы изменяете размер холста, контекст очищается. Вам понадобится 2 полотна. Здесь это то же самое, что и прямой вызов drawImage с окончательными размерами.
Kaiido 07
2

Я не понимаю, почему никто не предлагает createImageBitmap.

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

прекрасно работает (при условии, что вы установили идентификаторы для изображения и холста).

cagdas_ucar
источник
Потому что это не широко поддерживается caniuse.com/#search=createImageBitmap
Мэтт
createImageBitmap поддерживается 73% всех пользователей. В зависимости от вашего варианта использования этого может быть достаточно. Просто Safari отказывается добавлять для него поддержку. Думаю, стоит упомянуть как возможное решение.
cagdas_ucar
Хорошее решение, но, к сожалению, оно не работает в firefox
vcarel
1

Я написал небольшую 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;
Дияз Якубов
источник