]> mj.ucw.cz Git - libucw.git/commitdiff
slightly changed dealing with color spaces
authorPavel Charvat <pavel.charvat@netcentrum.cz>
Wed, 1 Nov 2006 13:38:11 +0000 (14:38 +0100)
committerPavel Charvat <pavel.charvat@netcentrum.cz>
Wed, 1 Nov 2006 13:38:11 +0000 (14:38 +0100)
(I will add CMYK -> RGB conversion soon ... bug #2819)

images/color.c
images/color.h
images/image-tool.c
images/image.c
images/images.h
images/io-libjpeg.c

index 8ea6022819cbefc49403ed0826b01e4029e03866..7dd3773cdf3cfabd92ef7d850f5aaf005d9c9a35 100644 (file)
 #include "images/images.h"
 #include "images/color.h"
 
+#include <string.h>
+
+uns color_space_channels[COLOR_SPACE_MAX] = {
+  [COLOR_SPACE_UNKNOWN] = 0,
+  [COLOR_SPACE_UNKNOWN_1] = 1,
+  [COLOR_SPACE_UNKNOWN_2] = 2,
+  [COLOR_SPACE_UNKNOWN_3] = 3,
+  [COLOR_SPACE_UNKNOWN_4] = 4,
+  [COLOR_SPACE_GRAYSCALE] = 1,
+  [COLOR_SPACE_RGB] = 3,
+  [COLOR_SPACE_XYZ] = 3,
+  [COLOR_SPACE_LAB] = 3,
+  [COLOR_SPACE_YCBCR] = 3,
+  [COLOR_SPACE_CMYK] = 4,
+  [COLOR_SPACE_YCCK] = 4,
+};
+
+byte *color_space_name[COLOR_SPACE_MAX] = {
+  [COLOR_SPACE_UNKNOWN] = "Unknown",
+  [COLOR_SPACE_UNKNOWN_1] = "1-channel",
+  [COLOR_SPACE_UNKNOWN_2] = "2-channels",
+  [COLOR_SPACE_UNKNOWN_3] = "3-channels",
+  [COLOR_SPACE_UNKNOWN_4] = "4-channels",
+  [COLOR_SPACE_GRAYSCALE] = "Grayscale",
+  [COLOR_SPACE_RGB] = "RGB",
+  [COLOR_SPACE_XYZ] = "XYZ",
+  [COLOR_SPACE_LAB] = "LAB",
+  [COLOR_SPACE_YCBCR] = "YCbCr",
+  [COLOR_SPACE_CMYK] = "CMYK",
+  [COLOR_SPACE_YCCK] = "YCCK",
+};
+
+byte *
+color_space_id_to_name(uns id)
+{
+  ASSERT(id < COLOR_SPACE_MAX);
+  return color_space_name[id];
+}
+
+uns
+color_space_name_to_id(byte *name)
+{
+  for (uns i = 1; i < COLOR_SPACE_MAX; i++)
+    if (!strcasecmp(name, color_space_name[i]))
+      return i;
+  return 0;
+}
+
 struct color color_black = { .color_space = COLOR_SPACE_GRAYSCALE };
 struct color color_white = { .c = { 255 }, .color_space = COLOR_SPACE_GRAYSCALE };
 
index 8c87e87c5414500d89a77bdb603e9a21dd5943c3..7ec32acc87cdfc5563194f22783b8d5f7c9766c3 100644 (file)
 
 #include "images/images.h"
 
-// A comparison of four multimedia RGB spaces, Danny Pascale
-
 enum {
   COLOR_SPACE_UNKNOWN = 0,
+  COLOR_SPACE_UNKNOWN_1 = 1,
+  COLOR_SPACE_UNKNOWN_2 = 2,
+  COLOR_SPACE_UNKNOWN_3 = 3,
+  COLOR_SPACE_UNKNOWN_4 = 4,
+  COLOR_SPACE_UNKNOWN_MAX = 4,
   COLOR_SPACE_GRAYSCALE,
   COLOR_SPACE_RGB,
   COLOR_SPACE_XYZ,
   COLOR_SPACE_LAB,
   COLOR_SPACE_LUV,
   COLOR_SPACE_YCBCR,
+  COLOR_SPACE_CMYK,
+  COLOR_SPACE_YCCK,
   COLOR_SPACE_MAX
 };
 
+extern uns color_space_channels[COLOR_SPACE_MAX];
+extern byte *color_space_name[COLOR_SPACE_MAX];
+
+byte *color_space_id_to_name(uns id);
+uns color_space_name_to_id(byte *name);
+
 /* Color spaces in the CIE 1931 chromacity diagram */
 
 struct color_space_chromacity_info {
index 3e40bcb95b5c2464f6fa5ab0f33bab1410148f7d..758000ab08c2014c51c3d01cf0794e409142a296 100644 (file)
@@ -29,7 +29,7 @@ Usage: image-tool [options] infile [outfile]\n\
 -F --output-format       output image format\n\
 -s --size                force output dimensions (100x200)\n\
 -b --fit-to-box          scale to fit the box (100x200)\n\
--c --colorspace          force output colorspace (Gray, GrayAlpha, RGB, RGBAlpha)\n\
+-c --colorspace          force output colorspace (Grayscale, Grayscale+Alpha, RGB, RGB+Alpha, ...)\n\
 -Q --jpeg-quality        JPEG quality (1..100)\n\
 -g --background          background color (hexadecimal RRGGBB)\n\
 -G --default-background  background applied only if the image contains no background info (RRGGBB, default=FFFFFF)\n\
@@ -168,6 +168,7 @@ main(int argc, char **argv)
     die("Cannot initialize image I/O");
 
   MSG("Reading %s", input_file_name);
+  byte cs_buf[IMAGE_CHANNELS_FORMAT_MAX_SIZE];
   io.fastbuf = bopen(input_file_name, O_RDONLY, 1 << 18);
   io.format = input_format ? : image_file_name_to_format(input_file_name);
   if (exif)
@@ -178,8 +179,8 @@ main(int argc, char **argv)
       bclose(io.fastbuf);
       printf("Format:      %s\n", image_format_to_extension(io.format) ? : (byte *)"?");
       printf("Dimensions:  %dx%d\n", io.cols, io.rows);
-      printf("Colorspace:  %s\n", (io.flags & IMAGE_IO_HAS_PALETTE) ? (byte *)"Palette" : image_channels_format_to_name(io.flags & IMAGE_CHANNELS_FORMAT));
-      printf("NumColors:   %d\n", io.number_of_colors);
+      printf("Colorspace:  %s\n", (io.flags & IMAGE_IO_HAS_PALETTE) ? (byte *)"Palette" : image_channels_format_to_name(io.flags, cs_buf));
+      printf("NumColors:   %u\n", io.number_of_colors);
       if (io.background_color.color_space)
         {
          byte rgb[3];
@@ -192,7 +193,7 @@ main(int argc, char **argv)
   else
     {
       MSG("%s %dx%d %s", image_format_to_extension(io.format) ? : (byte *)"?", io.cols, io.rows,
-         (io.flags & IMAGE_IO_HAS_PALETTE) ? (byte *)"Palette" : image_channels_format_to_name(io.flags & IMAGE_CHANNELS_FORMAT));
+         (io.flags & IMAGE_IO_HAS_PALETTE) ? (byte *)"Palette" : image_channels_format_to_name(io.flags, cs_buf));
       if (cols)
         if (fit_to_box)
          {
@@ -221,7 +222,7 @@ main(int argc, char **argv)
       io.fastbuf = bopen(output_file_name, O_WRONLY | O_CREAT | O_TRUNC, 1 << 18);
       io.format = output_format ? : image_file_name_to_format(output_file_name);
       MSG("%s %dx%d %s", image_format_to_extension(io.format) ? : (byte *)"?", io.cols, io.rows,
-         image_channels_format_to_name(io.flags & IMAGE_CHANNELS_FORMAT));
+         image_channels_format_to_name(io.flags, cs_buf));
       TRY(image_io_write(&io));
       bclose(io.fastbuf);
     }
index e42b06c18e9af0b247d65ea8d22067330d1a35a6..26445aa34835934fa73545f980fb4b6d28b92f0e 100644 (file)
 static inline uns
 flags_to_pixel_size(uns flags)
 {
-  uns pixel_size;
-  switch (flags & IMAGE_COLOR_SPACE)
-    {
-      case COLOR_SPACE_GRAYSCALE:
-       pixel_size = 1;
-       break;
-      case COLOR_SPACE_RGB:
-       pixel_size = 3;
-       break;
-      default:
-       ASSERT(0);
-    }
+  uns pixel_size = color_space_channels[flags & IMAGE_COLOR_SPACE];
   if (flags & IMAGE_ALPHA)
     pixel_size++;
   return pixel_size;
@@ -48,9 +37,14 @@ image_new(struct image_context *ctx, uns cols, uns rows, uns flags, struct mempo
       return NULL;
     }
   struct image *img;
-  uns pixel_size, row_pixels_size, row_size, align;
-  pixel_size = flags_to_pixel_size(flags);
-  switch (pixel_size)
+  uns channels, pixel_size, row_pixels_size, row_size, align;
+  pixel_size = channels = flags_to_pixel_size(flags);
+  if (!channels || channels > 4)
+    {
+      IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Invalid number of color channels");
+      return NULL;
+    }
+  switch (channels)
     {
       case 1:
       case 2:
@@ -90,6 +84,7 @@ image_new(struct image_context *ctx, uns cols, uns rows, uns flags, struct mempo
   byte *p = (byte *)img + sizeof(struct image);
   img->pixels = ALIGN_PTR(p, IMAGE_SSE_ALIGN_SIZE);
   img->flags = flags;
+  img->channels = channels;
   img->pixel_size = pixel_size;
   img->cols = cols;
   img->rows = rows;
@@ -110,10 +105,12 @@ image_clone(struct image_context *ctx, struct image *src, uns flags, struct memp
   flags |= src->flags & IMAGE_CHANNELS_FORMAT;
   if (!(img = image_new(ctx, src->cols, src->rows, flags, pool)))
     return NULL;
+  ASSERT(src->channels == img->channels);
   if (img->image_size)
     {
       if (src->pixel_size != img->pixel_size) /* conversion between aligned and unaligned RGB */
         {
+         ASSERT(src->channels == 3);
 #        define IMAGE_WALK_PREFIX(x) walk_##x
 #         define IMAGE_WALK_INLINE
 #        define IMAGE_WALK_IMAGE img
@@ -175,7 +172,7 @@ image_init_matrix(struct image_context *ctx, struct image *img, byte *pixels, un
   img->pixels = pixels;
   img->cols = cols;
   img->rows = rows;
-  img->pixel_size = flags_to_pixel_size(flags);
+  img->pixel_size = img->channels = flags_to_pixel_size(flags);
   img->row_size = row_size;
   img->row_pixels_size = cols * img->pixel_size;
   img->image_size = rows * row_size;
@@ -191,7 +188,7 @@ image_init_subimage(struct image_context *ctx UNUSED, struct image *img, struct
   img->pixels = src->pixels + left * src->pixel_size + top * src->row_size;
   img->cols = cols;
   img->rows = rows;
-  img->pixel_size = src->pixel_size;
+  img->pixel_size = img->channels = src->pixel_size;
   img->row_size = src->row_size;
   img->row_pixels_size = cols * src->pixel_size;
   img->image_size = src->row_size * rows;
@@ -201,45 +198,30 @@ image_init_subimage(struct image_context *ctx UNUSED, struct image *img, struct
 }
 
 byte *
-color_space_to_name(uns cs)
+image_channels_format_to_name(uns format, byte *buf)
 {
-  return image_channels_format_to_name(cs);
-}
-
-byte *
-image_channels_format_to_name(uns format)
-{
-  switch (format)
-    {
-      case COLOR_SPACE_GRAYSCALE:
-       return "Gray";
-      case COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA:
-       return "GrayAlpha";
-      case COLOR_SPACE_RGB:
-       return "RGB";
-      case COLOR_SPACE_RGB | IMAGE_ALPHA:
-       return "RGBAlpha";
-      default:
-       return NULL;
-    }
+  byte *cs_name = color_space_id_to_name(format & IMAGE_COLOR_SPACE);
+  uns l = strlen(cs_name);
+  memcpy(buf, cs_name, l + 1);
+  if (format & IMAGE_ALPHA)
+    strcpy(buf + l, "+Alpha");
+  return buf;
 }
 
 uns
 image_name_to_channels_format(byte *name)
 {
-  if (!strcasecmp(name, "gray"))
-    return COLOR_SPACE_GRAYSCALE;
-  if (!strcasecmp(name, "grayscale"))
-    return COLOR_SPACE_GRAYSCALE;
-  if (!strcasecmp(name, "grayalpha"))
-    return COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA;
-  if (!strcasecmp(name, "grayscalealpha"))
-    return COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA;
-  if (!strcasecmp(name, "rgb"))
-    return COLOR_SPACE_RGB;
-  if (!strcasecmp(name, "rgbalpha"))
-    return COLOR_SPACE_RGB | IMAGE_ALPHA;
-  if (!strcasecmp(name, "rgba"))
-    return COLOR_SPACE_RGB | IMAGE_ALPHA;
+  uns i;
+  if (i = color_space_name_to_id(name))
+    return i;
+  uns l = strlen(name);
+  if (l > 6 && !strcasecmp(name + l - 5, "+alpha"))
+    {
+      byte buf[l + 1];
+      memcpy(buf, name, l - 6);
+      buf[l - 6] = 0;
+      if (i = color_space_name_to_id(name))
+       return i;
+    }
   return 0;
 }
index dbaa055c5af5e6cdbfd02d7f897e75c34001ee10..f35caf59156623e9729952575c445c64264265de 100644 (file)
@@ -55,12 +55,12 @@ extern uns image_max_bytes;         /* ImageLib.ImageMaxBytes */
 #define IMAGE_SSE_ALIGN_SIZE 16
 
 enum image_flag {
-  IMAGE_COLOR_SPACE = 0x7,             /* mask for enum color_space */
-  IMAGE_ALPHA = 0x8,                   /* alpha channel */
-  IMAGE_PIXELS_ALIGNED = 0x10,         /* align pixel size to the nearest power of two  */
-  IMAGE_SSE_ALIGNED = 0x20,            /* align scanlines to multiples of 16 bytes (both start and size) */
-  IMAGE_NEED_DESTROY = 0x40,           /* image is allocated with xmalloc */
-  IMAGE_GAPS_PROTECTED = 0x80,         /* cannot access gaps between rows */
+  IMAGE_COLOR_SPACE = 0xf,             /* mask for enum color_space */
+  IMAGE_ALPHA = 0x10,                  /* alpha channel */
+  IMAGE_PIXELS_ALIGNED = 0x20,         /* align pixel size to the nearest power of two  */
+  IMAGE_SSE_ALIGNED = 0x40,            /* align scanlines to multiples of 16 bytes (both start and size) */
+  IMAGE_NEED_DESTROY = 0x80,           /* image is allocated with xmalloc */
+  IMAGE_GAPS_PROTECTED = 0x100,                /* cannot access gaps between rows */
   IMAGE_CHANNELS_FORMAT = IMAGE_COLOR_SPACE | IMAGE_ALPHA,
   IMAGE_PIXEL_FORMAT = IMAGE_CHANNELS_FORMAT | IMAGE_PIXELS_ALIGNED,
   IMAGE_ALIGNED = IMAGE_PIXELS_ALIGNED | IMAGE_SSE_ALIGNED,
@@ -73,6 +73,7 @@ struct image {
                                   unused bytes after the buffer (possible optimizations) */
   uns cols;                    /* number of columns */
   uns rows;                    /* number of rows */
+  uns channels;                        /* number of color channels */
   uns pixel_size;              /* size of pixel in bytes (1, 2, 3 or 4) */
   uns row_size;                        /* scanline size in bytes */
   uns row_pixels_size;         /* scanline size in bytes excluding rows gaps */
@@ -93,8 +94,8 @@ image_dimensions_valid(uns cols, uns rows)
   return cols && rows && cols <= image_max_dim && rows <= image_max_dim;
 }
 
-byte *color_space_to_name(uns cs);
-byte *image_channels_format_to_name(uns format);
+#define IMAGE_CHANNELS_FORMAT_MAX_SIZE 128
+byte *image_channels_format_to_name(uns format, byte *buf);
 uns image_name_to_channels_format(byte *name);
 
 struct color {
index 1d3d37c5029e046212bee97f484179b50bea9391..f4a72e29a70228afb7d98681e246347d531605b2 100644 (file)
@@ -7,7 +7,7 @@
  *     of the GNU Lesser General Public License.
  */
 
-#undef LOCAL_DEBUG
+#define LOCAL_DEBUG
 
 #include "lib/lib.h"
 #include "lib/mempool.h"
@@ -294,16 +294,38 @@ libjpeg_read_header(struct image_io *io)
     {
       case JCS_GRAYSCALE:
         io->flags = COLOR_SPACE_GRAYSCALE;
-       io->number_of_colors = 1 << 8;
         break;
+      case JCS_RGB:
+       io->flags = COLOR_SPACE_RGB;
+       break;
+      case JCS_YCbCr:
+       io->flags = COLOR_SPACE_YCBCR;
+       break;
+      case JCS_CMYK:
+       io->flags = COLOR_SPACE_CMYK;
+       break;
+      case JCS_YCCK:
+       io->flags = COLOR_SPACE_YCCK;
+       break;
       default:
-        io->flags = COLOR_SPACE_RGB;
-       io->number_of_colors = 1 << 24;
-        break;
+       if (unlikely(i->cinfo.num_components < 1 || i->cinfo.num_components > 4))
+         {
+           jpeg_destroy_decompress(&i->cinfo);
+           IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Invalid color space.");
+           return 0;
+         }
+       io->flags = COLOR_SPACE_UNKNOWN + i->cinfo.num_components;
+       break;
+    }
+  if (unlikely(i->cinfo.num_components != (int)color_space_channels[io->flags]))
+    {
+      jpeg_destroy_decompress(&i->cinfo);
+      IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Invalid number of color channels.");
+      return 0;
     }
   io->cols = i->cinfo.image_width;
   io->rows = i->cinfo.image_height;
-
+  io->number_of_colors = (i->cinfo.num_components < 4) ? (1U << (i->cinfo.num_components * 8)) : 0xffffffff;
   io->read_cancel = libjpeg_read_cancel;
   return 1;
 }
@@ -324,6 +346,15 @@ libjpeg_read_data(struct image_io *io)
       case COLOR_SPACE_RGB:
        i->cinfo.out_color_space = JCS_RGB;
        break;
+      case COLOR_SPACE_YCBCR:
+       i->cinfo.out_color_space = JCS_YCbCr;
+       break;
+      case COLOR_SPACE_CMYK:
+       i->cinfo.out_color_space = JCS_CMYK;
+       break;
+      case COLOR_SPACE_YCCK:
+       i->cinfo.out_color_space = JCS_YCCK;
+       break;
       default:
        jpeg_destroy_decompress(&i->cinfo);
        IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Unsupported color space.");
@@ -370,51 +401,53 @@ libjpeg_read_data(struct image_io *io)
   /* Decompress the image */
   struct image *img = rdi.image;
   jpeg_start_decompress(&i->cinfo);
-  switch (img->pixel_size)
+  if ((int)img->pixel_size == i->cinfo.num_components)
     {
-      /* grayscale or RGB */
-      case 1:
-      case 3:
-       {
-          byte *pixels = img->pixels;
-         for (uns r = img->rows; r--; )
-            {
-              jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&pixels, 1);
-              pixels += img->row_size;
-            }
-       }
-       break;
-      /* grayscale with alpha */
-      case 2:
-       {
-         byte buf[img->cols], *src;
-#        define IMAGE_WALK_PREFIX(x) walk_##x
-#         define IMAGE_WALK_INLINE
-#        define IMAGE_WALK_IMAGE img
-#         define IMAGE_WALK_UNROLL 4
-#         define IMAGE_WALK_COL_STEP 2
-#         define IMAGE_WALK_DO_ROW_START do{ src = buf; jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&src, 1); }while(0)
-#         define IMAGE_WALK_DO_STEP do{ walk_pos[0] = *src++; walk_pos[1] = 255; }while(0)
-#         include "images/image-walk.h"
-       }
-       break;
-      /* RGBA or aligned RGB */
-      case 4:
-       {
-         byte buf[img->cols * 3], *src;
-#        define IMAGE_WALK_PREFIX(x) walk_##x
-#         define IMAGE_WALK_INLINE
-#        define IMAGE_WALK_IMAGE img
-#         define IMAGE_WALK_UNROLL 4
-#         define IMAGE_WALK_COL_STEP 4
-#         define IMAGE_WALK_DO_ROW_START do{ src = buf; jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&src, 1); }while(0)
-#         define IMAGE_WALK_DO_STEP do{ *(u32 *)walk_pos = *(u32 *)src; walk_pos[3] = 255; src += 3; }while(0)
-#         include "images/image-walk.h"
+      byte *pixels = img->pixels;
+      for (uns r = img->rows; r--; )
+        {
+          jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&pixels, 1);
+          pixels += img->row_size;
+        }
+    }
+  else
+    {
+      switch (img->pixel_size)
+        {
+         case 2: /* Grayscale -> Grayscale+Alpha */
+           {
+             ASSERT(i->cinfo.num_components == 1);
+             byte buf[img->cols], *src;
+#            define IMAGE_WALK_PREFIX(x) walk_##x
+#             define IMAGE_WALK_INLINE
+#            define IMAGE_WALK_IMAGE img
+#             define IMAGE_WALK_UNROLL 4
+#             define IMAGE_WALK_COL_STEP 2
+#             define IMAGE_WALK_DO_ROW_START do{ src = buf; jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&src, 1); }while(0)
+#             define IMAGE_WALK_DO_STEP do{ walk_pos[0] = *src++; walk_pos[1] = 255; }while(0)
+#             include "images/image-walk.h"
+           }
+           break;
+         case 4: /* * -> *+Alpha or aligned * */
+           {
+             ASSERT(i->cinfo.num_components == 3);
+             byte buf[img->cols * 3], *src;
+#            define IMAGE_WALK_PREFIX(x) walk_##x
+#             define IMAGE_WALK_INLINE
+#            define IMAGE_WALK_IMAGE img
+#             define IMAGE_WALK_UNROLL 4
+#             define IMAGE_WALK_COL_STEP 4
+#             define IMAGE_WALK_DO_ROW_START do{ src = buf; jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&src, 1); }while(0)
+#             define IMAGE_WALK_DO_STEP do{ *(u32 *)walk_pos = *(u32 *)src; walk_pos[3] = 255; src += 3; }while(0)
+#             include "images/image-walk.h"
+           }
+           break;
+         default:
+           ASSERT(0);
        }
-       break;
-      default:
-       ASSERT(0);
+
     }
+
   ASSERT(i->cinfo.output_scanline == i->cinfo.output_height);
 
   /* Destroy libjpeg object */
@@ -459,18 +492,26 @@ libjpeg_write(struct image_io *io)
   switch (img->flags & IMAGE_COLOR_SPACE)
     {
       case COLOR_SPACE_GRAYSCALE:
-       i.cinfo.input_components = 1;
        i.cinfo.in_color_space = JCS_GRAYSCALE;
        break;
       case COLOR_SPACE_RGB:
-       i.cinfo.input_components = 3;
        i.cinfo.in_color_space = JCS_RGB;
        break;
+      case COLOR_SPACE_YCBCR:
+       i.cinfo.in_color_space = JCS_YCbCr;
+       break;
+      case COLOR_SPACE_CMYK:
+       i.cinfo.in_color_space = JCS_CMYK;
+       break;
+      case COLOR_SPACE_YCCK:
+       i.cinfo.in_color_space = JCS_YCCK;
+       break;
       default:
        jpeg_destroy_compress(&i.cinfo);
        IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Unsupported pixel format.");
        return 0;
     }
+  i.cinfo.input_components = color_space_channels[img->flags & IMAGE_COLOR_SPACE];
   jpeg_set_defaults(&i.cinfo);
   if (io->jpeg_quality)
     jpeg_set_quality(&i.cinfo, MIN(io->jpeg_quality, 100), 1);
@@ -490,50 +531,50 @@ libjpeg_write(struct image_io *io)
       DBG("Writing EXIF");
       jpeg_write_marker(&i.cinfo, JPEG_APP0 + 1, io->exif_data, io->exif_size);
     }
-  switch (img->pixel_size)
+  if ((int)img->pixel_size == i.cinfo.input_components)
     {
-      /* grayscale or RGB */
-      case 1:
-      case 3:
-       {
-          byte *pixels = img->pixels;
-         for (uns r = img->rows; r--; )
+      byte *pixels = img->pixels;
+      for (uns r = img->rows; r--; )
+        {
+          jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&pixels, 1);
+          pixels += img->row_size;
+        }
+    }
+  else
+    {
+      switch (img->pixel_size)
+        {
+         case 2: /* Grayscale+Alpha -> Grayscale */
            {
-              jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&pixels, 1);
-              pixels += img->row_size;
-            }
-       }
-       break;
-      /* grayscale with alpha (ignore alpha) */
-      case 2:
-       {
-         byte buf[img->cols], *dest = buf;
-#        define IMAGE_WALK_PREFIX(x) walk_##x
-#         define IMAGE_WALK_INLINE
-#        define IMAGE_WALK_IMAGE img
-#         define IMAGE_WALK_UNROLL 4
-#         define IMAGE_WALK_COL_STEP 2
-#         define IMAGE_WALK_DO_ROW_END do{ dest = buf; jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&dest, 1); }while(0)
-#         define IMAGE_WALK_DO_STEP do{ *dest++ = walk_pos[0]; }while(0)
-#         include "images/image-walk.h"
-       }
-       break;
-      /* RGBA (ignore alpha) or aligned RGB */
-      case 4:
-       {
-         byte buf[img->cols * 3], *dest = buf;
-#        define IMAGE_WALK_PREFIX(x) walk_##x
-#         define IMAGE_WALK_INLINE
-#        define IMAGE_WALK_IMAGE img
-#         define IMAGE_WALK_UNROLL 4
-#         define IMAGE_WALK_COL_STEP 4
-#         define IMAGE_WALK_DO_ROW_END do{ dest = buf; jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&dest, 1); }while(0)
-#         define IMAGE_WALK_DO_STEP do{ *dest++ = walk_pos[0]; *dest++ = walk_pos[1]; *dest++ = walk_pos[2]; }while(0)
-#         include "images/image-walk.h"
+             ASSERT(i.cinfo.input_components == 1);
+             byte buf[img->cols], *dest = buf;
+#            define IMAGE_WALK_PREFIX(x) walk_##x
+#             define IMAGE_WALK_INLINE
+#            define IMAGE_WALK_IMAGE img
+#             define IMAGE_WALK_UNROLL 4
+#             define IMAGE_WALK_COL_STEP 2
+#             define IMAGE_WALK_DO_ROW_END do{ dest = buf; jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&dest, 1); }while(0)
+#             define IMAGE_WALK_DO_STEP do{ *dest++ = walk_pos[0]; }while(0)
+#             include "images/image-walk.h"
+             break;
+           }
+         case 4: /* *+Alpha or aligned * -> * */
+           {
+             ASSERT(i.cinfo.input_components == 3);
+             byte buf[img->cols * 3], *dest = buf;
+#            define IMAGE_WALK_PREFIX(x) walk_##x
+#             define IMAGE_WALK_INLINE
+#            define IMAGE_WALK_IMAGE img
+#             define IMAGE_WALK_UNROLL 4
+#             define IMAGE_WALK_COL_STEP 4
+#             define IMAGE_WALK_DO_ROW_END do{ dest = buf; jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&dest, 1); }while(0)
+#             define IMAGE_WALK_DO_STEP do{ *dest++ = walk_pos[0]; *dest++ = walk_pos[1]; *dest++ = walk_pos[2]; }while(0)
+#             include "images/image-walk.h"
+             break;
+           }
+         default:
+           ASSERT(0);
        }
-       break;
-      default:
-       ASSERT(0);
     }
   ASSERT(i.cinfo.next_scanline == i.cinfo.image_height);
   jpeg_finish_compress(&i.cinfo);