OPENRNDR & Processing - Loops, shades and randomness

Loops, shades and randomness

Create a grid of circle pairs. In each position in the grid there is a larger circle and maybe (50% chance) a smaller circle. The larger circle has a shade of pink (either brighter or darker). The smaller circle fill color is picked randomly from a set of 4 colors. That set contains white twice to make it more frequent.

Processing / Java

import java.util.Arrays;
int seed = 1111;
int PINK = #FFC0CB;
int WHITE = #FFFFFF;
int BLACK = #000000;
ArrayList<Integer> colors = new ArrayList<Integer>(Arrays.asList(
  PINK, WHITE, WHITE, color(PINK, 128)));

void setup() {
  size(900, 450);
}

void draw() {
  randomSeed(seed);
  background(WHITE);
  for (int x=0; x<=15; x++) {
    for (int y=0; y<=5; y++) {
      PVector pos = new PVector(
        map(x, 0, 15, 100, width - 100), 
        map(y, 0, 5, 100, height - 100)
        );
      stroke(PINK, 102);
      float amount = noise(pos.x * 0.007, pos.y * 0.004) * 3 - 0.7;
      fill(amount > 1 ? lerpColor(PINK, WHITE, amount-1) : 
                        lerpColor(PINK, BLACK, 1-amount));
      circle(pos.x, pos.y, 40);
      if (random(1) < 0.5) {
        fill(colors.get((int)random(colors.size())));
        circle(pos.x, pos.y, 20);
      }
    }
  }
}

OPENRNDR / Kotlin

Imports
// imports are added automatically by the IDE
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.Random
import org.openrndr.math.Vector2
import org.openrndr.math.map
fun main() = application {
    configure {
        width = 900
        height = 450
    }
    program {
        val colors = listOf(ColorRGBa.PINK, ColorRGBa.WHITE,
                            ColorRGBa.WHITE, ColorRGBa.PINK.shade(0.5))
        extend {
            Random.resetState()
            drawer.background(ColorRGBa.WHITE)
            for (x in 0..15) {
                for (y in 0..5) {
                    val pos = Vector2(
                        map(0.0, 15.0, 100.0, width - 100.0, x * 1.0),
                        map(0.0, 5.0, 100.0, height - 100.0, y * 1.0)
                    )
                    drawer.stroke = ColorRGBa.PINK.opacify(0.4)
                    val amount = Random.simplex(pos.x * 0.004, pos.y * 0.003) + 0.5
                    drawer.fill = ColorRGBa.PINK.shade(amount)
                    drawer.circle(pos, 20.0)
                    if (Random.bool()) {
                        drawer.fill = Random.pick(colors)
                        drawer.circle(pos, 10.0)
                    }
                }
            }
        }
    }
}

Concept Processing OPENRNDR
flip a coin random(1) < 0.5 Random.bool()
pick a random item from a list colors.get( (int)random(colors.size())) Random.pick(colors)
reset the random number generator randomSeed(value); //noiseSeed(value); Random.resetState() or Random.seed = "capybara"
get a random noise value noise(x, y) Random.perlin(x, y) or Random.simplex(x, y) or Random.value(x, y) and even more
count from 0 to 15 for(int x=0; x<=15; x++)
for(int x=0; x<16; x++)
.
for(x in 0..15)
for(x in 0 until 16)
(0..15).foreach
get a translucent variation of a color fill(PINK, alpha) ColorRGBa.PINK.opacify(0.4)
get brighter or darker variation of a color if(amount > 1) lerpColorPINK, WHITE, amount-1) else lerpColor(BLACK, PINK, amount) ColorRGBa.PINK.shade(amount)

Notes:

Different noise values are used to achieve similar color shades in both programs. Noise functions and their arguments produce different aesthetics, so you just need to play with the values to get what you want.

By resetting the random seed I avoid a flickering animation, because I obtain the same sequence of random numbers on each frame. But if you want it to flicker remove the randomSeed() / Random.resetState() :slight_smile:

