Thay đổi kích thước hình ảnh trong khung vẽ HTML5


314

Tôi đang cố gắng tạo một hình ảnh thu nhỏ ở phía máy khách bằng cách sử dụng javascript và phần tử canvas, nhưng khi tôi thu nhỏ hình ảnh xuống, nó trông rất tệ. Trông như thể nó bị thu nhỏ trong photoshop với bộ lấy mẫu được đặt thành 'Hàng xóm gần nhất' thay vì bicubic. Tôi biết có thể làm cho điều này trở nên đúng, bởi vì trang web này có thể làm điều đó tốt chỉ bằng cách sử dụng một khung vẽ. Tôi đã thử sử dụng cùng một mã mà họ làm như được hiển thị trong liên kết "[Nguồn]", nhưng nó vẫn trông rất tệ. Có cái gì tôi đang thiếu, một số cài đặt cần phải được đặt hoặc một cái gì đó?

BIÊN TẬP:

Tôi đang cố gắng thay đổi kích thước một jpg. Tôi đã thử thay đổi kích thước jpg tương tự trên trang web được liên kết và trong photoshop, và nó trông ổn khi thu nhỏ.

Đây là mã có liên quan:

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:

Có vẻ như tôi đã nhầm, trang web được liên kết đã không làm tốt hơn công việc thu nhỏ hình ảnh. Tôi đã thử các phương pháp khác được đề xuất và không có phương pháp nào tốt hơn. Đây là những gì các phương pháp khác nhau dẫn đến:

Photoshop:

văn bản thay thế

Tranh sơn dầu:

văn bản thay thế

Hình ảnh với kết xuất hình ảnh: tối ưu hóa được thiết lập và thu nhỏ theo chiều rộng / chiều cao:

văn bản thay thế

Hình ảnh với kết xuất hình ảnh: tối ưu hóa được thiết lập và thu nhỏ với -moz-Transform:

văn bản thay thế

Canvas thay đổi kích thước trên pixastic:

văn bản thay thế

Tôi đoán điều này có nghĩa là firefox không sử dụng lấy mẫu bicubic như được yêu cầu. Tôi sẽ phải đợi cho đến khi họ thực sự thêm nó.

EDIT3:

Ảnh gốc


Bạn có thể đăng mã bạn đang sử dụng để thay đổi kích thước hình ảnh không?
Xavi

Bạn đang cố gắng thay đổi kích thước hình ảnh GIF hoặc hình ảnh tương tự với bảng màu hạn chế? Ngay cả trong photoshop, những hình ảnh này cũng không giảm xuống trừ khi bạn chuyển đổi chúng sang RGB.
leepowers

Bạn có thể gửi một bản sao của hình ảnh gốc?
Xavi

Thay đổi kích thước hình ảnh bằng cách sử dụng javascript là một chút ít - bạn không chỉ sử dụng sức mạnh xử lý của khách hàng để thay đổi kích thước hình ảnh, mà bạn đang thực hiện nó trên mỗi lần tải trang. Tại sao không chỉ lưu một phiên bản thu nhỏ từ photoshop và phục vụ nó thay thế / song song với hình ảnh gốc?
xác định

29
Bởi vì tôi đang tạo một trình tải lên hình ảnh với khả năng thay đổi kích thước và cắt hình ảnh trước khi tải chúng lên.
Telanor

Câu trả lời:


393

Vậy bạn sẽ làm gì nếu tất cả các trình duyệt (thực ra, Chrome 5 đã cho tôi một trình duyệt khá tốt) sẽ không cung cấp cho bạn chất lượng lấy mẫu đủ tốt? Bạn tự thực hiện chúng rồi! Ôi thôi, chúng ta đang bước vào thời đại mới của Web 3.0, trình duyệt tuân thủ HTML5, trình biên dịch javascript JIT siêu tối ưu, máy đa lõi (), với hàng tấn bộ nhớ, bạn sợ điều gì? Này, có từ java trong javascript, vì vậy nó sẽ đảm bảo hiệu suất, phải không? Kìa, mã tạo hình thu nhỏ:

// 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";
};

... Mà bạn có thể tạo ra kết quả như thế này!

img717.imageshack.us/img717/8910/lanczos58.png

vì vậy dù sao, đây là phiên bản 'cố định' của ví dụ của bạn:

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

Bây giờ là lúc để loại bỏ các trình duyệt tốt nhất của bạn ra khỏi đó và xem cái nào ít có khả năng làm tăng huyết áp của khách hàng của bạn!

Umm, thẻ mỉa mai của tôi đâu?

(vì nhiều phần của mã dựa trên Anrieff Gallery Generator nên nó cũng nằm trong GPL2? I dunno)

