HTML5 Canvas Resize (Downscale) ภาพคุณภาพสูงหรือไม่


149

ฉันใช้องค์ประกอบผ้าใบ HTML5 เพื่อปรับขนาดภาพฉันเบราว์เซอร์ของฉัน ปรากฎว่ามีคุณภาพต่ำมาก ฉันพบสิ่งนี้: ปิดใช้งานการแก้ไขเมื่อปรับขนาด <canvas>แต่ไม่ช่วยเพิ่มคุณภาพ

ด้านล่างนี้คือรหัส css และ js ของฉันรวมถึงรูปภาพที่เรียกว่า Photoshop และปรับขนาดใน Canvas API

ฉันต้องทำอย่างไรเพื่อให้ได้คุณภาพสูงสุดเมื่อปรับขนาดรูปภาพในเบราว์เซอร์

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

CSS:

canvas, img {
    image-rendering: optimizeQuality;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: optimize-contrast;
    -ms-interpolation-mode: nearest-neighbor;
}

JS:

var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {


   var originalContext = $originalCanvas[0].getContext('2d');   
   originalContext.imageSmoothingEnabled = false;
   originalContext.webkitImageSmoothingEnabled = false;
   originalContext.mozImageSmoothingEnabled = false;
   originalContext.drawImage(this, 0, 0, 379, 500);
});

รูปภาพถูกปรับขนาดด้วย photoshop:

ป้อนคำอธิบายรูปภาพที่นี่

ภาพถูกปรับขนาดบนผ้าใบ:

ป้อนคำอธิบายรูปภาพที่นี่

แก้ไข:

ฉันพยายามลดการลดขนาดในขั้นตอนมากกว่าหนึ่งขั้นตามที่เสนอใน:

การปรับขนาดรูปภาพใน HTML5 canvasและ Html5 canvas drawImage: วิธีการลดรอยหยัก

นี่คือฟังก์ชั่นที่ฉันใช้:

function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
    var imgWidth = img.width, 
        imgHeight = img.height;

    var ratio = 1, ratio1 = 1, ratio2 = 1;
    ratio1 = maxWidth / imgWidth;
    ratio2 = maxHeight / imgHeight;

    // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
    if (ratio1 < ratio2) {
        ratio = ratio1;
    }
    else {
        ratio = ratio2;
    }

    var canvasContext = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");
    var canvasCopy2 = document.createElement("canvas");
    var copyContext2 = canvasCopy2.getContext("2d");
    canvasCopy.width = imgWidth;
    canvasCopy.height = imgHeight;  
    copyContext.drawImage(img, 0, 0);

    // init
    canvasCopy2.width = imgWidth;
    canvasCopy2.height = imgHeight;        
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


    var rounds = 2;
    var roundRatio = ratio * rounds;
    for (var i = 1; i <= rounds; i++) {
        console.log("Step: "+i);

        // tmp
        canvasCopy.width = imgWidth * roundRatio / i;
        canvasCopy.height = imgHeight * roundRatio / i;

        copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

        // copy back
        canvasCopy2.width = imgWidth * roundRatio / i;
        canvasCopy2.height = imgHeight * roundRatio / i;
        copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

    } // end for


    // copy back to canvas
    canvas.width = imgWidth * roundRatio / rounds;
    canvas.height = imgHeight * roundRatio / rounds;
    canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


}

นี่คือผลลัพธ์ถ้าฉันใช้การลดขนาด 2 ขั้นตอน:

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือผลลัพธ์ถ้าฉันใช้การลดขนาด 3 ขั้นตอน:

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือผลลัพธ์ถ้าฉันใช้การลดขนาด 4 ขั้นตอน:

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือผลลัพธ์ถ้าฉันใช้การลดขนาดลง 20 ก้าว:

ป้อนคำอธิบายรูปภาพที่นี่

หมายเหตุ: ปรากฎว่าจาก 1 ขั้นตอนถึง 2 ขั้นตอนมีการปรับปรุงคุณภาพของภาพเป็นจำนวนมาก แต่ยิ่งคุณเพิ่มขั้นตอนในกระบวนการมากเท่าใดภาพก็ยิ่งเลือนมากขึ้นเท่านั้น

