I am doing some blendMode(BURN) to paint some shapes. And other shapes I need to painted directly in the resulting color of the previous shapes, so I need to generate the blended resulting color my self.
I am looking for something like:
let blendedColor = blendColorsBurn(color1, color2);
There's no built in function for this. In 2d mode p5.js leverages the canvas's globalCompositeOperation
property, which when set to color-burn
performs the following color blending operation:
Divides the inverted bottom layer by the top layer, and then inverts the result.
This sounds simple enough, but I wanted to verify it means what it sounds like it means, so I decided to try a quick test implementation. Press the shift and control keys to see the built in BURN blendMode vs my calculations respectively.
let existingContent;
let newContent;
let manualBurn;
let scaleFactor = 1;
function preload() {
existingContent = loadImage("https://www.paulwheeler.us/files/existing-content.png");
newContent = loadImage("https://www.paulwheeler.us/files/new-content.png");
}
function setup() {
createCanvas(windowWidth, windowHeight);
noLoop();
scaleFactor = height / newContent.height;
manualBurn = createImage(newContent.width, newContent.height);
// Divides the inverted bottom layer by the top layer, and then inverts the result.
for (let x = 0; x < newContent.width; x++) {
for (let y = 0; y < newContent.height; y++) {
const c1 = existingContent.get(x, y);
const c2 = newContent.get(x, y);
if (alpha(c2) > 0) {
let a = alpha(c1) / 255;
// Inverted bottom layer
let [inv_r, inv_g, inv_b, inv_a] = [
// Subtracting from alpha instead of 1 is to deal with pre-multiplied alpha
a - red(c1) / 255,
a - green(c1) / 255,
a - blue(c1) / 255,
1 - alpha(c1) / 255,
];
// divided by the top layer
let [div_r, div_g, div_b, div_a] = [
inv_r / (red(c2) / 255),
inv_g / (green(c2) / 255),
inv_b / (blue(c2) / 255),
inv_a / (alpha(c2) / 255),
];
// inverted
let out = [255 * (1 - div_r), 255 * (1 - div_g), 255 * (1 - div_b), max(alpha(c2), 255 * (1 - div_a))];
manualBurn.set(x, y, out);
} else {
manualBurn.set(x, y, c1);
}
}
}
manualBurn.updatePixels();
}
function keyPressed() {
redraw();
}
function keyReleased() {
redraw();
}
function draw() {
clear();
scale(scaleFactor);
if (keyIsDown(CONTROL)) {
blendMode(BLEND);
image(manualBurn, 0, 0);
} else {
image(
existingContent,
0,
0
);
if (keyIsDown(SHIFT)) {
blendMode(BURN);
image(newContent, 0, 0);
}
}
}
html, body {
margin: 0;
padding: 0;
}
body {
background-image: url("https://www.paulwheeler.us/files/checkerboard.jpeg");
background-repeat: repeat;
background-size: 200px;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
There were definitely some gotchas:
colorMode(RGB, 1, 1, 1, 1)
does not work because the red()
, green()
, and blue()
function round to integers 🤦♂️