Mengubah ukuran gambar di kanvas HTML5

314

Saya mencoba membuat gambar mini di sisi klien menggunakan javascript dan elemen kanvas, tetapi ketika saya mengecilkan gambar, itu tampak mengerikan. Sepertinya itu dirampingkan di photoshop dengan resampling diatur ke 'Nearest Neighbor' alih-alih Bicubic. Saya tahu ini mungkin untuk membuat ini terlihat benar, karena situs ini dapat melakukannya dengan baik menggunakan kanvas juga. Saya sudah mencoba menggunakan kode yang sama seperti yang ditunjukkan pada tautan "[Sumber]", tetapi masih terlihat mengerikan. Apakah ada sesuatu yang saya lewatkan, beberapa pengaturan yang perlu diatur atau sesuatu?

EDIT:

Saya mencoba mengubah ukuran jpg. Saya telah mencoba mengubah ukuran jpg yang sama di situs yang ditautkan dan di photoshop, dan itu terlihat baik-baik saja ketika dirampingkan.

Ini kode yang relevan:

reader.onloadend = function(e)
{
    var img = new Image();
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");

    img.onload = function()
    {
        var ratio = 1;

        if(img.width > maxWidth)
            ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
            ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    };

    img.src = reader.result;
}

EDIT2:

Sepertinya saya salah, situs web tertaut tidak melakukan pekerjaan yang lebih baik dari perampingan gambar. Saya mencoba metode lain yang disarankan dan tidak ada yang terlihat lebih baik. Inilah yang menghasilkan berbagai metode:

Photoshop:

teks alternatif

Kanvas:

teks alternatif

Gambar dengan rendering gambar: optimalkan set kualitas dan diskalakan dengan lebar / tinggi:

teks alternatif

Gambar dengan rendering gambar: optimalkan set kualitas dan diskalakan dengan -moz-transform:

teks alternatif

Ubah ukuran kanvas di pixastic:

teks alternatif

Saya kira ini berarti firefox tidak menggunakan pengambilan sampel bikubik seperti yang seharusnya. Saya hanya harus menunggu sampai mereka benar-benar menambahkannya.

EDIT3:

Gambar asli

Telanor
sumber
Bisakah Anda memposting kode yang Anda gunakan untuk mengubah ukuran gambar?
Xavi
Apakah Anda mencoba mengubah ukuran gambar GIF atau gambar serupa dengan palet terbatas? Bahkan di photoshop gambar-gambar ini tidak menurun dengan baik kecuali jika Anda mengubahnya menjadi RGB.
Leepowers
Bisakah Anda memposting salinan gambar asli?
Xavi
Mengubah ukuran gambar menggunakan javascript agak kludge - tidak hanya Anda menggunakan kekuatan pemrosesan klien untuk mengubah ukuran gambar, Anda melakukannya pada setiap beban halaman. Mengapa tidak hanya menyimpan versi downscaled dari photoshop dan sebaliknya melayani / bersama-sama dengan gambar asli?
mendefinisikan
29
Karena saya membuat pengunggah gambar dengan kemampuan untuk mengubah ukuran dan memotong gambar sebelum mengunggahnya.
Telanor

Jawaban:

393

Jadi apa yang Anda lakukan jika semua browser (sebenarnya, Chrome 5 memberi saya yang cukup bagus) tidak akan memberi Anda kualitas resampling yang cukup baik? Anda menerapkannya sendiri! Oh ayolah, kita memasuki era baru Web 3.0, browser yang mendukung HTML5, kompiler javascript JIT super dioptimalkan, mesin multi-core (†), dengan banyak memori, apa yang Anda takutkan? Hei, ada kata java di javascript, jadi itu harus menjamin kinerjanya, kan? Lihatlah, thumbnail yang menghasilkan kode:

// returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function(x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1;
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    };
}

// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width : sx,
        height : Math.round(img.height * sx / img.width),
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(this.process1, 0, this, 0);
}

thumbnailer.prototype.process1 = function(self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)
                            + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(self.process1, 0, self, u);
    else
        setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
};

... yang dengannya Anda dapat menghasilkan hasil seperti ini!

img717.imageshack.us/img717/8910/lanczos358.png

jadi bagaimanapun, ini adalah versi 'tetap' dari contoh Anda:

img.onload = function() {
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
    // but feel free to raise it up to 8. Your client will appreciate
    // that the program makes full use of his machine.
    document.body.appendChild(canvas);
};

Sekarang saatnya untuk mengadu peramban terbaik Anda di luar sana dan melihat mana yang paling mungkin meningkatkan tekanan darah klien Anda!

Umm, di mana tag sarkasme saya?

(karena banyak bagian dari kode ini didasarkan pada Anrieff Gallery Generator apakah itu juga tercakup dalam GPL2? Saya tidak tahu)

sebenarnya karena keterbatasan javascript, multi-core tidak didukung.

Syockit
sumber
2
Saya sebenarnya telah mencoba mengimplementasikannya sendiri, melakukan seperti yang Anda lakukan, menyalin kode dari editor gambar sumber terbuka. Karena saya tidak dapat menemukan dokumentasi yang solid pada algoritma saya kesulitan mengoptimalkannya. Pada akhirnya, milik saya agak lambat (butuh beberapa detik untuk mengubah ukuran gambar). Ketika saya mendapat kesempatan, saya akan mencoba Anda dan melihat apakah ini lebih cepat. Dan saya pikir para pekerja web membuat multi-core javascript mungkin sekarang. Saya akan mencoba menggunakan mereka untuk mempercepatnya, tetapi saya mengalami kesulitan mencari cara untuk menjadikan ini menjadi algoritma multithreaded
Telanor
3
Maaf, lupakan itu! Saya sudah mengedit balasannya. Lagipula itu tidak akan cepat, bicubic harus lebih cepat. Belum lagi algoritma yang saya gunakan bukan pengubahan ukuran 2 arah yang biasa (yaitu baris demi baris, horizontal, dan vertikal), jadi looot lebih lambat.
syockit
5
Anda luar biasa dan pantas mendapatkan banyak sekali kejutan.
Rocklan
5
Ini menghasilkan hasil yang layak, tetapi membutuhkan 7,4 detik untuk gambar 1,8 MP di versi terbaru Chrome ...
mpen
2
Bagaimana metode seperti ini mengatur skor sedemikian tinggi ?? Solusi yang ditampilkan sepenuhnya gagal untuk memperhitungkan skala logaritmik yang digunakan untuk menyimpan informasi warna. Sebuah RGB dari 127.127.127 adalah seperempat kecerahan 255, 255, 255 tidak setengah. Pengambilan sampel dalam larutan menghasilkan gambar yang gelap. Malu ini ditutup karena ada metode yang sangat sederhana dan cepat untuk menurunkan ukuran yang menghasilkan hasil yang lebih baik daripada Photoshop (OP pasti memiliki preferensi yang salah) sampel yang diberikan
Blindman67
37

Algoritma resize / resample gambar cepat menggunakan filter Hermite dengan JavaScript. Mendukung transparansi, memberikan kualitas yang baik. Pratinjau:

masukkan deskripsi gambar di sini

Pembaruan : versi 2.0 ditambahkan pada GitHub (lebih cepat, pekerja web + objek yang dapat ditransfer). Akhirnya saya berhasil!

Git: https://github.com/viliusle/Hermite-resize
Demo: http://viliusle.github.io/miniPaint/

/**
 * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
 * 
 * @param {HtmlElement} canvas
 * @param {int} width
 * @param {int} height
 * @param {boolean} resize_canvas if true, canvas will be resized. Optional.
 */
function resample_single(canvas, width, height, resize_canvas) {
    var width_source = canvas.width;
    var height_source = canvas.height;
    width = Math.round(width);
    height = Math.round(height);

    var ratio_w = width_source / width;
    var ratio_h = height_source / height;
    var ratio_w_half = Math.ceil(ratio_w / 2);
    var ratio_h_half = Math.ceil(ratio_h / 2);

    var ctx = canvas.getContext("2d");
    var img = ctx.getImageData(0, 0, width_source, height_source);
    var img2 = ctx.createImageData(width, height);
    var data = img.data;
    var data2 = img2.data;

    for (var j = 0; j < height; j++) {
        for (var i = 0; i < width; i++) {
            var x2 = (i + j * width) * 4;
            var weight = 0;
            var weights = 0;
            var weights_alpha = 0;
            var gx_r = 0;
            var gx_g = 0;
            var gx_b = 0;
            var gx_a = 0;
            var center_y = (j + 0.5) * ratio_h;
            var yy_start = Math.floor(j * ratio_h);
            var yy_stop = Math.ceil((j + 1) * ratio_h);
            for (var yy = yy_start; yy < yy_stop; yy++) {
                var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
                var center_x = (i + 0.5) * ratio_w;
                var w0 = dy * dy; //pre-calc part of w
                var xx_start = Math.floor(i * ratio_w);
                var xx_stop = Math.ceil((i + 1) * ratio_w);
                for (var xx = xx_start; xx < xx_stop; xx++) {
                    var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
                    var w = Math.sqrt(w0 + dx * dx);
                    if (w >= 1) {
                        //pixel too far
                        continue;
                    }
                    //hermite filter
                    weight = 2 * w * w * w - 3 * w * w + 1;
                    var pos_x = 4 * (xx + yy * width_source);
                    //alpha
                    gx_a += weight * data[pos_x + 3];
                    weights_alpha += weight;
                    //colors
                    if (data[pos_x + 3] < 255)
                        weight = weight * data[pos_x + 3] / 250;
                    gx_r += weight * data[pos_x];
                    gx_g += weight * data[pos_x + 1];
                    gx_b += weight * data[pos_x + 2];
                    weights += weight;
                }
            }
            data2[x2] = gx_r / weights;
            data2[x2 + 1] = gx_g / weights;
            data2[x2 + 2] = gx_b / weights;
            data2[x2 + 3] = gx_a / weights_alpha;
        }
    }
    //clear and resize canvas
    if (resize_canvas === true) {
        canvas.width = width;
        canvas.height = height;
    } else {
        ctx.clearRect(0, 0, width_source, height_source);
    }

    //draw
    ctx.putImageData(img2, 0, 0);
}
ViliusL
sumber
Mungkin Anda bisa memasukkan tautan ke demo miniPaint dan repo Github Anda?
syockit
1
Apakah Anda juga akan membagikan versi webworkers? Mungkin karena pengaturan overhead, ini lebih lambat untuk gambar kecil, tetapi bisa berguna untuk gambar sumber yang lebih besar.
syockit
menambahkan demo, tautan git, juga versi multi-core. Tapi saya tidak menghabiskan terlalu banyak waktu untuk mengoptimalkan versi multicore ... Versi tunggal yang saya yakini dioptimalkan dengan baik.
ViliusL
Perbedaan besar dan kinerja yang baik. Terima kasih banyak! sebelum dan sesudah
KevBurnsJr
2
@Vilius Ah sekarang saya ingat mengapa pekerja web tidak bekerja dengan baik. Mereka tidak pernah berbagi memori sebelumnya, dan masih belum memilikinya sekarang! Mungkin suatu hari nanti ketika mereka berhasil mengatasinya, kode Anda akan datang untuk digunakan (itu, atau mungkin orang menggunakan PNaCl sebagai gantinya)
syockit
26

Coba pica - itu adalah resizer yang sangat dioptimalkan dengan algorythms yang dapat dipilih. Lihat demo .

Misalnya, gambar asli dari posting pertama diubah ukurannya dalam 120ms dengan filter Lanczos dan jendela 3px atau 60ms dengan filter Box dan jendela 0.5px. Untuk ukuran 17mb gambar berukuran besar 5000x3000px membutuhkan ~ 1d di desktop dan 3d di ponsel.

Semua prinsip ukuran dijelaskan dengan sangat baik di utas ini, dan pica tidak menambahkan ilmu roket. Tapi ini dioptimalkan dengan sangat baik untuk JIT-s modern, dan siap digunakan di luar kotak (via npm atau bower). Juga, ia menggunakan pekerja web saat tersedia untuk menghindari antarmuka membeku.

Saya juga berencana untuk segera menambahkan dukungan unsharp mask, karena ini sangat berguna setelah downscale.

Vitaly
sumber
14

Saya tahu ini adalah utas lama tetapi mungkin bermanfaat bagi beberapa orang seperti saya bahwa berbulan-bulan setelahnya mengenai masalah ini untuk pertama kalinya.

