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)
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);
As you can see, it doesn’t look that bad with shapes (it actually does), just don’t use it for text:
Changing out the background color to 127, 0
looks a bit better, but still pretty terrible.
It doesn’t look as bad with this SVG, though.
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.
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.