From 693af3995b46d531342d68ac83b794113d0a7a90 Mon Sep 17 00:00:00 2001
From: "Antti S. Lankila" <alankila@bel.fi>
Date: Sun, 10 Jun 2012 22:31:38 +0300
Subject: [PATCH] Add support for sRGB surfaces

sRGB format is defined as a new format type, PIXMAN_TYPE_ARGB_SRGB. One format
of this type is provided, PIXMAN_a8r8g8b8_sRGB.

Use of sRGB format triggers wide processing, and the pixel fetch/store
functions handle the relevant conversion between color spaces. Pixman itself is
thought to compose in the linear light color space.

Simple sRGB color blender test can be used to determine if the sRGB processing
works as expected. It adds a #f0f and #0f0 images together with linear alpha
ramp. At midpoint, the sRGB-aware processing yields a result close to #bbb
instead of #888.

sRGB conversion is tabularized.

For sRGB to linear, we are using only 256 values because the expectation is
that all source formats use 8 bits per component precision. For linear to
sRGB, it turns out that only 4096 brightness levels were required to generate
all of the 256 sRGB color values, and therefore only 12 bits per component are
considered during store. As a special case, a no-op sRGB->linear->sRGB
conversion is constructed to be lossless by adjusting the conversion tables
slightly where necessary.
---
 .gitignore              |    2 +
 demos/Makefile.am       |    4 +-
 demos/srgb-test.c       |  100 +++++++++++++++++++++++++++++++++++
 pixman/Makefile.sources |    5 ++
 pixman/make-srgb.pl     |  112 +++++++++++++++++++++++++++++++++++++++
 pixman/pixman-access.c  |  134 ++++++++++++++++++++++++++++++++++++++++++++++-
 pixman/pixman-image.c   |    3 +-
 pixman/pixman-private.h |   15 +++++-
 pixman/pixman.c         |    1 +
 pixman/pixman.h         |    4 ++
 test/composite.c        |  109 +++++++++++++++++++++++++++++++++-----
 test/stress-test.c      |    1 +
 test/utils.c            |    1 +
 13 files changed, 473 insertions(+), 18 deletions(-)
 create mode 100644 demos/srgb-test.c
 create mode 100644 pixman/make-srgb.pl

diff --git a/.gitignore b/.gitignore
index b9853b1..a4d9f99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,12 +36,14 @@ demos/gradient-test
 demos/quad2quad
 demos/radial-test
 demos/screen-test
+demos/srgb-test
 demos/trap-test
 demos/tri-test
 pixman/pixman-combine32.c
 pixman/pixman-combine32.h
 pixman/pixman-combine64.c
 pixman/pixman-combine64.h
+pixman/pixman-srgb.c
 pixman/pixman-version.h
 test/a1-trap-test
 test/affine-test
diff --git a/demos/Makefile.am b/demos/Makefile.am
index 9aac1f5..d8fb0da 100644
--- a/demos/Makefile.am
+++ b/demos/Makefile.am
@@ -20,7 +20,8 @@ DEMOS =				\
 	trap-test		\
 	tri-test		\
 	quad2quad		\
-	checkerboard
+	checkerboard		\
+	srgb-test
 
 EXTRA_DIST = parrot.c parrot.jpg
 
@@ -35,6 +36,7 @@ convolution_test_SOURCES = convolution-test.c $(GTK_UTILS)
 radial_test_SOURCES = radial-test.c $(GTK_UTILS)
 tri_test_SOURCES = tri-test.c $(GTK_UTILS)
 checkerboard_SOURCES = checkerboard.c $(GTK_UTILS)
+srgb_test_SOURCES = srgb-test.c $(GTK_UTILS)
 
 noinst_PROGRAMS = $(DEMOS)
 
