Creating your own complex Pimage

Not sure if this is a thing, but I like how processing allows you to manipulate pixels using loadPixels, and updatePixels. I understand that this topic probably treads on the domain of shaders, but as I have yet to grasp the concept of vertex shaders and pixels shaders I was just wondering if processing offers the ability to create more complex shapes than just squares using the “createImage” method.

Note

createImage requires width, height and format which therefore produces a square.

https://processing.org/reference/createImage_.html

Thanks in advance.

Hey there Paul,

I’m not completely sure I understand your problem. Wouldn’t you use PShape in case of complex shapes? Do you mean if you can use loadPixels in combination which PShapes?

I guess that would be right.

The main issue I’m trying to deal with is framerate. Drawing a 500x500 square using points and for loops, is taxing on the cpu, and therefore slow, A PImage of the same size even with for loop interactions performs significantly faster. However createImage cannot create anything other than a square.

@LuckSmith did a little experimenting with drawing rects vs. points a while back. It doesn’t answer your question, but it’s related to your current method of drawing squares.

Curious to see what other suggestions will pop up :slight_smile:

Did you see this

https://processing.org/tutorials/pixels/

Also, look at PGraphics - you can use any command there you like.

Hi @paulgoux,

I suppose it depends on how complex you want these shapes to be. Maybe signed distance fields would be a topic of interest? You could tailor Processing functions to look more like a shading language, then try porting some simple fragment shaders to a sketch which uses load- and updatePixels. For example, first, to make Processing a bit more like a shading language:

static class Vec2 {
  float x = 0.0, y = 0.0;
  
  Vec2(float x, float y) {
    this.x = x;
    this.y = y;
  }
}

Vec2 abs(Vec2 a) {
  return new Vec2(abs(a.x), abs(a.y));
}

float clamp(float v, float lb, float ub) {
  return min(max(v, lb), ub);
}

float div(float a, float b) {
  return b == 0.0 ? 0.0 : a / b;
}

float dot(Vec2 a, Vec2 b) {
  return a.x * b.x + a.y * b.y;
}

float length(Vec2 v) {
  return sqrt(dot(v, v));
}

Vec2 max(Vec2 a, float b) {
  return new Vec2(max(a.x, b), max(a.y, b));
}

float mix(float a, float b, float t) {
  return (1.0 - t) * a + t * b;
}

Vec2 mul(Vec2 a, float b) {
  return new Vec2(a.x * b, a.y * b);
}

Vec2 sub(Vec2 a, Vec2 b) {
  return new Vec2(a.x - b.x, a.y - b.y);
}

Obviously, there are limits to this. For one, custom operators cannot be defined for an object. Functions would have to be rewritten to avoid creating new objects (for the sake of performance).

Then, some signed distance field shape creation functions. I referenced Inigo Quilez when defining these.

/* Cf. http://iquilezles.org/www/articles/distfunctions/distfunctions.htm */

float box(Vec2 p, Vec2 b) {
  Vec2 d = sub(abs(p), b);
  return min(max(d.x, max(d.y, 0.0)), 0.0) + length(max(d, 0.0));
}

float capsule(Vec2 p, Vec2 a, Vec2 b, float r) {
  Vec2 pa = sub(p, a);
  Vec2 ba = sub(b, a);
  float h = clamp(div(dot(pa, ba), dot(ba, ba)), 0.0, 1.0);
  return length(sub(pa, mul(ba, h))) - r;
}

float sphere(Vec2 p, float s) {
  return length(p) - s;
}

Last, a main sketch which uses loadPixels and updatePixels:

// For capsule shape.
Vec2 origin = new Vec2(-0.5, -0.5);
Vec2 dest = new Vec2(0.5, 0.5);
float radius = 0.125;

// For box shape.
Vec2 bound = new Vec2(0.325, 0.175);

// For sphere shape.
float size = 0.5;

void setup() {
  size(512, 512);
  colorMode(RGB, 1.0, 1.0, 1.0, 1.0);
}

void draw() {

  // To normalize x and y to [0, 1].
  float hNorm = 1.0 / (height - 1.0);
  float wNorm = 1.0 / (width - 1.0);
  
  // To animate the mixing of shapes.
  Vec2 mouse = new Vec2(mouseX * wNorm, mouseY * hNorm);

  loadPixels();
  for (int i = 0, y = 0; y < height; ++y) {

    // t is y shifted from [0, 1] to [-1, 1].
    float t = (y * hNorm) * 2.0 - 1.0;
    for (int x = 0; x < width; ++x, ++i) {

      // s is x shifted from [0, 1] to [-1, 1].
      float s = (x * wNorm) * 2.0 - 1.0;
      
      // Create coordinate from s and t.
      Vec2 coord = new Vec2(s, t);

      // Create factors from each shape function.
      float fac0 = box(coord, bound);
      float fac1 = capsule(coord, origin, dest, radius);
      float fac2 = sphere(coord, size);

      // Combine factors from capsule, box and sphere shape.
      float fac = min(fac1, mix(fac0, fac2, mouse.x));

      // Wrap the factor to [0, 1].
      fac -= floor(fac);

      // Convert the factor to a color.
      pixels[i] = color(fac, fac, fac, 1.0);
    }
  }
  updatePixels();
}

Long term, however, my guess is that writing a shader would get you closer to the performance you’re looking for.

Best,
Jeremy

3 Likes

thank you so much for this, this has really helped me to understand the foundations of shaders, which I’d currently been struggling with.