这是我很久以前所做的。 今天翻阅上一个演示时,突然发现了它。 我认为还不错。 分享它,每个人都将学习。
标题首先使这个演示的背景
:
我们公司生产的Webapp基于后端的图像滤镜,渐变和其他效果,也就是说,前端不负责渲染效果,而是将滤镜或渐变的参数发送到后端, 端,后端生成相应的效果图。效果图发送到前端,前端将效果图显示给用户。 通过所描述的过程,可以知道从前端到后端,然后从后端到前端,这将花费大量时间,并且此处理将对后端服务器造成很大压力。 要解决此问题,前端只能实现滤镜和渐变效果,但前提是前端和后端渲染效果必须保持一致。
问题是这样的。 由于问题已得到解决,因此请首先考虑编写演示以首先查看效果。 由于我们的项目基于webpack react redux,并且渲染选择了konvajs,这是一个基于Canvas开发的2d js框架库,您可以单击此处进入其官方网站。 然后,我们的演示也应该基于konvajs,以便以后移植到我们的应用程序中。 在检查konvajs API之后,我们发现它对过滤器的支持已经相当完善,但对渐变的支持仍然很弱。 不支持某些基本渐变,例如线性渐变和径向渐变。 由既然不支持我们只能考虑自己实现了,怎么实现呢?这确实难倒了我这个前端汪,在漫查资料无果的情况下我找后端大佬拿到了他们实现渐变的函数,代码如下:
//圆形渐变
private static IMOperation createCycleGradientIMOperation(Integer originalWidth, Integer originalHeight, Double midpoint) throws Exception {
if (midpoint == null) {
midpoint = 0.0;
}
if (midpoint < 0 || midpoint > 1) {
throw new IllegalArgumentException("midpoint must be 0 to 1. midpoint:" + midpoint);
}
int opacityWidth, opacityHeight;
opacityWidth = (int)(midpoint * originalWidth / 2);
opacityHeight = (int)(midpoint * originalHeight / 2);
IMOperation op = new IMOperation();
op.openOperation();
op.size(originalWidth, originalHeight);
op.addRawArgs("xc:white");
op.alpha("Set");
op.channel("A");
double dd;
if (midpoint == 1) {
dd = 1;
} else {
dd = 1.0 / (originalWidth / 2 - opacityWidth);
}
String expression = String.format("ot=h/w;xi=abs(i-(w/2));yj=abs(j-(h/2));xo=%d;yo=%d;rro=hypot(xo,yo);rr=hypot(w/2,h/2);dd=%f;w>h?1-((sqrt(xi^2*ot^2+xi^2+(yj^2/ot^2)+yj^2))-rro)*dd:1-((sqrt(yj^2*ot^2+xi^2+(xi^2/ot^2)+yj^2))-rro)*dd", opacityWidth, opacityHeight, dd);
op.fx(expression);
op.closeOperation();
op.addImage();
op.compose("atop");
op.composite();
return op;
}
//矩形渐变
private static IMOperation createRectangleGradientIMOperation(Integer originalWidth, Integer originalHeight, Double midpoint) throws Exception {
if (midpoint == null) {
midpoint = 0.0;
}
if (midpoint < 0 || midpoint > 1) {
throw new IllegalArgumentException("midpoint must be 0 to 1. midpoint:" + midpoint);
}
int opacityWidth, opacityHeight;
opacityWidth = (int)(midpoint * originalWidth / 2);
opacityHeight = (int)(midpoint * originalHeight / 2);
IMOperation op = new IMOperation();
op.openOperation();
op.size(originalWidth, originalHeight);
op.addRawArgs("xc:white");
op.alpha("Set");
op.channel("A");
String expression = String.format("ot=h/w;xi=abs(i-(w/2));yj=abs(j-(h/2));xo=%d;yo=%d;rro=hypot(xo,yo);rr=hypot(w/2,h/2);dd=1/(rr-rro);yj>xi*ot?1-((hypot(yj/ot,yj)-rro)*dd):1-((hypot(ot*xi,xi)-rro)*dd)", opacityWidth, opacityHeight);
op.fx(expression);
op.closeOperation();
op.addImage();
op.compose("atop");
op.composite();
return op;
}
//菱形渐变
private static IMOperation createDiamondGradientIMOperation(Integer originalWidth, Integer originalHeight, Double midpoint) throws Exception {
if (midpoint == null) {
midpoint = 0.0;
}
if (midpoint < 0 || midpoint > 1) {
throw new IllegalArgumentException("midpoint must be 0 to 1. midpoint:" + midpoint);
}
int opacityWidth, opacityHeight;
opacityWidth = (int)(midpoint * originalWidth / 2);
opacityHeight = (int)(midpoint * originalHeight / 2);
IMOperation op = new IMOperation();
op.openOperation();
op.size(originalWidth, originalHeight);
op.addRawArgs("xc:white");
op.alpha("Set");
op.channel("A");
double dd;
if (midpoint == 1) {
dd = 1;
} else {
dd = 1.0 / (originalWidth / 2 - opacityWidth);
}
String expression = String.format("ot=h/w;xi=abs(i-(w/2));yj=abs(j-(h/2));xo=%d;yo=%d;dd=%f;1-((xi+(yj/ot)-xo)*dd)", opacityWidth, opacityHeight, dd);
op.fx(expression);
op.closeOperation();
op.addImage();
op.compose("atop");
op.composite();
return op;
}
尽管代码的数量并不多,而且看起来很简单,但是一开始并不清楚,然后在我意识到这些代码中最关键的部分是表达式之前,我冷静下来考虑一下。 这个表达式是干什么用的? 实际上,它是处理每个像素,即通过某种算法改变每个像素的值以实现梯度。 实际上,这在我们理解之后更加简单。 我们只需要通过konvajs取出图像的像素数据,并使用上面表达式中的公式处理每个像素的数据,然后将旧像素数据替换为处理后的新像素数据。 最好检查官方网站的API以获取特定的API。 您可以查看文章末尾的演示,下面是我根据自己的理解转换的javascript代码并进行了简单的包装:
const gradientFixer = {
radial: function(w, h, i, j, ow, oh, dd) {
const ot = h / w;
const xi = Math.abs(i - (w / 2));
const yj = Math.abs(j - (h / 2));
const xo = ow;
const yo = oh;
const rro = Math.hypot(xo, yo);
const rr = Math.hypot(w / 2, h / 2);
if (w > h) {
return Math.round(255 * (1 - (Math.sqrt(Math.pow(xi, 2) * Math.pow(ot, 2) + Math.pow(xi, 2) + (Math.pow(yj, 2) / Math.pow(ot, 2)) + Math.pow(yj, 2)) - rro) * dd));
} else {
return Math.round(255 * (1 - ((Math.sqrt(Math.pow(yj, 2) * Math.pow(ot, 2) + Math.pow(xi, 2) + (Math.pow(xi, 2) / Math.pow(ot, 2)) + Math.pow(yj, 2))), rro) * dd));
}
},
rect: function(w, h, i, j, ow, oh, dd) {
const ot = h / w;
const xi = Math.abs(i - (w / 2));
const yj = Math.abs(j - (h / 2));
const xo = ow;
const yo = oh;
const rro = Math.hypot(xo, yo);
const rr = Math.hypot(w / 2, h / 2);
if (yj > xi * ot) {
return Math.round(255 * (1 - (Math.hypot(yj / ot, yj) - rro) * dd));
} else {
return Math.round(255 * (1 - (Math.hypot(ot * xi, xi) - rro) * dd));
}
},
diamond: function(w, h, i, j, ow, oh, dd) {
const ot = h / w;
const xi = Math.abs(i - (w / 2));
const yj = Math.abs(j - (h / 2));
const xo = ow;
const yo = oh;
const rro = Math.hypot(xo, yo);
const rr = Math.hypot(w / 2, h / 2);
return Math.round(255 * (1 - ((xi + (yj / ot) - xo) * dd)));
}
};
/**
* 生成渐变效果
* @param {[type]} imageData 像素数据
* @param {[type]} midPoint 中心点
* @param {String} type 渐变类型
*/
function gradient(imageData, midPoint, type = 'radial') {
const { width, height } = imageData;
const midpoint = midPoint || 0;
if (midpoint < 0 || midpoint > 1) {
throw new Error('midpoint must be 0 to 1. current midpoint is:' + midpoint);
}
const opacityWidth = Math.floor(midpoint * width / 2);
const opacityHeight = Math.floor(midpoint * height / 2);
const newImageData = new ImageData(width, height);
let dd;
if (midpoint === 1) {
dd = 1;
} else {
dd = 1 / (width / 2 - opacityWidth);
}
const fixFunction = gradientFixer[type];
if (fixFunction) {
for (let i = 0, data = imageData.data; i < height; i++) {
for (let j = 0; j < width; j++) {
const index = i * width * 4 + j * 4,
r = data[index],
g = data[index + 1],
b = data[index + 2];
const a = fixFunction(width, height, j, i, opacityWidth, opacityHeight, dd);
imageData.data[index] = r;
imageData.data[index + 1] = g;
imageData.data[index + 2] = b;
imageData.data[index + 3] = a;
}
}
}
}
如果看懂了上面的代码了,后面的实现就很简单了,这里就不做赘述了哈。欢迎大家指错!!