diff --git a/demos/srgb-test.c b/demos/srgb-test.c
new file mode 100644
index 0000000..0fb127c
--- /dev/null
+++ b/demos/srgb-test.c
@@ -0,0 +1,100 @@
+#include <math.h>
+
+#include "pixman.h"
+#include "gtk-utils.h"
+
+static uint32_t
+linear_argb_to_premult_argb (float a,
+			     float r,
+			     float g,
+			     float b)
+{
+    r *= a;
+    g *= a;
+    b *= a;
+    return (uint32_t) (a * 255.0f + 0.5f) << 24
+	 | (uint32_t) (r * 255.0f + 0.5f) << 16
+	 | (uint32_t) (g * 255.0f + 0.5f) <<  8
+	 | (uint32_t) (b * 255.0f + 0.5f) <<  0;
+}
+
+static float
+lin2srgb (float linear)
+{
+    if (linear < 0.0031308f)
+    {
+	return linear * 12.92f;
+    }
+    else
+    {
+	return 1.055f * powf (linear, 1.0f/2.4f) - 0.055f;
+    }
+}
+
+static uint32_t
+linear_argb_to_premult_srgb_argb (float a,
+				  float r,
+				  float g,
+				  float b)
+{
+    r = lin2srgb (r * a);
+    g = lin2srgb (g * a);
+    b = lin2srgb (b * a);
+    return (uint32_t) (a * 255.0f + 0.5f) << 24
+	 | (uint32_t) (r * 255.0f + 0.5f) << 16
+	 | (uint32_t) (g * 255.0f + 0.5f) <<  8
+	 | (uint32_t) (b * 255.0f + 0.5f) <<  0;
+}
+
+int
+main (int argc, char **argv)
+{
+#define WIDTH 400
+#define HEIGHT 200
+    int y, x, p;
+    float alpha;
+ 
+    uint32_t *dest = malloc (WIDTH * HEIGHT * 4);
+    uint32_t *src1 = malloc (WIDTH * HEIGHT * 4);
+    pixman_image_t *dest_img, *src1_img;
+   
+    dest_img = pixman_image_create_bits (PIXMAN_a8r8g8b8_sRGB,
+					 WIDTH, HEIGHT,
+					 dest,
+					 WIDTH * 4);
+    src1_img = pixman_image_create_bits (PIXMAN_a8r8g8b8,
+					 WIDTH, HEIGHT,
+					 src1,
+					 WIDTH * 4);
+
+    for (y = 0; y < HEIGHT; y ++)
+    {
+	p = WIDTH * y;
+	for (x = 0; x < WIDTH; x ++)
+	{
+	     alpha = (float) x / WIDTH;
+	     src1[p + x] = linear_argb_to_premult_argb (alpha, 1, 0, 1);
+	     dest[p + x] = linear_argb_to_premult_srgb_argb (1-alpha, 0, 1, 0);
+	}
+    }
+    
+    pixman_image_composite (PIXMAN_OP_ADD, src1_img, NULL, dest_img,
+			    0, 0, 0, 0, 0, 0, WIDTH, HEIGHT);
+    pixman_image_unref (src1_img);
+    free (src1);
+
+    pixman_image_unref (dest_img);
+
+    /* Now that the picture has been correctly constructed,
+     * we hand it over to our support library as argb which it
+     * knows how to handle (it doesn't understand _sRGB format). */
+    dest_img = pixman_image_create_bits (PIXMAN_a8r8g8b8,
+			 		 WIDTH, HEIGHT,
+					 dest,
+					 WIDTH * 4);
+    show_image (dest_img);
+    pixman_image_unref (dest_img);
+    free (dest);
+    
+    return 0;
+}
diff --git a/pixman/Makefile.sources b/pixman/Makefile.sources
index 11f959d..d1a2581 100644
--- a/pixman/Makefile.sources
+++ b/pixman/Makefile.sources
@@ -22,6 +22,7 @@ libpixman_sources =			\
 	pixman-region16.c		\
 	pixman-region32.c		\
 	pixman-solid-fill.c		\
+	pixman-srgb.c			\
 	pixman-timer.c			\
 	pixman-trap.c			\
 	pixman-utils.c			\
@@ -43,8 +44,12 @@ BUILT_SOURCES =				\
 	pixman-combine32.h		\
 	pixman-combine64.c		\
 	pixman-combine64.h		\
+	pixman-srgb.c			\
 	$(NULL)
 
+pixman-srgb.c: make-srgb.pl
+	$(PERL) $< > $@ || ($(RM) $@; exit 1)
+
 pixman-combine32.c: pixman-combine.c.template make-combine.pl
 	$(PERL) $(lastword $+) 8 < $< > $@ || ($(RM) $@; exit 1)
 pixman-combine32.h: pixman-combine.h.template make-combine.pl