Berikut adalah beberapa kode yang mengubah ukuran gambar setiap kali Anda memuat ulang gambar. Saya sadar ini tidak optimal sama sekali, tetapi saya berikan sebagai bukti konsep.

Juga, maaf karena menggunakan jQuery untuk penyeleksi sederhana tapi saya merasa terlalu nyaman dengan sintaksis.

$(document).on('ready', createImage);
$(window).on('resize', createImage);

var createImage = function(){
  var canvas = document.getElementById('myCanvas');
  canvas.width = window.innerWidth || $(window).width();
  canvas.height = window.innerHeight || $(window).height();
  var ctx = canvas.getContext('2d');
  img = new Image();
  img.addEventListener('load', function () {
    ctx.drawImage(this, 0, 0, w, h);
  });
  img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};
html, body{
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #000;
}
canvas{
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Canvas Resize</title>
  </head>
  <body>
    <canvas id="myCanvas"></canvas>
  </body>
</html>

Fungsi createImage saya dipanggil sekali ketika dokumen dimuat dan setelah itu dipanggil setiap kali jendela menerima acara mengubah ukuran.

Saya mengujinya di Chrome 6 dan Firefox 3.6, keduanya di Mac. "Teknik" ini memakan prosesor seolah-olah itu adalah es krim di musim panas, tetapi itu berhasil.

cesarsalazar
sumber
9

Saya telah memasang beberapa algoritma untuk melakukan interpolasi gambar pada array piksel html kanvas yang mungkin berguna di sini:

https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2

Ini dapat disalin / ditempelkan dan dapat digunakan di dalam pekerja web untuk mengubah ukuran gambar (atau operasi lain yang memerlukan interpolasi - Saya menggunakan mereka untuk merusak gambar saat ini).

Saya belum menambahkan barang-barang lanczos di atas, jadi silakan menambahkannya sebagai perbandingan jika Anda mau.

Daniel
sumber
6

Ini adalah fungsi javascript yang diadaptasi dari kode @ Telanor. Ketika melewati base64 gambar sebagai argumen pertama ke fungsi, itu mengembalikan base64 dari gambar yang diubah ukurannya. maxWidth dan maxHeight adalah opsional.

function thumbnail(base64, maxWidth, maxHeight) {

  // Max size for thumbnail
  if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
  if(typeof(maxHeight) === 'undefined') var maxHeight = 500;

  // Create and initialize two canvas
  var canvas = document.createElement("canvas");
  var ctx = canvas.getContext("2d");
  var canvasCopy = document.createElement("canvas");
  var copyContext = canvasCopy.getContext("2d");

  // Create original image
  var img = new Image();
  img.src = base64;

  // Determine new ratio based on max size
  var ratio = 1;
  if(img.width > maxWidth)
    ratio = maxWidth / img.width;
  else if(img.height > maxHeight)
    ratio = maxHeight / img.height;

  // Draw original image in second canvas
  canvasCopy.width = img.width;
  canvasCopy.height = img.height;
  copyContext.drawImage(img, 0, 0);

  // Copy and resize second canvas to first canvas
  canvas.width = img.width * ratio;
  canvas.height = img.height * ratio;
  ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);

  return canvas.toDataURL();

}
Christophe Marois
sumber
pendekatan Anda sangat cepat tetapi menghasilkan gambar fuzzy seperti yang Anda lihat di sini: stackoverflow.com/questions/18922880/…
confile
6

Saya sangat menyarankan Anda memeriksa tautan ini dan memastikannya disetel ke true.

Mengontrol perilaku penskalaan gambar

Diperkenalkan di Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 memperkenalkan properti mozImageSmoothingEnabled ke elemen kanvas; jika nilai Boolean ini salah, gambar tidak akan dihaluskan saat diskalakan. Properti ini benar secara default. lihat cetak polos?

  1. cx.mozImageSmoothingEnabled = false;
Evan Carroll
sumber
5

Jika Anda hanya mencoba mengubah ukuran gambar, saya akan merekomendasikan pengaturan widthdan heightgambar dengan CSS. Ini contoh singkatnya:

.small-image {
    width: 100px;
    height: 100px;
}

