Skip to content

Instantly share code, notes, and snippets.

@CarsonSlovoka
Last active July 19, 2021 12:20
Show Gist options
  • Save CarsonSlovoka/80505cd107212bf7d74939b22a7ea07a to your computer and use it in GitHub Desktop.
Save CarsonSlovoka/80505cd107212bf7d74939b22a7ea07a to your computer and use it in GitHub Desktop.
Set background image with canvas (you can adjust the size, the proportion will come same as original)

腳本來源

思路

整體思路

類似於:

dw/dh = k*(sw-w)/(sh-h)

也就是當我們重新指派了dw和dh的比率,由於舊有的sw/sh未必能與其完全相符

而為了能和他相符,我們允許讓原先的圖片可以被裁減一塊區域(就是上面的w和h),使其能和dw/dh相符

其中裁減的w或者h,裁減的地方要看要怎麼裁,由於圖片有兩端,所以一般的裁切是兩端各裁切一部分,也就是代碼提到的offsetXoffsetY的意思,

通常我們兩端各裁一半,所以初始設定是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)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment