要旋转图像并对其进行缩放,以使图像的边缘在任何时候都不会在画布边界内,您需要计算从画布中心到角落的距离。
// assuming canvas is the canvas and image is the image.
var dist = Math.sqrt(Math.pow(canvas.width/2,2)+Math.pow(canvas.height/2,2));
现在你需要找到图像中心到其最近边缘的距离;
var imgDist = Math.min(image.width,image.height)/2
所以现在我们有了获取图像在画布中心绘制时始终适合画布的最小比例所需的信息。
var minScale = dist / imgDist;
现在围绕图像的中心旋转、缩放并将其放置在画布的中心
首先计算一个像素的上边缘(X轴)的方向和大小
// ang is the rotation in radians
var dx = Math.cos(ang) * minScale;
var dy = Math.sin(ang) * minScale;
然后创建转换矩阵,其中前两个数字是 X 轴,后两个数字是 Y 轴,与 X 轴顺时针方向成 90 度,最后两个数字是画布像素坐标的原点,位于画布原点左上角。
// ctx is the canvas 2D context
ctx.setTransform(dx, dy, -dy, dx, canvas.width / 2, canvas.height / 2);
现在绘制偏移一半高度和宽度的图像。
ctx.drawImage(image,-image.width / 2, - image.height / 2);
最后一件事就是恢复默认转换
ctx.setTransform(1,0,0,1,0,0);
你就完成了。
作为一个函数
// ctx is canvas 2D context
// angle is rotation in radians
// image is the image to draw
function drawToFitRotated(ctx, angle, image){
var dist = Math.sqrt(Math.pow(ctx.canvas.width /2, 2 ) + Math.pow(ctx.canvas.height / 2, 2));
var imgDist = Math.min(image.width, image.height) / 2;
var minScale = dist / imgDist;
var dx = Math.cos(angle) * minScale;
var dy = Math.sin(angle) * minScale;
ctx.setTransform(dx, dy, -dy, dx, ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.drawImage(image, -image.width / 2, - image.height / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
更新
由于这是一个有趣的问题,我想看看我是否可以弄清楚如何最适合旋转的图像,以确保图像始终填满画布,同时显示尽可能多的图像。解决方案意味着随着图像的旋转,比例会发生变化,并且由于边角会发生变化,所以比例有 4 个点,在这些点上它突然停止收缩并再次开始增长。尽管如此,这对于旋转但需要填充画布的静态图像来说是很好的。
最好的描述方式是用图片
图像是绿色的,画布是红色的。图像的旋转是角度 R 我们需要找到线 C-E 和 C-F 的长度。 Ih 和 Iw 是图像高度和宽度的一半。
从画布中,我们计算出线 C-B 的长度,即画布高度 (ch) 和宽度 (cw) 的一半平方和的平方根。
那么我们需要角度 A,它是 (ch) / 长度线 C-B 的 acos
现在我们有了角度 A,我们可以计算出角度 A1 和 A2。现在我们有了长度 C-B 和角度 A1、A2,我们可以解出 C-E 和 C-F 的直角三角形 C-E-B 和 C-F-B。
即 C-E = 长度 C-B * cos(A1) 和 C-F = 长度 C-B * cos(A2)
然后得到图像高度的比例,即长度 C-E 除 Ih 和 C-E 除 Iw。由于图像方面可能与我们需要适应的矩形不匹配,我们需要保持两个比例的最大值。
所以我们最终得到了一个适合的规模。
还有一个问题。数学适用于 0 -90 度的角度,但其余的则失败。幸运的是,每个象限的问题都是对称的。对于 > 180 的角度,我们镜像并向后旋转 180 度,然后对于角度 > 90
然后它只是创建矩阵和绘制图像的问题。
在演示中,我添加了两个盒子。 red BLUE 是画布轮廓的一半大小的副本,Blue RED 是图像的一半大小。我这样做是为了让您了解为什么图像在旋转时缩放不平滑。
我会为自己保留这个功能,它也很方便。
查看新功能的演示代码。
demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
var image = new Image();
image.src = "http://i.imgur.com/gwlPu.jpg";
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // half canvas width and height
var ch = h / 2;
// Refer to diagram in answer
function drawBestFit(ctx, angle, image){
var iw = image.width / 2; // half image width and height
var ih = image.height / 2;
// get the length C-B
var dist = Math.sqrt(Math.pow(cw,2) + Math.pow(ch,2));
// get the angle A
var diagAngle = Math.asin(ch/dist);
// Do the symmetry on the angle
a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
if(a1 > Math.PI){
a1 -= Math.PI;
}
if(a1 > Math.PI/2 && a1 <= Math.PI){
a1 = (Math.PI/2) - (a1-(Math.PI/2));
}
// get angles A1, A2
var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
var ang2 = Math.abs(diagAngle - Math.abs(a1));
// get lenghts C-E and C-F
var dist1 = Math.cos(ang1) * dist;
var dist2 = Math.cos(ang2) * dist;
// get the max scale
var scale = Math.max(dist2/(iw),dist1/(ih));
// create the transform
var dx = Math.cos(angle) * scale;
var dy = Math.sin(angle) * scale;
ctx.setTransform(dx, dy, -dy, dx, cw, ch);
ctx.drawImage(image, -iw, - ih);
// draw outline of image half size
ctx.strokeStyle = "red";
ctx.lineWidth = 2 * (1/scale);
ctx.strokeRect(-iw / 2, -ih / 2, iw, ih)
// reset the transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
// draw outline of canvas half size
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.strokeRect(cw - cw / 2, ch - ch / 2, cw, ch)
}
// Old function
function drawToFitRotated(ctx, angle, image){
var dist = Math.sqrt(Math.pow(cw,2) + Math.pow(ch,2));
var imgDist = Math.min(image.width, image.height) / 2;
var minScale = dist / imgDist;
var dx = Math.cos(angle) * minScale;
var dy = Math.sin(angle) * minScale;
ctx.setTransform(dx, dy, -dy, dx, cw, ch);
ctx.drawImage(image, -image.width / 2, - image.height / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
var angle = 0;
function update(){ // animate the image
if(image.complete){
angle += 0.01;
drawBestFit(ctx,angle,image);
}
// animate until resize then stop remove canvas and flag that it has stopped
if(!STOP){
requestAnimationFrame(update);
}else{
STOP = false;
var canv = document.getElementById("canv");
if(canv !== null){
document.body.removeChild(canv);
}
}
}
update();
}
/** FrameUpdate.js end **/
var STOP = false; // flag to tell demo app to stop
// resize Tell demo to stop then wait for it to stop and start it again
function resizeEvent(){
var waitForStopped = function(){
if(!STOP){ // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped,200);
}
STOP = true;
setTimeout(waitForStopped,100);
}
// if resize stop demo and restart
window.addEventListener("resize",resizeEvent);
// start demo
demo();