整體思路
類似於:
dw/dh = k*(sw-w)/(sh-h)
也就是當我們重新指派了dw和dh的比率,由於舊有的sw/sh未必能與其完全相符
而為了能和他相符,我們允許讓原先的圖片可以被裁減一塊區域(就是上面的w和h),使其能和dw/dh相符
其中裁減的w或者h,裁減的地方要看要怎麼裁,由於圖片有兩端,所以一般的裁切是兩端各裁切一部分,也就是代碼提到的offsetX
和offsetY
的意思,
通常我們兩端各裁一半,所以初始設定是0.5
整體思路
類似於:
dw/dh = k*(sw-w)/(sh-h)
也就是當我們重新指派了dw和dh的比率,由於舊有的sw/sh未必能與其完全相符
而為了能和他相符,我們允許讓原先的圖片可以被裁減一塊區域(就是上面的w和h),使其能和dw/dh相符
其中裁減的w或者h,裁減的地方要看要怎麼裁,由於圖片有兩端,所以一般的裁切是兩端各裁切一部分,也就是代碼提到的offsetX
和offsetY
的意思,
通常我們兩端各裁一半,所以初始設定是0.5
/** | |
* | |
* @param {HTMLImageElement} img | |
* @param {Number} dx: destination | |
* @param {Number} dy | |
* @param {Number} dw | |
* @param {Number} dh | |
* @param {Number} offsetX: | |
* @param {Number} offsetY: | |
* */ | |
function drawImageProp(ctx, img, {dx = 0, dy = 0, dw = undefined, dh = undefined, offsetX = 0.5, offsetY = 0.5}) { | |
dw = dw ?? ctx.canvas.width | |
dh = dh ?? ctx.canvas.height | |
// keep bounds [0.0, 1.0] | |
offsetX = clamp(offsetX, 0, 1) | |
offsetY = clamp(offsetY, 0, 1) | |
let iw = img.width, | |
ih = img.height, | |
ratio = Math.min(dw / iw, dh / ih), | |
nw = iw * ratio, // new prop. width // nw 和 dw 其實很像,dw是我們所期望的大小。但是因為比例要不變,所以會計算一個ratio,這個nw才是最後的大小 | |
nh = ih * ratio, // new prop. height | |
sx, sy, sw, sh, ar = 1; | |
// decide which gap to fill | |
if (nw < dw) ar = dw / nw // 所以當new w < dw 的時候就會有空細跑出來,就要填滿 | |
if (Math.abs(ar - 1) < 1e-14 && nh < dh) ar = dh / nh // updated | |
nw *= ar | |
nh *= ar | |
// source rectangle | |
sw = iw / (nw / dw) // nw一定是小於dw,所以source的w一定是比原來的小 // 基本上sw就好像iw(image.width),只是考量到最後長寬要等比,所以才有後面的除像跑出來 | |
sh = ih / (nh / dh) | |
sx = (iw - sw) * offsetX | |
sy = (ih - sh) * offsetY | |
// make sure source rectangle is valid | |
if (sx < 0) sx = 0 | |
if (sy < 0) sy = 0 | |
if (sw > iw) sw = iw | |
if (sh > ih) sh = ih | |
img.onload = (event) => { | |
// fill image in dest. rectangle | |
ctx.drawImage(event.target, sx, sy, sw, sh, dx, dy, dw, dh) | |
} | |
} |
window.onload = () => { | |
const testArray = [ | |
["Full", (ctx, img)=>{drawImageProp(ctx, img, {offsetY:0})}], // If you don't want to it cutting from two sides, you can set "offsetY = 0" then it will cut a large part from the bottom | |
["1/2",(ctx, img)=>{drawImageProp(ctx, img, {dx:window.innerWidth/4, dy:window.innerHeight/4, dw: window.innerWidth/2, dh:window.innerHeight/2})}], | |
["3/4",(ctx, img)=>{drawImageProp(ctx, img, {dx:window.innerWidth/8, dy:window.innerHeight/8, dw: window.innerWidth*3/4, dh:window.innerHeight*3/4})}] | |
] | |
for (const [testName, drawFunc] of testArray) { | |
const btn = document.createElement("button") | |
btn.innerText = testName | |
btn.onclick = () => { | |
document.querySelectorAll(`canvas`).forEach(e=>e.remove()) // remove old data | |
const img = new Image(590, 470) | |
img.src = "https://upload.wikimedia.org/wikipedia/commons/b/bd/Test.svg" | |
const canvas = document.createElement("canvas") | |
canvas.width = window.innerWidth | |
canvas.height = window.innerHeight | |
const ctx = canvas.getContext("2d") | |
drawFunc(ctx, img) | |
document.querySelector(`body`).append(canvas) | |
} | |
document.querySelector(`body`).append(btn) | |
} | |
} |