Questions regarding my rasterized poster

Hey, guys. I was really inspired by one of the generative posters created by the extremely talented Tim Rodenbröker (https://www.instagram.com/p/B8oM8r7i1ns/?utm_source=ig_web_copy_link), so I tried to create something similar as a part of one of my school projects. The idea was to create a poster of two rasterized images with different color values, which will blend into each other with the help of lerpColor() and some oscillating waves.

I did my best and for the most part, my attempt wasn’t that bad. However, I found an issue while playing around with my code. Basically, processing doesn’t recognize that there is a second image being imported to the sketch and projects 2 rasterized duplicates of the first one. Any ideas on how to fix this?

I tried using blendMode() but I wasn’t exactly sure how to integrate it within the code. Other than that I also wanted to ask if you guys have any suggestions for achieving smoother wave transitions. In my case the wave transition equals trans = width*0.01*cos(radians(frameCount)); but its kinda aggressive compared to example from Instagram. Here is another example for smooth transition https://www.instagram.com/p/B35F69mC8-e/?utm_source=ig_web_copy_link

P.S you can use whatever .jpg images you find. Just rename them to image.jpg and image2.jpg Size doesn’t matter because they both will be resized.

Thanks in advance for any help you are able to provide :blush:

PImage img;
PImage img2;

float tilesX = 200;
float tilesY = 200;
float tileW;
float tileH;

color c;
color c2;
color c3;
float bri;
float bri2;

float waveX;
float waveX2;
float wave3;
float trans;

void setup(){
size(600,900,P3D);
img= loadImage("image.jpg");
img2= loadImage("image2.jpg");
img.resize (600,0);
img2.resize (600,0);

tileW = width/tilesX;
tileH = height/tilesY; 

noCursor();
noStroke();
fill(0);
}

void draw(){
background(255);

for(int x = 0; x < tilesX; x++){
for(int y = 0; y < tilesY; y++){

c = img.get(int(x*tileW) ,int(y*tileH));
c2 = img2.get(int(x*tileW) ,int(y*tileH));

bri = map(brightness(c),0,255,4,1);
bri2 = map(brightness(c2),0,255,4,1);

waveX = tan(radians(frameCount*6-0.5*x));
waveX2 = -tan(radians(frameCount*10-0.5*x));
wave3 = tan(radians(frameCount*5+x*1+y*1));

c3 = lerpColor(c, c2, wave3);

trans = width*0.01*cos(radians(frameCount));

stroke(21);
strokeWeight(bri);

stroke(80,255,50); //here I define the first color using rgb values
point(x*tileW + waveX*trans, y*tileH + waveX*trans);
stroke(80,200,210); //here I define the second color using rgb values
point(x*tileW + waveX2*trans, y*tileH + waveX2*trans);

}
}
}

Hi,

Welcome to the forum! :wink:

The issue is that you are getting the brightness for both images like this :

bri = map(brightness(c),0,255,4,1);
bri2 = map(brightness(c2),0,255,4,1);

But then you only use the first one :

strokeWeight(bri);

But for the rest of the loop, the stroke is the same as the first image that’s why it looks duplicated.

Also for performance purposes, it’s recommended to use directly the pixels array as stated in the documentation:

Getting the color of a single pixel with get(x, y) is easy, but not as fast as grabbing the data directly from pixels[] . The equivalent statement to get(x, y) using pixels[] is pixels[y*width+x] . See the reference for pixels[] for more information.


The blendMode() is used to set way pixels with transparency will blend together. Think of it as layers in a photo editing program.

However your code works without using blendMode, but you can add transparency to the colors by adding a fourth parameter so they are not hiding mutually when they cross.

For the smoothing, it depends on the number of points that you display since it impacts performance. Decreasing it to 100 and increasing the size of the dots (but that’s maybe not the effect you want) speeds things up.

Also you use the frameCount variable in the formula of the transition but this variable represents the number of frames since the beginning of the sketch which are integers : 0 1 2 3 4...

However if you convert them to radians, you’ll have a transition that has steps because the transition between each frame is done integer by integer.

You would rather need to declare a float variable and increase it a little bit each time :

float time = 0;

void draw() {
  time += 0.05;
}

Hope it helps! :wink:

I appreciate the time and effort you put into that. Your suggestions have greatly enhanced my understanding of how processing works. I was finally able to fix some of the abovementioned issues. Moreover, I got some really interesting results by using blendMode(MULTIPLY);

Sorry for bothering you again but can I ask one more question? When it comes to the transitions, I was wondering if its possible to achieve an effect, similar to the one used by him (linking his post again https://www.instagram.com/p/B8oM8r7i1ns/?utm_source=ig_web_copy_link) His transition starts off slow and calmly, and it gets gradually more intense with time. It looks as he somehow controls the speed of the oscillating waves How he does that?

I’ve played around and combined some functions such as sin(),cos(),tan() and map() and so on to see how those work together, but at the end my transition still lacks such complexity float trans = width*0.01*cos(radians(frameCount))+2*width*0.02+time;

Other than that, I found out that the lerpColor doesn’t work properly because the newly created color c3; is not used anywhere in the sketch. How can I fix that? Is it necessary to create a PGraphics element, so it can work properly or there is another way.

Thanks again :blush:

c = img.get(int(x*tileW) ,int(y*tileH));
c2 = img2.get(int(x*tileW) ,int(y*tileH));

bri = map(brightness(c),0,255,4,1);
bri2 = map(brightness(c2),0,255,4,1);

c3 = lerpColor(c, c2, wave3);
1 Like

Yess sure we are here for that! :wink:

Nice! It’s useful when you don’t want to have the basic alpha blending.

If you are not satisfied with the trigonometric functions like cos(), sin() and tan(), you can refer to this very cool webpage where you have a list of commonly used easing functions :

Try to mess around with some of those and you can get the effect you’re after.

It depends, what do you want to do with this color? It’s the interpolation between the color of two pixels from the images with the wave3 parameter : you can use it everywhere you want, to add another layer, to modify the size of the dots…

Have fun!

Woah, the easing functions cheat sheet is hella crazy :exploding_head: :muscle: I never thought processing offers that many possibilities. In my case, I am particularly interested in using easeInOutQuint or easeOutExpo in my sketch, so I tried to implement the provided code for each of the easing functions in my project. Unfortunately, I couldn’t run my sketch, because of an error, which I can’t find.

To be honest, it won’t be a lie if I say, that I find the easing functions far more complicated and difficult to understand :cold_face:, but I am eager to decipher them. In addition, I wasn’t able to find any processing-related tutorials on those easing functions, so If you know any, please feel free to send me the links in your spare time. :innocent:

For example, as mentioned on the website, the variable x represents the absolute progress of the animation in the bounds of 0 (beginning of the animation) and 1 (end of animation). in the following function

function easeOutExpo(x: number): number {
return x === 1 ? 1 : 1 - pow(2, -10 * x);
}

And there it comes my new extremely messy and not-working sketch:

PImage img;
PImage img2;

float tilesX = 200;
float tilesY = 200;
float tileW;
float tileH;

color c;
color c2;
color c3;
float bri;
float bri2;

float waveX;
float waveX2;
float wave3;

float trans;


void setup(){
size(600,900,P3D);
img= loadImage("image.jpg");
img2= loadImage("image2.jpg");
img.resize (600,0);
img2.resize (600,0);

tileW = width/tilesX;
tileH = height/tilesY; 

noCursor();
noStroke();
fill(0);
}

void draw(){
background(255);

for(int x = 0; x < tilesX; x++){
for(int y = 0; y < tilesY; y++){

c = img.get(int(x*tileW) ,int(y*tileH));
c2 = img2.get(int(x*tileW) ,int(y*tileH));

bri = map(brightness(c),0,255,4,1);
bri2 = map(brightness(c2),0,255,4,1);

waveX = tan(radians(frameCount*6-0.5*x));
waveX2 = -tan(radians(frameCount*10-0.5*x));
wave3 = tan(radians(frameCount*5+x*1+y*1));

c3 = lerpColor(c, c2, wave3);

float easeOutExpo(x: 0): 1 { //I am not quite sure if thats the right way to execute the easing function
return x === 1 ? 1 : 1 - pow(2, -10 * x);
} 

trans = width*0.01*cos(radians(frameCount))+easeOutExpo; //the idea was to use the easeOutExpo to ease the whole transition, while keeping some of the old trigonometric functions.

stroke(21);

strokeWeight(bri);
stroke(80,255,50); //here I define the first color using rgb values
point(x*tileW + waveX*trans, y*tileH + waveX*trans);
blend(MULTIPLY);
strokeWeight(bri2);
stroke(80,200,210); //here I define the second color using rgb values
point(x*tileW + waveX2*trans, y*tileH + waveX2*trans);

}
}
}

On the original processing website, I found the following example for easing Easing \ Examples \ Processing.org I was able to run my old sketch by declaring the float easing = 2.05; at the start and by adding it to trans = width*0.01*cos(radians(frameCount))+easying; Sadly, I didn’t achieve the result I wanted as the easing didn’t make any difference at all.

One more, I am so sorry to bother you again.

1 Like

Hi again, glad you liked it! :wink:

The easings website is more oriented for the web (CSS and JavaScript) but if you translate the code of the function in any programming language, it should work. So it’s not specific to Processing, it’s just math functions!

Don’t try to understand how they work mathematically at first, but if you talk about the syntax in JavaScript, this is how the easeOutExpo function looks like in Java :

float easeOutExpo(float x) {
  if (x == 1) {
    return 1;
  } else {
    return 1 - pow(2, -10 * x);
  }
}

Which in a condensed way is equivalent to :

float easeOutExpo(float x) {
  return (x == 1) ? 1 : (1 - pow(2, -10 * x));
}

(using the ? ternary operator)

This is essentially using this function :

So we give to the function a value x (bottom axis) representing the time of the animation normalized between 0 and 1 and it gives us back a value also between 0 and 1 which we can use to drive to movement of our objects.

But let’s suppose that you want a repeating motion, how are you going to do this? If I go from 0 to 1 and loop again to 0 then 1 then … your motion is going to be discontinuous because the function start at 0 and end at 1 so there will be a gap :cry:

→ What if we could have a way to get a value that would go from 0 to 1 then go back to 0 and repeat indefinitely? Can you try to find that function?

Look at this super nice gif I made (using Processing of course) :grinning: :grinning:

ease

Hey, josephh. Before continuing with my coding gibberish, I wanted once again to thank you for lending a hand and providing me with all that knowledge. I highly appreciate your help and support. Not to forget how great is your interactive “teaching style” which encourages me to go beyond my limits.

→ What if we could have a way to get a value that would go from 0 to 1 then go back to 0 and repeat indefinitely? Can you try to find that function?

I may sound like a complete idiot, but if I am not wrong, the trigonometric function you talk about is tan As far as I know, the values of this specific function oscillate in the range between infinity to -infinity. Was I anywhere close, or my take is just another example of my neverending quest to keep you laughing and entertained with my stupidity? xD :joy: :upside_down_face:

Even if I did guess right, I still can understand how can I nest the easing function in my sketch. Since my images are being affected primarily by the tan here: float waveX = tan(radians(frameCount*6-0.5*x)); does this mean I should rewrite the synthax line of the easing function as it follows (Probably not, as I wasn’t able to run the sketch)

float easeOutExpo(float waveX) { return (waveX == 1) ? 1 : (1 - pow(2, -10 * waveX)); }
Or should I use other float value from my sketch such as float tileW or float tileH respectively?

Besides, I am not sure if it is mathematically correct, but while playing around I was able to create a loop that goes from one value to another and then repeats itself indefinitely by adding the width to the following equation.
trans = width*0.01*cos(radians(frameCount))+width*0.01; Sadly, that has nothing to do with the easing function. However, I thought it was worth mentioning.

Also, I made some progress while further experimenting with the example I’ve mentioned before. Easing \ Examples \ Processing.org I mean, I was able to run the code and the easing is notable to some extent, however, for some reason, I don’t see the whole rasterized images, but only some random parts of the images. How can I fix that? Here is the code

PImage img;
PImage img2;

float tilesX = 200;
float tilesY = 200;
float tileW;
float tileH;

float time;
float easing = 0.05;

color c;
color c2;
color c3;
float bri;
float bri2;

float waveX;
float waveX2;
float waveY;
float waveY2;
float wave3;

float trans;


void setup(){
size(600,900,P3D);
img= loadImage("image.jpg");
img2= loadImage("n2.jpg");
img.resize (600,0);
img2.resize (600,0);

tileW = width/tilesX;
tileH = height/tilesY; 

noCursor();
noStroke();
fill(0);
}

void draw(){
time += 5.55;
background(255);

for(int x = 0; x < tilesX; x++){
for(int y = 0; y < tilesY; y++){

c = img.get(int(x*tileW) ,int(y*tileH));
c2 = img2.get(int(x*tileW) ,int(y*tileH));

bri = map(brightness(c),0,255,4,1);
bri2 = map(brightness(c2),0,255,4,1);

waveX = tan(radians(frameCount*6-0.5*x));
waveX2 = -tan(radians(frameCount*10-0.5*x));
waveY = tan(radians(frameCount+x));
waveY2 = tan(radians(frameCount+x));
wave3 = tan(radians(frameCount*5+x*1+y*1));

c3 = lerpColor(c, c2, wave3);

float targetX = waveX;
  float dx = targetX -tileW;
  tileW += dx + easing;
  
  float targetY = waveY;
  float dy = targetY- tileH;
   tileH += dy + easing;

trans = width*0.01*cos(radians(frameCount))+width*0.01; 

stroke(21);

strokeWeight(bri);
stroke(80,255,50); //here I define the first color using rgb values
point(x*tileW + waveX*trans, y*tileH);
blendMode(MULTIPLY);
strokeWeight(bri2);
stroke(80,200,210); //here I define the second color using rgb values
point(x*tileW + waveX2*trans, y*tileH + waveY2*trans);

}
}
}

Did you just say trigonometric function ? tan is not the only one but cos and sin are the most obvious ones!! And they are heavily used to create infinitely looping animations :wink:

→ So what if you say : easeOutExpo(cos(time)); where time is the animation time (from 0 to …)?

(instead of using frameCount as the value driving your animation)

There’s too much to debug for me here but here is a cleaned version of your code (without the animation part) :

PImage img;
PImage img2;

int tilesX = 100;
int tilesY = 100;

int tileW;
int tileH;

float easing = 0.05;

float time = 0;


void setup() {
  size(600, 900, P3D);

  // Load images
  img = loadImage("image.png");
  img2 = loadImage("image2.png");
  
  // Resize images
  img.resize(width, height);
  img2.resize(width, height);

  tileW = width / tilesX;
  tileH = height / tilesY;

  // Load pixels for both images
  img.loadPixels();
  img2.loadPixels();
  
  // Global drawing options
  noCursor();
  noStroke();
  fill(0);
  blendMode(MULTIPLY);
}


void draw() {
  background(255);

  for (int x = 0; x < tilesX; x++) {
    for (int y = 0; y < tilesY; y++) {
      // Get pixel index
      int loc = x * tileW + y * tileH * img.width;
      
      // Get image pixels
      color c = img.pixels[loc];
      color c2 = img2.pixels[loc];
      
      // Compute brightness
      float bri = map(brightness(c), 0, 255, 6, 1);
      float bri2 = map(brightness(c2), 0, 255, 6, 1);
      
      // Waves
      float waveX = tan(radians(time * 6 - 0.5 * x));
      float waveX2 = -tan(radians(time * 10 - 0.5 * x));
      float waveY2 = tan(radians(time + x));

      float trans = width * 0.01 * cos(radians(time)) + width * 0.01; 
      
      // Draw first image
      strokeWeight(bri);
      stroke(80, 255, 50);
      point((x * tileW) + (waveX * trans), y * tileH);
      
      // Draw second image
      strokeWeight(bri2);
      stroke(80, 200, 210);
      point((x * tileW) + (waveX2 * trans), (y * tileH) + (waveY2 * trans));
    }
  }
  
  // Increase the time (controls the speed of the animation)
  time += 0.2;
}

Tips :

  • Add comments to your code if you can, it’s always more clear. Don’t put too much, and don’t repeat.

  • Break lines between your pieces of code

  • Respect indentation (spaces before each line) → you can press Ctrl+T in the Processing editor to auto format your code!!

  • Put spaces between operators and commas like :

    map(x,0,5+6,10*-2,100); -> map(x, 0, 5 + 6, 10 * -2, 100);
    
  • The waveX, waveX2, bri, c, c2… variables don’t need to be global (ie outside any function) because you don’t need to access them outside the draw function where they are meant to be created.

Happy coding! :grinning: