]> git.imager.perl.org - imager.git/blobdiff - PNG/impng.c
PNG-rework: forward png flush output through to i_io_flush()
[imager.git] / PNG / impng.c
index 583a2b7ad474396ebb7aacc6d41917b61b49fe1e..f9a7a25272a3b4a034bd177cd5b7f1936ea70054 100644 (file)
@@ -1,5 +1,6 @@
 #include "impng.h"
 #include "png.h"
+#include <stdlib.h>
 
 /* this is a way to get number of channels from color space 
  * Color code to channel number */
@@ -20,6 +21,18 @@ read_paletted(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim w
 static i_img *
 read_bilevel(png_structp png_ptr, png_infop info_ptr, i_img_dim width, i_img_dim height);
 
+static int
+write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im);
+
+static int
+write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im);
+
+static int
+write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits);
+
+static int
+write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im);
+
 unsigned
 i_png_lib_version(void) {
   return png_access_version_number();
@@ -42,7 +55,9 @@ wiol_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
 
 static void
 wiol_flush_data(png_structp png_ptr) {
-  /* XXX : This needs to be added to the io layer */
+  io_glue *ig = png_get_io_ptr(png_ptr);
+  if (!i_io_flush(ig))
+    png_error(png_ptr, "Error flushing output");
 }
 
 static void
@@ -78,6 +93,8 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
   int aspect_only, have_res;
   unsigned char *data;
   unsigned char * volatile vdata = NULL;
+  int bits;
+  int is_bilevel = 0, zero_is_white;
 
   mm_log((1,"i_writepng(im %p ,ig %p)\n", im, ig));
 
@@ -108,13 +125,45 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
 
   channels=im->channels;
 
-  if (channels > 2) { cspace = PNG_COLOR_TYPE_RGB; channels-=3; }
-  else { cspace=PNG_COLOR_TYPE_GRAY; channels--; }
-  
-  if (channels) cspace|=PNG_COLOR_MASK_ALPHA;
-  mm_log((1,"cspace=%d\n",cspace));
+  if (i_img_is_monochrome(im, &zero_is_white)) {
+    is_bilevel = 1;
+    bits = 1;
+    cspace = PNG_COLOR_TYPE_GRAY;
+    mm_log((1, "i_writepng: bilevel output\n"));
+  }
+  else if (im->type == i_palette_type) {
+    int colors = i_colorcount(im);
+
+    cspace = PNG_COLOR_TYPE_PALETTE;
+    bits = 1;
+    while ((1 << bits) < colors) {
+      bits += bits;
+    }
+    mm_log((1, "i_writepng: paletted output\n"));
+  }
+  else {
+    switch (channels) {
+    case 1:
+      cspace = PNG_COLOR_TYPE_GRAY;
+      break;
+    case 2:
+      cspace = PNG_COLOR_TYPE_GRAY_ALPHA;
+      break;
+    case 3:
+      cspace = PNG_COLOR_TYPE_RGB;
+      break;
+    case 4:
+      cspace = PNG_COLOR_TYPE_RGB_ALPHA;
+      break;
+    default:
+      fprintf(stderr, "Internal error, channels = %d\n", channels);
+      abort();
+    }
+    bits = im->bits > 8 ? 16 : 8;
+    mm_log((1, "i_writepng: direct output\n"));
+  }
 
-  channels = im->channels;
+  mm_log((1,"i_writepng: cspace=%d, bits=%d\n",cspace, bits));
 
   /* Create and initialize the png_struct with the desired error handler
    * functions.  If you want to use the default stderr and longjump method,
@@ -164,8 +213,10 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
    */
   png_set_user_limits(png_ptr, width, height);
 
-  png_set_IHDR(png_ptr, info_ptr, width, height, 8, cspace,
+  mm_log((1, ">png_set_IHDR\n"));
+  png_set_IHDR(png_ptr, info_ptr, width, height, bits, cspace,
               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+  mm_log((1, "<png_set_IHDR\n"));
 
   have_res = 1;
   if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) {
@@ -189,14 +240,30 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
                  aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER);
   }
 
-  png_write_info(png_ptr, info_ptr);
-
-  vdata = data = mymalloc(im->xsize * im->channels);
-  for (y = 0; y < height; y++) {
-    i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
-    png_write_row(png_ptr, (png_bytep)data);
+  if (is_bilevel) {
+    if (!write_bilevel(png_ptr, info_ptr, im)) {
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 0;
+    }
+  }
+  else if (im->type == i_palette_type) {
+    if (!write_paletted(png_ptr, info_ptr, im, bits)) {
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 0;
+    }
+  }
+  else if (bits == 16) {
+    if (!write_direct16(png_ptr, info_ptr, im)) {
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 0;
+    }
+  }
+  else {
+    if (!write_direct8(png_ptr, info_ptr, im)) {
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 0;
+    }
   }
-  myfree(data);
 
   png_write_end(png_ptr, info_ptr);
 
@@ -228,12 +295,9 @@ i_readpng_wiol(io_glue *ig) {
   png_infop info_ptr;
   png_uint_32 width, height;
   int bit_depth, color_type, interlace_type;
-  int number_passes,y;
-  int channels,pass;
+  int channels;
   unsigned int sig_read;
   i_png_read_state rs;
-  i_img_dim wmax, hmax;
-  size_t bytes;
 
   rs.warnings = NULL;
   sig_read  = 0;
@@ -385,7 +449,6 @@ static i_img *
 read_direct16(png_structp png_ptr, png_infop info_ptr, int channels,
             i_img_dim width, i_img_dim height) {
   i_img * volatile vim = NULL;
-  int color_type = png_get_color_type(png_ptr, info_ptr);
   i_img_dim x, y;
   int number_passes, pass;
   i_img *im;
@@ -600,6 +663,27 @@ read_paletted(png_structp png_ptr, png_infop info_ptr, int channels,
   return im;
 }
 
+struct png_text_name {
+  const char *keyword;
+  const char *tagname;
+};
+
+static const struct png_text_name
+text_tags[] = {
+  { "Author", "png_author" },
+  { "Comment", "i_comment" },
+  { "Copyright", "png_copyright" },
+  { "Creation Time", "png_creation_time" },
+  { "Description", "png_description" },
+  { "Disclaimer", "png_disclaimer" },
+  { "Software", "png_software" },
+  { "Source", "png_source" },
+  { "Title", "png_title" },
+  { "Warning", "png_warning" }
+};
+
+static const int text_tags_count = sizeof(text_tags) / sizeof(*text_tags);
+
 static void
 get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth) {
   png_uint_32 xres, yres;
@@ -640,6 +724,242 @@ get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth)
   /* the various readers can call png_set_expand(), libpng will make
      it's internal record of bit_depth at least 8 in that case */
   i_tags_setn(&im->tags, "png_bits", bit_depth);
+
+  
+  {
+    int intent;
+    if (png_get_sRGB(png_ptr, info_ptr, &intent)) {
+      i_tags_setn(&im->tags, "png_srgb_intent", intent);
+    }
+  }
+  {
+    double gamma;
+    if (png_get_gAMA(png_ptr, info_ptr, &gamma)) {
+      i_tags_set_float2(&im->tags, "png_gamma", 0, gamma, 4);
+    }
+  }
+  {
+    double white_x, white_y;
+    double red_x, red_y;
+    double green_x, green_y;
+    double blue_x, blue_y;
+    if (png_get_cHRM(png_ptr, info_ptr, &white_x, &white_y,
+                    &red_x, &red_y, &green_x, &green_y,
+                    &blue_x, &blue_y)) {
+      i_tags_set_float2(&im->tags, "png_chroma_white_x", 0, white_x, 4);
+      i_tags_set_float2(&im->tags, "png_chroma_white_y", 0, white_y, 4);
+      i_tags_set_float2(&im->tags, "png_chroma_red_x", 0, red_x, 4);
+      i_tags_set_float2(&im->tags, "png_chroma_red_y", 0, red_y, 4);
+      i_tags_set_float2(&im->tags, "png_chroma_green_x", 0, green_x, 4);
+      i_tags_set_float2(&im->tags, "png_chroma_green_y", 0, green_y, 4);
+      i_tags_set_float2(&im->tags, "png_chroma_blue_x", 0, blue_x, 4);
+      i_tags_set_float2(&im->tags, "png_chroma_blue_y", 0, blue_y, 4);
+    }
+  }
+
+  {
+    int num_text;
+    png_text *text;
+
+    if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
+      int i;
+      for (i = 0; i < num_text; ++i) {
+       int j;
+       char tag_name[50];
+       sprintf(tag_name, "png_text%d_key", i);
+       i_tags_set(&im->tags, tag_name, text[i].key, -1);
+       sprintf(tag_name, "png_text%d_text", i);
+       i_tags_set(&im->tags, tag_name, text[i].text, -1);
+       sprintf(tag_name, "png_text%d_type", i);
+       i_tags_set(&im->tags, tag_name, 
+                  (text[i].compression == PNG_TEXT_COMPRESSION_NONE
+                   || text[i].compression == PNG_TEXT_COMPRESSION_zTXt) ?
+                  "text" : "itxt", -1);
+
+       for (j = 0; j < text_tags_count; ++j) {
+         if (strcmp(text_tags[j].keyword, text[i].key) == 0) {
+           i_tags_set(&im->tags, text_tags[j].tagname, text[i].text, -1);
+           break;
+         }
+       }
+      }
+    }
+  }
+}
+
+static int
+write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im) {
+  unsigned char *data, *volatile vdata = NULL;
+  i_img_dim y;
+
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    if (vdata)
+      myfree(vdata);
+
+    return 0;
+  }
+
+  png_write_info(png_ptr, info_ptr);
+
+  vdata = data = mymalloc(im->xsize * im->channels);
+  for (y = 0; y < im->ysize; y++) {
+    i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
+    png_write_row(png_ptr, (png_bytep)data);
+  }
+  myfree(data);
+
+  return 1;
+}
+
+static int
+write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im) {
+  unsigned *data, *volatile vdata = NULL;
+  unsigned char *tran_data, * volatile vtran_data = NULL;
+  i_img_dim samples_per_row = im->xsize * im->channels;
+  
+  i_img_dim y;
+
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    if (vdata)
+      myfree(vdata);
+    if (vtran_data)
+      myfree(vtran_data);
+
+    return 0;
+  }
+
+  png_write_info(png_ptr, info_ptr);
+
+  vdata = data = mymalloc(samples_per_row * sizeof(unsigned));
+  vtran_data = tran_data = mymalloc(samples_per_row * 2);
+  for (y = 0; y < im->ysize; y++) {
+    i_img_dim i;
+    unsigned char *p = tran_data;
+    i_gsamp_bits(im, 0, im->xsize, y, data, NULL, im->channels, 16);
+    for (i = 0; i < samples_per_row; ++i) {
+      p[0] = data[i] >> 8;
+      p[1] = data[i] & 0xff;
+      p += 2;
+    }
+    png_write_row(png_ptr, (png_bytep)tran_data);
+  }
+  myfree(tran_data);
+  myfree(data);
+
+  return 1;
+}
+
+static int
+write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits) {
+  unsigned char *data, *volatile vdata = NULL;
+  i_img_dim y;
+  unsigned char pal_map[256];
+  png_color pcolors[256];
+  i_color colors[256];
+  int count = i_colorcount(im);
+  int i;
+
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    if (vdata)
+      myfree(vdata);
+
+    return 0;
+  }
+
+  i_getcolors(im, 0, colors, count);
+  if (im->channels < 3) {
+    /* convert the greyscale palette to color */
+    int i;
+    for (i = 0; i < count; ++i) {
+      i_color *c = colors + i;
+      c->channel[3] = c->channel[1];
+      c->channel[2] = c->channel[1] = c->channel[0];
+    }
+  }
+
+  if (i_img_has_alpha(im)) {
+    int i;
+    int bottom_index = 0, top_index = count-1;
+
+    /* fill out the palette map */
+    for (i = 0; i < count; ++i)
+      pal_map[i] = i;
+
+    /* the PNG spec suggests sorting the palette by alpha, but that's
+       unnecessary - all we want to do is move the opaque entries to
+       the end */
+    while (bottom_index < top_index) {
+      if (colors[bottom_index].rgba.a == 255) {
+       pal_map[bottom_index] = top_index;
+       pal_map[top_index--] = bottom_index;
+      }
+      ++bottom_index;
+    }
+  }
+
+  for (i = 0; i < count; ++i) {
+    int srci = i_img_has_alpha(im) ? pal_map[i] : i;
+
+    pcolors[i].red = colors[srci].rgb.r;
+    pcolors[i].green = colors[srci].rgb.g;
+    pcolors[i].blue = colors[srci].rgb.b;
+  }
+
+  png_set_PLTE(png_ptr, info_ptr, pcolors, count);
+
+  if (i_img_has_alpha(im)) {
+    unsigned char trans[256];
+    int i;
+
+    for (i = 0; i < count && colors[pal_map[i]].rgba.a != 255; ++i) {
+      trans[i] = colors[pal_map[i]].rgba.a;
+    }
+    png_set_tRNS(png_ptr, info_ptr, trans, i, NULL);
+  }
+
+  png_write_info(png_ptr, info_ptr);
+
+  png_set_packing(png_ptr);
+
+  vdata = data = mymalloc(im->xsize);
+  for (y = 0; y < im->ysize; y++) {
+    i_gpal(im, 0, im->xsize, y, data);
+    if (i_img_has_alpha(im)) {
+      i_img_dim x;
+      for (x = 0; x < im->xsize; ++x)
+       data[x] = pal_map[data[x]];
+    }
+    png_write_row(png_ptr, (png_bytep)data);
+  }
+  myfree(data);
+
+  return 1;
+}
+
+static int
+write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im) {
+  unsigned char *data, *volatile vdata = NULL;
+  i_img_dim y;
+
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    if (vdata)
+      myfree(vdata);
+
+    return 0;
+  }
+
+  png_write_info(png_ptr, info_ptr);
+
+  png_set_packing(png_ptr);
+
+  vdata = data = mymalloc(im->xsize);
+  for (y = 0; y < im->ysize; y++) {
+    i_gsamp(im, 0, im->xsize, y, data, NULL, 1);
+    png_write_row(png_ptr, (png_bytep)data);
+  }
+  myfree(data);
+
+  return 1;
 }
 
 static void