มีวิธีการแก้ปัญหาที่ภาพเพิ่มความคลุมเครือมากขึ้นเมื่อคุณเพิ่มขั้นตอนหรือไม่?

แก้ไข 2013-10-04: ฉันลองใช้อัลกอริทึมของ GameAlchemist นี่คือผลลัพธ์เมื่อเปรียบเทียบกับ Photoshop

PhotoShop รูปภาพ:

รูปภาพ PhotoShop

อัลกอริทึมของ GameAlchemist:

อัลกอริทึมของ GameAlchemist


2
คุณอาจลองปรับขนาดภาพของคุณแบบค่อยเป็นค่อยไป: stackoverflow.com/questions/18761404/…
เครื่องหมาย

1
ซ้ำเป็นไปได้ของผ้าใบ HTML5 drawImage: วิธีการใช้การลดรอยหยัก ดูว่าไม่ได้ผล หากรูปภาพมีขนาดใหญ่และลดลงเป็นขนาดเล็กคุณจะต้องทำตามขั้นตอน (ดูตัวอย่างรูปภาพในลิงก์)

2
@confile การปิดการแก้ไขจะทำให้แย่ที่สุด คุณต้องการเปิดใช้งาน ดูลิงค์ที่ฉันให้ไว้ข้างต้น ฉันแสดงวิธีใช้ขั้นตอนต่าง ๆ เพื่อลดขนาดรูปภาพที่ใหญ่ขึ้นและรักษาคุณภาพ และอย่างที่สกอตต์บอกว่าคุณต้องการให้ความสำคัญกับคุณภาพมากกว่าความเร็ว

1
@ Ken-AbdiasSoftware ฉันพยายามให้คุณเข้าใกล้ แต่ปัญหาคือมันจะยิ่งแย่ลงยิ่งฉันใช้สเกลที่ชาญฉลาดมากขึ้น ความคิดใดที่จะแก้ไขได้อย่างไร
Confile

3
โอกาสที่จะจำลองการทำงานของซอฟต์แวร์แก้ไขภาพระดับมืออาชีพที่มีราคาแพงโดยใช้ HTML5 นั้นค่อนข้างจะใช่ไหม? คุณอาจเข้าใกล้ (ish) ได้ แต่เหมือนกับที่ใช้ใน Photoshop ฉันคิดว่าคงเป็นไปไม่ได้!
เลียม

คำตอบ:


171

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

หากต้องการลดขนาดภาพเราจำเป็นต้องเปลี่ยนพิกเซลสี่เหลี่ยม * p แต่ละพิกเซลในภาพต้นฉบับให้เป็นพิกเซลเดียวในภาพปลายทาง

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

แต่มีข้อยกเว้นสำหรับเรื่องนี้: เนื่องจากการลดขนาดภาพลง 2X นั้นง่ายมากในการคำนวณ (เฉลี่ย 4 พิกเซลเพื่อสร้างหนึ่งภาพ) และใช้สำหรับพิกเซลเรตินา / HiDPI กรณีนี้จึงได้รับการจัดการอย่างถูกต้อง - เบราว์เซอร์ใช้ประโยชน์จากพิกเซล 4 ตัว หนึ่ง-.

แต่ ... หากคุณใช้การสุ่มตัวอย่างครั้งละสองครั้ง 2X คุณจะพบปัญหาว่าข้อผิดพลาดในการปัดเศษที่ต่อเนื่องจะเพิ่มเสียงรบกวนมากเกินไป
ยิ่งไปกว่านั้นคุณจะไม่ปรับขนาดด้วยพลังสองระดับเสมอและการปรับขนาดเป็นพลังงานที่ใกล้ที่สุด + การปรับขนาดครั้งสุดท้ายนั้นมีเสียงดังมาก

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

นี่คือตัวอย่างของสเกลแคนวาสเทียบกับสเกลที่สมบูรณ์แบบของฉันในสเกล 1/3 ของซอมบี้

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

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือรหัสในการลดขนาดพิกเซลที่สมบูรณ์แบบ:

ผลซอ: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
ซอตัวเอง: http://jsfiddle.net/gamealchemist/r6aVp/

// scales the image by (float) scale < 1
// returns a canvas containing the scaled image.
function downScaleImage(img, scale) {
    var imgCV = document.createElement('canvas');
    imgCV.width = img.width;
    imgCV.height = img.height;
    var imgCtx = imgCV.getContext('2d');
    imgCtx.drawImage(img, 0, 0);
    return downScaleCanvas(imgCV, scale);
}

// scales the canvas by (float) scale < 1
// returns a new canvas containing the scaled image.
function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale; // square scale = area of source pixel within target
    var sw = cv.width; // source image width
    var sh = cv.height; // source image height
    var tw = Math.floor(sw * scale); // target image width
    var th = Math.floor(sh * scale); // target image height
    var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
    var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
    var tX = 0, tY = 0; // rounded tx, ty
    var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
    // weight is weight of current source point within target.
    // next weight is weight of current source point within next target's point.
    var crossX = false; // does scaled px cross its current px right border ?
    var crossY = false; // does scaled px cross its current px bottom border ?
    var sBuffer = cv.getContext('2d').
    getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
    var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
    var sR = 0, sG = 0,  sB = 0; // source's current point r,g,b
    /* untested !
    var sA = 0;  //source alpha  */    

    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale; // y src position within target
        tY = 0 | ty;     // rounded : target pixel's y
        yIndex = 3 * tY * tw;  // line index within target array
        crossY = (tY != (0 | ty + scale)); 
        if (crossY) { // if pixel is crossing botton target pixel
            wy = (tY + 1 - ty); // weight of point within target pixel
            nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
        }
        for (sx = 0; sx < sw; sx++, sIndex += 4) {
            tx = sx * scale; // x src position within target
            tX = 0 |  tx;    // rounded : target pixel's x
            tIndex = yIndex + tX * 3; // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // if pixel is crossing target pixel's right
                wx = (tX + 1 - tx); // weight of point within target pixel
                nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
            }
            sR = sBuffer[sIndex    ];   // retrieving r,g,b for curr src px.
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];

            /* !! untested : handling alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // or use /256 instead ??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) { // pixel does not cross
                // just add components weighted by squared scale.
                tBuffer[tIndex    ] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) { // cross on X only
                w = wx * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tX+1) px                
                nw = nwx * scale
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) { // cross on Y only
                w = wy * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tY+1) px                
                nw = nwy * scale
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else { // crosses both x and y : four target points involved
                // add weighted component for current px
                w = wx * wy;
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // for tX + 1; tY px
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // for tX ; tY + 1 px
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // for tX + 1 ; tY +1 px
                nw = nwx * nwy;
                tBuffer[tIndex + 3 * tw + 3] += sR * nw;
                tBuffer[tIndex + 3 * tw + 4] += sG * nw;
                tBuffer[tIndex + 3 * tw + 5] += sB * nw;
            }
        } // end for sx 
    } // end for sy

    // create result canvas
    var resCV = document.createElement('canvas');
    resCV.width = tw;
    resCV.height = th;
    var resCtx = resCV.getContext('2d');
    var imgRes = resCtx.getImageData(0, 0, tw, th);
    var tByteBuffer = imgRes.data;
    // convert float32 array into a UInt8Clamped Array
    var pxIndex = 0; //  
    for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
        tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
        tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
        tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
        tByteBuffer[tIndex + 3] = 255;
    }
    // writing result to canvas.
    resCtx.putImageData(imgRes, 0, 0);
    return resCV;
}

เป็นหน่วยความจำที่ค่อนข้างโลภเนื่องจากจำเป็นต้องมีบัฟเฟอร์แบบลอยเพื่อเก็บค่ากลางของอิมเมจปลายทาง (-> ถ้าเรานับจำนวนผลลัพธ์เราใช้หน่วยความจำของอิมเมจต้นทาง 6 เท่าในอัลกอริธึมนี้)
นอกจากนี้ยังมีราคาค่อนข้างแพงเนื่องจากพิกเซลแหล่งที่มาแต่ละจุดจะถูกใช้ไม่ว่าขนาดปลายทางจะเป็นเท่าใดและเราต้องจ่ายค่า getImageData / putImageDate ค่อนข้างช้าเช่นกัน
แต่ไม่มีวิธีใดที่จะเร็วไปกว่าการประมวลผลแต่ละค่าของแหล่งที่มาในกรณีนี้และสถานการณ์ก็ไม่ได้แย่ขนาดนั้น: สำหรับอิมเมจ Wombat 740 * 556 ของฉันการประมวลผลใช้เวลาระหว่าง 30 และ 40 มิลลิวินาที


มันจะเร็วขึ้นหรือไม่ถ้าคุณปรับขนาดภาพก่อนที่จะนำไปวางบนผืนผ้าใบ?
confile

ฉันไม่เข้าใจ ... ดูเหมือนว่าเป็นสิ่งที่ฉันทำ บัฟเฟอร์และ Canvas i สร้าง (resCV) มีขนาดของภาพที่ปรับขนาด ฉันคิดว่าวิธีเดียวที่จะทำให้เร็วขึ้นก็คือการใช้การคำนวณจำนวนเต็มแบบ breshensam แต่ 40ms นั้นช้าสำหรับวิดีโอเกม (25 fps) ไม่ใช่สำหรับแอปพลิเคชั่นวาด
GameAlchemist

คุณเห็นโอกาสที่จะทำให้อัลกอริทึมของคุณเร็วขึ้นในขณะที่รักษาคุณภาพไว้หรือไม่?
confile

1
ฉันพยายามปัดเศษบัฟเฟอร์ (ส่วนล่าสุดของอัลกอริทึม) โดยใช้ 0 | แทน Mat.ceil มันเร็วขึ้นเล็กน้อย แต่อย่างไรก็ตามมีค่าใช้จ่ายค่อนข้างมากกับ get / putImageData และอีกครั้งเราไม่สามารถหลีกเลี่ยงการประมวลผลแต่ละพิกเซล
GameAlchemist

4
ตกลงดังนั้นฉันดูรหัส: คุณอยู่ใกล้มากจากโซลูชัน ข้อผิดพลาดสองประการ: ดัชนีของคุณถูกปิดโดยหนึ่งสำหรับ tX + 1 (พวกเขาคือ +3, +4, +5, +6 แทนที่จะเป็น +4, +5, +6, +7) และการเปลี่ยนบรรทัดใน rgba คือ mul โดย 4 ไม่ใช่ 3 ฉันเพิ่งทดสอบค่าสุ่ม 4 ค่าเพื่อตรวจสอบ (0.1, 0.15, 0.33, 0.8) ดูเหมือนไม่เป็นไร ซอที่อัปเดตของคุณอยู่ที่นี่: jsfiddle.net/gamealchemist/kpQyE/3
GameAlchemist

51

ตัวอย่างผ้าใบที่รวดเร็วมีคุณภาพดี: http://jsfiddle.net/9g9Nv/442/

อัปเดต:เวอร์ชัน 2.0 (เร็วกว่าเว็บเวิร์ค + วัตถุที่โอนย้ายได้) - https://github.com/viliusle/Hermite-resize

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

ฉันต้องการคุณภาพที่ดีที่สุด
confile

18
คงที่ฉันเปลี่ยน "ดี" เป็น "ดีที่สุด" ไม่เป็นไรตอนนี้หรือไม่ : D ในทางกลับกันถ้าคุณต้องการตัวอย่างที่ดีที่สุด - ใช้ imagemagick
ViliusL

@confile imgur.com ปลอดภัยที่จะใช้ใน jsfiddle แต่ผู้ดูแลระบบทำอะไรผิดหรือเปล่า? คุณไม่เห็นคุณภาพที่ดีเนื่องจากเบราว์เซอร์ของคุณมีข้อผิดพลาดร้ายแรง CORS (ไม่สามารถใช้รูปภาพจากไซต์ระยะไกล)
ViliusL

โอเคคุณสามารถใช้ภาพ PNG อื่นกับพื้นที่โปร่งใส มีความคิดเกี่ยวกับเรื่องนี้ไหม?
confile

4
@ แน่ใจว่าคุณพูดถูกในบางกรณีภาพโปร่งใสมีปัญหาในพื้นที่ที่คมชัด ฉันพลาดกรณีเหล่านี้ด้วยการทดสอบ ปรับขนาดคงที่ยังสนับสนุนการแก้ไขภาพระยะไกลบนซอ: jsfiddle.net/9g9Nv/49
ViliusL

28

ข้อเสนอแนะ 1 - ขยายกระบวนการไปป์ไลน์

คุณสามารถใช้ขั้นตอนลงตามที่ฉันอธิบายในลิงก์ที่คุณอ้างถึง แต่ดูเหมือนว่าคุณจะใช้ในทางที่ผิด

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

ทุกครั้งที่คุณกดตัวอย่างภาพคุณจะหลวมรายละเอียดและข้อมูล คุณไม่สามารถคาดหวังว่าภาพที่ได้จะมีความคมชัดเหมือนต้นฉบับ

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

ลองด้วยขั้นตอนพิเศษเพียงหนึ่งขั้นหรือที่หนึ่งในสอง

convolutions

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

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

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

การใช้งาน:

sharpen(context, width, height, mixFactor);

ค่าmixFactorคือระหว่าง [0.0, 1.0] และอนุญาตให้คุณลดทอนเอฟเฟกต์คมชัด - กฏของหัวแม่มือ: ยิ่งขนาดน้อยลงเอฟเฟกต์ที่ต้องการจะน้อยลง

ฟังก์ชั่น (ตามตัวอย่างนี้ ):

function sharpen(ctx, w, h, mix) {

    var weights =  [0, -1, 0,  -1, 5, -1,  0, -1, 0],
        katet = Math.round(Math.sqrt(weights.length)),
        half = (katet * 0.5) |0,
        dstData = ctx.createImageData(w, h),
        dstBuff = dstData.data,
        srcBuff = ctx.getImageData(0, 0, w, h).data,
        y = h;
        
    while(y--) {

        x = w;

        while(x--) {

            var sy = y,
                sx = x,
                dstOff = (y * w + x) * 4,
                r = 0, g = 0, b = 0, a = 0;

            for (var cy = 0; cy < katet; cy++) {
                for (var cx = 0; cx < katet; cx++) {

                    var scy = sy + cy - half;
                    var scx = sx + cx - half;

                    if (scy >= 0 && scy < h && scx >= 0 && scx < w) {

                        var srcOff = (scy * w + scx) * 4;
                        var wt = weights[cy * katet + cx];

                        r += srcBuff[srcOff] * wt;
                        g += srcBuff[srcOff + 1] * wt;
                        b += srcBuff[srcOff + 2] * wt;
                        a += srcBuff[srcOff + 3] * wt;
                    }
                }
            }

            dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
            dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
            dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
            dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
        }
    }

    ctx.putImageData(dstData, 0, 0);
}

ผลลัพธ์ของการใช้ชุดค่าผสมนี้จะเป็น:

ออนไลน์สาธิตที่นี่

ส่งผลให้ตัวอย่างลดลงและเพิ่มความชัดเจนของการแปลง

ขึ้นอยู่กับจำนวนการเหลาที่คุณต้องการเพิ่มในการผสมผสานที่คุณสามารถได้รับจากการเริ่มต้น "พร่ามัว" ถึงคมชัดมาก:

การเปลี่ยนแปลงของคมชัด

ข้อเสนอแนะที่ 2 - การใช้อัลกอริทึมในระดับต่ำ

หากคุณต้องการได้ผลลัพธ์ที่มีคุณภาพดีที่สุดคุณจะต้องอยู่ในระดับต่ำและลองใช้ตัวอย่างเช่นอัลกอริธึมใหม่นี้เพื่อทำสิ่งนี้

ดูการลดลงของการสุ่มตัวอย่างภาพตามการแก้ไข (2011) จาก IEEE
นี่คือการเชื่อมโยงกับกระดาษเต็ม (PDF)

ไม่มีการใช้งานของอัลกอริทึมนี้ใน JavaScript AFAIK ในขณะนี้ดังนั้นคุณจึงควรอยู่ในมือหากคุณต้องการที่จะทำงานนี้

สาระสำคัญคือ (ข้อความที่ตัดตอนมาจากกระดาษ):

บทคัดย่อ

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

สแนปชอตจากกระดาษ

(ดูลิงค์ที่ให้ไว้สำหรับรายละเอียดทั้งหมดสูตร ฯลฯ )


นี่เป็นทางออกที่ยอดเยี่ยม ขอบคุณ!
Confile

นี่เป็นทางออกที่ดี ฉันลองในไฟล์ png ที่มีพื้นที่โปร่งใส นี่คือผลลัพธ์: jsfiddle.net/confile/5CD4Nคุณมีความคิดว่าจะทำอย่างไรเพื่อให้มันใช้งานได้?
Confile

1
นี่คืออัจฉริยะ! แต่โปรดคุณช่วยอธิบายสิ่งที่คุณกำลังทำ? ฮ่า ๆ .. ฉันอยากรู้เกี่ยวกับสิ่งที่ต้องทำ ... อาจเป็นแหล่งเรียนรู้?
carinlynchin

1
@Carine ที่อาจมีความคิดเห็นที่ไม่ดีนักเล็กน้อย แต่การลดขนาด resamples กลุ่มของพิกเซลเพื่อเฉลี่ยค่าใหม่ที่แสดงถึงกลุ่มนั้น นี่คือเอฟเฟ็กต์ตัวกรองความถี่ต่ำซึ่งแนะนำการเบลอโดยรวม ในการชดเชยการสูญเสียความคมชัดเพียงใช้การบิดที่คมชัด เนื่องจากความคมชัดอาจเด่นชัดมากเราสามารถผสมเข้ากับภาพแทนเพื่อให้เราสามารถควบคุมระดับการลับภาพได้ หวังว่าจะให้ข้อมูลเชิงลึกบางอย่าง

21

หากคุณต้องการใช้แคนวาสเท่านั้นผลลัพธ์ที่ดีที่สุดคือการลงล่างหลายครั้ง แต่นั่นยังไม่ดีพอ เพื่อคุณภาพที่ดีขึ้นคุณต้องมีการใช้ js บริสุทธิ์ เราเพิ่งเปิดตัวpica - ตัวลดความเร็วสูงพร้อมคุณภาพ / ความเร็วผันแปร กล่าวโดยย่อก็คือปรับขนาด 1280 * 1024px ใน ~ 0.1s และรูปภาพ 5000 * 3000px ใน 1 วินาทีด้วยคุณภาพสูงสุด (ตัวกรอง lanczos พร้อม 3 lobes) Pica มีตัวอย่างที่คุณสามารถเล่นกับรูปภาพระดับคุณภาพและแม้แต่ลองบนอุปกรณ์มือถือ

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


16

เหตุใดจึงใช้ Canvas เพื่อปรับขนาดรูปภาพ เบราว์เซอร์สมัยใหม่ทั้งหมดใช้การแก้ไขแบบ bicubic ซึ่งเป็นกระบวนการเดียวกับ Photoshop เพียงระบุขนาดภาพที่คุณต้องการ (ใช้เพียงมิติเดียวความสูงหรือความกว้างเพื่อปรับขนาดตามสัดส่วน)

เบราว์เซอร์ส่วนใหญ่รองรับนี้รวมถึง IE เวอร์ชันที่ใหม่กว่า รุ่นก่อนหน้านี้อาจต้องใช้เบราว์เซอร์ที่เฉพาะเจาะจง CSS

ฟังก์ชั่นที่เรียบง่าย (ใช้ jQuery) เพื่อปรับขนาดภาพจะเป็นดังนี้:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

จากนั้นใช้ค่าที่ส่งคืนเพื่อปรับขนาดรูปภาพในมิติเดียวหรือทั้งสอง

เห็นได้ชัดว่ามีการปรับแต่งที่แตกต่างกันที่คุณสามารถทำได้ แต่สิ่งนี้จะทำให้งานเสร็จ

วางรหัสต่อไปนี้ลงในคอนโซลของหน้านี้และดูว่าเกิดอะไรขึ้นกับ gravatars:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

$('.user-gravatar32 img').each(function(){
  var newDimensions = resizeImage( this, 150);
  this.style.width = newDimensions.width + "px";
  this.style.height = newDimensions.height + "px";
});

2
โปรดทราบว่าหากคุณระบุเพียงหนึ่งมิติเบราว์เซอร์ (ทันสมัย) จะรักษาอัตราส่วนธรรมชาติของภาพโดยอัตโนมัติ
André Dion

38
บางทีเขาอาจต้องการส่งภาพที่ปรับขนาดแล้วไปยังเซิร์ฟเวอร์
Sergiu Paraschiv

2
@Sergiu: ไม่จำเป็น แต่โปรดทราบว่าถ้าคุณจะไปจากภาพขนาดเล็กมากไปยังภาพที่มีขนาดใหญ่มากคุณจะไม่ได้รับผลลัพธ์ที่ยอดเยี่ยมแม้แต่จากเซิร์ฟเวอร์
Robusto

2
@ Robusto ฉันต้องใส่รูปภาพใน canvas หลังจากนั้นและส่งไปยังเซิร์ฟเวอร์ในภายหลัง ฉันต้องการลดขนาดรูปภาพขนาดใหญ่ลงเป็นรูปภาพขนาดเล็กปรับสีในพื้นที่วาดภาพและส่งผลลัพธ์ไปยังเซิร์ฟเวอร์ คุณคิดว่าฉันควรทำอย่างไร?
confile

9
@Robusto นี่คือปัญหา การแสดงภาพขนาดเล็กบนไคลเอนต์นั้นเป็นเรื่องง่าย img.width nad img.height นั้นสำคัญมาก ฉันต้องการลดขนาดลงเพียงครั้งเดียวไม่ใช่บนเซิร์ฟเวอร์อีกครั้ง
confile

8

ไม่ได้เป็นคำตอบที่เหมาะสมสำหรับผู้ที่ต้องการจริงๆที่จะปรับขนาดภาพของตัวเองแต่เพียงการหดขนาดไฟล์

ฉันมีปัญหากับรูปภาพ "โดยตรงจากกล้อง" ซึ่งลูกค้าของฉันมักจะอัปโหลดใน JPEG ที่ "ไม่บีบอัด"

ไม่รู้จักกันดีว่าผ้าใบรองรับ (ในเบราว์เซอร์ส่วนใหญ่ 2017) เพื่อเปลี่ยนคุณภาพของ JPEG

data=canvas.toDataURL('image/jpeg', .85) # [1..0] default 0.92

ด้วยเคล็ดลับนี้ฉันสามารถลดขนาดภาพ 4k x 3k ด้วย> 10Mb เป็น 1 หรือ 2Mb ได้ขึ้นอยู่กับความต้องการของคุณ

ดูนี่


4

นี่คือบริการเชิงมุมที่นำกลับมาใช้ใหม่สำหรับการปรับขนาดภาพ / ผ้าใบคุณภาพสูง: https://gist.github.com/fisch0920/37bac5e741eaec60e983

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

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

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

4

นี่คือตัวกรองการปรับขนาด Hermite ที่ปรับปรุงแล้วซึ่งใช้งาน 1 คนเพื่อให้หน้าต่างไม่หยุดนิ่ง

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

3

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

drawImage ()ฟังก์ชั่นใช้เส้นการแก้ไขที่ใกล้ที่สุดเพื่อนบ้านวิธี resampling ที่ทำงานได้ดีเมื่อคุณไม่ได้ปรับขนาดลดลงกว่าครึ่งหนึ่งของขนาดเดิม

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

ฟังก์ชั่นนี้ลดขนาดตัวอย่างลงครึ่งหนึ่งจนกว่าจะถึงขนาดที่ต้องการ:

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

คุณช่วยโพสต์ jsfiddle และรูปภาพที่เกิดขึ้นได้ไหม?
confile

ในลิงค์ด้านล่างคุณสามารถค้นหาภาพผลลัพธ์โดยใช้เทคนิคนี้
Jesús Carrera

1

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

/* 
 * @parame canvas => canvas object
 * @parame rate => the pixel quality
 */
