Thay đổi kích thước hình ảnh bằng canvas javascript (trơn tru)


90

Tôi đang cố gắng thay đổi kích thước một số hình ảnh bằng canvas nhưng tôi không biết cách làm mịn chúng. Trên photoshop, các trình duyệt, vv .. có một vài thuật toán họ sử dụng (ví dụ: bicubic, bilinear) nhưng tôi không biết liệu chúng có được tích hợp vào canvas hay không.

Đây là trò chơi của tôi: 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);

Thẻ đầu tiên là thẻ hình ảnh đã thay đổi kích thước bình thường và thẻ thứ hai là canvas. Chú ý làm thế nào một trong những canvas không được mịn. Làm thế nào tôi có thể đạt được 'sự trơn tru'?

Câu trả lời:


136

Bạn có thể sử dụng bước xuống để đạt được kết quả tốt hơn. Hầu hết các trình duyệt dường như sử dụng nội suy tuyến tính thay vì hai khối khi thay đổi kích thước hình ảnh.

( Cập nhật Đã thêm một thuộc tính chất lượng vào thông số kỹ thuật, imageSmoothingQualityhiện chỉ có sẵn trong Chrome.)

Trừ khi ai đó chọn không làm mịn hoặc láng giềng gần nhất, trình duyệt sẽ luôn nội suy hình ảnh sau khi thu nhỏ nó vì chức năng này như một bộ lọc thông thấp để tránh răng cưa.

Nhị tuyến sử dụng 2x2 pixel để thực hiện nội suy trong khi nhị phân sử dụng 4x4, do đó, bằng cách thực hiện nó theo các bước, bạn có thể gần với kết quả nhị phân trong khi sử dụng nội suy hai tuyến tính như được thấy trong hình ảnh kết quả.

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>

Tùy thuộc vào mức độ quyết liệt của bạn thay đổi kích thước, bạn có thể bỏ qua bước 2 nếu sự khác biệt ít hơn.

Trong bản demo, bạn có thể thấy kết quả mới bây giờ giống với phần tử hình ảnh.


1
@steve heh, đôi khi những điều này xảy ra :) Đối với hình ảnh, bạn thường có thể ghi đè điều này bằng cách đặt css BTW.

Ken, kết quả đầu tiên rất tốt nhưng khi tôi thay đổi hình ảnh, bạn có thể thấy nó quá mờ jsfiddle.net/kcHLG Có thể làm gì trong trường hợp này và những trường hợp khác?
steve

@steve bạn có thể giảm số bước xuống chỉ 1 hoặc không (đối với một số hình ảnh, điều này hoạt động tốt). Xem thêm câu trả lời này tương tự như câu trả lời này nhưng ở đây tôi đã thêm một tích chập làm sắc nét vào nó để bạn có thể làm cho hình ảnh thu được sắc nét hơn sau khi nó đã được giảm tỷ lệ.

1
@steve đây là một fiddle sửa đổi với Bill chỉ sử dụng một bước thêm: jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic mã là phần tiếp theo của mã OP. Nếu bạn mở fiddle, bạn sẽ thấy ctx được xác định. Tôi đã gạch đầu dòng nó ở đây để tránh hiểu lầm.

27

fiddle của Trung Le Nguyen Nhat hoàn toàn không đúng (nó chỉ sử dụng hình ảnh gốc ở bước cuối cùng)
nên tôi đã viết fiddle tổng quát của riêng mình để so sánh hiệu suất:

VĨ CẦM

Về cơ bản đó là:

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

Câu trả lời được đánh giá thấp nhất từng thấy.
Amsakanna

17

Tôi đã tạo một dịch vụ Angular có thể tái sử dụng để xử lý thay đổi kích thước hình ảnh / canvas chất lượng cao cho bất kỳ ai quan tâm: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

Dịch vụ này bao gồm 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í là chậm hơn, trong khi phương pháp giảm tỉ lệ từng bước tạo ra kết quả chống 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
    })
})

Xin lỗi về điều đó - tôi đã thay đổi tên người dùng github của mình. Vừa cập nhật liên kết gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2

3
Tôi thấy những góc chữ, tôi có cảm giác hài hước
SuperUberDuper

8

Mặc dù một số đoạn mã ngắn và hiệu quả, nhưng chúng không hề tầm thường để làm theo và hiểu được.

Vì tôi không phải là người thích "sao chép-dán" từ stack-tràn, tôi muốn các nhà phát triển hiểu mã mà họ đang đẩy vào phần mềm của họ, hy vọng bạn sẽ thấy những điều dưới đây hữu ích.

DEMO : Thay đổi kích thước hình ảnh với JS và HTML Canvas Demo fiddler.

Bạn có thể tìm thấy 3 phương pháp khác nhau để thực hiện việc thay đổi kích thước này, điều này sẽ giúp bạn hiểu cách mã hoạt động và tại sao.

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

Bạn có thể tìm thấy mã đầy đủ của cả bản demo và phương thức TypeScript mà bạn có thể muốn sử dụng trong mã của mình trong dự án GitHub.

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

Đây là mã cuối cùng:

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

Tôi đã tạo một thư viện cho phép bạn giảm tỷ lệ phần trăm trong khi vẫn giữ tất cả dữ liệu màu.

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

Tệp đó bạn có thể đưa vào trình duyệt. Kết quả sẽ giống như photoshop hoặc hình ảnh ảo thuật, bảo toàn tất cả dữ liệu màu, lấy điểm ảnh trung bình, thay vì lấy những cái gần đó và bỏ những cái khác. Nó không sử dụng công thức để đoán giá trị trung bình, nó lấy giá trị trung bình chính xác.


1
Tôi có lẽ sẽ sử dụng WebGL để thay đổi kích cỡ hiện nay
Funkodebat

4

Dựa trên câu trả lời K3N, tôi thường viết lại mã cho bất kỳ ai muốn

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

CẬP NHẬT JSFIDDLE DEMO

Đây là DEMO TRỰC TUYẾN của tôi


2
Điều này sẽ không hoạt động: mỗi khi bạn thay đổi kích thước canvas, nó sẽ xóa ngữ cảnh của nó. Bạn cần 2 tấm bạt. Ở đây, nó giống như trực tiếp gọi drawImage với các kích thước cuối cùng.
Kaiido

2

Tôi không hiểu tại sao không ai gợi ý createImageBitmap.

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

hoạt động đẹp (giả sử bạn đặt id cho hình ảnh và canvas).


Bởi vì nó không được hỗ trợ rộng rãi nên caniuse.com/#search=createImageBitmap
Matt

createImageBitmap được hỗ trợ cho 73% tất cả người dùng. Tùy thuộc vào trường hợp sử dụng của bạn, nó có thể đủ tốt. Chỉ là Safari từ chối thêm hỗ trợ cho nó. Tôi nghĩ đó là một giải pháp khả thi.
cagdas_ucar

Giải pháp tuyệt vời, nhưng rất tiếc nó không hoạt động trên firefox
vcarel

1

Tôi đã viết tiện ích js nhỏ để cắt và thay đổi kích thước hình ảnh trên giao diện người dùng. Đây là liên kết về dự án GitHub. Ngoài ra, bạn có thể lấy blob từ hình ảnh cuối cùng để gửi nó.

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