diff --git a/pixman/make-srgb.pl b/pixman/make-srgb.pl
new file mode 100644
index 0000000..ebde2ea
--- /dev/null
+++ b/pixman/make-srgb.pl
@@ -0,0 +1,112 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+sub linear_to_srgb
+{
+    my ($c) = @_;
+
+    if ($c < 0.0031308)
+    {
+	return $c * 12.92;
+    }
+    else
+    {
+	return 1.055 * $c ** (1.0/2.4) - 0.055;
+    }
+}
+
+sub srgb_to_linear
+{
+    my ($c) = @_;
+
+    if ($c < 0.04045)
+    {
+	return $c / 12.92;
+    }
+    else
+    {
+	return (($c + 0.055) / 1.055) ** 2.4
+    }
+}
+
+my @linear_to_srgb;
+for my $linear (0 .. 4095)
+{
+    my $srgb = int(linear_to_srgb($linear / 4095.0) * 255.0 + 0.5);
+    push @linear_to_srgb, $srgb;
+}
+
+my @srgb_to_linear;
+for my $srgb (0 .. 255)
+{
+    my $linear = int(srgb_to_linear($srgb / 255.0) * 65535.0 + 0.5);
+    push @srgb_to_linear, $linear;
+}
+
+# Ensure that we have a lossless sRGB and back conversion loop.
+# some of the darkest shades need a little bias -- maximum is just
+# 5 increments out of 16. This gives us useful property with
+# least amount of error in the sRGB-to-linear table, and keeps the actual
+# table lookup in the other direction as simple as possible.
+for my $srgb (0 .. $#srgb_to_linear)
+{
+    my $add = 0;
+    while (1)
+    {
+	my $linear = $srgb_to_linear[$srgb];
+	my $srgb_lossy = $linear_to_srgb[$linear >> 4];
+	last if $srgb == $srgb_lossy;
+
+	# Add slight bias to this component until it rounds correctly
+	$srgb_to_linear[$srgb] ++;
+	$add ++;
+    }
+    die "Too many adds at $srgb" if $add > 5;
+}
+
+print <<"PROLOG";
+/* WARNING: This file is generated by $0.
+ * Please edit that file instead of this one.
+ */
+
+#include <stdint.h>
+
+#include "config.h"
+#include "pixman-private.h"
+
+PROLOG
+
+print "const uint8_t linear_to_srgb[" . @linear_to_srgb . "] =\n";
+print "{\n";
+for my $linear (0 .. $#linear_to_srgb)
+{
+    if (($linear % 10) == 0)
+    {
+	print "\t";
+    }
+    print sprintf("%d, ", $linear_to_srgb[$linear]);
+    if (($linear % 10) == 9)
+    {
+	print "\n";
+    }
+}
+print "\n};\n";
+print "\n";
+
+print "const uint16_t srgb_to_linear[" . @srgb_to_linear . "] =\n";
+print "{\n";
+for my $srgb (0 .. $#srgb_to_linear)
+{
+    if (($srgb % 10) == 0)
+    {
+	print "\t";
+    }
+    print sprintf("%d, ", $srgb_to_linear[$srgb]);
+    if (($srgb % 10) == 9)
+    {
+	print "\n";
+    }
+}
+print "\n};\n";
+
diff --git a/pixman/pixman-access.c b/pixman/pixman-access.c
index 6743887..9feafc4 100644
--- a/pixman/pixman-access.c
+++ b/pixman/pixman-access.c
@@ -32,8 +32,8 @@
 #include <string.h>
 #include <assert.h>
 
-#include "pixman-private.h"
 #include "pixman-accessor.h"
+#include "pixman-private.h"
 
 #define CONVERT_RGB24_TO_Y15(s)						\
     (((((s) >> 16) & 0xff) * 153 +					\
@@ -210,6 +210,7 @@ get_shifts (pixman_format_code_t  format,
 	break;
 
     case PIXMAN_TYPE_ARGB:
+    case PIXMAN_TYPE_ARGB_SRGB:
 	*b = 0;
 	*g = *b + PIXMAN_FORMAT_B (format);
 	*r = *g + PIXMAN_FORMAT_G (format);
@@ -1027,6 +1028,130 @@ fetch_pixel_generic_64 (bits_image_t *image,
     return result;
 }
 
+/* The 32_sRGB paths should be deleted after narrow processing
+ * is no longer invoked for formats that are considered wide.
+ * (Also see fetch_pixel_generic_lossy_32) */
+static void
+fetch_scanline_a8r8g8b8_32_sRGB (pixman_image_t *image,
+                                 int             x,
+                                 int             y,
+                                 int             width,
+                                 uint32_t       *buffer,
+                                 const uint32_t *mask)
+{
+    const uint32_t *bits = image->bits.bits + y * image->bits.rowstride;
+    const uint32_t *pixel = (uint32_t *)bits + x;
+    const uint32_t *end = pixel + width;
+    uint32_t tmp;
+    
+    while (pixel < end)
+    {
+	tmp = READ (image, pixel++);
+	*buffer++ =                 (tmp >> 24)               << 24
+		  | (srgb_to_linear[(tmp >> 16) & 0xff] >> 8) << 16
+		  | (srgb_to_linear[(tmp >>  8) & 0xff] >> 8) <<  8
+		  | (srgb_to_linear[(tmp >>  0) & 0xff] >> 8) <<  0;
+    }
+}
+
+static void
+fetch_scanline_a8r8g8b8_64_sRGB (pixman_image_t *image,
+                                 int             x,
+                                 int             y,
+                                 int             width,
+                                 uint32_t       *b,
+                                 const uint32_t *mask)
+{
+    const uint32_t *bits = image->bits.bits + y * image->bits.rowstride;
+    const uint32_t *pixel = (uint32_t *)bits + x;
+    const uint32_t *end = pixel + width;
+    uint64_t *buffer = (uint64_t *)b;
+    uint32_t tmp;
+    
+    while (pixel < end)
+    {
+	tmp = READ (image, pixel++);
+	*buffer++ = (uint64_t)               ((tmp >> 24) * 257)  << 48
+		  | (uint64_t) srgb_to_linear[(tmp >> 16) & 0xff] << 32
+		  | (uint64_t) srgb_to_linear[(tmp >>  8) & 0xff] << 16
+		  | (uint64_t) srgb_to_linear[(tmp >>  0) & 0xff] <<  0;
+    }
+}
+
+static uint32_t
+fetch_pixel_a8r8g8b8_32_sRGB (bits_image_t *image,
+			      int           offset,
+			      int           line)
+{
+    uint32_t *bits = image->bits + line * image->rowstride;
+    uint32_t tmp = READ (image, bits + offset);
+    return                 (tmp >> 24)               << 24
+	 | (srgb_to_linear[(tmp >> 16) & 0xff] >> 8) << 16
+	 | (srgb_to_linear[(tmp >>  8) & 0xff] >> 8) <<  8
+	 | (srgb_to_linear[(tmp >>  0) & 0xff] >> 8) <<  0;
+}
+
+static uint64_t
+fetch_pixel_a8r8g8b8_64_sRGB (bits_image_t *image,
+			      int           offset,
+			      int	    line)
+{
+    uint32_t *bits = image->bits + line * image->rowstride;
+    uint32_t tmp = READ (image, bits + offset);
+    return (uint64_t)               ((tmp >> 24) * 257)  << 48
+	 | (uint64_t) srgb_to_linear[(tmp >> 16) & 0xff] << 32
+	 | (uint64_t) srgb_to_linear[(tmp >>  8) & 0xff] << 16
+	 | (uint64_t) srgb_to_linear[(tmp >>  0) & 0xff] <<  0;
+}
+
+static void
+store_scanline_a8r8g8b8_32_sRGB (bits_image_t   *image,
+                                 int             x,
+                                 int             y,
+                                 int             width,
+                                 const uint32_t *v)
+{
+    uint32_t *bits = image->bits + image->rowstride * y;
+    uint64_t *values = (uint64_t *)v;
+    uint32_t *pixel = bits + x;
+    uint64_t tmp;
+    int i;
+    
+    for (i = 0; i < width; ++i)
+    {
+	tmp = values[i];
+	WRITE (image, pixel++,
+		  ((uint32_t)     (tmp >> 24     )          << 24)
+		| (linear_to_srgb[(tmp >> 16 << 4) & 0xfff] << 16)
+		| (linear_to_srgb[(tmp >>  8 << 4) & 0xfff] <<  8)
+		| (linear_to_srgb[(tmp >>  0 << 4) & 0xfff] <<  0));
+    }
+}
+
+static void
+store_scanline_a8r8g8b8_64_sRGB (bits_image_t  *image,
+                                int             x,
+                                int             y,
+                                int             width,
+                                const uint32_t *v)
+{
+    uint32_t *bits = image->bits + image->rowstride * y;
+    uint64_t *values = (uint64_t *)v;
+    uint32_t *pixel = bits + x;
+    uint64_t tmp;
+    int i;
+    
+    for (i = 0; i < width; ++i)
+    {
+	tmp = values[i];
+	WRITE (image, pixel++,
+		  ((uint32_t)     (tmp >> 56)          << 24)
+		| (linear_to_srgb[(tmp >> 36) & 0xfff] << 16)
+		| (linear_to_srgb[(tmp >> 20) & 0xfff] <<  8)
+		| (linear_to_srgb[(tmp >>  4) & 0xfff] <<  0));
+    }
+}
+
 /*
  * XXX: The transformed fetch path only works at 32-bpp so far.  When all
  * paths have wide versions, this can be removed.
@@ -1079,6 +1204,13 @@ static const format_info_t accessors[] =
     FORMAT_INFO (r8g8b8x8),
     FORMAT_INFO (x14r6g6b6),
 
+/* sRGB formats */
+  { PIXMAN_a8r8g8b8_sRGB,
+    fetch_scanline_a8r8g8b8_32_sRGB,
+    fetch_scanline_a8r8g8b8_64_sRGB,
+    fetch_pixel_a8r8g8b8_32_sRGB, fetch_pixel_a8r8g8b8_64_sRGB,
+    store_scanline_a8r8g8b8_32_sRGB, store_scanline_a8r8g8b8_64_sRGB },
+
 /* 24bpp formats */
     FORMAT_INFO (r8g8b8),
     FORMAT_INFO (b8g8r8),
diff --git a/pixman/pixman-image.c b/pixman/pixman-image.c
index 8b634a7..15597bd 100644
--- a/pixman/pixman-image.c
+++ b/pixman/pixman-image.c
@@ -909,7 +909,8 @@ _pixman_image_get_solid (pixman_implementation_t *imp,
     }
 
     /* If necessary, convert RGB <--> BGR. */
-    if (PIXMAN_FORMAT_TYPE (format) != PIXMAN_TYPE_ARGB)
+    if (PIXMAN_FORMAT_TYPE (format) != PIXMAN_TYPE_ARGB
+	&& PIXMAN_FORMAT_TYPE (format) != PIXMAN_TYPE_ARGB_SRGB)
     {
 	result = (((result & 0xff000000) >>  0) |
 	          ((result & 0x00ff0000) >> 16) |
diff --git a/pixman/pixman-private.h b/pixman/pixman-private.h
index 72e3b4f..f6fe241 100644
--- a/pixman/pixman-private.h
+++ b/pixman/pixman-private.h
@@ -856,7 +856,8 @@ pixman_list_move_to_front (pixman_list_t *list, pixman_link_t *link)
     (PIXMAN_FORMAT_A (f) > 8 ||						\
      PIXMAN_FORMAT_R (f) > 8 ||						\
      PIXMAN_FORMAT_G (f) > 8 ||						\
-     PIXMAN_FORMAT_B (f) > 8)
+     PIXMAN_FORMAT_B (f) > 8 ||						\
+     PIXMAN_FORMAT_TYPE (f) == PIXMAN_TYPE_ARGB_SRGB)
 
 #ifdef WORDS_BIGENDIAN
 #   define SCREEN_SHIFT_LEFT(x,n)	((x) << (n))
@@ -1052,4 +1053,16 @@ void pixman_timer_register (pixman_timer_t *timer);
 
 #endif /* PIXMAN_TIMERS */
 
+/* sRGB<->linear conversion tables. Linear color space is the same
+ * as sRGB but the components are in linear light (gamma 1.0).
+ *
+ * linear_to_srgb maps linear value from 0 to 4095 ([0.0, 1.0])
+ * and returns 8-bit sRGB value.
+ *
+ * srgb_to_linear maps 8-bit sRGB value to 16-bit linear value
+ * with range 0 to 65535 ([0.0, 1.0]).
+ */
+extern const uint8_t linear_to_srgb[4096];
+extern const uint16_t srgb_to_linear[256];
+
 #endif /* PIXMAN_PRIVATE_H */
diff --git a/pixman/pixman.c b/pixman/pixman.c
index 0137c3c..994ef38 100644
--- a/pixman/pixman.c
+++ b/pixman/pixman.c
@@ -1017,6 +1017,7 @@ pixman_format_supported_source (pixman_format_code_t format)
     case PIXMAN_a2r10g10b10:
     case PIXMAN_x2r10g10b10:
     case PIXMAN_a8r8g8b8:
+    case PIXMAN_a8r8g8b8_sRGB:
     case PIXMAN_x8r8g8b8:
     case PIXMAN_a8b8g8r8:
     case PIXMAN_x8b8g8r8:
diff --git a/pixman/pixman.h b/pixman/pixman.h
index 7233ceb..e1cb90a 100644
--- a/pixman/pixman.h
+++ b/pixman/pixman.h
@@ -653,6 +653,7 @@ struct pixman_indexed
 #define PIXMAN_TYPE_YV12	7
 #define PIXMAN_TYPE_BGRA	8
 #define PIXMAN_TYPE_RGBA	9
+#define PIXMAN_TYPE_ARGB_SRGB	10
 
 #define PIXMAN_FORMAT_COLOR(f)				\
 	(PIXMAN_FORMAT_TYPE(f) == PIXMAN_TYPE_ARGB ||	\
@@ -676,6 +677,9 @@ typedef enum {
     PIXMAN_x2b10g10r10 = PIXMAN_FORMAT(32,PIXMAN_TYPE_ABGR,0,10,10,10),
     PIXMAN_a2b10g10r10 = PIXMAN_FORMAT(32,PIXMAN_TYPE_ABGR,2,10,10,10),
 
+/* sRGB formats */
+    PIXMAN_a8r8g8b8_sRGB = PIXMAN_FORMAT(32,PIXMAN_TYPE_ARGB_SRGB,8,8,8,8),
+
 /* 24bpp formats */
     PIXMAN_r8g8b8 =	 PIXMAN_FORMAT(24,PIXMAN_TYPE_ARGB,0,8,8,8),
     PIXMAN_b8g8r8 =	 PIXMAN_FORMAT(24,PIXMAN_TYPE_ABGR,0,8,8,8),
diff --git a/test/composite.c b/test/composite.c
index bdecd75..2900151 100644
--- a/test/composite.c
+++ b/test/composite.c
@@ -50,6 +50,32 @@ static const color_t colors[] =
     { 0.5, 0.0, 0.0, 0.5 },
 };
 
+static double
+convert_srgb_to_linear (double c)
+{
+    if (c <= 0.04045)
+    {
+	return c / 12.92;
+    }
+    else
+    {
+	return powf ((c + 0.055) / 1.055, 2.4);
+    }
+}
+
+static double
+convert_linear_to_srgb (double c)
+{
+    if (c <= 0.0031308)
+    {
+	return c * 12.92;
+    }
+    else
+    {
+	return 1.055 * powf (c, 1.0/2.4) - 0.055;
+    }
+}
+
 static uint16_t
 _color_double_to_short (double d)
 {
@@ -99,6 +125,9 @@ static const format_t formats[] =
     P(x2b10g10r10),
     P(a2r10g10b10),
     P(a2b10g10r10),
+    
+    /* sRGB formats */
+    P(a8r8g8b8_sRGB),
 
     /* 24 bpp formats */
     P(r8g8b8),
@@ -524,17 +553,8 @@ composite_test (image_t *dst,
 		pixman_bool_t component_alpha,
 		int testno)
 {
-    pixman_color_t fill;
     color_t expected, tdst, tsrc, tmsk;
     pixel_checker_t checker;
-    pixman_image_t *solid;
-
-    /* Initialize dst */
-    compute_pixman_color (dst->color, &fill);
-    solid = pixman_image_create_solid_fill (&fill);
-    pixman_image_composite32 (PIXMAN_OP_SRC, solid, NULL, dst->image,
-			      0, 0, 0, 0, 0, 0, dst->size, dst->size);
-    pixman_image_unref (solid);
 
     if (mask)
     {
@@ -553,17 +573,56 @@ composite_test (image_t *dst,
     }
 
     tdst = *dst->color;
-    round_color (dst->format->format, &tdst);
-
     tsrc = *src->color;
-    if (src->size)
-	round_color (src->format->format, &tsrc);
 
     if (mask)
     {
 	tmsk = *mask->color;
-	if (mask->size)
+    }
+
+    /* It turns out that by construction all source, mask etc. colors are
+     * linear because they are made from fills, and fills are always in linear
+     * color space.  However, if they have been converted to bitmaps, we need
+     * to simulate the sRGB approximation to pass the test cases.
+     */
+    if (src->size)
+    {
+	if (PIXMAN_FORMAT_TYPE (src->format->format) == PIXMAN_TYPE_ARGB_SRGB)
+        {
+	    tsrc.r = convert_linear_to_srgb (tsrc.r);
+	    tsrc.g = convert_linear_to_srgb (tsrc.g);
+	    tsrc.b = convert_linear_to_srgb (tsrc.b);
+	    round_color (src->format->format, &tsrc);
+	    tsrc.r = convert_srgb_to_linear (tsrc.r);
+	    tsrc.g = convert_srgb_to_linear (tsrc.g);
+	    tsrc.b = convert_srgb_to_linear (tsrc.b);
+	}
+        else
+        {
+	    round_color (src->format->format, &tsrc);
+	}
+    }
+
+    if (mask && mask->size)
+    {
+	if (PIXMAN_FORMAT_TYPE (mask->format->format) == PIXMAN_TYPE_ARGB_SRGB)
+	{
+	    tmsk.r = convert_linear_to_srgb (tmsk.r);
+	    tmsk.g = convert_linear_to_srgb (tmsk.g);
+	    tmsk.b = convert_linear_to_srgb (tmsk.b);
 	    round_color (mask->format->format, &tmsk);
+	    tmsk.r = convert_srgb_to_linear (tmsk.r);
+	    tmsk.g = convert_srgb_to_linear (tmsk.g);
+	    tmsk.b = convert_srgb_to_linear (tmsk.b);
+	}
+	else
+	{
+	    round_color (mask->format->format, &tmsk);
+	}
+    }
+
+    if (mask)
+    {
 	if (component_alpha && PIXMAN_FORMAT_R (mask->format->format) == 0)
 	{
 	    /* Ax component-alpha masks expand alpha into
@@ -573,6 +632,21 @@ composite_test (image_t *dst,
 	}
     }
 
+    if (PIXMAN_FORMAT_TYPE (dst->format->format) == PIXMAN_TYPE_ARGB_SRGB)
+    {
+	tdst.r = convert_linear_to_srgb (tdst.r);
+	tdst.g = convert_linear_to_srgb (tdst.g);
+	tdst.b = convert_linear_to_srgb (tdst.b);
+    	round_color (dst->format->format, &tdst);
+	tdst.r = convert_srgb_to_linear (tdst.r);
+	tdst.g = convert_srgb_to_linear (tdst.g);
+	tdst.b = convert_srgb_to_linear (tdst.b);
+    }
+    else
+    {
+    	round_color (dst->format->format, &tdst);
+    }
+
     do_composite (op->op,
 		  &tsrc,
 		  mask? &tmsk : NULL,
@@ -580,6 +654,13 @@ composite_test (image_t *dst,
 		  &expected,
 		  component_alpha);
 
+    if (PIXMAN_FORMAT_TYPE (dst->format->format) == PIXMAN_TYPE_ARGB_SRGB)
+    {
+	expected.r = convert_linear_to_srgb (expected.r);
+	expected.g = convert_linear_to_srgb (expected.g);
+	expected.b = convert_linear_to_srgb (expected.b);
+    }
+
     pixel_checker_init (&checker, dst->format->format);
 
     if (!pixel_checker_check (&checker, get_value (dst->image), &expected))
diff --git a/test/stress-test.c b/test/stress-test.c
index 7fc067e..2051de1 100644
--- a/test/stress-test.c
+++ b/test/stress-test.c
@@ -25,6 +25,7 @@ static const pixman_format_code_t image_formats[] =
     PIXMAN_x14r6g6b6,
     PIXMAN_r8g8b8,
     PIXMAN_b8g8r8,
+    PIXMAN_a8r8g8b8_sRGB,
     PIXMAN_r5g6b5,
     PIXMAN_b5g6r5,
     PIXMAN_x2r10g10b10,
diff --git a/test/utils.c b/test/utils.c
index 0abc32c..ba3cd21 100644
--- a/test/utils.c
+++ b/test/utils.c
@@ -868,6 +868,7 @@ pixel_checker_init (pixel_checker_t *checker, pixman_format_code_t format)
 	break;
 
     case PIXMAN_TYPE_ARGB:
+    case PIXMAN_TYPE_ARGB_SRGB:
 	checker->bs = 0;
 	checker->gs = checker->bs + PIXMAN_FORMAT_B (format);
 	checker->rs = checker->gs + PIXMAN_FORMAT_G (format);
-- 
1.7.9.5