:point_down: Share your questions and comments below | :mag_right: Find other OPENRNDR & Processing posts

3 Likes

Looking good! Random.resetState() should be called automatically by the init block, were you having a different behavior?

Resetting it avoids animation. I didn’t want to get new values on every frame. I’ll mention that.

There was a discussion in slack about which one of these to use:

  • A. for(x in 0 until 10) { ... }
  • B. for(x in 0..9) { ... }
  • C. (0..9).forEach { ... }

Case A, not including the top value (x < 10 in Java), is more common than including it (case B, x <= 10 in Java). The reason I chose B is because I find it less confusing when mapping the looped variable.

For example, if I wanted to map the previous ranges to the range (100.0, 400.0), I would use

  • map(0.0, 9.0, 100.0, 400.0, x * 1.0)

In case A, we have 10 in the loop but 9.0 in the map (because we count from 0 up to - but not including - 10). In cases B and C we have 9 both in the loop and in the map, which I think helps avoid errors.

Note: forEach can also be used to loop but, unlike for and while, it does not provide break nor continue.

1 Like

Hi Abe,

I’ve been playing a little with your above example. The loop idiom you use takes a little time to understand coming from Processing but that is just a matter of getting accustomed to it. I have however a few questions:

  • Regarding the noise functions, I’m not getting the same behaviour between the two.
  • I’ve added a little harmonic motion, but again I’m not getting the same motion.
  • Is there a rectMode in OPENRNDR? I haven’t found one and I’m wondering about translations such as rotations for rectangles…

Here’s my P5 code

float xGridMax = 100.0;
float yGridMax = 15.0;

void setup() {
  size(800, 460);
}

void draw() {

  background(255);
  noStroke();
  for (int x=0; x<=xGridMax; x++) {
    for (int y=0; y<=yGridMax; y++) {
      PVector pos = new PVector(
        map(x, 0, xGridMax, 100, width - 100), 
        map(y, 0, yGridMax, 100, height - 100)
      );

      float amount = noise(pos.x * 0.015, pos.y * 0.015);
      float alpha = map(amount, 0, 1, 0, 255);
      fill(0, 0, 255, alpha);
      float rectH = map(sin((pos.y + pos.x) + frameCount*0.075), -1.0, 1.0, -yGridMax, yGridMax); 
      rect(pos.x, pos.y, 5, rectH);
    }
  }
}

and here’s my OPENRNDR

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.Random
import org.openrndr.math.Vector2
import org.openrndr.math.map


fun main() = application {
    configure {
        width = 800
        height = 460
    }
    program {

        var xGridMax = 100.0
        var yGridMax = 15.0

        extend {
            drawer.background(ColorRGBa.WHITE)
            drawer.stroke = null

            for (x in 0..xGridMax.toInt()) {
                for (y in 0..yGridMax.toInt()) {
                    val pos = Vector2(
                        map(0.0, xGridMax, 100.0, width - 100.0, x * 1.0),
                        map(0.0, yGridMax, 100.0, height - 100.0, y * 1.0)
                    )
                    val amount = Random.perlin(pos.x * 0.015, pos.y * 0.015)
                    val alpha = map(0.0, 1.0, 0.0, 255.0, amount)
                    drawer.fill = ColorRGBa(0.0, 0.0, 255.0, alpha)

                    val rectH = map(-1.0,1.0,-yGridMax, yGridMax,
                        Math.sin((pos.y + pos.x) + frameCount*0.075))
                    drawer.rectangle(pos,5.0, rectH)

                }
            }
        }
    }
}

PS: Sorry for the formatting or rather lack of it.

Hi Mark,