thực do hạn chế về javascript, đa lõi không được hỗ trợ.


2
Tôi đã thực sự cố gắng tự thực hiện nó, làm như bạn đã làm, sao chép mã từ trình chỉnh sửa hình ảnh nguồn mở. Vì tôi không thể tìm thấy bất kỳ tài liệu vững chắc nào về thuật toán, tôi đã có một thời gian khó khăn để tối ưu hóa nó. Cuối cùng, tôi khá chậm chạp (mất vài giây để thay đổi kích thước hình ảnh). Khi tôi có cơ hội, tôi sẽ thử của bạn và xem nó có nhanh hơn không. Và tôi nghĩ rằng các webworkers tạo javascript đa lõi có thể bây giờ. Tôi sẽ thử sử dụng chúng để tăng tốc, nhưng tôi gặp khó khăn khi tìm cách biến nó thành một thuật toán đa luồng
Telanor

3
Xin lỗi, quên điều đó! Tôi đã chỉnh sửa trả lời. Dù sao nó cũng sẽ không nhanh, bicubic nên nhanh hơn. Chưa kể thuật toán tôi sử dụng không phải là thay đổi kích thước 2 chiều thông thường (là theo từng dòng, ngang rồi dọc), do đó, nó chậm hơn.
syockit

5
Bạn thật tuyệt vời và xứng đáng với hàng tấn sự tuyệt vời.
Rocklan

5
Điều này tạo ra kết quả tốt, nhưng mất 7,4 giây cho hình ảnh 1,8 MP trong phiên bản Chrome mới nhất ...
mpen 16/213

2
Làm thế nào các phương pháp như thế này quản lý để đạt điểm cao như vậy ?? Giải pháp hiển thị hoàn toàn không tính đến thang đo logarit được sử dụng để lưu trữ thông tin màu. Một RGB có 127.127.127 là một phần tư độ sáng của 255, 255, 255 không bằng một nửa. Việc lấy mẫu xuống trong dung dịch dẫn đến hình ảnh bị tối. Xấu hổ vì điều này đã bị đóng vì có một phương pháp rất đơn giản và nhanh chóng để giảm kích thước tạo ra kết quả thậm chí còn tốt hơn so với mẫu Photoshop (OP phải có các tùy chọn được đặt sai) được đưa ra
Blindman67

37

Thuật toán thay đổi kích thước / thay đổi hình ảnh nhanh bằng bộ lọc Hermite với JavaScript. Hỗ trợ minh bạch, cho chất lượng tốt. Xem trước:

nhập mô tả hình ảnh ở đây

Cập nhật : phiên bản 2.0 được thêm trên GitHub (nhanh hơn, nhân viên web + đối tượng có thể chuyển nhượng). Cuối cùng tôi đã làm cho nó hoạt động!

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

Có lẽ bạn có thể bao gồm các liên kết đến bản demo miniPaint và repo Github của bạn?
syockit

1
Bạn cũng sẽ chia sẻ phiên bản webworkers chứ? Có lẽ do cài đặt quá cao, nó chậm hơn đối với các hình ảnh nhỏ, nhưng nó có thể hữu ích cho các hình ảnh nguồn lớn hơn.
syockit

thêm bản demo, liên kết git, cũng là phiên bản đa lõi. Btw tôi đã không dành quá nhiều thời gian để tối ưu hóa phiên bản đa lõi ... Phiên bản đơn tôi tin là được tối ưu hóa tốt.
ViliusL

Sự khác biệt lớn và hiệu suất tốt. Cảm ơn rât nhiều! trướcsau
KevBurnsJr

2
@ViliusL Bây giờ tôi đã nhớ tại sao nhân viên web không làm việc tốt như vậy. Họ đã không chia sẻ bộ nhớ trước đó và bây giờ vẫn chưa có nó! Có thể một ngày nào đó khi họ quản lý để sắp xếp nó, mã của bạn sẽ được sử dụng (điều đó hoặc có thể mọi người sử dụng PNaCl thay thế)
syockit

26

Hãy thử pica - đó là một trình khôi phục được tối ưu hóa cao với các đại số có thể lựa chọn. Xem bản demo .

Ví dụ: hình ảnh gốc từ bài đăng đầu tiên được thay đổi kích thước trong 120ms với bộ lọc Lanczos và cửa sổ 3px hoặc 60ms với bộ lọc Box và cửa sổ 0,5px. Đối với hình ảnh lớn 17mb, 5000x3000px thay đổi kích thước mất ~ 1 giây trên máy tính để bàn và 3 giây trên thiết bị di động.

