clipping

Theo Veenker <[email protected]>
Newsgroups gmane.comp.lib.cairo
Message-ID <[email protected]>
Hi all,

Since 1.12.0 I have noticed a problem with drawing text (using pango) onto
an xlib surface or similar surface when there is global scaling applied and
the text drawing involves some clipping.

My purpose is as follows. Let's say I have some graphics (containing text)
which is normally displayed in a fullscreen window, but in some situations
I want to show a scaled down version of the window, so the user can see a
mock-up of the fullscreen version. It's nothing special. It works as expected
with cairo 1.10.2 and earlier. It also works fine when drawing onto an image
surface.

I have created a test case which demonstrates the bug. See attached images
for the 1.10.2 (OK) and for 1.12.x (not OK). The drawing routine for the
test case is this:

void drawText(cairo_surface_t *dst, PangoLayout *layout,
     int winwidth, int winheight, int virtualwidth, int virtualheight)
{
     double scalingx = (double)winwidth / virtualwidth;
     double scalingy = (double)winheight / virtualheight;

     cairo_t *cr = cairo_create(dst);

     // Apply 'global' scaling.
     cairo_scale(cr, scalingx, scalingy);

     // Let's assume the drawing involves some clipping.
     cairo_rectangle(cr, 10, 10, virtualwidth-20, virtualheight-20);
     cairo_clip(cr);

     // Clear background.
     cairo_set_source_rgb(cr, 0.8, 1, 1);
     cairo_paint(cr);

     // Draw text.
     cairo_translate(cr, 15, 15);
     cairo_set_source_rgb(cr, 0, 0, 0);
     pango_cairo_show_layout(cr, layout);
     cairo_new_path(cr);

     cairo_destroy(cr);
}

As said this works fine when drawing into an image surface or when using
cairo <= 1.10.2. There seems to be a problem with the clipping code.
Commenting out the cairo_clip() call makes the program work as expected
besides that there's no clipping going on.

Attached are two test programs scaledtextclipbug1.c and scaledtextclipbug2.c.
The first shows the graphic artifacts when you start it. The latter is an
interactive version to play with (F1=freeze virtual size, F2=make virtual
size equal to window size). After starting scaledtextclipbug2 hit F1 and
then resize the window horizontally; you'll see the weirdness.

Note the problem only affects text.

I apologize not having reported the problem before, but I just didn't have
the time to work out a test case.

Theo

-- 
cairo mailing list
[email protected]
http://lists.cairographics.org/mailman/listinfo/cairo
scaledtextclipbug1.c (text/x-csrc, 4.8 KB)
// gcc -o scaledtextclipbug1 -g -Wall scaledtextclipbug1.c -lX11 -lXext $(pkg-config --cflags --libs pangocairo)

#include <pango/pangocairo.h>
#include <cairo-xlib.h>
#include <math.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

// 0: draw to xlib surface
// 1: draw to similar xlib surface
// 2: draw to image surface
#define USE_BUFFER      0

void drawText(cairo_surface_t *dst, PangoLayout *layout,
    int winwidth, int winheight, int virtualwidth, int virtualheight)
{
    if (!dst) return;

    double scalingx = (double)winwidth / virtualwidth;
    double scalingy = (double)winheight / virtualheight;

    printf("window: %dx%d\t\tcontent: %dx%d\tscaling: %d%%x%d%%\n",
        winwidth, winheight, virtualwidth, virtualheight,
        (int)(100*scalingx), (int)(100 *scalingy));

    cairo_t *cr = cairo_create(dst);

    // Apply 'global' scaling.
    cairo_scale(cr, scalingx, scalingy);

    // -----------------------------------------------------------

    // Let's assume the drawing involves some clipping.
    cairo_rectangle(cr, 10, 10, virtualwidth-20, virtualheight-20);
    cairo_clip(cr);

    // Clear background.
    cairo_set_source_rgb(cr, 0.8, 1, 1);
    cairo_paint(cr);

    // Draw text.
    cairo_translate(cr, 15, 15);
    cairo_set_source_rgb(cr, 0, 0, 0);
//    pango_cairo_update_layout(cr, layout);
    pango_cairo_show_layout(cr, layout);
    cairo_new_path(cr);

    // -----------------------------------------------------------

    cairo_destroy(cr);
}

void copyBufferToSurface(cairo_surface_t *dst, cairo_surface_t *src)
{
    if (!dst || !src) return;

    cairo_t *cr = cairo_create(dst);

    cairo_set_source_surface(cr, src, 0, 0);
    cairo_paint(cr);

    cairo_destroy(cr);
}

