$OpenBSD: patch-src_cairo-xlib-surface_c,v 1.6 2007/07/24 17:47:07 steven Exp $
--- src/cairo-xlib-surface.c.orig	Wed Jun 27 20:05:33 2007
+++ src/cairo-xlib-surface.c	Sun Jul  8 12:20:08 2007
@@ -494,6 +494,255 @@ _swap_ximage_to_native (XImage *ximage)
     }
 }
 
+
+#if 0
+static void _set_optimal_cmap(Display *dpy, Colormap cmap) {
+    int i, r, g, b;
+    XColor cm[256];
+
+    for (i = 0; i < 256; i++) {
+	r = i >> 5;
+	g = (i >> 2) & 0x7;
+	b = (i << 1) & 0x7;
+	cm[i].pixel = i;
+	cm[i].flags = DoRed | DoGreen | DoBlue;
+	cm[i].red   = r << 13 | r << 10 | r << 7 | r << 4 | r << 1 | r >> 2;
+	cm[i].green = g << 13 | g << 10 | g << 7 | g << 4 | g << 1 | g >> 2;
+	cm[i].blue  = b << 13 | b << 10 | b << 7 | b << 4 | b << 1 | b >> 2;
+    }
+    XStoreColors(dpy, cmap, cm, 256);
+}
+#endif
+
+struct clut_r3g3b2 {
+    struct clut_r3g3b2 *next;
+    Display            *dpy;
+    Colormap           cmap;
+    uint32_t           clut[256];
+    unsigned char      ilut[256];
+};
+
+static struct clut_r3g3b2 * _get_clut_r3g3b2(Display *dpy, Colormap cmap) {
+    static struct clut_r3g3b2 *first = NULL;
+    int i,j, min, d;
+    struct clut_r3g3b2 *clut;
+    unsigned char r,g,b, r2,g2,b2;
+    
+    clut = first;
+    while(clut) {
+	if ( clut->dpy == dpy && clut->cmap == cmap )
+	    return clut;
+	clut = clut->next;
+    }
+    
+    clut = calloc(1, sizeof(*clut));
+    if(clut == NULL) {
+	fprintf(stderr, "cairo-xlib: cannot allocate clut_r3g3b2\n");
+	return NULL;
+    }
+    clut->next = first;
+    clut->dpy = dpy;
+    clut->cmap = cmap;
+    first = clut;
+
+    /* Construct the clut from Colormap */
+    for (i = 0; i < 256; i++) {
+	XColor xcol;
+	xcol.pixel = i;
+	XQueryColor(dpy, cmap, &xcol);
+	clut->clut[i] = ( ( ((uint32_t)xcol.red   & 0xff00 ) << 8) |
+			  ( ((uint32_t)xcol.green & 0xff00 ) ) |
+			  ( ((uint32_t)xcol.blue  & 0xff00 ) >> 8) );
+    }
+    /*
+      
+    Find the best matching color in the colormap for all r3g3b2
+    values. The distance is maybe not perceptively valid, but it
+    should not be too bad.
+    
+    */
+    for (i = 0; i < 256; i++) {
+	r = i >> 5;
+	g = (i >> 2) & 0x7;
+	b = (i << 1) & 0x7;
+	min = 255;
+	for(j = 0; j < 256; j++) {
+	    r2 = (clut->clut[j] & 0xff0000) >> 21;
+	    g2 = (clut->clut[j] & 0x00ff00) >> 13;
+	    b2 = (clut->clut[j] & 0x0000ff) >> 5;
+	    if ( r2 == r && g2 == g && (b2 & 0x6) == b ) {
+		clut->ilut[i] = j;
+		break;
+	    }
+	    /*
+	      Squares make higher bits much more important than lower
+	      ones.
+	    */
+	    d  = (r2 ^ r) * (r2 ^ r);
+	    d += (g2 ^ g) * (g2 ^ g);
+	    d += (b2 ^ b) * (b2 ^ b);
+	    if(d < min) {
+		clut->ilut[i] = j;
+		min = d;
+	    }
+	}
+    }
+    
+    return clut;
+}
+
+static const char * _visualClass[] = {
+    "StaticGray",
+    "GrayScale",
+    "StaticColor",
+    "PseudoColor",
+    "TrueColor",
+    "DirectColor"
+};
+
+
+static void _print_Visual(Visual *v) {
+    if(v)
+	fprintf(stderr,
+		"  Visual: %p\n"
+		"    class: %s\n"
+		"    bpRGB: %i\n"
+		"    map_entries: %i\n"
+		"    RGB masks: %08lx %08lx %08lx\n",
+		v,
+		_visualClass[v->class],
+		v->bits_per_rgb,
+		v->map_entries,
+		v->red_mask, v->green_mask, v->blue_mask);
+    else
+	fprintf(stderr, "  Visual: NULL\n");
+}
+
+
+#if 0
+static void _print_XImage(XImage *x)
+{
+    const char * format[] = { "XYBitmap", "XYPixmap", "ZPixmap" };
+    if(XImage)
+	fprintf(stderr, 
+		"  XImage: %p\n"
+		"    size: %ix%i\n"
+		"    xoffset: %i\n"
+		"    format: %s\n"
+		"    depth: %i\n"
+		"    bpp: %i\n"
+		"    stride: %i\n"
+                "    RGB masks: %08lx %08lx %08lx\n"
+		"    bitmap_unit: %i\n"
+		"    bitmap_pad: %i\n",
+		x,
+		x->width,
+		x->height,
+		x->xoffset,
+		format[x->format],
+		x->depth,
+		x->bits_per_pixel,
+		x->bytes_per_line,
+		x->red_mask, x->green_mask, x->blue_mask,
+		x->bitmap_unit, x->bitmap_pad);
+    else
+	fprintf(stderr, "  XImage: NULL\n")
+}
+
+const char * _cairoFormats[] = { "ARGB32", "RGB24", "A8", "A1" };
+
+static void _print_cairo_image_surface(cairo_image_surface_t *i)
+{
+    if(i)
+	fprintf(stderr,
+		"  CairoImage: %p\n"
+		"    size: %ix%i\n"
+		"    format: %s\n"
+		"    depth: %i\n"
+		"    stride: %i\n",
+		i,
+		i->width,
+		i->height,
+		_cairoFormats[i->format],
+		i->depth,
+		i->stride);
+    else
+	fprintf(stderr, "  CairoImage: NULL\n")
+}
+
+static void _print_cairo_format_masks(cairo_format_masks_t *m)
+{
+    if(m)
+	printf(stderr,
+	       "  CairoFormatMask: %p\n"
+	       "    bpp: %i\n"
+	       "    ARGB masks: %lx %lx %lx %lx\n",
+	       m,
+	       m->bpp, m->alpha_mask,
+	       m->red_mask, m->green_mask, m->blue_mask);
+    else
+	fprintf(stderr, "  CairoFormatMasks: NULL\n")
+}
+#endif
+
+#define WORKAROUND_NONE           0
+#define WORKAROUND_8BIT_GRAYLEVEL 1
+#define WORKAROUND_8BIT_PALETTE   2
+#define WORKAROUND_R5G6B5         3
+
+static const char *workarounds[] = {
+    "NONE",
+    "8BIT_GRAYLEVEL",
+    "8BIT_PALETTE",
+    "R5G6B5"
+};
+
+static void
+_print_XRenderPictFormat(XRenderPictFormat *f)
+{
+    if(f)
+	fprintf(stderr,
+		"  XRenderPictFormat: %p\n"
+		"    format: %lu\n"
+                "    type: %i\n"
+		"    depth: %i\n"
+		"    red: %hx mask: %hx\n"
+		"    green: %hx mask: %hx\n"
+		"    blue: %hx mask: %hx\n"
+		"    alpha: %hx mask: %hx\n",
+		f,
+		f->id, f->type, f->depth,
+		f->direct.red, f->direct.redMask,
+		f->direct.green, f->direct.greenMask,
+		f->direct.blue, f->direct.blueMask,
+		f->direct.alpha, f->direct.alphaMask
+		);
+    else
+	fprintf(stderr,
+		"  XRenderPictFormat: NULL\n");
+}
+
+static void
+_print_surface(cairo_xlib_surface_t *s)
+{
+    if(!s) {
+	fprintf(stderr, "CairoXlibSurface: NULL\n");
+	    return;
+    }
+    fprintf(stderr,
+	    "CairoXlibSurface: %p\n"
+	    "  render version: %i.%i\n"
+	    "  width: %i\n"
+	    "  height: %i\n"
+	    "  depth: %i\n",
+	    s,
+	    s->render_major, s->render_minor,
+	    s->width, s->height, s->depth);
+    _print_Visual(s->visual);
+    _print_XRenderPictFormat(s->xrender_format);
+}
+
+
 static cairo_status_t
 _get_image_surface (cairo_xlib_surface_t    *surface,
 		    cairo_rectangle_int16_t *interest_rect,
@@ -652,9 +901,14 @@ _get_image_surface (cairo_xlib_surface_t    *surface,
 						 ximage->bytes_per_line);
 	if (image->base.status)
 	    goto FAIL;
+	if (surface->workaround != WORKAROUND_NONE) {
+	    fprintf(stderr, "cancelling workaround %s\n", workarounds[surface->workaround]);
+	    surface->workaround = WORKAROUND_NONE;
+	}
     }
     else
     {
+#if 0
 	/*
 	 * XXX This can't work.  We must convert the data to one of the
 	 * supported pixman formats.  Pixman needs another function
@@ -669,6 +923,98 @@ _get_image_surface (cairo_xlib_surface_t    *surface,
 						    ximage->bytes_per_line);
 	if (image->base.status)
 	    goto FAIL;
+#else
+	/*
+	 * Otherwise, we construct a buffer containing RGB24 data
+	 * using the specified workaround.
+	 */
+	uint32_t *data, *dst, *clut;
+	uint8_t  *src8;
+	uint16_t *src16;
+	int i,j;
+	
+	if(surface->visual == NULL) {
+	    fprintf(stderr, "cairo-xlib: no visual for surface\n");
+	    goto FAIL;
+	}
+
+	if (surface->workaround == WORKAROUND_NONE) {
+	    fprintf(stderr, "cairo-xlib: no workaround for surface\n");
+	    _print_surface(surface);
+	    goto FAIL;
+	}
+	
+	data = (uint32_t*)malloc(ximage->height * ximage->width * 4);
+	if(data == NULL) {
+	    fprintf(stderr, "cairo-xlib: cannot allocate ARGB buffer\n");
+	    goto FAIL;
+	}
+	
+	switch (surface->workaround) {
+	    
+	case WORKAROUND_8BIT_GRAYLEVEL:
+
+	    dst = data;
+	    for(j = 0; j < ximage->height; j++) {
+		src8 = (uint8_t *) (ximage->data + ximage->bytes_per_line * j);
+		for(i = 0; i < ximage->width; i++) {
+		    *dst++ = (*src8 << 16) | (*src8 << 8) | *src8;
+		    src8++;
+		}
+	    }
+	    break;
+	    
+	case WORKAROUND_8BIT_PALETTE:
+
+	    if(surface->clut == NULL) {
+		surface->clut = _get_clut_r3g3b2(
+		    surface->dpy,
+		    DefaultColormapOfScreen(surface->screen));
+	    }
+
+	    if(surface->clut == NULL) {
+		free(data);
+		goto FAIL;
+	    }
+
+	    clut = surface->clut->clut;
+	    src8 = (uint8_t*) ximage->data;
+	    dst = data;
+	    for(j = 0; j < ximage->height; j++) {
+		for(i = 0; i < ximage->width; i++)
+		    *dst++ = clut[src8[i]];
+		src8 += ximage->bytes_per_line;
+	    }
+	    break;
+
+	case WORKAROUND_R5G6B5:
+
+	    src16 = (uint16_t*)ximage->data;
+	    dst = data;
+	    for(j = 0; j < ximage->height; j++) {
+		for(i = 0; i < ximage->width; i++) {
+		    *dst++ = ( ( ((src16[i] & 0xf800) << 8) | ((src16[i] & 0xe000) << 3) ) |
+			       ( ((src16[i] & 0x07e0) << 5) | ((src16[i] & 0x0600) >> 1) ) |
+			       ( ((src16[i] & 0x001f) << 3) | ((src16[i] & 0x001f) >> 2) ) );
+		}
+		src16 += ximage->bytes_per_line / sizeof(*src16);
+	    }
+	    break;
+	default:
+	    fprintf(stderr, "cairo-xlib: dunno what to do with surface\n");
+	    _print_surface(surface);
+	    goto FAIL;
+	}
+	free(ximage->data);
+	image = (cairo_image_surface_t*)
+	    cairo_image_surface_create_for_data((unsigned char *)data, CAIRO_FORMAT_RGB24, ximage->width, ximage->height, ximage->width*4);
+	
+	if (image->base.status) {
+	    fprintf(stderr, "cairo-xlib: cairo_image_surface_create_for_data failed\n");
+	    free(data);
+	    goto FAIL;
+	}
+#endif
     }
 
     /* Let the surface take ownership of the data */
