[小菜一碟] 在前端使用 JavaScript 操作 Canvas 來合併/縮放/裁切圖片

在我剛學習網頁程式設計的那個年代,要在前端網頁上對圖片做除了顯示/隱藏/放大/縮小以外的處理,大都是丟到後端處理完後再丟回來,拜網頁設計技術進步所賜,生出了 Canvas 這個東西,讓我們可以利用客戶端的運算資源執行繪圖的工作,甚至要搞出一個純前端的小畫家是完全沒有問題的。

Canvas 所包含的功能非常豐富,我沒辦法在一篇文章中鉅細靡遺地介紹,我僅就用 drawImage() 方法來合併/縮放/裁切圖片當做範例,看看用 Canvas 要怎麼完成這些需求?

CanvasRenderingContext2D.drawImage() 是 Canvas 很常用的 API 之一,它可以將一個圖片來源繪製到 Canvas 上,而圖片來源的類型包括 HTMLImageElementSVGImageElementHTMLVideoElementHTMLCanvasElementImageBitmapOffscreenCanvasVideoFrame,這些元素上的影像皆可以繪製到 Canvas 上。

drawImage(image, dx, dy) 合併圖片

下面的範例,我畫面上會有兩張圖片,一大一小,合併之後會繪製到 Canvas 上,預期合併的結果是小的圖片會被放在大的圖片的中間。

<p>
    <img src="https://i.imgur.com/2HMwva8.jpg" crossorigin="anonymous" id="bigger" />
    <img src="https://i.imgur.com/Adjd5KF.png" crossorigin="anonymous" id="smaller" />
    <button id="merge">Merge</button>
    <canvas id="result"></canvas>
</p>
<script>
    document.querySelector("#merge").addEventListener("click", function (e) {
        const bigger = document.querySelector("#bigger");
        const smaller = document.querySelector("#smaller");

        const canvas = document.querySelector("#result");
        const canvasCtx = canvas.getContext("2d");

        // Canvas 預設大小是 300×150,配合繪製圖片大小,調整 Canvas 的大小。
        canvas.width = bigger.width;
        canvas.height = bigger.height;

        canvasCtx.drawImage(bigger, 0, 0);
        canvasCtx.drawImage(smaller, (bigger.width / 2) - (smaller.width / 2), (bigger.height / 2) - (smaller.height / 2));
    });
</script>

這裡我們使用 drawImage() 的第一個多載方法 drawImage(image, dx, dy),其中 image 指的是圖片來源,dxdy 則是指要從目標畫布上的哪一個點開始畫起?

drawImage(image, dx, dy, dWidth, dHeight) 縮放圖片

drawImage() 還有兩個多載方法,第二個多載方法是 drawImage(image, dx, dy, dWidth, dHeight),多了 dWidthdHeight 參數,這兩個參數可以讓我們將圖片來源,繪製在指定的大小之內。

如果指定的大小跟原本圖片的比例不對,會導致繪製的結果變形,這點要特別注意。
<p>
    <img src="https://i.imgur.com/2HMwva8.jpg" crossorigin="anonymous" id="bigger" />
    <img src="https://i.imgur.com/Adjd5KF.png" crossorigin="anonymous" id="smaller" />
    <button id="merge">Merge</button>
    <canvas id="result"></canvas>
</p>
<script>
    document.querySelector("#merge").addEventListener("click", function (e) {
        const bigger = document.querySelector("#bigger");
        const smaller = document.querySelector("#smaller");

        const canvas = document.querySelector("#result");
        const canvasCtx = canvas.getContext("2d");

        // Canvas 預設大小是 300×150,配合繪製圖片大小,調整 Canvas 的大小。
        canvas.width = bigger.width;
        canvas.height = bigger.height;

        canvasCtx.drawImage(bigger, 0, 0, bigger.width / 2, bigger.height / 2);
        canvasCtx.drawImage(smaller, (bigger.width / 2) - (smaller.width / 2), (bigger.height / 2) - (smaller.height / 2), smaller.width * 1.5, smaller.height * 1.5);
    });
</script>

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) 裁切圖片

drawImage() 的第三個多載方法 drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight),多了 sxsysWidthsHeight 四個參數,但是他們被定義在方法的前面,使用上要小心,這四個參數可以讓我們只擷取圖片來源的某個區塊,繪製在目標畫布上。

<p>
    <img src="https://i.imgur.com/2HMwva8.jpg" crossorigin="anonymous" id="bigger" />
    <img src="https://i.imgur.com/Adjd5KF.png" crossorigin="anonymous" id="smaller" />
    <button id="merge">Merge</button>
    <canvas id="result"></canvas>
</p>
<script>
    document.querySelector("#merge").addEventListener("click", function (e) {
        const bigger = document.querySelector("#bigger");
        const smaller = document.querySelector("#smaller");

        const canvas = document.querySelector("#result");
        const canvasCtx = canvas.getContext("2d");

        // Canvas 預設大小是 300×150,配合繪製圖片大小,調整 Canvas 的大小。
        canvas.width = bigger.width;
        canvas.height = bigger.height;

        canvasCtx.drawImage(bigger, 139, 304, 286, 255, 0, 0, 286, 255);
        canvasCtx.drawImage(smaller, 47, 18, 111, 82, 0, 300, 111, 82);
    });
</script>

儲存圖片

同場加映,圖片處理完了,如果想儲存下來要怎麼做?我在畫面上增加一個 Save 按鈕,搭配 <a> 的一個 download 屬性來完成這件事情。

<p>
    <img src="https://i.imgur.com/2HMwva8.jpg" crossorigin="anonymous" id="bigger" />
    <img src="https://i.imgur.com/Adjd5KF.png" crossorigin="anonymous" id="smaller" />
    <button id="merge">Merge</button>
    <button id="save">Save</button>
    <canvas id="result"></canvas>
</p>
<script>
    document.querySelector("#merge").addEventListener("click", function (e) {
        const bigger = document.querySelector("#bigger");
        const smaller = document.querySelector("#smaller");

        const canvas = document.querySelector("#result");
        const canvasCtx = canvas.getContext("2d");

        // Canvas 預設大小是 300×150,配合繪製圖片大小,調整 Canvas 的大小。
        canvas.width = bigger.width;
        canvas.height = bigger.height;

        canvasCtx.drawImage(bigger, 139, 304, 286, 255, 0, 0, 286, 255);
        canvasCtx.drawImage(smaller, 47, 18, 111, 82, 0, 300, 111, 82);
    });

    document.querySelector("#save").addEventListener("click", function (e) {
        const canvas = document.querySelector("#result");
        const anchor = document.createElement("a");

        anchor.download = "image.png";
        anchor.href = canvas.toDataURL();
        anchor.click();
    });
</script>

以上,透過幾個範例來簡單介紹 Canvas drawImage() 的使用方式,希望對有需要的朋友有一點幫助,在撰寫文章的過程中,打開我很久以前做過類似需求的程式碼,嗯,很快我就把它給關掉了,太恐怖了。

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學