#if USE_BUFFER
cairo_surface_t* createBuffer(cairo_surface_t* other, int w, int h)
{
#if USE_BUFFER == 1
    cairo_surface_t* surface =
        cairo_surface_create_similar(other, CAIRO_CONTENT_COLOR, w, h);
#else
    cairo_surface_t* surface =
        cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
#endif

    cairo_t* cr = cairo_create(surface);
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_paint(cr);
    cairo_destroy(cr);

    return surface;
}
#endif

int main()
{
    Display *display;
    Window win;
    XEvent event;
    int quit;
    int winwidth = 363, winheight = 200;
    int virtualwidth = 365, virtualheight = winheight;

    cairo_surface_t *surface = NULL;
    cairo_surface_t *buffer = NULL;

    PangoContext *context;
    PangoLayout *layout;
    PangoFontDescription *fontdescrip;

    fontdescrip = pango_font_description_from_string("Sans-Serif 25");
    context = pango_font_map_create_context(pango_cairo_font_map_get_default());
    pango_context_set_font_description(context, fontdescrip);

    layout = pango_layout_new(context);
    pango_layout_set_text(layout,
        "All human beings are born free and equal in "
        "dignity and rights. They are endowed with reason "
        "and conscience and should act towards one another "
        "in a spirit of brotherhood.", -1);
    pango_layout_set_width(layout, virtualwidth * PANGO_SCALE);
    g_object_unref(context);

    // Open display and create window.
    display = XOpenDisplay(NULL);
    if (!display) {
        printf("Error: unable to open display\n");
        return -1;
    }
    win = XCreateSimpleWindow(display,
        RootWindow(display, DefaultScreen(display)),
        0, 0, winwidth, winheight, 0,
        BlackPixel(display, DefaultScreen(display)),
        WhitePixel(display, DefaultScreen(display)));

    surface = cairo_xlib_surface_create(display, win,
        DefaultVisual(display, DefaultScreen(display)),
        winwidth, winheight);
#if USE_BUFFER
    buffer = createBuffer(surface, winwidth, winheight);
#endif

    // Enter event loop.
    XSelectInput(display, win, StructureNotifyMask | ExposureMask |
        KeyPressMask | ButtonPressMask);
    XMapWindow(display, win);
    quit = 0;
    while (!quit) {
        int index;
        KeySym keysym;
        XNextEvent(display, &event);
        switch (event.type) {
            case Expose:
                if (event.xexpose.count != 0) break;
                drawText(buffer ? buffer : surface, layout,
                    winwidth, winheight, virtualwidth, virtualheight);
                if (buffer) copyBufferToSurface(surface, buffer);
                break;

            case KeyPress:
                index = (event.xkey.state & (ShiftMask|LockMask)) ? 1 : 0;
                keysym = XLookupKeysym(&event.xkey, index);
                if (keysym == XK_Escape) {
                    quit = 1;
                }
                break;
        }
    }

    g_object_unref(layout);
    if (surface) cairo_surface_destroy(surface);
    if (buffer) cairo_surface_destroy(buffer);

    XDestroyWindow(display, win);
    XCloseDisplay(display);

    return 0;
}
scaledtextclipbug2.c (text/x-csrc, 6.1 KB)
// gcc -o scaledtextclipbug2 -g -Wall scaledtextclipbug2.c -lX11 -lXext $(pkg-config --cflags --libs pangocairo)

#include <pango/pangocairo.h>
#include <cairo-xlib.h>
#include <math.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

// 0: draw to xlib surface
// 1: draw to similar xlib surface
// 2: draw to image surface
#define USE_BUFFER      0

void drawText(cairo_surface_t *dst, PangoLayout *layout,
    int winwidth, int winheight, int virtualwidth, int virtualheight)
{
    if (!dst) return;

    double scalingx = (double)winwidth / virtualwidth;
    double scalingy = (double)winheight / virtualheight;

    printf("window: %dx%d\t\tcontent: %dx%d\tscaling: %d%%x%d%%\n",
        winwidth, winheight, virtualwidth, virtualheight,
        (int)(100*scalingx), (int)(100 *scalingy));

    cairo_t *cr = cairo_create(dst);

    // Apply 'global' scaling.
    cairo_scale(cr, scalingx, scalingy);

    // -----------------------------------------------------------

    // Let's assume the drawing involves some clipping.
    cairo_rectangle(cr, 10, 10, virtualwidth-20, virtualheight-20);
    cairo_clip(cr);

    // Clear background.
    cairo_set_source_rgb(cr, 0.8, 1, 1);
    cairo_paint(cr);

    // Draw text.
    cairo_translate(cr, 15, 15);
    cairo_set_source_rgb(cr, 0, 0, 0);
//    pango_cairo_update_layout(cr, layout);
    pango_cairo_show_layout(cr, layout);
    cairo_new_path(cr);

    // -----------------------------------------------------------

    cairo_destroy(cr);
}

