在 HTML5 中翻转图片

貌似 HTML5 的 Canvas 只提供了图片的旋转、缩放功能,没有提供图片翻转(水平翻转或垂直翻转)的支持,搜索加试验之后,得到几种实现图片翻转的方法,记录一下。

第一种最简单的是使用 CSS,代码片断如下:

<style>
.flip-x {
	filter: FlipH; /* IE only */
	-moz-transform: matrix(-1, 0, 0, 1, 0, 0);
	-webkit-transform: matrix(-1, 0, 0, 1, 0, 0);
}
</style>
<img src="https://oldj.net/images/oldj.net.png" class="flip-x" />

支持 IE 、Firefox 等各大浏览器。不过,如果想在 HTML5 的 Canvas 中翻转一个图片,CSS 就无能为力了。

在 Canvas 中翻转图片大致有两种思路,一种是先“翻转”画布,在上面画好需要的图片后再将画布“翻转”回来;另一种是先在画布上正常画上原图,用 getImageData 方法取得图片的每一个象素的数据,再将数据镜像交换一下。

先来看“翻转”画布的方法,代码大致类似于这样:

// 正常绘制:
// ctx.drawImage(img, px, py);

// 水平“翻转”画布
ctx.translate(canvas_width, 0);
ctx.scale(-1, 1);
// 下面画的图片是水平翻转的
ctx.drawImage(img, canvas_width - img.width - px, py);
// 画布恢复正常
ctx.translate(canvas_width, 0);
ctx.scale(-1, 1);

可以看到,主要用到了 translate 以及 scale 方法。先用 translate 方法将坐标原点设为画布右上角(默认为左上角),再用 scale(-1, 1) 的方式将画布水平翻转,在上面画好图之后再恢复即可。

另一种象素级的操作原理上也非常简单,就不多解释了,可以直接看源码:

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>test</title>
<style type="text/css">
#cv {
border: solid 1px #333;
}
</style>
</head>
<body>
<canvas id="cv" width="300" height="240"></canvas>

var canvas = document.getElementById("cv"),
	ctx = canvas.getContext("2d"),
	img = new Image();

img.src = "/images/oldj_gmail.png";

function show() {
	// 正常图片
	ctx.drawImage(img, 10, 10);

	// 水平翻转
	ctx.translate(300, 0);
	ctx.scale(-1, 1);
	// 下面画的图片是水平翻转的
	ctx.drawImage(img, 300 - img.width - 10, 60);
	// 恢复正常
	ctx.translate(300, 0);
	ctx.scale(-1, 1);

	// 下面的图片是正常的
	ctx.drawImage(img, 10, 110);


	// 象素级水平翻转图片的方法
	show2();
}

function show2() {
	// 象素级水平翻转的方法

	// 先画一个正常的画片
	var px = 10,
		py = 160;

	ctx.drawImage(img, px, py);

	// 取得这个图片的数据,图片与当前页面必须同域,否则会出错
	var img_data = ctx.getImageData(px, py, img.width, img.height),

		x, y, p, i, i2, t,
		h = img_data.height,
		w = img_data.width,
		w_2 = w / 2;

	// 将 img_data 的数据水平翻转
	for (y = 0; y < h; y ++) {
		for (x = 0; x < w_2; x ++) {
			i = (y<<2) * w + (x<<2);
			i2 = ((y + 1) << 2) * w - ((x + 1) << 2);
			for (p = 0; p < 4; p ++) {
				t = img_data.data[i + p];
				img_data.data[i + p] = img_data.data[i2 + p];
				img_data.data[i2 + p] = t;
			}
		}
	}

	// 重绘水平翻转后的图片
	ctx.putImageData(img_data, px, py);
}

img.onload = function () {
	show();
};
</script>
</body>
</html>

最终效果如下图所示:

Canvas 水平翻转图片

如果你的浏览器支持 Canvas 标签,你也可以点击这儿查看示例。