Perhatikan bahwa heightdan widthjuga dapat diatur menggunakan JavaScript. Berikut contoh kode cepat:

var img = document.getElement("my-image");
img.style.width = 100 + "px";  // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE

Juga, untuk memastikan gambar yang diubah ukurannya terlihat bagus, tambahkan aturan css berikut ke pemilih gambar:

Sejauh yang saya tahu, semua browser kecuali IE menggunakan algoritma bicubic untuk mengubah ukuran gambar secara default, sehingga gambar yang diubah ukurannya akan terlihat bagus di Firefox dan Chrome.

Jika mengatur css widthdan heighttidak berfungsi, Anda mungkin ingin bermain dengan css transform:

Jika karena alasan apa pun Anda perlu menggunakan kanvas, harap perhatikan bahwa ada dua cara mengubah ukuran gambar: dengan mengubah ukuran kanvas dengan css atau dengan menggambar gambar pada ukuran yang lebih kecil.

Lihat pertanyaan ini untuk lebih jelasnya.

Xavi
sumber
5
Sayangnya, mengubah ukuran kanvas atau menggambar gambar dengan ukuran yang lebih kecil tidak akan menyelesaikan masalah (di Chrome).
Nestor
1
Chrome 27 menghasilkan gambar yang diubah ukurannya bagus, tetapi Anda tidak bisa menyalin hasilnya ke kanvas; mencoba melakukannya akan menyalin gambar asli sebagai gantinya.
syockit
4

Untuk mengubah ukuran gambar dengan lebar kurang dari aslinya, saya menggunakan:

    function resize2(i) {
      var cc = document.createElement("canvas");
      cc.width = i.width / 2;
      cc.height = i.height / 2;
      var ctx = cc.getContext("2d");
      ctx.drawImage(i, 0, 0, cc.width, cc.height);
      return cc;
    }
    var cc = img;
    while (cc.width > 64 * 2) {
      cc = resize2(cc);
    }
    // .. than drawImage(cc, .... )

dan itu berfungsi =).

Yaffle
sumber
4

saya mendapatkan gambar ini dengan mengklik kanan elemen kanvas di firefox dan menyimpan sebagai.

teks alternatif

var img = new Image();
img.onload = function () {
    console.debug(this.width,this.height);
    var canvas = document.createElement('canvas'), ctx;
    canvas.width = 188;
    canvas.height = 150;
    document.body.appendChild(canvas);
    ctx = canvas.getContext('2d');
    ctx.drawImage(img,0,0,188,150);
};
img.src = 'original.jpg';

jadi bagaimanapun, ini adalah versi 'tetap' dari contoh Anda:

var img = new Image();
// added cause it wasnt defined
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);

var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
// adding it to the body

document.body.appendChild(canvasCopy);

var copyContext = canvasCopy.getContext("2d");

img.onload = function()
{
        var ratio = 1;

        // defining cause it wasnt
        var maxWidth = 188,
            maxHeight = 150;

        if(img.width > maxWidth)
                ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
                ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        // the line to change
        // ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
        // the method signature you are using is for slicing
        ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
};

// changed for example
img.src = 'original.jpg';
robert
sumber
Saya sudah mencoba melakukan apa yang Anda lakukan dan itu tidak keluar bagus seperti milik Anda. Kecuali saya melewatkan sesuatu, satu-satunya perubahan yang Anda lakukan adalah menggunakan tanda tangan metode penskalaan alih-alih mengiris, kan? Untuk beberapa alasan itu tidak berfungsi untuk saya.
Telanor
3

Masalah dengan beberapa solusi ini adalah mereka mengakses langsung data piksel dan mengulanginya untuk melakukan downsampling. Tergantung pada ukuran gambar, ini bisa menjadi sumber daya yang sangat intensif, dan akan lebih baik untuk menggunakan algoritma internal browser.

Fungsi drawImage () menggunakan metode interpolasi linier, tetangga-tetangga terdekat. Itu bekerja dengan baik ketika Anda tidak mengubah ukuran lebih dari setengah ukuran aslinya .

Jika Anda beralih ke hanya mengubah ukuran maks satu setengah pada satu waktu, hasilnya akan cukup baik, dan jauh lebih cepat daripada mengakses data piksel.

Fungsi ini downsample menjadi setengah sekaligus hingga mencapai ukuran yang diinginkan:

  function resize_image( src, dst, type, quality ) {
     var tmp = new Image(),
         canvas, context, cW, cH;

     type = type || 'image/jpeg';
     quality = quality || 0.92;

     cW = src.naturalWidth;
     cH = src.naturalHeight;

     tmp.src = src.src;
     tmp.onload = function() {

        canvas = document.createElement( 'canvas' );

        cW /= 2;
        cH /= 2;

        if ( cW < src.width ) cW = src.width;
        if ( cH < src.height ) cH = src.height;

        canvas.width = cW;
        canvas.height = cH;
        context = canvas.getContext( '2d' );
        context.drawImage( tmp, 0, 0, cW, cH );

        dst.src = canvas.toDataURL( type, quality );

        if ( cW <= src.width || cH <= src.height )
           return;

        tmp.src = dst.src;
     }

  }
  // The images sent as parameters can be in the DOM or be image objects
  resize_image( $( '#original' )[0], $( '#smaller' )[0] );

Kredit untuk pos ini

Jesús Carrera
sumber
2

Jadi sesuatu yang menarik yang saya temukan beberapa waktu lalu ketika bekerja dengan kanvas yang mungkin bisa membantu:

Untuk mengubah ukuran kontrol kanvas sendiri, Anda perlu menggunakan height=""dan width=""atribut (atau canvas.width/ canvas.heightelemen). Jika Anda menggunakan CSS untuk mengubah ukuran kanvas, itu sebenarnya akan meregangkan (yaitu: mengubah ukuran) konten kanvas agar sesuai dengan kanvas penuh (bukan hanya menambah atau mengurangi area kanvas.

Layak dicoba untuk mencoba menggambar gambar menjadi kontrol kanvas dengan atribut tinggi dan lebar diatur ke ukuran gambar dan kemudian menggunakan CSS untuk mengubah ukuran kanvas ke ukuran yang Anda cari. Mungkin ini akan menggunakan algoritma pengubahan ukuran yang berbeda.

Perlu juga dicatat bahwa kanvas memiliki efek berbeda di browser yang berbeda (dan bahkan versi berbeda dari browser yang berbeda). Algoritme dan teknik yang digunakan di browser cenderung berubah seiring waktu (terutama dengan Firefox 4 dan Chrome 6 yang keluar begitu cepat, yang akan sangat menekankan kinerja rendering kanvas).

Selain itu, Anda mungkin ingin mencobanya juga, karena SVG kemungkinan menggunakan algoritma yang berbeda juga.

Semoga berhasil!

mattbasta
sumber
1
Mengatur lebar atau tinggi kanvas melalui atribut HTML menyebabkan kanvas dihapus, jadi tidak mungkin dilakukan pengubahan ukuran dengan metode itu. Juga, SVG dimaksudkan untuk berurusan dengan gambar matematika. Saya harus bisa menggambar PNG dan semacamnya, sehingga tidak akan membantu saya di luar sana.
Telanor
Mengatur tinggi & lebar kanvas dan mengubah ukuran menggunakan CSS tidak membantu, saya telah menemukan (di Chrome). Bahkan melakukan pengubahan ukuran menggunakan -webkit-transform daripada lebar / tinggi CSS tidak membuat interpolasi berjalan.
Nestor
2

Saya merasa modul yang saya tulis akan menghasilkan hasil yang mirip dengan photoshop, karena mempertahankan data warna dengan membuat rata-rata, bukan menerapkan algoritma. Agak lambat, tapi bagi saya itu yang terbaik, karena menjaga semua data warna.

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

Itu tidak mengambil tetangga terdekat dan menjatuhkan piksel lain, atau mencicipi grup dan mengambil rata-rata acak. Dibutuhkan proporsi yang tepat setiap piksel sumber harus di-output ke piksel tujuan. Warna piksel rata-rata di sumber akan menjadi warna piksel rata-rata di tujuan, yang menurut saya adalah rumus lainnya.

contoh cara menggunakan ada di bagian bawah https://github.com/danschumann/limby-resize

UPDATE OCT 2018 : Dewasa ini contoh saya lebih bersifat akademis daripada yang lain. Webgl cukup banyak 100%, jadi Anda sebaiknya mengubah ukurannya untuk menghasilkan hasil yang serupa, tetapi lebih cepat. PICA.js melakukan ini, saya percaya. -

Funkodebat
sumber
1

Saya mengubah jawaban @ syockit serta pendekatan step-down menjadi layanan Angular yang dapat digunakan kembali untuk siapa saja yang tertarik: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Saya memasukkan kedua solusi karena mereka berdua memiliki pro / kontra sendiri. Pendekatan konvolusi lanczos adalah kualitas yang lebih tinggi dengan biaya lebih lambat, sedangkan pendekatan downcaling langkah-bijaksana menghasilkan hasil yang cukup antialiased 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
sumber
1

Resizer gambar Javascript cepat dan sederhana:

https://github.com/calvintwr/blitz-hermite-resize

const blitz = Blitz.create()

/* Promise */
blitz({
    source: DOM Image/DOM Canvas/jQuery/DataURL/File,
    width: 400,
    height: 600
}).then(output => {
    // handle output
})catch(error => {
    // handle error
})

/* Await */
let resized = await blizt({...})

/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
    // run your callback.
})

