$NetBSD: patch-ae,v 1.9 2007/11/30 20:49:25 drochner Exp $

Fixes cairo on 8-bit pseudo color and other 8-bit displays.
See https://bugs.freedesktop.org/show_bug.cgi?id=4945

--- src/cairo-xlib-surface-private.h.orig	2007-11-27 07:20:12.000000000 +0100
+++ src/cairo-xlib-surface-private.h
@@ -39,6 +39,14 @@
 
 typedef struct _cairo_xlib_surface cairo_xlib_surface_t;
 
+struct clut_r3g3b2 {
+    struct clut_r3g3b2 *next;
+    Display            *dpy;
+    Colormap           cmap;
+    uint32_t           clut[256];
+    unsigned char      ilut[256];
+};
+
 struct _cairo_xlib_surface {
     cairo_surface_t base;
 
@@ -89,6 +97,9 @@ struct _cairo_xlib_surface {
     cairo_filter_t filter;
     int repeat;
     XTransform xtransform;
+
+    struct clut_r3g3b2 *clut;
+    int workaround;
 };
 
 enum {
--- src/cairo-xlib-surface.c.orig	2007-11-27 07:20:12.000000000 +0100
+++ src/cairo-xlib-surface.c
@@ -108,6 +108,10 @@ static const XTransform identity = { {
 #define CAIRO_SURFACE_RENDER_HAS_PICTURE_TRANSFORM(surface)	CAIRO_SURFACE_RENDER_AT_LEAST((surface), 0, 6)
 #define CAIRO_SURFACE_RENDER_HAS_FILTERS(surface)	CAIRO_SURFACE_RENDER_AT_LEAST((surface), 0, 6)
 
+#define WORKAROUND_NONE 0
+#define WORKAROUND_8BIT_PALETTE 1
+#define WORKAROUND_8BIT_DIRECT 2
+
 static int
 _CAIRO_FORMAT_DEPTH (cairo_format_t format)
 {
@@ -494,6 +498,74 @@ _swap_ximage_to_native (XImage *ximage)
     }
 }
 
+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)
+	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 cairo_status_t
 _get_image_surface (cairo_xlib_surface_t    *surface,
 		    cairo_rectangle_int16_t *interest_rect,
@@ -655,18 +727,77 @@ _get_image_surface (cairo_xlib_surface_t
     }
     else
     {
+        if ((surface->clut != NULL) && (surface->workaround == WORKAROUND_8BIT_PALETTE)) {
+	    
+      	    /*
+	     * Otherwise, we construct a buffer containing RGB24 data
+	     * using the specified workaround.
+	     */
+	    uint32_t *data, *dst, *clut;
+	    uint8_t  *src8;
+	    int i,j;
+
+	    data = (uint32_t*)malloc(ximage->height * ximage->width * 4);
+	    if (data == NULL) {
+		_cairo_error(CAIRO_STATUS_NO_MEMORY);
+		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;
+	    }
+	    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);
+        } else if (surface->workaround == WORKAROUND_8BIT_DIRECT) {
+
+	    uint32_t *data, *dst;
+	    uint8_t  *src8;
+	    int i,j;
+
+	    data = (uint32_t*)malloc(ximage->height * ximage->width * 4);
+	    if (data == NULL) {
+		_cairo_error(CAIRO_STATUS_NO_MEMORY);
+		goto FAIL;
+	    }
+
+	    src8 = (uint8_t*) ximage->data;
+	    dst = data;
+	    for (j = 0; j < ximage->height; j++) {
+		for (i = 0; i < ximage->width; i++)
+	      		    *dst++ = (src8[i] & masks.red_mask << 21) | 
+			             (src8[i] & masks.green_mask << 10 ) | 
+			             (src8[i] & masks.blue_mask );
+		 
+		src8 += ximage->bytes_per_line;
+	    }
+	    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);
+	}else if (surface->workaround == WORKAROUND_NONE){
 	/*
 	 * 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,
+            image = (cairo_image_surface_t*)
+	      _cairo_image_surface_create_with_masks ((unsigned char *) ximage->data,
 						    &masks,
 						    ximage->width,
 						    ximage->height,
 						    ximage->bytes_per_line);
+	}
+	
 	if (image->base.status)
 	    goto FAIL;
     }
@@ -770,6 +901,31 @@ _cairo_xlib_surface_ensure_gc (cairo_xli
     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,
@@ -782,22 +938,78 @@ _draw_image_surface (cairo_xlib_surface_
 {
     XImage ximage;
     unsigned int bpp, alpha, red, green, blue;
+    unsigned int depth = image->depth;
+    unsigned int stride = image->stride;
     int native_byte_order = _native_byte_order_lsb () ? LSBFirst : MSBFirst;
     cairo_status_t status;
 
     pixman_format_get_masks (pixman_image_get_format (image->pixman_image),
 			     &bpp, &alpha, &red, &green, &blue);
 
+    if ((surface->clut != NULL) && (surface->workaround == WORKAROUND_8BIT_PALETTE)) {
+      static unsigned char *buf = NULL;
+      static int size = 0;
+      int i, j;
+      unsigned char *data, *ilut;
+      uint32_t *src;
+      uint8_t *dst8;
+
+      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;
+              ximage.data = data;
+
+    }else if (surface->workaround == WORKAROUND_8BIT_DIRECT){
+      static unsigned char *buf = NULL;
+      static int size = 0;
+      int i, j;
+      unsigned char *data;
+      uint32_t *src;
+      uint8_t *dst8;
+
+      if (_make_space_for(&buf, &size, &stride, image->width, image->height, 1))
+            return CAIRO_STATUS_NO_MEMORY;
+      data = buf;
+      src = (uint32_t*)image->data;
+      for (j=0;j<image->height;j++) {
+            dst8 = data + j * stride;
+            for (i=0;i<image->width;i++) {
+              dst8[i] = ((*src >> 16) & 0xe0) |
+                        ((*src >> 11) & 0x1c) |
+                        ((*src >> 6)  & 0x03) ;
+              src++;
+            }
+      }
+      alpha = red = green = blue = 0;
+      depth = bpp = 8;
+              ximage.data = data;
+    } else if( surface->workaround == WORKAROUND_NONE){
+      ximage.data = (char *)image->data;
+    }
+
     ximage.width = image->width;
     ximage.height = image->height;
     ximage.format = ZPixmap;
-    ximage.data = (char *)image->data;
+    // ximage.data is assigned above
     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;
@@ -2043,6 +2255,20 @@ _cairo_xlib_surface_create_internal (Dis
     surface->have_clip_rects = FALSE;
     surface->clip_rects = surface->embedded_clip_rects;
     surface->num_clip_rects = 0;
+    surface->clut = NULL;
+
+    if (xrender_format == NULL &&
+       (visual==NULL?FALSE:(visual->class == PseudoColor || visual->class == StaticColor))) {
+        surface->clut = _get_clut_r3g3b2(dpy,
+            DefaultColormapOfScreen(surface->screen));
+    }else if (xrender_format == NULL &&
+	      (visual==NULL?FALSE:((visual->class == TrueColor) 
+				   && (surface->visual->red_mask == 0x07)
+				   && (surface->visual->green_mask == 0x38)
+				   && (surface->visual->blue_mask == 0xc0))))
+        surface->workaround = WORKAROUND_8BIT_DIRECT;
+    else 
+        surface->workaround = WORKAROUND_NONE;
 
     return (cairo_surface_t *) surface;
 }
