Ubah ukuran gambar dengan kanvas javascript (mulus)

90

Saya mencoba mengubah ukuran beberapa gambar dengan kanvas tetapi saya tidak tahu cara menghaluskannya. Pada photoshop, browser, dll. Ada beberapa algoritma yang mereka gunakan (misalnya bicubic, bilinear) tetapi saya tidak tahu apakah ini dibuat di kanvas atau tidak.

Ini biola saya: 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);

Yang pertama adalah tag gambar yang diubah ukurannya normal, dan yang kedua adalah kanvas. Perhatikan bagaimana kanvasnya tidak semulus itu. Bagaimana saya bisa mencapai 'kelancaran'?

steve
sumber

Jawaban:

136

Anda dapat menggunakan langkah turun untuk mencapai hasil yang lebih baik. Sebagian besar browser tampaknya menggunakan interpolasi linier daripada bi-kubik saat mengubah ukuran gambar.

( Perbarui Ada telah menambahkan properti kualitas ke spesifikasi, imageSmoothingQualityyang saat ini hanya tersedia di Chrome.)

Kecuali jika seseorang memilih tidak ada penghalusan atau tetangga terdekat, browser akan selalu menginterpolasi gambar setelah menurunkan ukurannya karena ini berfungsi sebagai filter akses rendah untuk menghindari aliasing.

Bi-linear menggunakan 2x2 piksel untuk melakukan interpolasi sedangkan bi-kubik menggunakan 4x4 sehingga dengan melakukan langkah-langkah Anda bisa mendekati hasil bi-kubik saat menggunakan interpolasi bi-linear seperti yang terlihat pada gambar yang dihasilkan.

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>

Bergantung pada seberapa drastis pengubahan ukuran Anda, Anda dapat melewati langkah 2 jika perbedaannya kurang.

Dalam demo Anda dapat melihat hasil baru sekarang sangat mirip dengan elemen gambar.


sumber
1
@steve heh, terkadang hal ini terjadi :) Untuk gambar biasanya Anda dapat menimpanya dengan menyetel css BTW.
Ken, hasil pertama bekerja dengan baik tetapi ketika saya mengubah gambar, Anda bisa melihatnya terlalu kabur jsfiddle.net/kcHLG Apa yang bisa dilakukan dalam kasus ini dan lainnya?
steve
@steve Anda dapat mengurangi jumlah langkah menjadi hanya 1 atau tidak sama sekali (untuk beberapa gambar ini berfungsi dengan baik). Lihat juga jawaban ini yang mirip dengan ini tetapi di sini saya menambahkan konvolusi pertajam sehingga Anda dapat membuat gambar yang dihasilkan lebih tajam setelah diturunkan skalanya.
1
@steve di sini adalah biola yang dimodifikasi dengan Bill hanya menggunakan satu langkah ekstra: jsfiddle.net/AbdiasSoftware/kcHLG/1
1
@neaumusic kode tersebut merupakan kelanjutan dari kode OPs. Jika Anda membuka biola, Anda akan melihat ctx sedang didefinisikan. Saya telah memasukkannya di sini untuk menghindari kesalahpahaman.
27

Karena biola Trung Le Nguyen Nhat tidak benar sama sekali (hanya menggunakan gambar asli di langkah terakhir)
saya menulis biola umum saya sendiri dengan perbandingan kinerja:

BIOLA

Pada dasarnya ini:

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);
}
Sebastian Ott
sumber
Jawaban paling diremehkan yang pernah ada.
Amsakanna
17

Saya membuat layanan Angular yang dapat digunakan kembali untuk menangani pengubahan ukuran gambar / kanvas berkualitas tinggi bagi siapa saja yang tertarik: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

Layanan ini mencakup dua solusi karena keduanya memiliki pro / kontra sendiri. Pendekatan konvolusi lanczos berkualitas lebih tinggi dengan biaya menjadi lebih lambat, sedangkan pendekatan penurunan skala bertahap menghasilkan hasil antialiased yang wajar dan secara signifikan lebih cepat.

Contoh penggunaan:

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.dll
sumber
Maaf tentang itu - Saya telah mengubah nama pengguna github saya. Baru saja memperbarui tautan gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2
3
Saya melihat kata bersudut, saya merasakan perasaan lucu itu
SuperUberDuper
8

Meskipun beberapa cuplikan kode tersebut pendek dan berfungsi, namun tidak mudah untuk diikuti dan dipahami.

Karena saya bukan penggemar "copy-paste" dari stack-overflow, saya ingin para pengembang memahami kode yang mereka masukkan ke dalam perangkat lunak mereka, semoga hal di bawah ini berguna.

DEMO : Mengubah ukuran gambar dengan fiddler JS dan HTML Canvas Demo.

Anda mungkin menemukan 3 metode berbeda untuk melakukan pengubahan ukuran ini, yang akan membantu Anda memahami cara kerja kode dan alasannya.

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

Kode lengkap dari kedua demo, dan metode TypeScript yang mungkin ingin Anda gunakan dalam kode Anda, dapat ditemukan di proyek GitHub.

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

Ini adalah kode terakhir:

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
        }
    };
}}
Eyal c
sumber
4

Saya membuat perpustakaan yang memungkinkan Anda menurunkan persentase apa pun sambil menyimpan semua data warna.

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

File itu bisa Anda masukkan ke dalam browser. Hasilnya akan terlihat seperti photoshop atau image magick, mempertahankan semua data warna, rata-rata piksel, daripada mengambil yang terdekat dan menjatuhkan yang lain. Itu tidak menggunakan rumus untuk menebak rata-rata, itu mengambil rata-rata yang tepat.

Funkodebat
sumber
1
Saya mungkin akan menggunakan webgl untuk mengubah ukuran sekarang
Funkodebat
4

Berdasarkan jawaban K3N, saya menulis ulang kode secara umum untuk siapapun yang mau

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

PERBARUI DEMO JSFIDDLE

Ini DEMO ONLINE saya

Trung Le Nguyen Nhat
sumber
2
Ini tidak akan berfungsi: setiap kali Anda mengubah ukuran kanvas, konteksnya akan terhapus. Anda membutuhkan 2 kanvas. Di sini sama saja dengan memanggil drawImage dengan dimensi akhir secara langsung.
Kaiido
2

Saya tidak mengerti mengapa tidak ada yang menyarankan createImageBitmap.

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

bekerja dengan indah (dengan asumsi Anda menetapkan id untuk gambar dan kanvas).

cagdas_ucar
sumber
Karena Ini tidak didukung secara luas caniuse.com/#search=createImageBitmap
Matt
createImageBitmap didukung untuk 73% dari semua pengguna. Bergantung pada kasus penggunaan Anda, ini mungkin cukup baik. Hanya Safari yang menolak menambahkan dukungan untuk itu. Saya pikir itu layak disebut sebagai solusi yang memungkinkan.
cagdas_ucar
Solusi yang bagus, tetapi sayangnya tidak berfungsi pada firefox
vcarel
1

Saya menulis utilitas js kecil untuk memotong dan mengubah ukuran gambar di front-end. Ini tautan di proyek GitHub. Anda juga bisa mendapatkan gumpalan dari gambar akhir untuk mengirimkannya.

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;
Diyaz Yakubov
sumber