在 Canvas 上对透明区域的边缘应用效果
Apply effects to edge of transparent area on Canvas
我有两张画布 - 大小相同。 MainCanvas 完全充满了背景图像。在 SecondaryCanvas 上,我有一个小图像 - 一个具有一些透明区域的公司徽标(基本上所有白色像素的 alpha 都已降低为 0)。
我想对 SecondaryCanvas 应用某种斜角或内部阴影,但仅限于有实际图像的地方。通常我看到人们画一条路径来做这个,但我不可能追踪非透明区域的边缘。
我该怎么做?我突然想到我可以逐个像素扫描整个 SecondaryCanvas 并检查它的相邻像素是否透明(试图找到 'edges')...并应用适当的颜色到像素,如果它...但这似乎非常 CPU 密集。
这是我所知道的最快的使用卷积过滤器的方法。
从源图像复制到目标。我也为此解决方案添加了一些标志。
函数(抱歉拼写错误)。
embose(imageDataSource、imageDataDestination、方法、emboseImageEdge、perceptualCorrect、edgeOnly)
- imageDataSource:源图像的像素数据
- imageDataDestination:目的地的像素数据。必须不同
比来源或结果将是不正确的。必须相同尺寸
- 方法:(字符串)“3 像素软”、“3 像素”、“1 像素”之一 //
忘记了默认值,所以如果传递未知方法将会崩溃。我确定你
可以修复
- emboseImageEdge:(布尔值)如果图像边缘为真像素
将被浮雕。
- perceptualCorrect:(布尔值)如果为真,则使用人类感知计算
亮度。运行时间稍慢。
- edgeOnly:(布尔值)如果为 true,则仅对接近透明的像素进行浮雕
边缘。否则根据那里的亮度对所有不透明像素进行浮雕。
函数return如果处理则为真,否则为假。如果为 false,则不更改任何像素。
待办事项。
我忘记了添加反转,但这很容易做到,只需反转 val = -val
embos 数组中的所有值,然后将中心值设置为 1(必须为 1,否则看起来不好)。
您还可以通过旋转 embos 数组中的 negative/positive 线来旋转光的方向,但这有点复杂。
对于较软的浮雕,创建较大的卷积 embos
阵列。设置 size
和 halfSize
匹配。 EG 3 像素浮雕需要一个 7 x 7 阵列,其中 size=7
和 halfSize = 3
具有一组相似的值。 embos
数组越大,函数越慢。
对于非常大的图像,这将阻止页面。如果将此功能用于大图像,请将此功能移至 Web Worker。
因为它使用卷积过滤器,所以它可以适应做许多其他过滤器。虽然这个只使用像素亮度,但很容易修改为每个颜色通道的过滤器。所以可以适应这种过滤器类型。高斯模糊、模糊、锐化、边缘检测等等。
查看代码示例底部如何使用它。
希望这能满足您的需求,或者可以对其进行调整。如有任何问题,请随时提出。抱歉,评论目前很少,但我时间不够。 return 会尽快解决这个问题。
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
// Groover API log dependency replacement
function log(data){
// console.log(data); // just suck it up
}
// Groover Bitmaps API dependency replacement
// Extracted from Groover.Bitmaps
var createImage= function(w,h){ // create a image of requier size
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d"); // tack the context onto the image
return image;
}
function embose(imageDataSource,imageDataDestination, method, emboseImageEdge, perceptualCorrect,edgeOnly){
"use strict";
var dataS = imageDataSource.data;
var dataD = imageDataDestination.data;
var w = imageDataSource.width;
var h = imageDataSource.height;
if(dataS.length !== dataD.length){
return false; // failed due to size mismatch
}
var embos,size,halfSize;
var lMethod = method.toLowerCase(); // some JS engines flag reasignment of
// arguments as unoptimised as this
// is computationally intensive create a
// new var for lowercase
if(lMethod === "2 pixel soft" ){
embos = [
-0.25 , -0.5 , -1, -1 , 0.5,
-0.5 , -1 , -1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , -1 , 1, 1 , 0.5,
-0.5 , 1, 1, 0.5 , 0.25
];
size = 5;
halfSize = 2;
}else
if(lMethod === "2 pixel" ){
embos = [
-1 , -1 , -1, -1 , 1,
-1 , -1 , -1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , 1, 1, 1 , 1
];
size = 5;
halfSize = 2;
}else
if(lMethod === "1 pixel" ){
embos = [
-1 , -1, -1,
-1, 1, 1,
1 , 1, 1
];
size = 3;
halfSize = 1;
}
var deb = 0
var x,y,l,pl,g,b,a,ind,scx,scy,cx,cy,cInd,nearEdge,pc;
pc = perceptualCorrect; // just for readability
for(y = 0; y < h; y++){
for(x = 0; x < w; x++){
ind = y*4*w+x*4;
l = 0;
nearEdge = false;
if(dataS[ind+3] !== 0){ // ignor transparent pixels
for (cy=0; cy<size; cy++) {
for (cx=0; cx<size; cx++) {
scy = y + cy - halfSize;
scx = x + cx - halfSize;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
cInd = (scy*w+scx)*4;
if(dataS[cInd+3] === 0){
nearEdge = true;
}
l += pc?(embos[cy*size+cx] *
((Math.pow(dataS[cInd++]*0.2126,2)+
Math.pow(dataS[cInd++]*0.7152,2)+
Math.pow(dataS[cInd++]*0.0722,2)
)/3)):
(embos[cy*size+cx] *
((dataS[cInd++]+
dataS[cInd++]+
dataS[cInd++]
)/3));
}else
if(emboseImageEdge){
nearEdge = true;
}
}
}
if((nearEdge && edgeOnly) || ! edgeOnly){
if(pc){
pl = Math.sqrt((Math.pow(dataS[ind]*0.2126,2) + Math.pow(dataS[ind+1]*0.7152,2) + Math.pow(dataS[ind+2]*0.0722,2))/3);
l = Math.sqrt(Math.max(0,l));
if(pl > 0){
pl = l/pl;
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = dataS[ind]; // alpha not effected
}else{ // black pixel
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind] = dataS[ind];
}
}else{
l = Math.max(0,l);
pl = (dataS[ind]+dataS[ind+1]+dataS[ind+2])/3;
if(pl > 0){
pl = l/pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind]; // alpha not effected
}else{ // black pixel
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind] = dataS[ind];
}
}
}else{ // if not edge then just copy image pixel to dest
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind];
}
}else{ // set transparent pixel to zero
dataD[ind+3] = 0;
}
}
}
// all done
return true; // return success
}
var img = createImage(128,128);
img.ctx.font = "32px arial black";
img.ctx.textAlign = "center";
img.ctx.textBaseline = "middle";
img.ctx.lineCap = "round";
img.ctx.lineJoin = "round";
img.ctx.lineWidth = 4
img.ctx.strokeStyle = "#3AD";
img.ctx.fillStyle = "#334";
img.ctx.strokeText("LOGO!",64,64);
img.ctx.fillText("LOGO!",64,64);
ctx.drawImage(img,0,0);
var img1 = createImage(128,128);
var imgData = img.ctx.getImageData(0,0,128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"2 pixel soft",false, true, false)){
img1.ctx.putImageData(imgData1,0,0);
log("ONe")
ctx.drawImage(img1,128,0);
}
img.ctx.fillStyle = "#DA3"; // make is look better for the sell ;)
img.ctx.fillText("LOGO!",64,64);
var imgData = img.ctx.getImageData(0,0,128,128);
var img1 = createImage(128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"2 pixel",false, true, true)){
img1.ctx.putImageData(imgData1,0,0);
ctx.drawImage(img1,0,128);
}
var img1 = createImage(128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"1 pixel",false, false, false)){
img1.ctx.putImageData(imgData1,0,0);
ctx.drawImage(img1,128,128);
}
.canC {
width:256px;
height:256px;
}
<canvas class="canC" id="canV" width=256 height=256></canvas>
我有两张画布 - 大小相同。 MainCanvas 完全充满了背景图像。在 SecondaryCanvas 上,我有一个小图像 - 一个具有一些透明区域的公司徽标(基本上所有白色像素的 alpha 都已降低为 0)。
我想对 SecondaryCanvas 应用某种斜角或内部阴影,但仅限于有实际图像的地方。通常我看到人们画一条路径来做这个,但我不可能追踪非透明区域的边缘。
我该怎么做?我突然想到我可以逐个像素扫描整个 SecondaryCanvas 并检查它的相邻像素是否透明(试图找到 'edges')...并应用适当的颜色到像素,如果它...但这似乎非常 CPU 密集。
这是我所知道的最快的使用卷积过滤器的方法。
从源图像复制到目标。我也为此解决方案添加了一些标志。
函数(抱歉拼写错误)。
embose(imageDataSource、imageDataDestination、方法、emboseImageEdge、perceptualCorrect、edgeOnly)
- imageDataSource:源图像的像素数据
- imageDataDestination:目的地的像素数据。必须不同 比来源或结果将是不正确的。必须相同尺寸
- 方法:(字符串)“3 像素软”、“3 像素”、“1 像素”之一 // 忘记了默认值,所以如果传递未知方法将会崩溃。我确定你 可以修复
- emboseImageEdge:(布尔值)如果图像边缘为真像素 将被浮雕。
- perceptualCorrect:(布尔值)如果为真,则使用人类感知计算 亮度。运行时间稍慢。
- edgeOnly:(布尔值)如果为 true,则仅对接近透明的像素进行浮雕 边缘。否则根据那里的亮度对所有不透明像素进行浮雕。
函数return如果处理则为真,否则为假。如果为 false,则不更改任何像素。
待办事项。
我忘记了添加反转,但这很容易做到,只需反转 val = -val
embos 数组中的所有值,然后将中心值设置为 1(必须为 1,否则看起来不好)。
您还可以通过旋转 embos 数组中的 negative/positive 线来旋转光的方向,但这有点复杂。
对于较软的浮雕,创建较大的卷积 embos
阵列。设置 size
和 halfSize
匹配。 EG 3 像素浮雕需要一个 7 x 7 阵列,其中 size=7
和 halfSize = 3
具有一组相似的值。 embos
数组越大,函数越慢。
对于非常大的图像,这将阻止页面。如果将此功能用于大图像,请将此功能移至 Web Worker。
因为它使用卷积过滤器,所以它可以适应做许多其他过滤器。虽然这个只使用像素亮度,但很容易修改为每个颜色通道的过滤器。所以可以适应这种过滤器类型。高斯模糊、模糊、锐化、边缘检测等等。
查看代码示例底部如何使用它。
希望这能满足您的需求,或者可以对其进行调整。如有任何问题,请随时提出。抱歉,评论目前很少,但我时间不够。 return 会尽快解决这个问题。
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
// Groover API log dependency replacement
function log(data){
// console.log(data); // just suck it up
}
// Groover Bitmaps API dependency replacement
// Extracted from Groover.Bitmaps
var createImage= function(w,h){ // create a image of requier size
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d"); // tack the context onto the image
return image;
}
function embose(imageDataSource,imageDataDestination, method, emboseImageEdge, perceptualCorrect,edgeOnly){
"use strict";
var dataS = imageDataSource.data;
var dataD = imageDataDestination.data;
var w = imageDataSource.width;
var h = imageDataSource.height;
if(dataS.length !== dataD.length){
return false; // failed due to size mismatch
}
var embos,size,halfSize;
var lMethod = method.toLowerCase(); // some JS engines flag reasignment of
// arguments as unoptimised as this
// is computationally intensive create a
// new var for lowercase
if(lMethod === "2 pixel soft" ){
embos = [
-0.25 , -0.5 , -1, -1 , 0.5,
-0.5 , -1 , -1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , -1 , 1, 1 , 0.5,
-0.5 , 1, 1, 0.5 , 0.25
];
size = 5;
halfSize = 2;
}else
if(lMethod === "2 pixel" ){
embos = [
-1 , -1 , -1, -1 , 1,
-1 , -1 , -1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , 1, 1, 1 , 1
];
size = 5;
halfSize = 2;
}else
if(lMethod === "1 pixel" ){
embos = [
-1 , -1, -1,
-1, 1, 1,
1 , 1, 1
];
size = 3;
halfSize = 1;
}
var deb = 0
var x,y,l,pl,g,b,a,ind,scx,scy,cx,cy,cInd,nearEdge,pc;
pc = perceptualCorrect; // just for readability
for(y = 0; y < h; y++){
for(x = 0; x < w; x++){
ind = y*4*w+x*4;
l = 0;
nearEdge = false;
if(dataS[ind+3] !== 0){ // ignor transparent pixels
for (cy=0; cy<size; cy++) {
for (cx=0; cx<size; cx++) {
scy = y + cy - halfSize;
scx = x + cx - halfSize;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
cInd = (scy*w+scx)*4;
if(dataS[cInd+3] === 0){
nearEdge = true;
}
l += pc?(embos[cy*size+cx] *
((Math.pow(dataS[cInd++]*0.2126,2)+
Math.pow(dataS[cInd++]*0.7152,2)+
Math.pow(dataS[cInd++]*0.0722,2)
)/3)):
(embos[cy*size+cx] *
((dataS[cInd++]+
dataS[cInd++]+
dataS[cInd++]
)/3));
}else
if(emboseImageEdge){
nearEdge = true;
}
}
}
if((nearEdge && edgeOnly) || ! edgeOnly){
if(pc){
pl = Math.sqrt((Math.pow(dataS[ind]*0.2126,2) + Math.pow(dataS[ind+1]*0.7152,2) + Math.pow(dataS[ind+2]*0.0722,2))/3);
l = Math.sqrt(Math.max(0,l));
if(pl > 0){
pl = l/pl;
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = dataS[ind]; // alpha not effected
}else{ // black pixel
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind] = dataS[ind];
}
}else{
l = Math.max(0,l);
pl = (dataS[ind]+dataS[ind+1]+dataS[ind+2])/3;
if(pl > 0){
pl = l/pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind]; // alpha not effected
}else{ // black pixel
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind] = dataS[ind];
}
}
}else{ // if not edge then just copy image pixel to dest
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind];
}
}else{ // set transparent pixel to zero
dataD[ind+3] = 0;
}
}
}
// all done
return true; // return success
}
var img = createImage(128,128);
img.ctx.font = "32px arial black";
img.ctx.textAlign = "center";
img.ctx.textBaseline = "middle";
img.ctx.lineCap = "round";
img.ctx.lineJoin = "round";
img.ctx.lineWidth = 4
img.ctx.strokeStyle = "#3AD";
img.ctx.fillStyle = "#334";
img.ctx.strokeText("LOGO!",64,64);
img.ctx.fillText("LOGO!",64,64);
ctx.drawImage(img,0,0);
var img1 = createImage(128,128);
var imgData = img.ctx.getImageData(0,0,128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"2 pixel soft",false, true, false)){
img1.ctx.putImageData(imgData1,0,0);
log("ONe")
ctx.drawImage(img1,128,0);
}
img.ctx.fillStyle = "#DA3"; // make is look better for the sell ;)
img.ctx.fillText("LOGO!",64,64);
var imgData = img.ctx.getImageData(0,0,128,128);
var img1 = createImage(128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"2 pixel",false, true, true)){
img1.ctx.putImageData(imgData1,0,0);
ctx.drawImage(img1,0,128);
}
var img1 = createImage(128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"1 pixel",false, false, false)){
img1.ctx.putImageData(imgData1,0,0);
ctx.drawImage(img1,128,128);
}
.canC {
width:256px;
height:256px;
}
<canvas class="canC" id="canV" width=256 height=256></canvas>