Natural Color Mixing in Processing - image

I am trying to naturally mix green tint on one photo (elephant) and red tint on another photo (ship) by placing the two opaque and tinted photos on top of one another. While both photos appear with equal prominence, when the program is run and both colors can be observed, the red tint (because it is the last tint applied) is more dominant and there is no discernable yellow color in the image no matter which tint is applied first. If anyone knows how to naturally mix colors in processing I would love to hear your advice. I also do not want to simply apply a yellow tint as the last tint. Thanks!
PImage elephant;
PImage ship;
void setup(){
size(695,473);
elephant = loadImage("elephantRider.png");
ship = loadImage("ship.jpg");
ship.filter(OPAQUE);
elephant.filter(OPAQUE);
}
void draw(){
background(255);
tint(0,255,0, 127);
image(elephant, 0,0);
tint(255,0,0,127);
image(ship, 0,0);
}

Think of Processing as painting. What you're doing now is a little bit like drawing one color to the screen, and then drawing another color on top of the first color.
Instead, what you want to do is mix the two colors together before you draw the result to the screen. Think of this as mixing two paints together to make a new color.
You can do this by taking the average of the two colors, something like this:
PImage img;
void setup() {
size(695, 473);
img = loadImage("image.jpg");
}
void draw() {
background(255);
//green
tint(0, 255, 0, 127);
image(img, width/2, height/2, 50, 50);
float blendedRed = (0 + 255) / 2;
float blendedGreen = (255 + 0) / 2;
float blendedBlue = (0 + 0) / 2;
// blended
tint(blendedRed, blendedGreen, blendedBlue, 127);
image(img, mouseX, mouseY, 50, 50);
}
This example is a little contrived because the averages are pretty trivial, but this approach should work for any color.
If you want something more advanced, note that color averaging is a pretty advanced topic. Googling "color averaging" is probably a good start.

Related

How do I blur/unblur the picture according to the mouseY position - going down - blur, going up - unblur

It's like the blur is in never ending loop.
This is what I've wrote so far, it's currently always blurring it up again and again, and I can't make it un-blur.
void draw() {
filter(BLUR, blurRate/60);
nextY = mouseY-blurRate;
blurRate= nextY-mouseY;
}
It will help to split the problem into smaller subproblems.
(Kevin Workman's article is a great start)
The code you shared looks like you're trying to do two things at once:
map the mouse Y position to a blur value
apply the blur value to the blur filter
Let's start with mapping the value.
If you know what the minimum / maximum blur value you need you can make your life easier using Processing's map() function. It takes a value(mouseY in your case) and maps it from one range (mouseY's min/max range: 0 to height) to another range (blur's min/max value, let's say 0 to 6)
The mapping range above (from 0, height to 0, 6) is trivial because can simply divide height / 6 and get the mapped value, but map() is pretty useful and worth getting the hang of.
Here's a minimal mapping example:
void setup(){
}
void draw(){
float blurValue = map(mouseY, 0, height, 0, 6);
background(0);
text("blurValue: " + blurValue, 5, height / 2);
}
The second part is applying the blur filter.
The catch here is avoiding this situation:
currently always blurring it up again and again,
This happens because you apply the blur filter in draw() multiple times per second to the content, so each pass blurs further.
It sounds like what you want is to apply the blur filter to the initial state of your image or graphics. The code you posted doesn't show what is it that you're trying to blur.
If using graphics rendered in processing, one option is to simply clear everything (using background()) and redrawing the graphics before applying the filter. With the filter applied in draw and nothing cleared/redrawn the effect is reapplied to the same (pre blurred) content continously.
Let's take this basic example:
void setup(){
}
void draw(){
line(mouseX, mouseY, pmouseX, pmouseY);
}
Notice that even draw we render a single tiny line, because the graphics aren't cleared (using background()) each tiny line accumulates to form a larger path.
Intuitively, adding blur will accumulate the effect:
void setup(){
}
void draw(){
line(mouseX, mouseY, pmouseX, pmouseY);
filter(BLUR, 0.6);
}
Another option, if you'd rather be more efficient and re-render the same graphics in draw(), you can get an image copy of what's been rendered so far which you could render continuously.
Let's say you're drawing something in setup().
You can easily call get()(with no arguments) to get a "snapshot" of what's been drawn so far a PImage.
Once you have that you can simply render it again and again in draw() as it was drawn in setup() using image().
Once image() is called you can then call filter() with be applied to the global Processing graphics, meaning only what's rendered (while the snapshot PImage will remain intact).
Here's an example illustrating the above:
PImage snapshot;
void setup(){
// draw something
background(0);
noFill();
strokeWeight(3);
for(int i = 0 ; i < 100; i++){
stroke(random(32, 128));
float size = random(3, 27);
ellipse(random(width), random(height), size, size);
}
// take a snapshot of the current graphics
snapshot = get();
}
void draw(){
float blurValue = map(mouseY, 0, height, 0, 6);
// render the image snapshot
image(snapshot, 0, 0);
// blur it
filter(BLUR, blurValue);
// display blur value (should be unblurred)
text("blurValue: " + blurValue, 5, height / 2);
}
Your question doesn't specify, but if you are using PImage, then you need to apply blur to a copy of the image to avoid re-applying blur to an already blurred image. Here's a PImage tweaked version of the above:
PImage snapshot;
void setup(){
// draw something
background(0);
noFill();
strokeWeight(3);
for(int i = 0 ; i < 100; i++){
stroke(random(32, 128));
float size = random(3, 27);
ellipse(random(width), random(height), size, size);
}
// take a snapshot of the current graphics
snapshot = get();
}
void draw(){
float blurValue = map(mouseY, 0, height, 0, 6);
// clone the original image via get()
PImage blurredImage = snapshot.get();
// blur the cloned image
blurredImage.filter(BLUR, blurValue);
// display the blurred image
image(blurredImage, 0, 0);
// display blur value (should be unblurred)
text("blurValue: " + blurValue, 5, height / 2);
}