@@ -770,6 +1116,30 @@ _cairo_xlib_surface_ensure_gc (cairo_xlib_surface_t *s
     return CAIRO_STATUS_SUCCESS;
 }
 
+static int
+make_space_for(unsigned char ** buf, int *size, int *stride, int width, int height, int Bpp) {
+    unsigned char * data;
+    int l;
+
+    *stride = width * Bpp;
+    if(*stride%4)
+	*stride += 4 - *stride % 4;
+    l = (*stride * height);
+    if (*size < l) {
+	if(*buf)
+	    data = realloc(*buf, l);
+	else
+	    data = malloc(l);
+	if(data) {
+	    *buf = data;
+	    *size = l;
+	} else {
+	    return -1;
+	}
+    } 
+    return 0;
+}
+
 static cairo_status_t
 _draw_image_surface (cairo_xlib_surface_t   *surface,
 		     cairo_image_surface_t  *image,
@@ -785,19 +1155,98 @@ _draw_image_surface (cairo_xlib_surface_t   *surface,
     int native_byte_order = _native_byte_order_lsb () ? LSBFirst : MSBFirst;
     cairo_status_t status;
 
+    static unsigned char *buf = NULL;
+    static int size = 0;
+    int i, j, depth, stride;
+    unsigned char *data, *ilut;
+    uint32_t *src;
+    uint8_t *dst8;
+    uint16_t *dst16;
+
     pixman_format_get_masks (pixman_image_get_format (image->pixman_image),
 			     &bpp, &alpha, &red, &green, &blue);
 
+
+    switch(surface->workaround) {
+
+    case WORKAROUND_NONE:
+	/* Default behaviour is supposed to work */
+	stride = image->stride;
+	depth = image->depth;
+	data = image->data;
+	break;
+
+    case WORKAROUND_8BIT_GRAYLEVEL:
+
+	if (make_space_for(&buf, &size, &stride, image->width, image->height, 1))
+	    return CAIRO_STATUS_NO_MEMORY;
+	data = buf;
+	
+	for(j=0;j<image->height;j++) {
+	    src = (uint32_t*)(image->data);
+	    dst8 = data + j * stride;
+	    for(i=0;i<image->width;i++) {
+		dst8[i] = ( 30 * ((*src >> 16) & 0xff) +
+			    59 * ((*src >> 8) & 0xff) +
+			    11 * (*src & 0xff) ) / 100;
+		src++;
+	    }
+	}
+	
+	alpha = red = green = blue = 0;
+	depth = bpp = 8;
+	break;
+	
+    case WORKAROUND_8BIT_PALETTE:
+	
+	if (make_space_for(&buf, &size, &stride, image->width, image->height, 1))
+	    return CAIRO_STATUS_NO_MEMORY;
+	data = buf;
+	src = (uint32_t*)image->data;
+	ilut = surface->clut->ilut;
+	for(j=0;j<image->height;j++) {
+	    dst8 = data + j * stride;
+	    for(i=0;i<image->width;i++) {
+		dst8[i] = ilut[ ((*src >> 16) & 0xe0) |
+				((*src >> 11) & 0x1c) |
+				((*src >> 6)  & 0x03) ];
+		src++;
+	    }
+	}
+	alpha = red = green = blue = 0;
+	depth = bpp = 8;
+	break;
+
+    case WORKAROUND_R5G6B5:
+
+	if (make_space_for(&buf, &size, &stride, image->width, image->height, 2))
+	    return CAIRO_STATUS_NO_MEMORY;
+	data = buf;
+	src = (uint32_t*)image->data;
+	for(j=0;j<image->height;j++) {
+	    dst16 = (uint16_t*)(data + j * stride);
+	    for(i=0;i<image->width;i++) {
+		dst16[i] = ( ((*src >> 8) & 0xf800) |
+			     ((*src >> 5) & 0x07e0) |
+			     ((*src >> 3) & 0x001f) );
+		src++;
+	    }
+	}
+	alpha = 0; red = 0xf800; green = 0x07e0; blue = 0x001f;
+	depth = bpp = 16;
+	break;
+    }
+    
     ximage.width = image->width;
     ximage.height = image->height;
     ximage.format = ZPixmap;
-    ximage.data = (char *)image->data;
+    ximage.data = 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.depth = depth;
+    ximage.bytes_per_line = stride;
     ximage.bits_per_pixel = bpp;
     ximage.red_mask = red;
     ximage.green_mask = green;
@@ -1945,6 +2394,7 @@ _cairo_xlib_surface_create_internal (Display		       *
 				     int			height,
 				     int			depth)
 {
+    static int _trace = -1;
     cairo_xlib_surface_t *surface;
     cairo_xlib_screen_info_t *screen_info;
 
@@ -2042,7 +2492,38 @@ _cairo_xlib_surface_create_internal (Display		       *
     surface->have_clip_rects = FALSE;
     surface->clip_rects = surface->embedded_clip_rects;
     surface->num_clip_rects = 0;
+    
+    surface->clut = NULL;
+    surface->workaround = WORKAROUND_NONE;
 
+    if (visual) {
+	/* Install the correct workaround */
+	switch (visual->class) {
+	case StaticGray:
+	case GrayScale:
+	    surface->workaround = WORKAROUND_8BIT_GRAYLEVEL;
+	    break;
+	case PseudoColor:
+	case StaticColor:
+	    surface->workaround = WORKAROUND_8BIT_PALETTE;
+	    break;
+	case TrueColor:
+	    if (visual->red_mask   == 0xf800 &&
+		visual->green_mask == 0x07e0 &&
+		visual->blue_mask  == 0x001f) {
+		surface->workaround = WORKAROUND_R5G6B5;
+	    }
+	}
+    }
+
+    if(_trace == -1)
+	_trace = getenv("DEBUG_CAIRO_XLIB") ? 1 : 0;
+    if(_trace) {
+	fprintf(stderr, "New surface with workaround %s\n",
+		workarounds[surface->workaround]);
+	_print_surface(surface);
+    }
+    
     return (cairo_surface_t *) surface;
 }
 
