Inner/Outer Stroke Offset Possible?

Using P5js, is it possible to create either an inner or outer equidistant stroke line which follows the contour of the shape?

Take for example the image below. if I had the capitol letter ‘E’ and wanted to draw the inner blue line. How can I code something like this in P5js?
e_outline

The ‘beginContour’ function seems to be the right idea, but don’t know how to apply this to advanced shapes.

The solution may be simple, I just can’t seem to wrap my brain around an implementation.

You could draw the shape with a specific stroke weight and extract the outlines by using a convolution. if you only want to get the inner or outer line, you can draw it only if it is inside or outside of your shape.

I wrote this code. It’s very messy but i hope you get the Idea:

void setup() {
  size(500, 500);
  
  PVector[] shape = new PVector[]{new PVector(100, 100), new PVector(400, 100), new PVector(400, 200), new PVector(300, 200), new PVector(300, 300), new PVector(400, 300), new PVector(400, 400), new PVector(100, 400)};
  
  background(255);
  
  PGraphics shapeGraphics = createGraphics(width, height);
  shapeGraphics.beginDraw();
  shapeGraphics.strokeCap(ROUND);
  shapeGraphics.stroke(0);
  shapeGraphics.strokeWeight(30);
  for(int i = 0; i < shape.length; i++) {
    PVector v1 = shape[i];
    PVector v2 = shape[(i+1)%shape.length];
    shapeGraphics.line(v1.x, v1.y, v2.x, v2.y);
  }
  shapeGraphics.endDraw();
  
  PGraphics innerArea = createGraphics(width, height);
  innerArea.beginDraw();
  innerArea.noStroke();
  innerArea.fill(0);
  innerArea.beginShape();
  for(PVector vert : shape)
    innerArea.vertex(vert.x, vert.y);
  innerArea.endShape(CLOSE);
  innerArea.endDraw();
  
  loadPixels();
  for(int x = 0; x < width; x++)
    for(int y = 0; y < height; y++) {
      color current = shapeGraphics.pixels[x + y*width];
      boolean sameColor = true;
      for(int dx = -1; dx <= 1; dx++)
        for(int dy = -1; dy <= 1; dy++)
          if(x+dx >= 0 && x+dx < width && y+dy >= 0 && y+dy < height && shapeGraphics.pixels[x+dx + (y+dy)*width] != current) {
            sameColor = false;
            dx = 1;
            dy = 1;
          }
      if(!sameColor && alpha(innerArea.pixels[x + y*width]) > 256/2)
        pixels[x + y*width] = #000000;
    }
  updatePixels();
  
  stroke(#FF0000);
  strokeWeight(2);
  noFill();
  beginShape();
  for(PVector vert : shape)
    vertex(vert.x, vert.y);
  endShape(CLOSE);
}

TimeLex,

Thank you for the fast response! Exactly what I was looking for.

This is a clever implementation. You obviously have a lot of experience with Processing.
I’ve been playing with the different parameters in your code to better understand what’s going on.

How about for a PFont character? If I read in an OTF font of the letter ‘D’ for example. How would you implement a solution to put an outline border on the inside or outside of that letter?

Thanks.

1 Like

There are open source embossed fonts available online.

One way would be to represent the arc as many small segments. However, it’s not perfect and can be very power hungry. It is also possible to paint a circle for each pixel of one color in an image. then you have the same effect and you just have to find out which pixels of it are in the figure. One possibility could be to group the contiguous pixels and the group with fewer pixels is the inner one. This technique could be used for any closed figure image. However, I recommend doing this step only once and saving the resulting image, as this process is very performance consuming.

Here the version for images:

PImage originImg;

void setup() {
  size(500, 500);
  
  noSmooth();
  
  originImg = imageFromText(" D ", width, height);
}

void draw() {
  background(0);
  
  blendMode(LIGHTEST);
  tint(#00FF00);
  image(originImg, 0, 0);
  tint(#00FFFF);
  image(outlines(wideImage(originImg, 150*max(mouseX, 1f)/width)), 0, 0);
}

void keyPressed() {
  originImg = imageFromText(" "+str(char(keyCode)).toUpperCase()+" ", width, height);
}

PImage wideImage(PImage in, float weight) {
  PGraphics out = createGraphics(in.width, in.height);
  out.beginDraw();
  out.background(#000000);
  out.noStroke();
  out.fill(#FFFFFF);
  for(int x = 0; x < in.width; x++)
    for(int y = 0; y < in.height; y++) {
      if(brightness(in.pixels[x + y*in.width]) > 256/2)
        out.circle(x, y, weight);
    }
  out.endDraw();
  return out;
}

PImage imageFromText(String text, int w, int h) {
  PGraphics out = createGraphics(w, h);
  out.beginDraw();
  out.background(#000000);
  out.fill(#FFFFFF);
  out.textAlign(CENTER, CENTER);
  out.textSize(h);
  float textWidth = out.textWidth(text);
  if(textWidth / w > 1f)
    out.textSize(h * w / textWidth);
  out.text(text, w/2f, h/2f);
  out.endDraw();
  return out;
}

PImage outlines(PImage in) {
  PImage out = createImage(in.width, in.height, ARGB);
  out.loadPixels();
  for(int x = 0; x < in.width; x++)
    for(int y = 0; y < in.height; y++) {
      color current = in.pixels[x + y*in.width];
      boolean sameColor = true;
      for(int dx = -1; dx <= 1; dx++)
        for(int dy = -1; dy <= 1; dy++)
          if(x+dx >= 0 && x+dx < in.width && y+dy >= 0 && y+dy < in.height && in.pixels[x+dx + (y+dy)*in.width] != current) {
            sameColor = false;
            dx = 1;
            dy = 1;
          }
      out.pixels[x + y*in.width] = sameColor ? #000000 : #FFFFFF;
    }
  out.updatePixels();
  return out;
}

I’ve not implemented the method for filtering the inner or outer line so you currently get both.

TimeLex,

Thanks so much. This is totally amazing!
I’m amazed at the pool of skilled talent this forum has to offer.

arbo.

1 Like