How to draw a fading object trail? Without background manipulation

I have a simple ellipse, moving across the screen, is there any simple code I could implement to have this ellipse draw a trail behind it that has its alpha fade over time to a certain extent? I still want the trail visible in the end but less bright than the casting ellipse.
You could also do something like this:
let positions = [];
function draw(){
positions.push(mouseX);
positions.push(mouseY);
for(let i in positions){
let x = positions[i];
let y = positions[i + 1];
fill(255, 255 - i * 10); noStroke();
ellipse(mouseX, mouseY, x, y)
}
if(positions.length > 20){
positions.shift();
positions.shift();
}
}
Assuming the ellipse is the only thing being drawn then there is a simple solution which is to, instead of drawing a fully opaque background on each frame, draw a semi-transparent background:
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(0, 35);
ellipse(mouseX, mouseY, 20, 20);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
However, there are some caveats, namely that for certain colors/transparency levels you will be left with permanent "ghost" of the trails due to some weird alpha blending math anomalies.

Inverting colors within shape in Processing

I want to draw shapes which invert the color of the pixels underneath them.
I know similar effects can be achieved in black and white using by setting the blendMode() to DIFFERENCE. However this does not work for colours.
I have also tried to use filter(INVERT) but this affects the whole canvas rather than specific areas.
Is there any way to efficiently achieve these aims in Processing, hopefully with built-in functionality?
I'm not seeing why blendMode does not work for this:
function setup() {
createCanvas(windowWidth, windowHeight);
rectMode(CENTER);
noStroke();
noLoop();
}
function draw() {
background(0);
translate(width / 2, height / 2);
push();
fill(0, 255, 0);
rect(0, 0, width * 0.6, height * 0.6);
pop();
push();
blendMode(DIFFERENCE);
fill(255);
ellipse(width * 0.3, height * 0.3, 100, 100);
pop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Well you could probably do:
color col = get(x, y);
color colInv = color(255 - col.r, 255 - col.g, 255 - col.b, 255 - col.a);
Not sure if you can do 255 - col all at once. Also I'm not sure if this would actually work, but it should...
Also get(x, y) takes a while, so you can also do it with the pixel array The coding train has a nice video on the pixel array.

PGraphics + noSmooth() + alpha = drawing artifacts

Note: I also asked this question on the Processing forum here.
I have this sample code:
PGraphics pg;
void setup() {
size(400, 500);
pg = createGraphics(width, height);
pg.noSmooth();
pg.beginDraw();
pg.background(0, 0, 255);
pg.endDraw();
}
void draw() {
if (mousePressed) {
pg.beginDraw();
pg.stroke(255, 254);
pg.point(mouseX, mouseY);
pg.endDraw();
}
image(pg, 0, 0, width, height);
}
I would expect this code to show a point wherever the user presses the mouse. Instead, I am only able to see points in a couple rectangular areas:
If I remove the call to pg.noSmooth() or if I remove the alpha value in the pg.stroke() call, then it works fine:
If I replace the pg.point() call with pg.ellipse() or pg.rect() then it also works fine.
It seems like the combination of using a PGraphics, the noSmooth() function, the point() function, and an alpha value results in this buggy behavior. I’ve tried in Processing 3.3 and Processing 3.5.2 and I see the same behavior in both.
Am I missing something obvious?
After a wee bit of digging up turns out the JAVA2D renderer draws a point as a diagonal line(line(x, y, x + EPSILON, y + EPSILON);) with a very very very small spacing (static final float EPSILON = 0.0001f;). My guess is this particular configuration the lack aliasing might mean both points of this diagonal line land on the same pixel and end up not being rendered on the top right area which. Why that area and how come this small distance I don't know, but it sounds a bit like the headaches Jakub Valtar and Andres Colubri had to deal with.
FWIW here's a hacky workaround: using a larger distance that does get rendered with transparency and no aliasing:
PGraphics pg;
void setup() {
size(400, 500);
noSmooth();
pg = createGraphics(width/20, height/20);
pg.beginDraw();
// just for debug purposes: rectangle with edge
pg.fill(0, 0, 255);
pg.rect(0,0,pg.width-1,pg.height-1);
pg.stroke(255,255,255, 128);
pg.endDraw();
}
void pointNoSmooth(PGraphics pg, float x,float y){
pg.beginShape();
pg.vertex(x,y);
pg.vertex(x + 0.75,y);//any less than 0.75 distance between vertices and there's nothing to render with aliasing
pg.endShape();
}
void draw() {
background(255);
if (mousePressed) {
pg.beginDraw();
pointNoSmooth(pg,mouseX,mouseY);
pg.endDraw();
}
// render upscaled
image(pg, 0, 0, width, height);
// render small preview in TL corner
image(pg,0,0);
}
Notice that I've set the PGraphics resolution 20 times smaller, then drawn it upscaled so it's easier to see where the pixels land on the PGraphics. I'm not scaling the mouseX,mouseY coordinates though, so you'll need to draw in the small top left preview when testing. That 0.75 distance does the trick: from what I've tested, anything smaller than 0.7499995 starts acting buggy again.

How to make the blend mode MULTIPLY not make all the image black in p5js

I have this codepen: https://codepen.io/giorgiomartini/pen/OjQpKd?editors=0010
Where I paint some shapes and some text, now I want to add a radial overlay between the shapes and the text.
So I created a drawgradient function and call it between the shapes and the text:
function drawGradient() {
blendMode(MULTIPLY)
for (let r = canvasX; r > 0; --r) {
let lightnes = map(r,0,canvasX,255,0)
fill(0, 0, lightnes)
ellipse(0, 0, r*1.8, r*2)
}
}
I want this gradient to have a multiply blend mode, so that it makes the whole thing a bit darker where the pixels are darker in the gradient.
But all i get is a full black overlay...
In photoshop or gimp, if you add a black and white radial gradient with a multiply blend mode it makes the background darker where there are darker pixles in the gradient.. but here in p5js it just makes everything black.
any ideas what am I doing wrong?
This is the mouseClicked function, and at the bottom, you can see the gradient function being called:
function mouseClicked(){
blendMode(BLEND)
const colsArray = randomColor({luminosity: 'light', format: 'hsl',count: 4})
background(colsArray[0])
translate(width/2, height/2)
////////////////////////////////////////////////////////////////// amt initial range
const arrayOfRandomNumsOfFirstProbStepX = createArrayOfRandomNums(amtOfSpotsInFirstProb,startProbStep,firstProbStepX)
const arrayOfRandomNumsOfFirstProbStepY = createArrayOfRandomNums(amtOfSpotsInFirstProb,startProbStep,firstProbStepY)
const arrayOfRandomNumsOfSecondProbStepX = createArrayOfRandomNums(amtOfSpotsInSecondProb,startProbStep,secondProbStepX)
const arrayOfRandomNumsOfSecondProbStepY = createArrayOfRandomNums(amtOfSpotsInSecondProb,startProbStep,secondProbStepY)
drawLinesAtRandomspots(6,random(50),colsArray)
//args => element, arrayOfRandomNumsOfProbStepX, arrayOfRandomNumsOfProbStepY, elmntSizeMin, elmntSizeMax,rot, colors
drawElmntsOnSomeProbabilityStep('ellipse',arrayOfRandomNumsOfFirstProbStepX, arrayOfRandomNumsOfFirstProbStepY , 10, 80, true, colsArray )
drawElmntsOnSomeProbabilityStep('rect',arrayOfRandomNumsOfSecondProbStepX, arrayOfRandomNumsOfSecondProbStepY, 5, 30, true, colsArray)
drawGradient()
addText()
}
Placing clear() at the beginning of the draw() function will clear the pixels within a buffer. This lets you use blendMode(MULTIPLY) without your overlapping shapes turning black after the first few frames.
If the effect you're going for is a linear gradient, it seems a little weird that you're drawing a bunch of ellipses to the screen.
ellipse(0, 0, r*1.8, r*2)
What do you expect this line to do?
Instead, I would think it would make more sense if you would draw a series of lines, a little bit darker or lighter each time. Here's an example:
function drawGradient() {
blendMode(MULTIPLY);
for (let lineX = 0; lineX < width; lineX++) {
let lightness = map(lineX, 0, width, 0, 255);
stroke(0, lightness)
line(lineX, 0, lineX, height)
}
}
This creates a horizontal gradient that fades from light to dark.
Edit: If you want a radial gradient, right now you're drawing a ton of dark circles on top of each other, so they're "stacking" to just become entirely black. You need to do a combination of drawing fewer circles (every 10 pixels instead of every 1 pixel, for example) and drawing them lighter. Here's an example:
function drawGradient() {
blendMode(MULTIPLY);
for (let r = 600; r > 0; r-=10) {
let lightness = map(r, 0, 600, 20, 0);
fill(0, lightness);
noStroke();
ellipse(0, 0, r, r);
}
}
This code draws circles every 10 pixels, and the darkest circle has an alpha of 20 instead of 255. This causes a much lighter gradient. You'll have to play with the exact values to get the effect you're going for.
If you have a follow-up question, please post a MCVE instead of your whole project. Just a couple of hard-coded shapes and a single gradient function would be enough, as long as we can run it. As of now your code is a little hard to debug because it contains a bunch of stuff not directly related to the problem. Good luck.

Resources