Subpixel rendering in Processing

Hello,

Lately I had been working on a rewrite of my Processing application in JavaFX, and I had been wondering how to create subpixel rendered shapes. I know it’s possible to use Java’s font smoothing for text (which - in JavaFX - is CoolType), which works great, and it’s possible to do this in Processing too:

import javafx.scene.canvas.GraphicsContext;
import javafx.scene.text.FontSmoothingType;

void setup() {
  GraphicsContext ctx = (GraphicsContext) surface.getNative().getGraphicsContext2D();
  ctx.setFontSmoothingType(FontSmoothingType.LCD);
  // draw some of that smoooooth CoolType text
}

However, I wanted something that’s usable cross-renderer, so I came up with my own solution. Introducing: TerribleType, the worst subpixel rendering. (also present in OpenJDK’s subpixel text rendering implementation for Swing)
TerribleType demo
Just look at that juicy color fringing!
However, unlike JavaFX and others, this rendering works on anything, shapes and SVGs included!

But how is it so good!?!?!?

Well, I put a lot of work into thinking through the possible solutions, like creating my own shape renderer (which was the worst of them all), but I finally got one that worked half-decent. That solution was to render all the shapes at triple the horizontal scale, and then “beat them down” to the correct scale using RGB channels. As you can see above, the solution isn’t as good as I imagined. Here’s the source code:

PImage subpixelRender(CanvasSpec spec) {
  PGraphics pg = createGraphics(spec.width * 3, spec.height);
  
  pg.beginDraw();
  pg.scale(3, 1);
  spec.draw(pg);
  pg.endDraw();
  
  PImage src = pg.get();
  PImage render = createImage(spec.width, spec.height, ARGB);
  
  src.loadPixels();
  render.loadPixels();
  
  for (int i = 0; i < src.pixels.length; i += 3) {
    float alpha = max(alpha(src.pixels[i]), alpha(src.pixels[i + 1]), alpha(src.pixels[i + 2]));
    float red = red(src.pixels[i]);
    float green = green(src.pixels[i + 1]);
    float blue = blue(src.pixels[i + 2]);
    color col = color(red, green, blue, alpha);
    render.pixels[i / 3] = col;
  }
  
  render.updatePixels();
  
  return render;
}

abstract class CanvasSpec {
  int width;
  int height;
  
  CanvasSpec(int w, int h) {
    this.width = w;
    this.height = h;
  }
  
  public abstract void draw(PGraphics pg);
}

How to use it:

PImage subp = subpixelRender(new CanvasSpec(width, height) { // try not to use the full width and height of the window, for performance reasons
  public void draw(PGraphics pg) {
    // important! if this isn't executed the rendering won't work properly
    // this isn't called by default because different colors may work better if there's too much color fringing
    pg.background(255, 0);
    pg.fill(255); pg.stroke(0);
    pg.ellipse(32, 32, 64, 64);
  }
});
image(subp, 0, 0);

screenshot of the example code
As you can see, it doesn’t look that bad with shapes (it actually does), just don’t use it for text:
screenshot of text
Changing out the background color to 127, 0 looks a bit better, but still pretty terrible.
screenshot of text with changed background
It doesn’t look as bad with this SVG, though.
screenshot of rendered SVG
Close up:


You can see some of the color on the bottom.
source: material design icons/test-tube
This SVG doesn’t look as good though.
v

So, there’s definitely room for improvement, and I will actually keep on improving this renderer so it produces (at least) acceptable images.

Now, let me clear up one thing:
Why the heck did you use abstract classes instead of Java 8 lambdas!?
There’s only one reason, and that reason is Processing’s parser. Behind the scenes Processing does pretty much all of its error checking and Processing to Java transformations using an ANTLR grammar, which doesn’t support Java 8 features yet. Yet.

Anyways, I hope you liked this post. I know, my renderer isn’t the best, and probably will never be, but hey, it works!
Feel free to use it in your own sketches, you don’t even have to give credit, but I’d appreciate if you would.

8 Likes

If it comes with “Juicy Color Fringing” then it sounds like you could sell that as an AfterEffects filter for a lot of money.

You should name your subpixelRender function to indicate its non-type purpose.

Pixel Beat-Down?

2 Likes

Well, I didn’t know the correct wording in English, what I actually meant is to squash them horizontally.

No, I think it is great! I also love “TerribleType.”

Common English might be compress (or crunch, or squash). But PixelSquasher doesn’t have the same ring to it as PixelBeatDowner, in my opinion. Although… “beat-down” also means “a one sided fight, an assault”, so maybe that is too violent. “Tamp down” is a bit more neutral…?

Well, the point is, thanks for sharing your subpixel renderer – you should name your hard work!

Yeah, I used Google Translate to get the correct word translated from my own language, but as we all know it doesn’t always do a great job. Anyways, thanks for your opinion!