Змінити розмір зображення за допомогою полотна javascript (плавно)


90

Я намагаюся змінити розмір деяких зображень на полотні, але не знаю, як їх згладити. У Photoshop, браузерах і т. Д. Існує кілька алгоритмів, які вони використовують (наприклад, бікубічний, білінійний), але я не знаю, вбудовані вони в полотно чи ні.

Ось моя скрипка: 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.)

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

Дволінійний використовує 2х2 пікселі для інтерполяції, тоді як бікубічний використовує 4х4, тому, роблячи це поетапно, ви можете наблизитись до бікубічного результату, використовуючи білінійну інтерполяцію, як видно на отриманих зображеннях.

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 хе, іноді таке трапляється :) Для зображень ви зазвичай можете це замінити, встановивши css BTW.

Кен, перший результат спрацював чудово, але коли я зміню зображення, ви бачите, що це занадто розмито jsfiddle.net/kcHLG Що можна зробити в цьому випадку та інші?
steve

@steve ви можете зменшити кількість кроків лише до 1 або жодного (для деяких зображень це чудово працює). Дивіться також цю відповідь , схожу на цю, але тут я додав до неї різку згортку, щоб ви могли зробити отримане зображення різкішим після його зменшення.

1
@steve ось модифікована скрипка з Біллом, використовуючи лише один додатковий крок: jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic код є продовженням коду OPs. Якщо відкрити скрипку, ви побачите, як визначається ctx. Я подав його тут, щоб уникнути непорозумінь.

27

Оскільки скрипка Trung Le Nguyen Nhat зовсім не правильна (вона просто використовує оригінальне зображення на останньому кроці),
я написав свою власну загальну скрипку з порівнянням продуктивності:

Скрипка

В основному це:

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);
}

Найбільш недооцінена відповідь, яку коли-небудь бачили.
Амсаканна

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

Вибачте за це - я змінив своє ім'я користувача github. Щойно оновив посилання gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2

3
Я побачив слово
angular

8

Хоча деякі з цих фрагментів коду є короткими і працюють, вони не є тривіальними, щоб їх слід було зрозуміти.

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

ДЕМО : Зміна розміру зображень за допомогою демонстраційного скрипта JS та HTML Canvas.

Ви можете знайти 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

Цей файл ви можете включити у браузер. Результати будуть виглядати як фотошоп або магія зображення, зберігаючи всі кольорові дані, усереднюючи пікселі, замість того, щоб брати найближчі та скидати інші. Він не використовує формулу для вгадування середніх значень, він бере точне середнє.


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 з кінцевими розмірами.
Каїдо

2

Я не розумію, чому ніхто не пропонує createImageBitmap.

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

працює чудово (за умови, що ви встановили ідентифікатори зображення та полотна).


Оскільки це не широко підтримується 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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.