function setCanvasSize(canvas, rate) {
    const scaleRate = rate;
    canvas.width = window.innerWidth * scaleRate;
    canvas.height = window.innerHeight * scaleRate;
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
    canvas.getContext('2d').scale(scaleRate, scaleRate);
}

0

แทนการ.85 , ถ้าเราเพิ่ม1.0 คุณจะได้รับคำตอบที่แน่นอน

data=canvas.toDataURL('image/jpeg', 1.0);

คุณสามารถรับภาพที่ชัดเจนและสดใส โปรดตรวจสอบ


0

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

function resizeCanvas(canvas, newWidth, newHeight) {
  let ctx = canvas.getContext('2d');
  let buffer = document.createElement('canvas');
  buffer.width = ctx.canvas.width;
  buffer.height = ctx.canvas.height;
  let ctxBuf = buffer.getContext('2d');
  

  let scaleX = newWidth / ctx.canvas.width;
  let scaleY = newHeight / ctx.canvas.height;

  let scaler = Math.min(scaleX, scaleY);
  //see if target scale is less than half...
  if (scaler < 0.5) {
    //while loop in case target scale is less than quarter...
    while (scaler < 0.5) {
      ctxBuf.canvas.width = ctxBuf.canvas.width * 0.5;
      ctxBuf.canvas.height = ctxBuf.canvas.height * 0.5;
      ctxBuf.scale(0.5, 0.5);
      ctxBuf.drawImage(canvas, 0, 0);
      ctxBuf.setTransform(1, 0, 0, 1, 0, 0);
      ctx.canvas.width = ctxBuf.canvas.width;
      ctx.canvas.height = ctxBuf.canvas.height;
      ctx.drawImage(buffer, 0, 0);

      scaleX = newWidth / ctxBuf.canvas.width;
      scaleY = newHeight / ctxBuf.canvas.height;
      scaler = Math.min(scaleX, scaleY);
    }
    //only if the scaler is now larger than half, double target scale trick...
    if (scaler > 0.5) {
      scaleX *= 2.0;
      scaleY *= 2.0;
      ctxBuf.canvas.width = ctxBuf.canvas.width * scaleX;
      ctxBuf.canvas.height = ctxBuf.canvas.height * scaleY;
      ctxBuf.scale(scaleX, scaleY);
      ctxBuf.drawImage(canvas, 0, 0);
      ctxBuf.setTransform(1, 0, 0, 1, 0, 0);
      scaleX = 0.5;
      scaleY = 0.5;
    }
  } else
    ctxBuf.drawImage(canvas, 0, 0);

  //wrapping things up...
  ctx.canvas.width = newWidth;
  ctx.canvas.height = newHeight;
  ctx.scale(scaleX, scaleY);
  ctx.drawImage(buffer, 0, 0);
  ctx.setTransform(1, 0, 0, 1, 0, 0);
}

-1

context.scale(xScale, yScale)

<canvas id="c"></canvas>
<hr/>
<img id="i" />

<script>
var i = document.getElementById('i');

i.onload = function(){
    var width = this.naturalWidth,
        height = this.naturalHeight,
        canvas = document.getElementById('c'),
        ctx = canvas.getContext('2d');

    canvas.width = Math.floor(width / 2);
    canvas.height = Math.floor(height / 2);

    ctx.scale(0.5, 0.5);
    ctx.drawImage(this, 0, 0);
    ctx.rect(0,0,500,500);
    ctx.stroke();

    // restore original 1x1 scale
    ctx.scale(2, 2);
    ctx.rect(0,0,500,500);
    ctx.stroke();
};

i.src = 'https://static.md/b70a511140758c63f07b618da5137b5d.png';
</script>

-1

DEMO : ปรับขนาดรูปภาพด้วยพุดเดิ้ล JS และ HTML Canvas Demo

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