不过,上面提到的两种方法都不是 HTML5 Canvas 原生支持的,性能上一定会有所损失。如果要频繁地翻转某个图像,看起来最好的办法还是先给它做一个镜像版本,而不是在程序里再翻转。

分类:编程标签:HTML5JavaScript

相关文章:

评论:

老白

非常感觉。准备着用html5 Canvas 写个曹操传,以前玩的那种,战棋类的,呵呵。写着玩的,没任何商业目的。以后会经常麻烦博主的,我对js 不太熟悉啊

老白

看了博主的象素级水平翻转的方法,能不能再深入一点,直接把原图像给编辑一下,比方说一个4848的图片,把它变成4896的图像,就是把未翻转和翻转的上下拼接一块组成一个新的图像,不知道能不能保存成文件

oldj

可以的,我做了一个简单的示例,你可以点击这儿查看。

mathwuyue

不是rotate就好了么?

吼吼

两种方法实际上是一样的,可能第一种性能更好。但第一种的缺陷是对整个画布操作。第二种适用于有多个图层时需要旋转某个图层。

谢谢分享

您好,能麻烦讲授一下这两句话的意思和用途嘛,谢谢:
i = (y<<2) * w + (x<<2);
i2 = ((y + 1) << 2) * w - ((x + 1) << 2);

oldj

“<<”是位移运算,“y<<2”是将y左移2位,相当于将它乘以4。因为每一像素包含r、g、b、a 4个值,所以从图片坐标转换到imageData索引时要乘以4。

这儿i、i2分别是原图和转换后的图片中某个像素数据开始处的索引。

谢谢分享

虽然试验成功,但还是不懂,这个公式是怎么得出的呢,为什么是这种运算关系?求指点。自己想改成垂直且水平翻转,但日思夜想搞不定。

oldj

其实就是坐标变换,假设某个点原来的坐标是 (x, y),图片的宽度、高度分别是 w、h,那么水平翻转后的新坐标就是 (w - x, y) 。

但 imageData 是一个一维的数组,长度为 w * h,其中点 (x, y) 对应的数组索引是 w * y + x,所以水平翻转后变成 w * y + w - x,也即 w * (y + 1) - x 。

这儿的 w * y + x 和 w * (y + w) - x 也就是那两行了:

i = (y<<2) * w + (x<<2);
i2 = ((y + 1) << 2) * w – (x << 2);

至于原文中写的是 ((x+1)<<2) 的原因,是因为我用的图片的宽度是 143px,是一个奇数,除不尽,如果写 (x<<2) 会有错位,所以这儿加了一个1用于修正。

如果你要垂直且水平翻转,应该就是 (x, y) 变成 (w - x, h - y) 吧?

试试:w * (h - y) + (w - x) = w * (h - y + 1) - x

即:
i2 = ((h - y + 1) << 2) * w – (x << 2);

谢谢分享

非常感谢,终于明白这个公式的由来,另外,经过这段时间的实验,发现像素翻转的方法无法将翻转后的图形以透明的方式叠加到原图上(比如翻转的是线框图形),如果需要翻转后改变线条颜色并与翻转前的图形叠加在一起,我的办法是,图像外 区域A(img.width,0)做像素翻转-->改变像素颜色-->复制区域A并drawImage 回顶坐标(0,0) 。虽然可以实现,但感觉有点复杂,有时需要连续点几次按钮,图形才会反应过来,也可能是我这有其他的问题,或许有更好的方法?Keep trying。。。不管怎样,主要的像素翻转问题解决了,真的是非常感谢博主的帮助。

killerlin

想请问一下如果想把水平改成垂直的话该怎么改ㄋ自己有试着参考看看却只能弄出水平==
function flipHorizontal() // 將影像水平翻轉
{
var contx = canvas.getContext('2d');
var imgData = contx.getImageData(0,0, canvas.width, canvas.height);
var x, y, p, i, i2, t;
var h = imgData.height;
var w = imgData.width;
var w2 = w / 2;

 for (y = 0; y &lt; h; y++) {
    for (x = 0; x &lt; w2; x++) {
        i = (y&lt;&lt;2) * w + (x&lt;&lt;2);
        i2 = ((y + 1) &lt;&lt; 2) * w - ((x + 1) &lt;&lt; 2);
        for (p = 0; p &lt; 4; p ++) {
            t = imgData.data[i + p];
            imgData.data[i + p] = imgData.data[i2 + p];
            imgData.data[i2 + p] = t;
        }
    }
} 
contx.putImageData(imgData, 0, 0);	 
}
oldj

和水平翻转类似,如果原坐标是 (x, y),图像的宽度、高度分别为 w、h,那么垂直翻转后的坐标将是 (x, h-y) 。

即上面的 i2 将会变成:

i2 = ((h-y-1)<<2) * w + (x<<2)

总的来说,那个 show2() 函数可以改写成这样:

function show2() {
	// 垂直翻转的方法

	// 先画一个正常的画片
	var px = 10,
		py = 160;

	ctx.drawImage(img, px, py);

	// 取得这个图片的数据,图片与当前页面必须同域,否则会出错
	var img_data = ctx.getImageData(px, py, img.width, img.height),

		x, y, p, i, i2, t,
		h = img_data.height,
		w = img_data.width,
        h_2 = h / 2;

	// 将 img_data 的数据垂直翻转
	for (y = 0; y < h_2; y ++) {
		for (x = 0; x < w; x ++) {
			i = (y<<2) * w + (x<<2);
//			i2 = ((y + 1) << 2) * w - ((x + 1) << 2);
            i2 = ((h-y-1)<<2) * w + (x<<2);
			for (p = 0; p < 4; p ++) {
				t = img_data.data[i + p];
				img_data.data[i + p] = img_data.data[i2 + p];
				img_data.data[i2 + p] = t;
			}
		}
	}

	// 重绘垂直翻转后的图片
	ctx.putImageData(img_data, px, py);
}

注意其中除了i2变化外,x、y循环的终止条件也有变化,y < h 变为了 y < h_2,x < w_2 变为了 x < w。

lei

不行啊 ,翻转出来的效果不对

小小

请问一下垂直翻转的话要怎么改?

oldj

见上一条评论的回复,谢谢。

killerlin

谢谢 我弄出来了最近无聊在搞CANVAS DIP刚好看到你的网页上有贴很多内容 就试着搞搞看了
XD

(我最近開始學html5~_~)

killerlin1

谢谢 我弄出来了最近无聊在搞CANVAS DIP刚好看到你的网页上有贴很多内容 就试着搞搞看了
XD

(我最近開始學html5~_~)

killerlin

再请问一下我刚刚在网页上看到这个逆时针旋转如果是想将他变成顺时针的话是不是要更改这部分? var p =(rcol+c) << 2;
var nr = (newH-1) - c;
var nc = r;
var t = (nr
newW+nc)4;
/
--------------------------------------------------------------------------------------*/

function ccw(cnvs) //逆时针旋转
{

	var contx = cnvs.getContext(&#039;2d&#039;);
	var imageData = contx.getImageData(0,0, canvas.width, canvas.height);
	
	
	var data = imageData.data;
	var row = imageData.height;
	var col = imageData.width;
	
	var newW = row;  
	var newH = col;		

	var ptsData = contx.createImageData(newW, newH);  /
	var pts = ptsData.data;		
	
	for (var r=0; r&lt;row; ++r)
	{
		for (var c=0; c&lt;col; ++c)
		{
			var p = (r*col+c) &lt;&lt; 2;  
			var nr = (newH-1) - c;
			var nc = r; 
			var	t = (nr*newW+nc)*4;	 	
			
			pts[t  ] = data[p  ]; //   red channel
			pts[t+1] = data[p+1]; // green channel
			pts[t+2] = data[p+2]; //  blue channel
			pts[t+3] = 255;       
		}
	}
	
	cnvs.width  = newW;  
	cnvs.height = newH;
	
	contx.putImageData(ptsData,0,0); 		
}

发表评论: