自动将HTML5画布裁剪为内容
Automatically Crop HTML5 canvas to contents
假设这是我的画布,上面画了一张邪恶的脸。我想使用toDataURL()
将我的邪恶脸导出为PNG;然而,整个画布都是光栅化的,包括邪恶的脸和画布边缘之间的"空白"。
+---------------+
| |
| |
| (.Y. ) |
| /_ |
| '____/ |
| |
| |
+---------------+
将画布裁剪/修剪/收缩到其内容的最佳方式是什么,这样我的PNG就不会比脸的"边界框"大,如下所示?最好的方法似乎是缩放画布,但假设内容是动态的。。。?我相信应该有一个简单的解决方案,但它让我无法理解,因为我在谷歌上搜索了很多。
+------+
|(.Y. )|
| /_ |
|'____/|
+------+
谢谢!
已编辑(见注释)
function cropImageFromCanvas(ctx) {
var canvas = ctx.canvas,
w = canvas.width, h = canvas.height,
pix = {x:[], y:[]},
imageData = ctx.getImageData(0,0,canvas.width,canvas.height),
x, y, index;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
index = (y * w + x) * 4;
if (imageData.data[index+3] > 0) {
pix.x.push(x);
pix.y.push(y);
}
}
}
pix.x.sort(function(a,b){return a-b});
pix.y.sort(function(a,b){return a-b});
var n = pix.x.length-1;
w = 1 + pix.x[n] - pix.x[0];
h = 1 + pix.y[n] - pix.y[0];
var cut = ctx.getImageData(pix.x[0], pix.y[0], w, h);
canvas.width = w;
canvas.height = h;
ctx.putImageData(cut, 0, 0);
var image = canvas.toDataURL();
}
如果我理解得很好,你想"修剪"掉图像/绘图周围的所有内容,并将画布调整到该大小(就像你在Photoshop中执行"修剪"命令一样)。
以下是我的操作方法。
-
运行所有画布像素,检查它们的alpha分量是否大于0(这意味着在该像素中绘制了一些东西)。例如,如果画布背景充满了纯色,您也可以检查r、g、b值。
-
获取非空的最左边像素的te坐标,和最右边像素的坐标相同。因此,您将获得一个想象中的"矩形"的坐标,该矩形包含非空的画布区域。
-
存储像素数据区域。
-
将您的画布调整为新的尺寸(我们在步骤2中获得的区域尺寸)
-
将保存的区域粘贴回画布。
等等,瞧:)
根据画布的大小,像素数据的加速速度相当慢(如果它很大,可能需要一段时间)。有一些优化可以使用原始画布pixeldata(我想MDN上有一篇关于这个主题的文章),我建议你在谷歌上搜索一下。
我在jsFiddle中准备了一个小草图,您可以将其用作代码的起点。
jsFiddle 的工作样本
希望我帮过你
c: 。
这是我的看法。我觉得所有其他的解决方案都过于复杂。不过,在创建了它之后,我现在看到它和其他的解决方案是一样的,希望他们只是共用一把小提琴,而不是一个函数。
function trimCanvas(canvas){
const context = canvas.getContext('2d');
const topLeft = {
x: canvas.width,
y: canvas.height,
update(x,y){
this.x = Math.min(this.x,x);
this.y = Math.min(this.y,y);
}
};
const bottomRight = {
x: 0,
y: 0,
update(x,y){
this.x = Math.max(this.x,x);
this.y = Math.max(this.y,y);
}
};
const imageData = context.getImageData(0,0,canvas.width,canvas.height);
for(let x = 0; x < canvas.width; x++){
for(let y = 0; y < canvas.height; y++){
const alpha = imageData.data[((y * (canvas.width * 4)) + (x * 4)) + 3];
if(alpha !== 0){
topLeft.update(x,y);
bottomRight.update(x,y);
}
}
}
const width = bottomRight.x - topLeft.x;
const height = bottomRight.y - topLeft.y;
const croppedCanvas = context.getImageData(topLeft.x,topLeft.y,width,height);
canvas.width = width;
canvas.height = height;
context.putImageData(croppedCanvas,0,0);
return canvas;
}
以下是ES语法的代码,简短、快速、简洁:
/**
* Trim a canvas.
*
* @author Arjan Haverkamp (arjan at avoid dot org)
* @param {canvas} canvas A canvas element to trim. This element will be trimmed (reference)
* @param {int} threshold Alpha threshold. Allows for trimming semi-opaque pixels too. Range: 0 - 255
* @returns {Object} Width and height of trimmed canvcas and left-top coordinate of trimmed area. Example: {width:400, height:300, x:65, y:104}
*/
const trimCanvas = (canvas, threshold = 0) => {
const ctx = canvas.getContext('2d'),
w = canvas.width, h = canvas.height,
imageData = ctx.getImageData(0, 0, w, h),
tlCorner = { x:w+1, y:h+1 },
brCorner = { x:-1, y:-1 };
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
if (imageData.data[((y * w + x) * 4) + 3] > threshold) {
tlCorner.x = Math.min(x, tlCorner.x);
tlCorner.y = Math.min(y, tlCorner.y);
brCorner.x = Math.max(x, brCorner.x);
brCorner.y = Math.max(y, brCorner.y);
}
}
}
const cut = ctx.getImageData(tlCorner.x, tlCorner.y, brCorner.x - tlCorner.x, brCorner.y - tlCorner.y);
canvas.width = brCorner.x - tlCorner.x;
canvas.height = brCorner.y - tlCorner.y;
ctx.putImageData(cut, 0, 0);
return {width:canvas.width, height:canvas.height, x:tlCorner.x, y:tlCorner.y};
}
这里投票最多的答案,以及我在网上发现的实现,都多修剪了一个像素,这在尝试从画布中修剪文本时非常明显。我自己写了一篇对我更有效的文章:
var img = new Image;
img.onload = () => {
var canvas = document.getElementById('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
document.getElementById('button').addEventListener('click', ()=>{
autoCropCanvas(canvas, ctx);
document.getElementById('button').remove();
});
};
img.src = 'data:image/png;base64,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';
function autoCropCanvas(canvas, ctx) {
var bounds = {
left: 0,
right: canvas.width,
top: 0,
bottom: canvas.height
};
var rows = [];
var cols = [];
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var x = 0; x < canvas.width; x++) {
cols[x] = cols[x] || false;
for (var y = 0; y < canvas.height; y++) {
rows[y] = rows[y] || false;
const p = y * (canvas.width * 4) + x * 4;
const [r, g, b, a] = [imageData.data[p], imageData.data[p + 1], imageData.data[p + 2], imageData.data[p + 3]];
var isEmptyPixel = Math.max(r, g, b, a) === 0;
if (!isEmptyPixel) {
cols[x] = true;
rows[y] = true;
}
}
}
for (var i = 0; i < rows.length; i++) {
if (rows[i]) {
bounds.top = i ? i - 1 : i;
break;
}
}
for (var i = rows.length; i--; ) {
if (rows[i]) {
bounds.bottom = i < canvas.height ? i + 1 : i;
break;
}
}
for (var i = 0; i < cols.length; i++) {
if (cols[i]) {
bounds.left = i ? i - 1 : i;
break;
}
}
for (var i = cols.length; i--; ) {
if (cols[i]) {
bounds.right = i < canvas.width ? i + 1 : i;
break;
}
}
var newWidth = bounds.right - bounds.left;
var newHeight = bounds.bottom - bounds.top;
var cut = ctx.getImageData(bounds.left, bounds.top, newWidth, newHeight);
canvas.width = newWidth;
canvas.height = newHeight;
ctx.putImageData(cut, 0, 0);
}
<canvas id=canvas style='border: 1px solid pink'></canvas>
<button id=button>crop canvas</button>
- Canvas Html5绘图应用程序,移动画布会导致重大问题
- 如何使用phaser使html5游戏在移动设备浏览器上运行
- HTML5音频加载和播放获胜'我不能在iPad上工作
- HTML5页面底部棒
- Google/html5语音识别JavaScript SDK Chrome网络工具包SpeechRecognition
- Is onfling available for html, html5
- 为什么HTML5拖放的目标是孩子?(可排序列表)
- 安卓平台上的QWebView HTML5地理位置
- HTML5在画布中加载较小的图像并保存实际大小的图像
- 如何检查用户在html5视频播放器中观看了完整的视频
- 使用具有内联样式的tidy-html5
- 从所有元素中删除HTML5验证
- 自动将HTML5画布裁剪为内容
- 在 HTML5 中裁剪图像
- 在HTML5画布中将图像裁剪为椭圆形
- 我如何使用HTML5画布和JavaScript裁剪图像,以便离线裁剪图像
- Html5, js和css3图像裁剪工具
- HTML5画布裁剪非矩形部分和保存图像
- HTML5画布;图像裁剪和多个图像
- 在HTML5图像画布中裁剪图像区域