抱歉各位。最近開始用 Canvas
碰到一個不知道該如何處理的問題。有找了許多範例試著處理。
但一直沒辦法正常解決。
先說說情況。
基本上來說,我因為為了要旋轉的關係。所以有將畫布直接依最大的寬或高,處理成一個正方型。
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
//配合旋轉將畫布列
if(tempimg.width > tempimg.height){
tempCanvas.setAttribute('width', tempimg.width);
tempCanvas.setAttribute('height', tempimg.width);
}else{
tempCanvas.setAttribute('width', tempimg.height);
tempCanvas.setAttribute('height', tempimg.height);
}
我目前遇到的問題是,旋轉縮放處理後。我會再將其做一個方塊裁切。
但因為tempCanvas已經存在控制項。導致裁切也無法正常對應。
我也試著用如下的方式試試看能否繪入後再做裁切。
但還是無法很正常
//圖片旋轉處理
if(setData.imgData.rotateScore!=0){
tempCtx.translate(tempCanvas.width / 2, tempCanvas.height / 2);
tempCtx.rotate(Math.PI / (180/setData.imgData.rotateScore));
tempMovX = movX;
tempMovY = movY;
movX = tempCanvas.width/-2 + tempMovX;
movY = tempCanvas.height/-2 + tempMovY;
}
tempCtx.drawImage(tempimg, movX, movY);
//進行裁切處理
tempCtx.beginPath();
tempCtx.rect(clipX, clipY, clipW, clipH);
tempCtx.clip();
tempCtx.drawImage(tempCanvas, 0, 0);
想問問會 Canvas 的大大們。
我該如何改,才能達到我想要的旋轉縮放處理後的圖,再做裁切。
我是 canvas 一開始就設定好要裁切的大小
算出需要的矩陣後,直接用 drawImage 把圖畫上去
/**
* 先將圖檔以(x, y)為中心,旋轉 deg 度
* 再以 (clipX, clipY) 為左上角座標,裁切出 clipW x clipH 的 canvas
*
* @param {image} img imageElement
* @param {int} x 旋轉中心 x 座標
* @param {int} y 旋轉中心 y 座標
* @param {number} deg 旋轉角度
* @param {int} clipX 裁切左上角 x 座標
* @param {int} clipY 裁切左上角 y 座標
* @param {int} clipW 裁切後的寬度
* @param {int} clipH 裁切後的高度
* @returns {canvas} canvas物件
*/
function getCanvasWithClip(img, x, y, deg, clipX, clipY, clipW, clipH) {
let cvs=document.createElement('canvas');
let ctx=cvs.getContext('2d');
cvs.width=clipW;
cvs.height=clipH;
//預存 sin 跟 cos 值
let c=Math.cos(Math.PI*deg/180);
let s=Math.sin(Math.PI*deg/180);
//設定轉換矩陣
ctx.transform(c, s, -s, c, -c*x+s*y+x-clipX, -s*x-c*y+y-clipY);
ctx.drawImage(img, 0, 0);
return cvs;
}
使用範例
let img=new Image();
img.addEventListener("load", function() {
let w=parseInt(this.width, 10);
let h=parseInt(this.height, 10);
let cvs=getCanvasWithClip(this, w/2, h/2, 30, w/2-30, h/2-30, 60, 60);
document.getElementById('draw').appendChild(cvs);
});
img.src='test.jpg';
矩陣是這樣算出來的
我沒說明清楚。
因為我目前設計的是合成的。
是數張小圖,會做縮放旋轉,並切好適合大小後。
再跟一張大圖合成。
也就是在小圖的生成中,就需要先切好大小。
要不然會跟其它圖相疊到。
縮放旋轉我已經是可以辦到了。
在不旋轉的情況下。我還可以正常裁切我需要的大小。
但一但旋轉後,我怎麼裁切都不對。目前我的問題點在這邊。
有點頭痛。
我是已經有想過,再生一個畫布出來處理。
但還是希望了有一次性的寫法來處理。
裁切出來的長方形,相對螢幕是傾斜的還是正的?
正的。
也就是縮放旋轉後的畫面。我會再用一個長方形裁切一次。
我是已經有想過,再生一個畫布出來處理。
我原本寫的函式可以生出 canvas
這個 canvas 可以當作 image,貼到要輸出的 canvas
因為上面矩陣的計算比較抽象
我後來發現可以改成 translate、rotate、… 的組合
下面這個函式順便添加了 scale 參數
/**
* 先將圖檔以(x, y)為中心,縮放 scale 倍,再旋轉 deg 度
* 接著以 (clipX, clipY) 為左上角座標,裁切出 clipW x clipH 的 canvas
*
* @param {image} img imageElement
* @param {int} x 旋轉中心 x 座標
* @param {int} y 旋轉中心 y 座標
* @param {number} deg 旋轉角度
* @param {number} scale 縮放倍率
* @param {int} clipX 裁切左上角 x 座標
* @param {int} clipY 裁切左上角 y 座標
* @param {int} clipW 裁切後的寬度
* @param {int} clipH 裁切後的高度
* @returns {canvas} canvas物件
*/
function getCanvasWithClip(img, x, y, deg, scale, clipX, clipY, clipW, clipH) {
let cvs=document.createElement('canvas');
let ctx=cvs.getContext('2d');
cvs.width=clipW;
cvs.height=clipH;
//設定轉換矩陣
ctx.translate(x-clipX, y-clipY);
ctx.rotate(Math.PI*deg/180);
ctx.scale(scale, scale);
ctx.translate(-x, -y);
//畫圖
ctx.drawImage(img, 0, 0);
return cvs;
}
使用範例
let img=new Image();
img.addEventListener("load", function() {
let w=parseInt(this.width, 10);
let h=parseInt(this.height, 10);
//主要的 canvas
let mainCanvas=document.createElement('canvas');
mainCanvas.width=600;
mainCanvas.height=600;
let ctx=mainCanvas.getContext('2d');
//以(w/2, h/2)為中心,縮小為50% 後旋轉 30 度,再裁切出中心區域 100 x 100 的圖片
let clipedCanvas=getCanvasWithClip(this, w/2, h/2, 30, 0.5, w/2-50, h/2-50, 100, 100);
//將剛剛切出來的圖片貼到 (100,100) 的位置
ctx.drawImage(clipedCanvas, 100,100);
document.getElementById('draw').appendChild(mainCanvas);
});
img.src='test.jpg';
PS. 如果是要傾斜的,我有其他方式可以不用產生新的 canvas。如果是正的,那直接產生 canvas 會比較簡單。不然的話可能要用ImageData做 buffer,我猜效能不見得會比較好。
目前參照你後面的寫法。可以勉強輸出。
主要是你上面的矩陣的計算方式給我很大的幫助。
不過目前輸出出來的圖,還是偏移很大。我還在查看計算的方式是否正確。
因為我原本的寫法。在還沒旋轉。其實輸出還算正常。
只有旋轉會跑掉。
不過現在用你的函式是全跑掉了@@"
我提供一下我的完整程式碼給你看好了。
//載入生成區 2000*1000
const ca = document.getElementById("prewImgOk");
const ctx = ca.getContext("2d");
//清空畫布
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, ca.width, ca.height);
const mainImg = document.getElementById("preview"); //帶入主圖資料
//預覽圖的寬高
var preVIewW = $("#design-result img").eq(0).width();
var preVIewH = $("#design-result img").eq(0).height();
var preVIewTop = $("#design-result img").eq(0).offset().top;
var preVIewLeft = $("#design-result img").eq(0).offset().left;
//生成區的寬高
var prewImgOkW = ca.width;
var prewImgOkH = ca.height;
var imgPk = prewImgOkW / preVIewW; //圖片之間的縮放比例
//進行多個貼圖合成
$.each(window.designTemplate.uploadData, function (i, v) {
//判斷是否有圖片資料,有的話才處理貼圖
if (v.imgData) {
//獲取貼圖區塊的位置及寬高
var imgBoxX = $(".img-uploaded").eq(i).offset().left;
var imgBoxY = $(".img-uploaded").eq(i).offset().top;
var imgBoxW = $(".img-uploaded").eq(i).width();
var imgBoxH = $(".img-uploaded").eq(i).height();
var tempimg = new Image();//生成新的IMG元件
//圖片載入應用處理
tempimg.onload = function () {
var setData = v;//獲得指定設定值
//暫時貼圖畫布區
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
//配合旋轉將畫布列
if (tempimg.width > tempimg.height) {
tempCanvas.setAttribute('width', tempimg.width);
tempCanvas.setAttribute('height', tempimg.width);
} else {
tempCanvas.setAttribute('width', tempimg.height);
tempCanvas.setAttribute('height', tempimg.height);
}
//移位比例計算
var movePv = tempCanvas.width / 300;
var clipX = (((clipW - tempimg.width) / 2) / setData.imgData.scaleScore) - (setData.imgData.moveScore.x * movePv);
var clipY = (((clipH - tempimg.height) / 2) / setData.imgData.scaleScore) - (setData.imgData.moveScore.y * movePv);
var clipW = tempimg.width * setData.imgData.scaleScore;
var clipH = tempimg.height * setData.imgData.scaleScore;
//這邊是先帶入你後續的函式做處理
clipedCanvas=getCanvasWithClip(tempimg, tempimg.width/2, tempimg.height/2, setData.imgData.rotateScore, setData.imgData.scaleScore, tempimg.width/2, tempimg.height/2, tempimg.width, tempimg.height);
tempCtx.drawImage(clipedCanvas, 0, 0);
//以下是我原本的寫法
/*
//目前圖片的處理
var clipW = tempimg.width * setData.imgData.scaleScore;
var clipH = tempimg.height * setData.imgData.scaleScore;
//進行XY定位裁切計算
var clipX = (((clipW - tempimg.width) / 2) / setData.imgData.scaleScore) - (setData.imgData.moveScore.x * movePv);
var clipY = (((clipH - tempimg.height) / 2) / setData.imgData.scaleScore) - (setData.imgData.moveScore.y * movePv);
var clipW = tempCanvas.width / setData.imgData.scaleScore;
var clipH = tempCanvas.height / setData.imgData.scaleScore;
console.log(setData.imgData.scaleScore, 'setData.imgData.scaleScore');
console.log(setData.imgData.moveScore, 'setData.imgData.moveScore');
console.log(clipW, 'clipW');
console.log(clipH, 'clipH');
console.log(clipX, 'clipX');
console.log(clipY, 'clipY');
//置中處理
var movX = 0;
var movY = 0;
if (tempimg.width > tempimg.height) {
movY = (tempimg.width - tempimg.height) / 2;
} else {
movX = (tempimg.height - tempimg.width) / 2;
}
console.log(clipW, 'editclipW');
console.log(clipH, 'editclipH');
console.log(clipX, 'editclipX');
console.log(clipY, 'editclipY');
//圖片旋轉處理
if (setData.imgData.rotateScore != 0) {
let deg = setData.imgData.rotateScore;
//cvs=getCanvasWithClip(tempimg, tempCanvas.width/2, tempCanvas.height/2, deg, clipX, clipY, clipW, clipH);
cvs = getCanvasWithClip(tempimg, tempCanvas.width / 2 + movX, tempCanvas.height / 2 + movY, deg, clipX, clipY, clipW, clipH);
tempCtx.drawImage(cvs, 0, 0);
/*
//預存 sin 跟 cos 值
let deg = setData.imgData.rotateScore;
let c=Math.cos(Math.PI*deg/180);
let s=Math.sin(Math.PI*deg/180);
tempMovX = movX;
tempMovY = movY;
movX = tempCanvas.width/-2 + tempMovX;
movY = tempCanvas.height/-2 + tempMovY;
//設定轉換矩陣
tempCtx.transform(c, s, -s, c, -c*movX+s*movY+movX-clipX, -s*movX-c*movY+movY-clipY);
tempCtx.drawImage(tempimg, 0, 0);
//tempCtx.translate(tempCanvas.width / 2, tempCanvas.height / 2);
//tempCtx.rotate(Math.PI / (180/setData.imgData.rotateScore));
} else {
tempCtx.beginPath();
tempCtx.rect(clipX, clipY, clipW, clipH);
tempCtx.clip();
tempCtx.drawImage(tempimg, movX, movY);
}
*/
//進行大圖合成
//處理寬高
var sw = (prewImgOkW * ((setData.width) * 0.01));
var w = (prewImgOkW * ((setData.width) * 0.01)) * setData.imgData.scaleScore;
console.log(setData.width, 'v.width');
var wpk = w / tempCanvas.width;
var swpk = sw / tempCanvas.width;
var sh = tempCanvas.height * swpk;
var h = tempCanvas.height * wpk;
var setX = (prewImgOkW * setData.position.left * 0.01) - 325;
var setY = prewImgOkH * setData.position.top * 0.01 - preVIewH;
setX -= (w - sw) / 2;
setY -= (h - sh) / 2;
ctx.drawImage(tempCanvas, setX, setY, w, h);//最後將暫存圖貼入
};
tempimg.src = v.imgData.origin;//載入對應圖片
}
});
//ctx.drawImage(mainImg,0,0);
setTimeout(function () {
ctx.drawImage(mainImg, 0, 0);//這邊是將最後的主圖做最後處理,暫時用的
}, 2000);
目前我用你最後的函數處理。
定位調整一下參數後。已經有調整成功了。
雖然還不太明白一件事。
//將剛剛切出來的圖片貼到 (100,100) 的位置
ctx.drawImage(clipedCanvas, 100,100);
這邊為何需要 100 100 的定位。
而且還有點小問題。旋轉後的圖案。直向圖還OK。但橫向圖還是會被壓扁。我目前還在查看這一部份。
目前是在思考將函數內的畫布。是否要將其化成正方型來處理。
化成正方型的畫布在90度時不會被壓扁。但截圖的位置還是有跑掉。
我還在試著要怎麼調整才對。
那個 (100, 100) 只是做為範例,數值可以依照需求改。
我這邊比較難回答的原因是無法完全了解需求,晚點我整理一下多個轉換疊加後要怎麼看。這樣也許比較幫得上忙。
要說明的話,其實是一個印刷圖。
有一張主要的圖,你可以將其視為一種佈景。
其上面會有5~8個區塊。可以自行上傳相片貼上去。
而上傳的相片,可以調整縮放旋轉。
在編輯過程中,我是利用單純的DIV跟CSS處理。
最後確定後,才會開始組合生圖。
所以程式上你看到的一些XY值。其實都是對應DIV上的圖層位置計算過來的。
我那段程式就是在做組合生圖的動作。
由於需要高解析。所以其畫布預設都很大。
主佈景圖是一個PNG。區塊的部份都是做成透空。
所以我在組合圖是先將相片圖貼好位置。再將佈景圖蓋上去。
其實還有一個最後動作是文字繪上。不過這部份我有處理好了。
我後來有找到不用另外建立 canvas 的方法
透過 save、clip、restore 可以滿足需求
做出來大概像這樣
const opts=[
{//路奇(綠色帽子)
src: { //人物在圖片的座標
x: 160,
y: 93,
},
dest: { //要放到 canvas 的座標
x: 50,
y: 100,
},
rot: -60, //旋轉角度
clipRadius: 50 //裁切圓半徑
},{//恐龍
src: {
x: 288,
y: 102,
},
dest: {
x: 150,
y: 100,
},
rot: 180,
clipRadius: 50
},{//馬力歐(紅色帽子)
src: {
x: 468,
y: 123,
},
dest: {
x: 250,
y: 100,
},
rot: 60,
clipRadius: 50
}
]
function draw(ctx, img, opt) {
ctx.save(); //儲存狀態
ctx.translate(opt.dest.x, opt.dest.y); //[C] 將原點移動到目的地
//裁切時,座標的判斷會套用[C]矩陣
//這邊用圓形只是方便示範而已,可以改任何形狀
ctx.beginPath();
ctx.arc(0, 0, opt.clipRadius, 0, Math.PI*2, true);
ctx.clip();
ctx.rotate(opt.rot*Math.PI/180); //[B] 旋轉指定角度
ctx.translate(-opt.src.x, -opt.src.y); //[A] 移動到原點
//畫圖時,會套用 [C][B][A] 矩陣的乘積(注意[A]會先作用,最後才是[C])
ctx.drawImage(img, 0, 0);
ctx.restore(); //回復之前儲存狀態,因此剛剛的 clip、rotate、translate等設定都會被取消
}
let img=new Image();
img.addEventListener("load", function() {
let cvs=document.querySelector('canvas');
cvs.width=300;
cvs.height=200;
let ctx=cvs.getContext('2d');
//依照設定把圖片變換後畫到 canvas
opts.forEach((opt)=>{
draw(ctx, img, opt);
});
});
img.src=document.querySelector('img').src;
這個看起來能用喔。也比較直覺。
謝啦!!