stroke/fill 的排序以创建混合重叠
Sequencing of stroke/fill to create a blended overlap
我有大量对象要在 canvas 上绘制。
对象的中心需要与标准 alpha 混合。
后面对象的边框(描边)需要出现以移除任何底层边框,同时保留完整的填充以进行混合。
作为代码片段的示例,尝试了几次失败 - 请注意 'desired' 结果是手动生成的。
解决方案也需要扩展,因为这是针对 requestAnimationFrame 的,将有数千个对象需要迭代,因此执行单独的 beginPath()/stroke() 组合不太可能可行。
var canvas = document.getElementById('canvas');
canvas.width = 600;
canvas.height = 600;
var ctx = canvas.getContext('2d');
//set up control objects
let objects = [{
x: 20,
y: 20,
w: 60,
h: 30,
rgba: "rgba(255, 0,0,.5)"
}, {
x: 40,
y: 30,
w: 60,
h: 30,
rgba: "rgba(0,255,0,.5)"
}]
//manually produce desired outcome
ctx.beginPath();
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y, myObject.w, myObject.h);
}
ctx.beginPath();
ctx.moveTo(40, 50);
ctx.lineTo(20, 50);
ctx.lineTo(20, 20);
ctx.lineTo(80, 20);
ctx.lineTo(80, 30);
ctx.rect(40, 30, 60, 30);
ctx.stroke();
ctx.font = "15px Arial"
ctx.fillStyle = "black";
ctx.fillText("Desired outcome - (done manually for example)", 120, 50);
//Attempt one: fill on iterate, stroke on end
ctx.beginPath();
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.rect(myObject.x, myObject.y + 70, myObject.w, myObject.h);
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y + 70, myObject.w, myObject.h);
}
ctx.stroke();
ctx.fillStyle = "black";
ctx.fillText("Attempt #1: inner corner of red box fully visible", 120, 120);
//Attempt two: fill and stroke on iterate
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.beginPath();
ctx.rect(myObject.x, myObject.y + 140, myObject.w, myObject.h);
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y + 140, myObject.w, myObject.h);
ctx.stroke();
}
ctx.fillStyle = "black";
ctx.fillText("Attempt #2: inner corner of red box partly visible", 120, 170);
ctx.fillText("(This also scales very badly into thousands of strokes)", 120, 190);
<canvas name="canvas" id="canvas" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
您可以通过两次绘制来实现此目的:
首先你将通过迭代
合成你的笔画
- 使用
globalCompositeOperation = "destination-out"
清除填充会落下的每个先前笔画
- 画这个矩形的笔画
完成后,您的 canvas 将只剩下最后一笔。
现在您必须绘制填充,但由于我们希望笔触位于填充之前,我们必须使用其他合成模式:"destination-over"
并迭代我们的 rects 逆序排列:
(async () => {
var canvas = document.getElementById('canvas');
canvas.width = 600;
canvas.height = 600;
var ctx = canvas.getContext('2d');
ctx.scale(2,2)
//set up control objects
let objects = [{
x: 20,
y: 20,
w: 60,
h: 30,
rgba: "rgba(255, 0,0,.5)"
}, {
x: 40,
y: 30,
w: 60,
h: 30,
rgba: "rgba(0,255,0,.5)"
},
{
x: 10,
y: 5,
w: 60,
h: 30,
rgba: "rgba(0,0,255,.5)"
}]
// first pass, composite the strokes
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.beginPath();
ctx.rect(myObject.x, myObject.y, myObject.w, myObject.h);
// erase the previous strokes where our fill will be
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "#000"; // must be opaque
ctx.fill();
// draw our stroke
ctx.globalCompositeOperation = "source-over";
ctx.stroke();
}
await wait(1000);
// second pass, draw the colored fills
// we will draw from behind to keep the stroke at frontmost
// so we need to iterate our objects in reverse order
for (let i = objects.length- 1; i >= 0; i--) {
let myObject = objects[i];
// draw behind
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y, myObject.w, myObject.h);
}
})();
function wait(ms){
return new Promise(res => setTimeout(res, ms));
}
<canvas name="canvas" id="canvas" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
当然,这也可以用在动画中:
var canvas = document.getElementById('canvas');
canvas.width = 100;
canvas.height = 100;
var ctx = canvas.getContext('2d');
//set up control objects
let objects = [{
x: 20,
y: 20,
w: 60,
h: 30,
rgba: "rgba(255, 0,0,.5)"
}, {
x: 40,
y: 30,
w: 60,
h: 30,
rgba: "rgba(0,255,0,.5)"
},
{
x: 10,
y: 5,
w: 60,
h: 30,
rgba: "rgba(0,0,255,.5)"
}]
objects.forEach( rect => {
rect.speedX = Math.random() * 2 - 1;
rect.speedY = Math.random() * 2 - 1;
});
requestAnimationFrame(anim);
onclick = anim
function anim() {
update();
draw();
requestAnimationFrame( anim );
}
function update() {
objects.forEach( rect => {
rect.x = rect.x + rect.speedX;
rect.y = rect.y + rect.speedY;
if(
rect.x + rect.w > canvas.width ||
rect.x < 0
) {
rect.speedX *= -1;
}
if(
rect.y + rect.h > canvas.height ||
rect.y < 0
) {
rect.speedY *= -1;
}
});
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// first pass, composite the strokes
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.beginPath();
ctx.rect(myObject.x, myObject.y, myObject.w, myObject.h);
// erase the previous strokes where our fill will be
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "#000"; // must be opaque
ctx.fill();
// draw our stroke
ctx.globalCompositeOperation = "source-over";
ctx.stroke();
}
// second pass, draw the colored fills
// we will draw from behind to keep the stroke at frontmost
// so we need to iterate our objects in reverse order
for (let i = objects.length- 1; i >= 0; i--) {
let myObject = objects[i];
// draw behind
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y, myObject.w, myObject.h);
}
ctx.globalCompositeOperation = "source-over";
}
<canvas name="canvas" id="canvas" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
我有大量对象要在 canvas 上绘制。
对象的中心需要与标准 alpha 混合。
后面对象的边框(描边)需要出现以移除任何底层边框,同时保留完整的填充以进行混合。
作为代码片段的示例,尝试了几次失败 - 请注意 'desired' 结果是手动生成的。
解决方案也需要扩展,因为这是针对 requestAnimationFrame 的,将有数千个对象需要迭代,因此执行单独的 beginPath()/stroke() 组合不太可能可行。
var canvas = document.getElementById('canvas');
canvas.width = 600;
canvas.height = 600;
var ctx = canvas.getContext('2d');
//set up control objects
let objects = [{
x: 20,
y: 20,
w: 60,
h: 30,
rgba: "rgba(255, 0,0,.5)"
}, {
x: 40,
y: 30,
w: 60,
h: 30,
rgba: "rgba(0,255,0,.5)"
}]
//manually produce desired outcome
ctx.beginPath();
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y, myObject.w, myObject.h);
}
ctx.beginPath();
ctx.moveTo(40, 50);
ctx.lineTo(20, 50);
ctx.lineTo(20, 20);
ctx.lineTo(80, 20);
ctx.lineTo(80, 30);
ctx.rect(40, 30, 60, 30);
ctx.stroke();
ctx.font = "15px Arial"
ctx.fillStyle = "black";
ctx.fillText("Desired outcome - (done manually for example)", 120, 50);
//Attempt one: fill on iterate, stroke on end
ctx.beginPath();
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.rect(myObject.x, myObject.y + 70, myObject.w, myObject.h);
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y + 70, myObject.w, myObject.h);
}
ctx.stroke();
ctx.fillStyle = "black";
ctx.fillText("Attempt #1: inner corner of red box fully visible", 120, 120);
//Attempt two: fill and stroke on iterate
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.beginPath();
ctx.rect(myObject.x, myObject.y + 140, myObject.w, myObject.h);
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y + 140, myObject.w, myObject.h);
ctx.stroke();
}
ctx.fillStyle = "black";
ctx.fillText("Attempt #2: inner corner of red box partly visible", 120, 170);
ctx.fillText("(This also scales very badly into thousands of strokes)", 120, 190);
<canvas name="canvas" id="canvas" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
您可以通过两次绘制来实现此目的:
首先你将通过迭代
合成你的笔画- 使用
globalCompositeOperation = "destination-out"
清除填充会落下的每个先前笔画
- 画这个矩形的笔画
完成后,您的 canvas 将只剩下最后一笔。
现在您必须绘制填充,但由于我们希望笔触位于填充之前,我们必须使用其他合成模式:"destination-over"
并迭代我们的 rects 逆序排列:
(async () => {
var canvas = document.getElementById('canvas');
canvas.width = 600;
canvas.height = 600;
var ctx = canvas.getContext('2d');
ctx.scale(2,2)
//set up control objects
let objects = [{
x: 20,
y: 20,
w: 60,
h: 30,
rgba: "rgba(255, 0,0,.5)"
}, {
x: 40,
y: 30,
w: 60,
h: 30,
rgba: "rgba(0,255,0,.5)"
},
{
x: 10,
y: 5,
w: 60,
h: 30,
rgba: "rgba(0,0,255,.5)"
}]
// first pass, composite the strokes
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.beginPath();
ctx.rect(myObject.x, myObject.y, myObject.w, myObject.h);
// erase the previous strokes where our fill will be
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "#000"; // must be opaque
ctx.fill();
// draw our stroke
ctx.globalCompositeOperation = "source-over";
ctx.stroke();
}
await wait(1000);
// second pass, draw the colored fills
// we will draw from behind to keep the stroke at frontmost
// so we need to iterate our objects in reverse order
for (let i = objects.length- 1; i >= 0; i--) {
let myObject = objects[i];
// draw behind
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y, myObject.w, myObject.h);
}
})();
function wait(ms){
return new Promise(res => setTimeout(res, ms));
}
<canvas name="canvas" id="canvas" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
当然,这也可以用在动画中:
var canvas = document.getElementById('canvas');
canvas.width = 100;
canvas.height = 100;
var ctx = canvas.getContext('2d');
//set up control objects
let objects = [{
x: 20,
y: 20,
w: 60,
h: 30,
rgba: "rgba(255, 0,0,.5)"
}, {
x: 40,
y: 30,
w: 60,
h: 30,
rgba: "rgba(0,255,0,.5)"
},
{
x: 10,
y: 5,
w: 60,
h: 30,
rgba: "rgba(0,0,255,.5)"
}]
objects.forEach( rect => {
rect.speedX = Math.random() * 2 - 1;
rect.speedY = Math.random() * 2 - 1;
});
requestAnimationFrame(anim);
onclick = anim
function anim() {
update();
draw();
requestAnimationFrame( anim );
}
function update() {
objects.forEach( rect => {
rect.x = rect.x + rect.speedX;
rect.y = rect.y + rect.speedY;
if(
rect.x + rect.w > canvas.width ||
rect.x < 0
) {
rect.speedX *= -1;
}
if(
rect.y + rect.h > canvas.height ||
rect.y < 0
) {
rect.speedY *= -1;
}
});
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// first pass, composite the strokes
for (let i = 0, l = objects.length; i < l; i++) {
let myObject = objects[i];
ctx.beginPath();
ctx.rect(myObject.x, myObject.y, myObject.w, myObject.h);
// erase the previous strokes where our fill will be
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "#000"; // must be opaque
ctx.fill();
// draw our stroke
ctx.globalCompositeOperation = "source-over";
ctx.stroke();
}
// second pass, draw the colored fills
// we will draw from behind to keep the stroke at frontmost
// so we need to iterate our objects in reverse order
for (let i = objects.length- 1; i >= 0; i--) {
let myObject = objects[i];
// draw behind
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = myObject.rgba;
ctx.fillRect(myObject.x, myObject.y, myObject.w, myObject.h);
}
ctx.globalCompositeOperation = "source-over";
}
<canvas name="canvas" id="canvas" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>