Tất cả các nguyên tắc thay đổi kích thước đã được mô tả rất tốt trong chủ đề này và pica không thêm khoa học tên lửa. Nhưng nó được tối ưu hóa rất tốt cho JIT-s hiện đại và sẵn sàng sử dụng ngoài hộp (thông qua npm hoặc bower). Ngoài ra, nó sử dụng webworkers khi có sẵn để tránh đóng băng giao diện.

Tôi cũng có kế hoạch sớm thêm hỗ trợ mặt nạ unsharp, vì nó rất hữu ích sau khi hạ cấp.


14

Tôi biết đây là một chủ đề cũ nhưng nó có thể hữu ích cho một số người như bản thân tôi rằng nhiều tháng sau khi gặp vấn đề này lần đầu tiên.

Dưới đây là một số mã thay đổi kích thước hình ảnh mỗi khi bạn tải lại hình ảnh. Tôi biết điều này không tối ưu chút nào, nhưng tôi cung cấp nó như một bằng chứng về khái niệm.

Ngoài ra, xin lỗi vì đã sử dụng jQuery cho các bộ chọn đơn giản nhưng tôi cảm thấy quá thoải mái với cú pháp.

$(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>

Hàm createdImage của tôi được gọi một lần khi tài liệu được tải và sau đó nó được gọi mỗi khi cửa sổ nhận được sự kiện thay đổi kích thước.

Tôi đã thử nghiệm nó trong Chrome 6 và Firefox 3.6, cả trên Mac. "Kỹ thuật" này ăn bộ xử lý như thể nó là kem vào mùa hè, nhưng nó thực hiện các mẹo.


9

Tôi đã đưa ra một số thuật toán để thực hiện phép nội suy hình ảnh trên các mảng pixel canvas html có thể hữu ích ở đây:

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

Chúng có thể được sao chép / dán và có thể được sử dụng bên trong nhân viên web để thay đổi kích thước hình ảnh (hoặc bất kỳ thao tác nào khác yêu cầu nội suy - tôi đang sử dụng chúng để làm mất hình ảnh tại thời điểm này).

Tôi chưa thêm các công cụ lanczos ở trên, vì vậy hãy thoải mái thêm nó vào để so sánh nếu bạn muốn.


6

Đây là chức năng javascript được điều chỉnh từ mã của @ Telanor. Khi truyền một hình ảnh base64 làm đối số đầu tiên cho hàm, nó sẽ trả về base64 của hình ảnh đã thay đổi kích thước. maxWidth và maxHeight là tùy chọn.

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

}

Cách tiếp cận của bạn rất nhanh nhưng nó tạo ra một hình ảnh mờ như bạn có thể thấy ở đây: stackoverflow.com/questions/18922880/
mẹo

6

Tôi thực sự khuyên bạn nên kiểm tra liên kết này và đảm bảo rằng nó được đặt thành đúng.

Kiểm soát hành vi chia tỷ lệ hình ảnh

Được giới thiệu trong Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 đã giới thiệu thuộc tính mozImageSmoothingEnables cho thành phần canvas; nếu giá trị Boolean này là sai, hình ảnh sẽ không được làm mịn khi thu nhỏ. Thuộc tính này là đúng theo mặc định. xem đồng bằng?

  1. cx.mozImageSmoothingEnables = false;

5

Nếu bạn chỉ đơn giản là cố gắng thay đổi kích thước hình ảnh, tôi khuyên bạn nên cài đặt widthheighthình ảnh bằng CSS. Đây là một ví dụ nhanh:

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

Lưu ý rằng heightwidthcũng có thể được đặt bằng JavaScript. Đây là mẫu mã nhanh:

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

Ngoài ra, để đảm bảo rằng hình ảnh đã thay đổi kích thước trông đẹp, hãy thêm các quy tắc css sau vào bộ chọn hình ảnh:

Theo như tôi có thể nói, tất cả các trình duyệt ngoại trừ IE sử dụng thuật toán bicubic để thay đổi kích thước hình ảnh theo mặc định, vì vậy hình ảnh đã thay đổi kích thước của bạn sẽ trông tốt trong Firefox và Chrome.

Nếu cài đặt css widthheightkhông hoạt động, bạn có thể muốn chơi với một css transform:

Nếu vì bất kỳ lý do gì bạn cần sử dụng canvas, xin lưu ý rằng có hai cách có thể thay đổi kích thước hình ảnh: bằng cách thay đổi kích thước canvas bằng css hoặc bằng cách vẽ hình ảnh ở kích thước nhỏ hơn.

Xem câu hỏi này để biết thêm chi tiết.


5
Không thay đổi kích thước khung vẽ cũng như vẽ hình ảnh ở kích thước nhỏ hơn sẽ giải quyết vấn đề (trong Chrome).
Nestor

1
Chrome 27 tạo ra hình ảnh có kích thước đẹp, nhưng bạn không thể sao chép kết quả vào khung vẽ; thay vào đó, cố gắng làm như vậy sẽ sao chép hình ảnh gốc.
syockit

4

Để thay đổi kích thước thành hình ảnh với chiều rộng nhỏ hơn bản gốc, tôi sử dụng:

    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, .... )

và nó hoạt động =).


4

tôi đã có được hình ảnh này bằng cách nhấp chuột phải vào phần tử canvas trong firefox và lưu dưới dạng.

văn bản thay thế

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

vì vậy dù sao, đây là phiên bản 'cố định' của ví dụ của bạn:

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

Tôi đã thử làm những gì bạn đã làm và nó không thành công như của bạn. Trừ khi tôi bỏ lỡ điều gì đó, thay đổi duy nhất bạn đã thực hiện là sử dụng chữ ký phương pháp chia tỷ lệ thay vì cắt lát, phải không? Đối với một số lý do nó không làm việc cho tôi.
Telanor

3

Vấn đề với một số giải pháp này là họ truy cập trực tiếp dữ liệu pixel và lặp qua nó để thực hiện lấy mẫu xuống. Tùy thuộc vào kích thước của hình ảnh, điều này có thể rất tốn tài nguyên và tốt hơn là sử dụng các thuật toán bên trong của trình duyệt.

Hàm drawImage () đang sử dụng phương pháp tái định tuyến tuyến tính, phương pháp tái định tuyến lân cận gần nhất. Điều đó hoạt động tốt khi bạn không thay đổi kích thước xuống hơn một nửa kích thước ban đầu .

Nếu bạn lặp lại để chỉ thay đổi kích thước tối đa một nửa tại một thời điểm, kết quả sẽ khá tốt và nhanh hơn nhiều so với truy cập dữ liệu pixel.

Hàm này giảm xuống còn một nửa cho đến khi đạt được kích thước mong muốn:

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

Tín dụng cho bài viết này


2

Vì vậy, một điều thú vị mà tôi đã tìm thấy cách đây một thời gian khi làm việc với canvas có thể hữu ích:

Để tự thay đổi kích thước điều khiển canvas, bạn cần sử dụng các thuộc tính height=""width=""(hoặc canvas.width/ canvas.heightphần tử). Nếu bạn sử dụng CSS để thay đổi kích thước khung vẽ, nó sẽ thực sự kéo dài (tức là: thay đổi kích thước) nội dung của khung vẽ để phù hợp với khung vẽ đầy đủ (thay vì chỉ tăng hoặc giảm diện tích của khung vẽ.

Sẽ rất đáng để thử vẽ hình ảnh vào điều khiển canvas với các thuộc tính chiều cao và chiều rộng được đặt thành kích thước của hình ảnh và sau đó sử dụng CSS để thay đổi kích thước của khung vẽ theo kích thước bạn đang tìm kiếm. Có lẽ điều này sẽ sử dụng một thuật toán thay đổi kích thước khác nhau.

Cũng cần lưu ý rằng canvas có các hiệu ứng khác nhau trong các trình duyệt khác nhau (và thậm chí các phiên bản khác nhau của các trình duyệt khác nhau). Các thuật toán và kỹ thuật được sử dụng trong các trình duyệt có thể sẽ thay đổi theo thời gian (đặc biệt là với Firefox 4 và Chrome 6 sắp ra mắt, điều này sẽ tập trung nhiều vào hiệu suất kết xuất canvas).

Ngoài ra, bạn cũng có thể muốn cung cấp cho SVG một shot, vì nó cũng có thể sử dụng một thuật toán khác.

May mắn nhất!


1
Đặt chiều rộng hoặc chiều cao của khung vẽ thông qua các thuộc tính HTML làm cho khung vẽ bị xóa, do đó không thể thay đổi kích thước được thực hiện với phương thức đó. Ngoài ra, SVG có nghĩa là để xử lý các hình ảnh toán học. Tôi cần có khả năng vẽ PNG và như vậy, để không giúp tôi ngoài đó.
Telanor

Đặt chiều cao và chiều rộng của khung vẽ và thay đổi kích thước bằng CSS không có ích, tôi đã tìm thấy (trong Chrome). Ngay cả khi thực hiện thay đổi kích thước bằng cách sử dụng -webkit-Transform thay vì chiều rộng / chiều cao CSS cũng không thực hiện được phép nội suy.
Nestor

2

Tôi có cảm giác mô-đun tôi đã viết sẽ tạo ra kết quả tương tự như photoshop, vì nó bảo tồn dữ liệu màu bằng cách lấy trung bình chúng, không áp dụng thuật toán. Nó hơi chậm, nhưng với tôi nó là tốt nhất, vì nó bảo toàn tất cả dữ liệu màu.

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

Nó không lấy hàng xóm gần nhất và thả các pixel khác, hoặc lấy mẫu một nhóm và lấy trung bình ngẫu nhiên. Nó lấy tỷ lệ chính xác mỗi pixel nguồn sẽ xuất ra pixel đích. Màu pixel trung bình trong nguồn sẽ là màu pixel trung bình ở đích, mà những công thức khác này, tôi nghĩ chúng sẽ không có.

một ví dụ về cách sử dụng nằm ở cuối https://github.com/danschumann/limby-resize

CẬP NHẬT THÁNG 10 NĂM 2018 : Những ngày này, ví dụ của tôi mang tính học thuật hơn bất kỳ điều gì khác. Webgl khá nhiều 100%, vì vậy bạn nên thay đổi kích thước với điều đó để tạo ra kết quả tương tự, nhưng nhanh hơn. PICA.js làm điều này, tôi tin. -


1

Tôi đã chuyển đổi câu trả lời của @ syockit cũng như cách tiếp cận từ chối thành dịch vụ Angular có thể sử dụng lại cho bất kỳ ai quan tâm: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Tôi bao gồm cả hai giải pháp vì cả hai đều có ưu / nhược điểm riêng. Phương pháp tích chập lanczos có chất lượng cao hơn với chi phí chậm hơn, trong khi phương pháp thu hẹp thông minh từng bước tạo ra kết quả khử răng cưa hợp lý và nhanh hơn đáng kể.

Ví dụ sử dụng:

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

1

Trình khôi phục hình ảnh Javascript nhanh và đơn giản:

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

Lịch sử

Đây thực sự là sau nhiều vòng nghiên cứu, đọc và thử.

Thuật toán resizer sử dụng tập lệnh Hermite của @ ViliusL (Trình khôi phục Hermite thực sự là nhanh nhất và cho đầu ra khá tốt). Mở rộng với các tính năng bạn cần.

Fork 1 worker để thực hiện thay đổi kích thước để nó không đóng băng trình duyệt của bạn khi thay đổi kích thước, không giống như tất cả các trình thay đổi JS khác ngoài đó.


0

Cảm ơn @syockit cho một câu trả lời tuyệt vời. tuy nhiên, tôi đã phải định dạng lại một chút như sau để làm cho nó hoạt động. Có lẽ do vấn đề quét 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";
}
});

-1

Tôi chỉ chạy một trang so sánh cạnh nhau và trừ khi có gì đó thay đổi gần đây, tôi không thể thấy giảm kích thước (tỷ lệ) tốt hơn bằng cách sử dụng canvas so với css đơn giản. Tôi đã thử nghiệm trong FF6 Mac OSX 10.7. Vẫn hơi mềm so với bản gốc.

Tuy nhiên, tôi đã vấp phải thứ gì đó tạo ra sự khác biệt lớn và đó là sử dụng các bộ lọc hình ảnh trong các trình duyệt hỗ trợ canvas. Bạn thực sự có thể điều khiển hình ảnh giống như trong Photoshop với độ mờ, làm sắc nét, bão hòa, gợn, thang độ xám, v.v.

Sau đó, tôi đã tìm thấy một trình cắm jQuery tuyệt vời giúp ứng dụng của các bộ lọc này nhanh chóng: http://codecanyon.net/item/jsmanipulation-jquery-image-manipulation-plugin/428234

Tôi chỉ cần áp dụng bộ lọc làm sắc nét ngay sau khi thay đổi kích thước hình ảnh sẽ mang lại cho bạn hiệu ứng mong muốn. Tôi thậm chí không phải sử dụng một yếu tố vải.


-1

Tìm kiếm một giải pháp đơn giản tuyệt vời?

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

Giải pháp này sẽ sử dụng các thuật toán thay đổi kích thước của trình duyệt! :)


Câu hỏi là về downsampling hình ảnh, không chỉ thay đổi kích thước nó.
Jesús Carrera

[...] Tôi đang cố gắng thay đổi kích thước một jpg. Tôi đã thử thay đổi kích thước jpg tương tự trên trang web được liên kết và trong photoshop, và nó có vẻ ổn khi thu nhỏ. [...] tại sao bạn không thể sử dụng <img> Jesus Carrera?
ale500
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.