diff --git a/src/cairo-xlib-surface.c b/src/cairo-xlib-surface.c index 194c0cb..768a2be 100644 --- a/src/cairo-xlib-surface.c +++ b/src/cairo-xlib-surface.c @@ -351,9 +351,15 @@ _noop_error_handler (Display *displa return False; /* return value is ignored */ } -static cairo_bool_t -_CAIRO_MASK_FORMAT (cairo_format_masks_t *masks, cairo_format_t *format) +static cairo_format_t +_cairo_format_from_masks (cairo_format_masks_t *masks, cairo_bool_t *exact_match) { + cairo_bool_t has_color, has_alpha; + cairo_content_t content; + + /* First look for an exact match. */ + *exact_match = TRUE; + switch (masks->bpp) { case 32: if (masks->alpha_mask == 0xff000000 && @@ -361,34 +367,49 @@ _CAIRO_MASK_FORMAT (cairo_format_masks_t masks->green_mask == 0x0000ff00 && masks->blue_mask == 0x000000ff) { - *format = CAIRO_FORMAT_ARGB32; - return True; + return CAIRO_FORMAT_ARGB32; } if (masks->alpha_mask == 0x00000000 && masks->red_mask == 0x00ff0000 && masks->green_mask == 0x0000ff00 && masks->blue_mask == 0x000000ff) { - *format = CAIRO_FORMAT_RGB24; - return True; + return CAIRO_FORMAT_RGB24; } break; case 8: if (masks->alpha_mask == 0xff) { - *format = CAIRO_FORMAT_A8; - return True; + return CAIRO_FORMAT_A8; } break; case 1: if (masks->alpha_mask == 0x1) { - *format = CAIRO_FORMAT_A1; - return True; + return CAIRO_FORMAT_A1; } break; } - return False; + + /* No exact match found. Do the best we can based on the content + * we can derive from the masks. + */ + *exact_match = FALSE; + + has_color = (masks->red_mask || + masks->green_mask || + masks->blue_mask); + has_alpha = masks->alpha_mask; + + if (has_color) + if (has_alpha) + content = CAIRO_CONTENT_COLOR_ALPHA; + else + content = CAIRO_CONTENT_COLOR; + else + content = CAIRO_CONTENT_ALPHA; + + return _cairo_format_from_content (content); } static void @@ -501,6 +522,98 @@ _swap_ximage_to_native (XImage *ximage) } } +static void +_cairo_format_masks_from_format (cairo_format_masks_t *masks, + cairo_format_t format) +{ + switch (format) { + case CAIRO_FORMAT_ARGB32: + masks->alpha_mask = 0xff000000; + masks->red_mask = 0x00ff0000; + masks->green_mask = 0x0000ff00; + masks->blue_mask = 0x000000ff; + return; + case CAIRO_FORMAT_RGB24: + masks->alpha_mask = 0; + masks->red_mask = 0x00ff0000; + masks->green_mask = 0x0000ff00; + masks->blue_mask = 0x000000ff; + return; + case CAIRO_FORMAT_A8: + masks->alpha_mask = 0xff; + masks->red_mask = 0; + masks->green_mask = 0; + masks->blue_mask = 0; + return; + /* We don't need to worry about A1 since + * _cairo_format_from_masks will have already returned an + * exact_match of A1 in this case. That is, there's no inexact + * match that will map to A1. */ + case CAIRO_FORMAT_A1: + ASSERT_NOT_REACHED; + } +} + +/* How much would you have to shift right in order to get the MSB of + * mask_a to be in the same position as the MSB of mask_b. This + * function can return a negative value, (in which case one should + * shift left). */ +static int +_right_shift_amount (unsigned long mask_a, + unsigned long mask_b) +{ + int msb_a, msb_b; + + msb_a = 0; + while (mask_a) { + msb_a++; + mask_a >>= 1; + } + + msb_b = 0; + while (mask_b) { + msb_b++; + mask_b >>= 1; + } + + return msb_a - msb_b; +} + +static void +_cairo_xlib_surface_get_masks (cairo_xlib_surface_t *surface, + int bits_per_pixel, + cairo_format_masks_t *masks) +{ + masks->bpp = bits_per_pixel; + + /* + * Compute the pixel format masks from either a visual or a + * XRenderFormat, failing we assume the drawable is an + * alpha-only pixmap as it could only have been created + * that way through the cairo_xlib_surface_create_for_bitmap + * function. + */ + if (surface->visual) { + masks->alpha_mask = 0; + masks->red_mask = surface->visual->red_mask; + masks->green_mask = surface->visual->green_mask; + masks->blue_mask = surface->visual->blue_mask; + } else if (surface->xrender_format) { + masks->red_mask = (unsigned long)surface->xrender_format->direct.redMask << surface->xrender_format->direct.red; + masks->green_mask = (unsigned long)surface->xrender_format->direct.greenMask << surface->xrender_format->direct.green; + masks->blue_mask = (unsigned long)surface->xrender_format->direct.blueMask << surface->xrender_format->direct.blue; + masks->alpha_mask = (unsigned long)surface->xrender_format->direct.alphaMask << surface->xrender_format->direct.alpha; + } else { + masks->red_mask = 0; + masks->green_mask = 0; + masks->blue_mask = 0; + if (surface->depth < 32) + masks->alpha_mask = (1 << surface->depth) - 1; + else + masks->alpha_mask = 0xffffffff; + } +} + static cairo_status_t _get_image_surface (cairo_xlib_surface_t *surface, cairo_rectangle_int16_t *interest_rect, @@ -512,6 +625,7 @@ _get_image_surface (cairo_xlib_surface_t int x1, y1, x2, y2; cairo_format_masks_t masks; cairo_format_t format; + cairo_bool_t exact_match; x1 = 0; y1 = 0; @@ -607,41 +721,14 @@ _get_image_surface (cairo_xlib_surface_t _swap_ximage_to_native (ximage); - /* - * Compute the pixel format masks from either a visual or a - * XRenderFormat, failing we assume the drawable is an - * alpha-only pixmap as it could only have been created - * that way through the cairo_xlib_surface_create_for_bitmap - * function. - */ - if (surface->visual) { - masks.bpp = ximage->bits_per_pixel; - masks.alpha_mask = 0; - masks.red_mask = surface->visual->red_mask; - masks.green_mask = surface->visual->green_mask; - masks.blue_mask = surface->visual->blue_mask; - } else if (surface->xrender_format) { - masks.bpp = ximage->bits_per_pixel; - masks.red_mask = (unsigned long)surface->xrender_format->direct.redMask << surface->xrender_format->direct.red; - masks.green_mask = (unsigned long)surface->xrender_format->direct.greenMask << surface->xrender_format->direct.green; - masks.blue_mask = (unsigned long)surface->xrender_format->direct.blueMask << surface->xrender_format->direct.blue; - masks.alpha_mask = (unsigned long)surface->xrender_format->direct.alphaMask << surface->xrender_format->direct.alpha; - } else { - masks.bpp = ximage->bits_per_pixel; - masks.red_mask = 0; - masks.green_mask = 0; - masks.blue_mask = 0; - if (surface->depth < 32) - masks.alpha_mask = (1 << surface->depth) - 1; - else - masks.alpha_mask = 0xffffffff; - } + _cairo_xlib_surface_get_masks (surface, ximage->bits_per_pixel, &masks); - /* - * Prefer to use a standard pixman format instead of the - * general masks case. + /* If the masks from the visual do not match a cairo format, then + * use the Render extension to copy the data to a picture with a + * format that does match. */ - if (_CAIRO_MASK_FORMAT (&masks, &format)) + format = _cairo_format_from_masks (&masks, &exact_match); + if (exact_match) { image = (cairo_image_surface_t*) cairo_image_surface_create_for_data ((unsigned char *) ximage->data, @@ -651,29 +738,66 @@ _get_image_surface (cairo_xlib_surface_t ximage->bytes_per_line); if (image->base.status) goto FAIL; + + /* Let the surface take ownership of the data */ + _cairo_image_surface_assume_ownership_of_data (image); + + ximage->data = NULL; + XDestroyImage (ximage); } else { - /* - * XXX This can't work. We must convert the data to one of the - * supported pixman formats. Pixman needs another function - * which takes data in an arbitrary format and converts it - * to something supported by that library. - */ - image = (cairo_image_surface_t*) - _cairo_image_surface_create_with_masks ((unsigned char *) ximage->data, - &masks, - ximage->width, - ximage->height, - ximage->bytes_per_line); + unsigned int stride; + unsigned char *data, *row; + unsigned long src; + uint32_t *dst; + uint32_t alpha, red, green, blue; + int x, y; + int alpha_shift, red_shift, green_shift, blue_shift; + cairo_format_masks_t format_masks; + + image = (cairo_image_surface_t *) + cairo_image_surface_create (format, ximage->width, ximage->height); if (image->base.status) goto FAIL; - } - /* Let the surface take ownership of the data */ - _cairo_image_surface_assume_ownership_of_data (image); - ximage->data = NULL; - XDestroyImage (ximage); + _cairo_format_masks_from_format (&format_masks, format); + alpha_shift = _right_shift_amount (masks.alpha_mask, format_masks.alpha_mask); + red_shift = _right_shift_amount (masks.red_mask, format_masks.red_mask); + green_shift = _right_shift_amount (masks.green_mask, format_masks.green_mask); + blue_shift = _right_shift_amount (masks.blue_mask, format_masks.blue_mask); + + data = cairo_image_surface_get_data (&image->base); + stride = cairo_image_surface_get_stride (&image->base); + for (y = 0; y < ximage->height; y++) { + row = data + y * stride; + dst = (uint32_t *) row; + for (x = 0; x < ximage->width; x++) { + src = XGetPixel (ximage, x, y); + if (alpha_shift > 0) + alpha = ((src & masks.alpha_mask) >> alpha_shift) & format_masks.alpha_mask; + else + alpha = ((src & masks.alpha_mask) << - alpha_shift) & format_masks.alpha_mask; + if (red_shift > 0) + red = ((src & masks.red_mask) >> red_shift) & format_masks.red_mask; + else + red = ((src & masks.red_mask) << - red_shift) & format_masks.red_mask; + if (green_shift > 0) + green = ((src & masks.green_mask) >> green_shift) & format_masks.green_mask; + else + green = ((src & masks.green_mask) << - green_shift) & format_masks.green_mask; + if (blue_shift > 0) + blue = ((src & masks.blue_mask) >> blue_shift) & format_masks.blue_mask; + else + blue = ((src & masks.blue_mask) << - blue_shift) & format_masks.blue_mask; + *dst = alpha|red|green|blue; + dst++; + } + + } + + XDestroyImage (ximage); + } *image_out = image; return CAIRO_STATUS_SUCCESS; @@ -746,36 +870,105 @@ _draw_image_surface (cairo_xlib_surface_ int dst_x, int dst_y) { + cairo_format_masks_t masks; + cairo_format_t xlib_format, image_format; + cairo_bool_t exact_match; XImage ximage; int bpp, alpha, red, green, blue; int native_byte_order = _native_byte_order_lsb () ? LSBFirst : MSBFirst; - pixman_format_get_masks (pixman_image_get_format (image->pixman_image), - &bpp, &alpha, &red, &green, &blue); + _cairo_xlib_surface_get_masks (surface, 32, &masks); + + xlib_format = _cairo_format_from_masks (&masks, &exact_match); + + image_format = cairo_image_surface_get_format (&image->base); ximage.width = image->width; ximage.height = image->height; ximage.format = ZPixmap; - ximage.data = (char *)image->data; ximage.byte_order = native_byte_order; ximage.bitmap_unit = 32; /* always for libpixman */ ximage.bitmap_bit_order = native_byte_order; ximage.bitmap_pad = 32; /* always for libpixman */ - ximage.depth = image->depth; - ximage.bytes_per_line = image->stride; - ximage.bits_per_pixel = bpp; - ximage.red_mask = red; - ximage.green_mask = green; - ximage.blue_mask = blue; + ximage.bits_per_pixel = masks.bpp; + ximage.red_mask = masks.red_mask; + ximage.green_mask = masks.green_mask; + ximage.blue_mask = masks.blue_mask; ximage.xoffset = 0; - XInitImage (&ximage); + if (exact_match && xlib_format == image_format) + { + pixman_format_get_masks (pixman_image_get_format (image->pixman_image), + &bpp, &alpha, &red, &green, &blue); + + ximage.depth = image->depth; + ximage.bytes_per_line = image->stride; + + ximage.data = (char *) image->data; + + XInitImage (&ximage); + } else { + unsigned int stride; + unsigned char *data, *row; + uint32_t *src; + uint32_t alpha, red, green, blue; + int x, y; + int alpha_shift, red_shift, green_shift, blue_shift; + cairo_format_masks_t image_format_masks; + + ximage.depth = surface->depth; + ximage.bytes_per_line = (4 * image->width); + + ximage.data = malloc (ximage.bytes_per_line * image->height); + if (ximage.data == NULL) + return CAIRO_STATUS_NO_MEMORY; + + XInitImage (&ximage); + + _cairo_format_masks_from_format (&image_format_masks, image_format); + + alpha_shift = _right_shift_amount (image_format_masks.alpha_mask, masks.red_mask); + red_shift = _right_shift_amount (image_format_masks.red_mask, masks.red_mask); + green_shift = _right_shift_amount (image_format_masks.green_mask, masks.green_mask); + blue_shift = _right_shift_amount (image_format_masks.blue_mask, masks.blue_mask); + + data = cairo_image_surface_get_data (&image->base); + stride = cairo_image_surface_get_stride (&image->base); + for (y = 0; y < ximage.height; y++) { + row = data + y * stride; + src = (uint32_t *) row; + for (x = 0; x < ximage.width; x++) { + if (alpha_shift > 0) + alpha = ((*src & image_format_masks.alpha_mask) >> alpha_shift) & masks.alpha_mask; + else + alpha = ((*src & image_format_masks.alpha_mask) << - alpha_shift) & masks.alpha_mask; + if (red_shift > 0) + red = ((*src & image_format_masks.red_mask) >> red_shift) & masks.red_mask; + else + red = ((*src & image_format_masks.red_mask) << - red_shift) & masks.red_mask; + + if (green_shift > 0) + green = ((*src & image_format_masks.green_mask) >> green_shift) & masks.green_mask; + else + green = ((*src & image_format_masks.green_mask) << - green_shift) & masks.green_mask; + if (blue_shift > 0) + blue = ((*src & image_format_masks.blue_mask) >> blue_shift) & masks.blue_mask; + else + blue = ((*src & image_format_masks.blue_mask) << - blue_shift) & masks.blue_mask; + XPutPixel (&ximage, x, y, alpha|red|green|blue); + src++; + } + } + } _cairo_xlib_surface_ensure_gc (surface); XPutImage(surface->dpy, surface->drawable, surface->gc, &ximage, 0, 0, dst_x, dst_y, image->width, image->height); + if (ximage.data != (char *) image->data) + free (ximage.data); + return CAIRO_STATUS_SUCCESS; }