Nice to see your experiment! :slight_smile:

  • To format your block of code you can add “code fences”: that’s an empty line with three backticks ``` before and after your code (I did that for you). Another option is to use the </> button above the editor, but I like it less because it indents everything and apparently it doesn’t do syntax highlighting.
  • Colors in OPENRNDR use normalized values. So use values between 0.0 and 1.0 for R, G, B and A.
  • Perlin noise returns values between -1.0 and 1.0 in OPENRNDR, so adjust your map accordingly.

After changing these I get the same look in both. With one difference though: in Processing it runs at 60 FPS and in OPENRNDR at about 30 FPS in this laptop.

OPENRNDR provides drawer.rectangles() which is more efficient and does run at 60 FPS for this program. BUT you can’t set the color of each rectangle manually afaik. Maybe @edwin can provide some suggestions here. The way I would colorize those rectangles to achieve the same look is with a shade style, but that’s not trivial unless you know about GLSL shaders already. In a nutshell, what the shade style would do is tell all pixels to have a certain alpha based on their position in the screen.

Hi Abe,

thank you for your kind reply and solutions. All makes sense now. From reading a little about your move from Processing to OPENRNDR in another post, you mention shaders and although I’ve only dabbled in a few with P5, I get the impression that its not the best environment for working with them. I imagine that OPENRNDR is far more capable and perhaps better designed for working with and incorporating GLSL? Having looked at OF and Cinder, I saw a substantial use of shaders and it is something I’d like to have more time to learn. Perhaps my learning of OPENRNDR could be orientated towards that? Any suggestions?

Best

Mark

Hi, I found it ok to work with shaders in Processing. Only that you can’t use the Processing IDE to edit them. I made a tool to make it a bit easier.

Then OPENRNDR is the only environment I know with shade styles, which let you add shader effects with one line of code.

What you can do with shaders is more or less the same in all environments. Ok, some may let you add your own attributes to vertices, some provide Compute shaders… but the basics are the same.

I think I should really record and put my shader workshop online :slight_smile: Would you be interested?

1 Like

Would love to read up more on working with shaders in OPENRNDR. :heart_eyes:

I’ve been reviewing the tutorials linked in the OPENRNDR website to make sure they stay up to date.

Here’s how I would write the program above 3 years later:

imports
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.Random
import org.openrndr.extra.shapes.grid
fun main() = application {
    configure {
        width = 900
        height = 450
    }
    program {
        val colors = listOf(ColorRGBa.PINK, ColorRGBa.WHITE,
                            ColorRGBa.WHITE, ColorRGBa.PINK.shade(0.5))
        val cells = drawer.bounds.grid(16, 6, 100.0, 100.0).flatten()
        extend {
            Random.resetState()
            drawer.clear(ColorRGBa.WHITE)
            cells.forEach { cell ->
                val brightness = Random.simplex(cell.center * 0.0035) + 0.5
                drawer.stroke = ColorRGBa.PINK.opacify(0.4)
                drawer.fill = ColorRGBa.PINK.shade(brightness)
                drawer.circle(cell.center, cell.width * 0.4)
                if (Random.bool()) {
                    drawer.fill = Random.pick(colors)
                    drawer.circle(cell.center, cell.width * 0.2)
                }
            }
        }
    }
}

The main differences are:

  • I would use Rectangle.grid() to create a grid layout, instead of writing my own for loops. The intention becomes clearer and the grid is now defined by 4 values instead of 14 (depending on how you count).
  • Random.simplex() now accepts Vector2 and Vector3 arguments. Both can be multiplied by a scalar, making the call to simplex a bit shorter (if you don’t care about scaling x and y differently).
  • I made the circle sizes proportional to the cell width. That way I can easily adjust column and row counts on the grid and have non-overlapping circles. Here with 32 x 12:

  • In some cases I would use colors.random() instead of Random.pick(colors) but the first one is not seeded and the second one is. In this case we want it seeded to avoid having the colors change on every animation frame. On the other hand, if we are displaying a static result, it would be better to render everything to a renderTarget/colorBuffer outside the extend {} block and then drawing that image instead of redrawing all the circles on every animation frame :slight_smile: