Josh Sanford
Josh Sanford

Reputation: 672

Failing to overwrite antialiased pixels in Cairo

While using Cairo 1.14.6 for display purposes, I found that overwriting the very same path with another color does not necessarily overwrite all pixels, and leaves undesirable artifacts behind.

As evidence of my claim I offer this output from a short self-contained example, the source for which follows further below.

enter image description here

An explanation of the six parts of the image, from left to right:

  1. Original shape stroked in blue.
  2. Original shape overwritten in RGBA white.
  3. Original shape overwritten in RGB white.
  4. Original shape overwritten in RGBA white with CAIRO_OPERATOR_SOURCE mode.
  5. Original shape overwritten in RGBA white with CAIRO_OPERATOR_SOURCE mode and CAIRO_ANTIALIAS_NONE.
  6. Original shape overwritten in RGBA white with CAIRO_OPERATOR_SOURCE mode and CAIRO_ANTIALIAS_BEST.

The image was generated from the following code:

#include "cairo/cairo.h"

#define M_PI 3.14159265358979323846

void draw_shape(cairo_t* cr, int x, int y) {
    cairo_arc(cr, 50 + x, 50 + y, 48, -M_PI, -M_PI / 2);
    cairo_stroke(cr);
    cairo_move_to(cr, x + 2, y + 2);
    cairo_line_to(cr, x + 48, y + 48);
    cairo_stroke(cr);
}

int main(int argc, char** argv) {
    int x = 0;
    int y = 0;
    cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 50);
    cairo_t* cr = cairo_create(surface);

    /* Draw a white background and a few shapes to overwrite */
    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
    cairo_paint(cr);
    cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
    draw_shape(cr, x, y); x += 50;
    draw_shape(cr, x, y); x += 50;
    draw_shape(cr, x, y); x += 50;
    draw_shape(cr, x, y); x += 50;
    draw_shape(cr, x, y); x += 50;
    draw_shape(cr, x, y); x += 50;
    x = 50;

    /* Leftmost shape is left unchanged for reference */

    /* Stroke in RGBA opaque white */
    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
    draw_shape(cr, x, y); x += 50;

    /* Stroke in RGB white */
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    draw_shape(cr, x + 0, y); x += 50;

    /* Stroke in opaque white without blending */
    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
    draw_shape(cr, x, y); x += 50;

    /* Stroke in opaque white without blending, with no antialiasing */
    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
    cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
    draw_shape(cr, x, y); x += 50;

    /* Stroke in opaque white without blending, with best antialiasing */
    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
    cairo_set_antialias(cr, CAIRO_ANTIALIAS_BEST);
    draw_shape(cr, x, y); x += 50;

    /* Write the results to a file */
    cairo_surface_write_to_png(surface, "output.png");

    return 0;
}

It doesn't make intuitive sense to me that overwriting the very same shape would not overwrite all of its pixels, especially if I force it into non-blending CAIRO_OPERATOR_SOURCE mode. The results are the same on the framebuffer that constitutes my actual surface, so this is not an issue with the backend.

Cairo is usually so good at what it does that I'm very surprised at this. Is there no way to overwrite an anti-aliased shape exactly in Cairo?

Upvotes: 0

Views: 347

Answers (1)

Josh Sanford
Josh Sanford

Reputation: 672

What I was trying to do is apparently not possible. I posted my question to the Cairo mailing list and was offered two options:

  1. Keep a copy of the original pixels before drawing over them: "Anti-aliasing involves blending. If you don’t want anti-aliasing, turn it off." (Link)
  2. Draw at a much higher resolution: "The only real solution is to draw a much higher resolution with coverage rounded to exactly zero or one for each pixel." (Link)

More specifically:

All that is stored in the pixel from the first drawing is what percentage of the pixel was covered by the shape. It does not remember exactly what parts of the pixel are covered.

Since antialiasing necessarily involves blending, and since Cairo does not remember what part of a subpixel led to the blending, it has no way of knowing how to undo that blending.

Upvotes: 3

Related Questions