Sejarah

Ini benar-benar setelah banyak putaran penelitian, membaca, dan mencoba.

Algoritma resizer menggunakan skrip Hermite @ ViliusL (resizer Hermite benar-benar tercepat dan memberikan output yang cukup baik). Diperpanjang dengan fitur yang Anda butuhkan.

Garpu 1 pekerja untuk melakukan pengubahan ukuran sehingga tidak membekukan browser Anda saat mengubah ukuran, tidak seperti semua pengubah ukuran JS lainnya di luar sana.

Calvintwr
sumber
0

Terima kasih @syockit untuk jawaban yang luar biasa. Namun, saya harus memformat ulang sedikit sebagai berikut untuk membuatnya berfungsi. Mungkin karena masalah pemindaian DOM:

$(document).ready(function () {

$('img').on("load", clickA);
function clickA() {
    var img = this;
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 50, 3);
    document.body.appendChild(canvas);
}

function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width: sx,
        height: Math.round(img.height * sx / img.width)
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(process1, 0, this, 0);
}

//returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function (x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    }
}

process1 = function (self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(process1, 0, self, u);
    else
        setTimeout(process2, 0, self);
};

process2 = function (self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
}
});
Manish
sumber
-1

Saya hanya menjalankan perbandingan halaman berdampingan dan kecuali ada sesuatu yang berubah baru-baru ini, saya tidak bisa melihat perampingan yang lebih baik (penskalaan) menggunakan kanvas vs css sederhana. Saya menguji di FF6 Mac OSX 10.7. Masih agak lunak vs aslinya.

Namun saya menemukan sesuatu yang membuat perbedaan besar dan menggunakan filter gambar di browser yang mendukung kanvas. Anda benar-benar dapat memanipulasi gambar seperti yang Anda bisa di Photoshop dengan blur, mempertajam, saturasi, riak, skala abu-abu, dll.

Saya kemudian menemukan plug-in jQuery yang luar biasa yang menjadikan aplikasi filter ini mudah: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

Saya cukup menerapkan filter mempertajam setelah mengubah ukuran gambar yang seharusnya memberi Anda efek yang diinginkan. Saya bahkan tidak perlu menggunakan elemen kanvas.

Julian Dormon
sumber
-1

Mencari solusi sederhana yang bagus?

var img=document.createElement('img');
img.src=canvas.toDataURL();
$(img).css("background", backgroundColor);
$(img).width(settings.width);
$(img).height(settings.height);

Solusi ini akan menggunakan algoritma pengubahan ukuran browser! :)

ale500
sumber
Pertanyaannya adalah tentang downsampling gambar, bukan hanya mengubah ukurannya.
Jesús Carrera
[...] Saya mencoba mengubah ukuran jpg. Saya telah mencoba mengubah ukuran jpg yang sama di situs yang ditautkan dan di photoshop, dan itu terlihat bagus ketika dirampingkan. [...] mengapa Anda tidak dapat menggunakan <img> Jesus Carrera?
ale500