ปรับขนาดภาพด้วยผ้าใบจาวาสคริปต์ (อย่างราบรื่น)


90

ฉันกำลังพยายามปรับขนาดภาพด้วยแคนวาส แต่ฉันไม่รู้ว่าจะทำอย่างไรให้เรียบ ใน photoshop เบราว์เซอร์และอื่น ๆ มีอัลกอริทึมบางอย่างที่พวกเขาใช้ (เช่น bicubic, bilinear) แต่ฉันไม่รู้ว่าสิ่งเหล่านี้ถูกสร้างขึ้นในผืนผ้าใบหรือไม่

นี่คือซอของฉัน: http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

อันแรกคือแท็กรูปภาพที่ปรับขนาดตามปกติและอันที่สองคือแคนวาส สังเกตว่าผ้าใบไม่เรียบเท่าไหร่ ฉันจะบรรลุ 'ความราบรื่น' ได้อย่างไร?

คำตอบ:


136

คุณสามารถใช้การก้าวลงเพื่อให้ได้ผลลัพธ์ที่ดีขึ้น เบราว์เซอร์ส่วนใหญ่ดูเหมือนจะใช้การแก้ไขเชิงเส้นแทนที่จะเป็นไบ - คิวบ์ในการปรับขนาดรูปภาพ

( อัปเดตมีการเพิ่มคุณสมบัติคุณภาพในข้อกำหนดimageSmoothingQualityซึ่งขณะนี้มีให้บริการใน Chrome เท่านั้น)

เว้นแต่ว่าใครจะเลือกไม่เรียบหรือเพื่อนบ้านที่ใกล้ที่สุดเบราว์เซอร์จะแก้ไขภาพหลังจากลดขนาดลงเสมอเนื่องจากฟังก์ชันนี้เป็นตัวกรองความถี่ต่ำเพื่อหลีกเลี่ยงการใช้นามแฝง

Bi-linear ใช้ 2x2 พิกเซลเพื่อทำการแก้ไขในขณะที่ bi-cubic ใช้ 4x4 ดังนั้นการทำตามขั้นตอนคุณจะได้ผลลัพธ์ใกล้เคียงกับ bi-cubic ในขณะที่ใช้การแก้ไขแบบ bi-linear ดังที่เห็นในภาพที่ได้

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

ขึ้นอยู่กับว่าการปรับขนาดของคุณรุนแรงเพียงใดคุณสามารถข้ามขั้นตอนที่ 2 ได้หากความแตกต่างน้อยกว่า

ในการสาธิตคุณจะเห็นผลลัพธ์ใหม่ที่คล้ายกับองค์ประกอบภาพมาก


1
@steve heh บางครั้งสิ่งเหล่านี้ก็เกิดขึ้น :) สำหรับภาพคุณสามารถลบล้างสิ่งนี้ได้โดยการตั้งค่า css BTW

เคนผลงานแรกใช้งานได้ดี แต่เมื่อฉันเปลี่ยนภาพคุณจะเห็นว่ามันเบลอเกินไปjsfiddle.net/kcHLGกรณีนี้และอื่น ๆ สามารถทำอะไรได้บ้าง?
สตีฟ

@steve คุณสามารถลดจำนวนขั้นตอนลงเหลือเพียง 1 หรือไม่มีเลย (สำหรับบางภาพก็ใช้ได้ดี) ดูคำตอบที่คล้ายกับคำตอบนี้ แต่ที่นี่ฉันได้เพิ่มการปรับความคมชัดลงไปเพื่อให้คุณสามารถทำให้ภาพที่ได้มีความคมชัดยิ่งขึ้นหลังจากลดขนาดแล้ว

1
@steve นี่คือซอที่แก้ไขด้วย Bill โดยใช้ขั้นตอนพิเศษเพียงขั้นตอนเดียว: jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic รหัสเป็นรหัสต่อเนื่องของ OPs หากคุณเปิดซอคุณจะเห็น ctx ถูกกำหนดไว้ ฉันได้ระบุไว้ที่นี่เพื่อหลีกเลี่ยงความเข้าใจผิด

29

เนื่องจากซอของ Trung Le Nguyen Nhatไม่ถูกต้องเลย (ใช้เพียงภาพต้นฉบับในขั้นตอนสุดท้าย)
ฉันจึงเขียนซอทั่วไปของตัวเองพร้อมการเปรียบเทียบประสิทธิภาพ:

ซอ

โดยทั่วไปคือ:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}

คำตอบที่ประเมินต่ำที่สุดเท่าที่เคยเห็นมา
อมสะคันนา

17