void copyBufferToSurface(cairo_surface_t *dst, cairo_surface_t *src)
{
    if (!dst || !src) return;

    cairo_t *cr = cairo_create(dst);

    cairo_set_source_surface(cr, src, 0, 0);
    cairo_paint(cr);

    cairo_destroy(cr);
}

#if USE_BUFFER
cairo_surface_t* createBuffer(cairo_surface_t* other, int w, int h)
{
#if USE_BUFFER == 1
    cairo_surface_t* surface =
        cairo_surface_create_similar(other, CAIRO_CONTENT_COLOR, w, h);
#else
    cairo_surface_t* surface =
        cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
#endif

    cairo_t* cr = cairo_create(surface);
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_paint(cr);
    cairo_destroy(cr);

    return surface;
}
#endif

int main()
{
    Display *display;
    Window win;
    XEvent event;
    int quit;
    int winwidth = 365, winheight = 200;
    int virtualwidth = winwidth, virtualheight = winheight;
    int freezevirtualwidth = 0;

    cairo_surface_t *surface = NULL;
    cairo_surface_t *buffer = NULL;

    PangoContext *context;
    PangoLayout *layout;
    PangoFontDescription *fontdescrip;

    fontdescrip = pango_font_description_from_string("Sans-Serif 25");
    context = pango_font_map_create_context(pango_cairo_font_map_get_default());
    pango_context_set_font_description(context, fontdescrip);

    layout = pango_layout_new(context);
    pango_layout_set_text(layout,
        "All human beings are born free and equal in "
        "dignity and rights. They are endowed with reason "
        "and conscience and should act towards one another "
        "in a spirit of brotherhood.", -1);
    pango_layout_set_width(layout, virtualwidth * PANGO_SCALE);
    g_object_unref(context);

    // Open display and create window.
    display = XOpenDisplay(NULL);
    if (!display) {
        printf("Error: unable to open display\n");
        return -1;
    }
    win = XCreateSimpleWindow(display,
        RootWindow(display, DefaultScreen(display)),
        0, 0, winwidth, winheight, 0,
        BlackPixel(display, DefaultScreen(display)),
        WhitePixel(display, DefaultScreen(display)));

    // Enter event loop.
    XSelectInput(display, win, StructureNotifyMask | ExposureMask |
        KeyPressMask | ButtonPressMask);
    XMapWindow(display, win);
    quit = 0;
    while (!quit) {
        int index;
        KeySym keysym;
        XNextEvent(display, &event);
        switch (event.type) {
            case Expose:
                if (event.xexpose.count != 0) break;
                drawText(buffer ? buffer : surface, layout,
                    winwidth, winheight, virtualwidth, virtualheight);
                if (buffer) copyBufferToSurface(surface, buffer);
                break;

            case ConfigureNotify:
                winwidth = event.xconfigure.width;
                winheight = event.xconfigure.height;
                if (surface) cairo_surface_destroy(surface);
                surface = cairo_xlib_surface_create(display, win,
                    DefaultVisual(display, DefaultScreen(display)),
                    winwidth, winheight);
#if USE_BUFFER
                if (buffer) cairo_surface_destroy(buffer);
                buffer = createBuffer(surface, winwidth, winheight);
#endif
                if (!freezevirtualwidth) {
                    virtualwidth = winwidth;
                    virtualheight = winheight;
                }
                pango_layout_set_width(layout, virtualwidth * PANGO_SCALE);

                drawText(buffer ? buffer : surface, layout,
                    winwidth, winheight, virtualwidth, virtualheight);
                if (buffer) copyBufferToSurface(surface, buffer);
                break;

            case KeyPress:
                index = (event.xkey.state & (ShiftMask|LockMask)) ? 1 : 0;
                keysym = XLookupKeysym(&event.xkey, index);
                if (keysym == XK_Escape) {
                    quit = 1;
                }
                else if (keysym == XK_F1) {
                    freezevirtualwidth = 1;
                }
                else if (keysym == XK_F2) {
                    freezevirtualwidth = 0;
                    virtualwidth = winwidth;
                    virtualheight = winheight;
                    pango_layout_set_width(layout, virtualwidth * PANGO_SCALE);
                    drawText(buffer ? buffer : surface, layout,
                        winwidth, winheight, virtualwidth, virtualheight);
                    if (buffer) copyBufferToSurface(surface, buffer);
                }
                break;
        }
    }

    g_object_unref(layout);
    if (surface) cairo_surface_destroy(surface);
    if (buffer) cairo_surface_destroy(buffer);

    XDestroyWindow(display, win);
    XCloseDisplay(display);

    return 0;
}
screenshot-not-ok.png (image/png, 18 KB) - not displayed
screenshot-ok.png (image/png, 21.6 KB) - not displayed
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.