CanvasRenderingContext2D:globalCompositeOperation 属性
CanvasRenderingContext2D.globalCompositeOperation
属性是 Canvas 2D API 的一个属性,用于设置绘制新形状时要应用的合成操作类型。
值
一个字符串,用于标识要使用的合成或混合模式操作。可以是以下任何值:
"source-over"
-
这是默认设置,将在现有画布内容之上绘制新形状。
"source-in"
-
新形状仅在新的形状和目标画布重叠的位置绘制。其他所有内容都将变为透明。
"source-out"
-
新形状在它不与现有画布内容重叠的地方绘制。
"source-atop"
-
新形状仅在它与现有画布内容重叠的地方绘制。
"destination-over"
-
新形状绘制在现有画布内容的后面。
"destination-in"
-
在新的形状和现有画布内容重叠的位置保留现有画布内容。其他所有内容都将变为透明。
"destination-out"
-
在不与新的形状重叠的地方保留现有内容。
"destination-atop"
-
仅在与新的形状重叠的地方保留现有画布。新形状绘制在画布内容的后面。
"lighter"
-
在两个形状重叠的地方,颜色由颜色值相加确定。
"copy"
-
只显示新的形状。
"xor"
-
在两个形状重叠的地方,形状将变为透明,而在其他所有地方则正常绘制。
"multiply"
-
顶层像素与底层像素相乘。结果将是更暗的图片。
"screen"
-
像素先反转,然后相乘,最后再次反转。结果将是更亮的图片(与
multiply
相反) "overlay"
-
multiply
和screen
的组合。底层中的深色部分将变暗,而浅色部分将变亮。 "darken"
-
保留两层中最暗的像素。
"lighten"
-
保留两层中最亮的像素。
"color-dodge"
-
将底层除以反转的顶层。
"color-burn"
-
将反转的底层除以顶层,然后反转结果。
"hard-light"
-
与
overlay
相似,它是multiply
和screen
的组合——但顶层和底层互换。 "soft-light"
-
hard-light
的更柔和版本。纯黑色或白色不会导致纯黑色或白色。 "difference"
-
将底层从顶层中减去——或反过来——以始终获得正值。
"exclusion"
-
与
difference
相似,但对比度较低。 "hue"
-
保留底层的亮度和色度,同时采用顶层的色调。
"saturation"
-
保留底层的亮度和色调,同时采用顶层的色度。
"color"
-
保留底层的亮度,同时采用顶层的色调和色度。
"luminosity"
-
保留底层的色调和色度,同时采用顶层的亮度。
示例
更改合成操作
此示例使用 globalCompositeOperation
属性来绘制两个矩形,在它们重叠的地方互相排除。
HTML
<canvas id="canvas"></canvas>
JavaScript
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.globalCompositeOperation = "xor";
ctx.fillStyle = "blue";
ctx.fillRect(10, 10, 100, 100);
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 100, 100);
结果
所有值的演示
全局值
此代码设置程序其余部分使用的全局值。
const canvas1 = document.createElement("canvas");
const canvas2 = document.createElement("canvas");
const gco = [
"source-over",
"source-in",
"source-out",
"source-atop",
"destination-over",
"destination-in",
"destination-out",
"destination-atop",
"lighter",
"copy",
"xor",
"multiply",
"screen",
"overlay",
"darken",
"lighten",
"color-dodge",
"color-burn",
"hard-light",
"soft-light",
"difference",
"exclusion",
"hue",
"saturation",
"color",
"luminosity",
].reverse();
const gcoText = [
"This is the default setting and draws new shapes on top of the existing canvas content.",
"The new shape is drawn only where both the new shape and the destination canvas overlap. Everything else is made transparent.",
"The new shape is drawn where it doesn't overlap the existing canvas content.",
"The new shape is only drawn where it overlaps the existing canvas content.",
"New shapes are drawn behind the existing canvas content.",
"The existing canvas content is kept where both the new shape and existing canvas content overlap. Everything else is made transparent.",
"The existing content is kept where it doesn't overlap the new shape.",
"The existing canvas is only kept where it overlaps the new shape. The new shape is drawn behind the canvas content.",
"Where both shapes overlap the color is determined by adding color values.",
"Only the new shape is shown.",
"Shapes are made transparent where both overlap and drawn normal everywhere else.",
"The pixels of the top layer are multiplied with the corresponding pixel of the bottom layer. A darker picture is the result.",
"The pixels are inverted, multiplied, and inverted again. A lighter picture is the result (opposite of multiply)",
"A combination of multiply and screen. Dark parts on the base layer become darker, and light parts become lighter.",
"Retains the darkest pixels of both layers.",
"Retains the lightest pixels of both layers.",
"Divides the bottom layer by the inverted top layer.",
"Divides the inverted bottom layer by the top layer, and then inverts the result.",
"A combination of multiply and screen like overlay, but with top and bottom layer swapped.",
"A softer version of hard-light. Pure black or white does not result in pure black or white.",
"Subtracts the bottom layer from the top layer or the other way round to always get a positive value.",
"Like difference, but with lower contrast.",
"Preserves the luma and chroma of the bottom layer, while adopting the hue of the top layer.",
"Preserves the luma and hue of the bottom layer, while adopting the chroma of the top layer.",
"Preserves the luma of the bottom layer, while adopting the hue and chroma of the top layer.",
"Preserves the hue and chroma of the bottom layer, while adopting the luma of the top layer.",
].reverse();
const width = 320;
const height = 340;
主程序
页面加载时,此代码将运行以设置和运行示例
window.onload = () => {
// lum in sRGB
const lum = {
r: 0.33,
g: 0.33,
b: 0.33,
};
// resize canvas
canvas1.width = width;
canvas1.height = height;
canvas2.width = width;
canvas2.height = height;
lightMix();
colorSphere();
runComposite();
return;
};
此代码 runComposite()
处理了大部分工作,它依赖于许多实用函数来完成困难的部分。
function createCanvas() {
const canvas = document.createElement("canvas");
canvas.style.background = `url(${op_8x8.data})`;
canvas.style.border = "1px solid #000";
canvas.style.margin = "5px";
canvas.width = width / 2;
canvas.height = height / 2;
return canvas;
}
function runComposite() {
const dl = document.createElement("dl");
document.body.appendChild(dl);
while (gco.length) {
const pop = gco.pop();
const dt = document.createElement("dt");
dt.textContent = pop;
dl.appendChild(dt);
const dd = document.createElement("dd");
const p = document.createElement("p");
p.textContent = gcoText.pop();
dd.appendChild(p);
const canvasToDrawOn = createCanvas();
const canvasToDrawFrom = createCanvas();
const canvasToDrawResult = createCanvas();
let ctx = canvasToDrawResult.getContext("2d");
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.drawImage(canvas1, 0, 0, width / 2, height / 2);
ctx.globalCompositeOperation = pop;
ctx.drawImage(canvas2, 0, 0, width / 2, height / 2);
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "rgb(0 0 0 / 80%)";
ctx.fillRect(0, height / 2 - 20, width / 2, 20);
ctx.fillStyle = "#FFF";
ctx.font = "14px arial";
ctx.fillText(pop, 5, height / 2 - 5);
ctx.restore();
ctx = canvasToDrawOn.getContext("2d");
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.drawImage(canvas1, 0, 0, width / 2, height / 2);
ctx.fillStyle = "rgb(0 0 0 / 80%)";
ctx.fillRect(0, height / 2 - 20, width / 2, 20);
ctx.fillStyle = "#FFF";
ctx.font = "14px arial";
ctx.fillText("existing content", 5, height / 2 - 5);
ctx.restore();
ctx = canvasToDrawFrom.getContext("2d");
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.drawImage(canvas2, 0, 0, width / 2, height / 2);
ctx.fillStyle = "rgb(0 0 0 / 80%)";
ctx.fillRect(0, height / 2 - 20, width / 2, 20);
ctx.fillStyle = "#FFF";
ctx.font = "14px arial";
ctx.fillText("new content", 5, height / 2 - 5);
ctx.restore();
dd.appendChild(canvasToDrawOn);
dd.appendChild(canvasToDrawFrom);
dd.appendChild(canvasToDrawResult);
dl.appendChild(dd);
}
}
实用函数
程序依赖于许多实用函数。
const lightMix = () => {
const ctx = canvas2.getContext("2d");
ctx.save();
ctx.globalCompositeOperation = "lighter";
ctx.beginPath();
ctx.fillStyle = "rgb(255 0 0 / 100%)";
ctx.arc(100, 200, 100, Math.PI * 2, 0, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "rgb(0 0 255 / 100%)";
ctx.arc(220, 200, 100, Math.PI * 2, 0, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "rgb(0 255 0 / 100%)";
ctx.arc(160, 100, 100, Math.PI * 2, 0, false);
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.fillStyle = "#f00";
ctx.fillRect(0, 0, 30, 30);
ctx.fill();
};
const colorSphere = (element) => {
const ctx = canvas1.getContext("2d");
const width = 360;
const halfWidth = width / 2;
const rotate = (1 / 360) * Math.PI * 2; // per degree
const offset = 0; // scrollbar offset
const oleft = -20;
const otop = -20;
for (let n = 0; n <= 359; n++) {
const gradient = ctx.createLinearGradient(
oleft + halfWidth,
otop,
oleft + halfWidth,
otop + halfWidth,
);
const color = Color.HSV_RGB({ H: (n + 300) % 360, S: 100, V: 100 });
gradient.addColorStop(0, "rgb(0 0 0 / 0%)");
gradient.addColorStop(0.7, `rgb(${color.R} ${color.G} ${color.B} / 100%)`);
gradient.addColorStop(1, "rgb(255 255 255 / 100%)");
ctx.beginPath();
ctx.moveTo(oleft + halfWidth, otop);
ctx.lineTo(oleft + halfWidth, otop + halfWidth);
ctx.lineTo(oleft + halfWidth + 6, otop);
ctx.fillStyle = gradient;
ctx.fill();
ctx.translate(oleft + halfWidth, otop + halfWidth);
ctx.rotate(rotate);
ctx.translate(-(oleft + halfWidth), -(otop + halfWidth));
}
ctx.beginPath();
ctx.fillStyle = "#00f";
ctx.fillRect(15, 15, 30, 30);
ctx.fill();
return ctx.canvas;
};
// HSV (1978) = H: Hue / S: Saturation / V: Value
Color = {};
Color.HSV_RGB = (o) => {
const S = o.S / 100;
let H = o.H / 360,
V = o.V / 100;
let R, G;
let A, B, C, D;
if (S === 0) {
R = G = B = Math.round(V * 255);
} else {
if (H >= 1) H = 0;
H *= 6;
D = H - Math.floor(H);
A = Math.round(255 * V * (1 - S));
B = Math.round(255 * V * (1 - S * D));
C = Math.round(255 * V * (1 - S * (1 - D)));
V = Math.round(255 * V);
switch (Math.floor(H)) {
case 0:
R = V;
G = C;
B = A;
break;
case 1:
R = B;
G = V;
B = A;
break;
case 2:
R = A;
G = V;
B = C;
break;
case 3:
R = A;
G = B;
B = V;
break;
case 4:
R = C;
G = A;
B = V;
break;
case 5:
R = V;
G = A;
B = B;
break;
}
}
return { R, G, B };
};
const createInterlace = (size, color1, color2) => {
const proto = document.createElement("canvas").getContext("2d");
proto.canvas.width = size * 2;
proto.canvas.height = size * 2;
proto.fillStyle = color1; // top-left
proto.fillRect(0, 0, size, size);
proto.fillStyle = color2; // top-right
proto.fillRect(size, 0, size, size);
proto.fillStyle = color2; // bottom-left
proto.fillRect(0, size, size, size);
proto.fillStyle = color1; // bottom-right
proto.fillRect(size, size, size, size);
const pattern = proto.createPattern(proto.canvas, "repeat");
pattern.data = proto.canvas.toDataURL();
return pattern;
};
const op_8x8 = createInterlace(8, "#FFF", "#eee");
结果
规范
规范 |
---|
HTML 标准 # dom-context-2d-globalcompositeoperation-dev |
浏览器兼容性
BCD 表格仅在启用 JavaScript 的浏览器中加载。