ฉันสร้างบริการ Angular ที่ใช้ซ้ำได้เพื่อจัดการการปรับขนาดภาพ / ผืนผ้าใบคุณภาพสูงสำหรับทุกคนที่สนใจ: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

บริการนี้มีสองโซลูชันเนื่องจากทั้งคู่มีข้อดี / ข้อเสียของตัวเอง แนวทางการแปลงของ lanczos มีคุณภาพสูงกว่าในราคาที่ช้าลงในขณะที่วิธีการลดขนาดแบบขั้นตอนจะให้ผลลัพธ์ที่มีการลดรอยหยักที่สมเหตุสมผลและเร็วกว่าอย่างมีนัยสำคัญ

ตัวอย่างการใช้งาน:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})

ขออภัย - ฉันได้เปลี่ยนชื่อผู้ใช้ github แล้ว เพิ่งอัปเดตลิงก์gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2

3
ฉันเห็นคำว่าเชิงมุมฉันรู้สึกตลก
SuperUberDuper

8

แม้ว่าข้อมูลโค้ดบางส่วนจะสั้นและใช้งานได้ แต่ก็ไม่สำคัญที่จะติดตามและทำความเข้าใจ

เนื่องจากฉันไม่ใช่แฟนของ "copy-paste" จาก stack-overflow ฉันจึงอยากให้นักพัฒนาเข้าใจโค้ดที่พวกเขาถูกผลักเข้าไปในซอฟต์แวร์หวังว่าคุณจะพบว่าด้านล่างมีประโยชน์

DEMO : การปรับขนาดภาพด้วย JS และ HTML Canvas Demo fiddler

คุณอาจพบวิธีการปรับขนาดได้ 3 วิธีซึ่งจะช่วยให้คุณเข้าใจว่าโค้ดทำงานอย่างไรและทำไม

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

โค้ดทั้งหมดของทั้งการสาธิตและเมธอด TypeScript ที่คุณอาจต้องการใช้ในโค้ดของคุณสามารถพบได้ในโครงการ GitHub

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

นี่คือรหัสสุดท้าย:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the dize by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}

4

ฉันสร้างไลบรารีที่ช่วยให้คุณลดขั้นตอนเปอร์เซ็นต์ใดก็ได้ในขณะที่เก็บข้อมูลสีทั้งหมด

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

ไฟล์นั้นคุณสามารถรวมไว้ในเบราว์เซอร์ ผลลัพธ์จะดูเหมือน photoshop หรือ image magick โดยจะรักษาข้อมูลสีทั้งหมดโดยใช้ค่าพิกเซลเฉลี่ยแทนที่จะถ่ายภาพที่อยู่ใกล้ ๆ แล้วทิ้งคนอื่น ๆ ไม่ได้ใช้สูตรในการเดาค่าเฉลี่ย แต่ใช้ค่าเฉลี่ยที่แน่นอน


1
ฉันคงใช้ webgl เพื่อปรับขนาดตอนนี้
Funkodebat

4

จากคำตอบของ K3N ฉันเขียนโค้ดใหม่สำหรับทุกคนที่ต้องการ

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

อัปเดตการสาธิต JSFIDDLE

นี่คือการสาธิตออนไลน์ของฉัน


2
วิธีนี้จะใช้ไม่ได้: ทุกครั้งที่คุณปรับขนาดแคนวาสผ้าใบจะล้างบริบท คุณต้องมีผืนผ้าใบ 2 ผืน ที่นี่มันเหมือนกับการเรียก drawImage โดยตรงด้วยมิติสุดท้าย
Kaiido

4

createImageBitmapผมไม่เข้าใจว่าทำไมไม่มีใครบอก

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

ทำงานได้อย่างสวยงาม (สมมติว่าคุณตั้งรหัสสำหรับรูปภาพและแคนวาส)


1
เนื่องจากไม่ได้รับการสนับสนุนอย่างกว้างขวางcaniuse.com/#search=createImageBitmap
แมตต์

2
createImageBitmap รองรับ 73% ของผู้ใช้ทั้งหมด อาจจะดีพอขึ้นอยู่กับกรณีการใช้งานของคุณ เป็นเพียง Safari ที่ปฏิเสธที่จะเพิ่มการรองรับ ฉันคิดว่ามันคุ้มค่าที่จะกล่าวถึงเป็นทางออกที่เป็นไปได้
cagdas_ucar

วิธีแก้ปัญหาที่ดี แต่ไม่สามารถใช้กับ firefox ได้
vcarel

1

ฉันเขียน js-utility ขนาดเล็กเพื่อครอบตัดและปรับขนาดภาพที่ส่วนหน้า นี่คือลิงค์ของโครงการ GitHub นอกจากนี้คุณจะได้รับหยดจากภาพสุดท้ายเพื่อส่ง

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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.