Index: png.c
===================================================================
RCS file: /cvs/gnome/gimp/plug-ins/common/png.c,v
retrieving revision 1.72
retrieving revision 1.74
diff -u -r1.72 -r1.74
--- png.c	13 Jun 2002 20:32:20 -0000	1.72
+++ png.c	14 Oct 2002 14:52:53 -0000	1.74
@@ -1,5 +1,5 @@
 /*
- * "$Id: png.c,v 1.72 2002/06/13 20:32:20 maurits Exp $"
+ * "$Id: png.c,v 1.74 2002/10/14 14:52:53 bolsh Exp $"
  *
  *   Portable Network Graphics (PNG) plug-in for The GIMP -- an image
  *   manipulation program
@@ -98,14 +98,19 @@
 					   gint32       drawable_ID,
 					   gint32       orig_image_ID);
 
-static void	respin_cmap		  (png_structp  pp,
-					   png_infop    info,
-					   gint32       image_ID);
+static void	respin_cmap		  (png_structp   pp,
+					   png_infop     info,
+                                           guchar       *remap,
+					   gint32        image_ID,
+                                           GimpDrawable *drawable);
 
 static gint	save_dialog               (void);
 static void	save_ok_callback          (GtkWidget   *widget,
 					   gpointer     data);
 
+static int find_unused_ia_colour          (guchar *pixels,
+				           int numpixels,
+				           int* colors);
 
 /*
  * Globals...
@@ -134,6 +139,56 @@
 
 MAIN()
 
+/* Try to find a colour in the palette which isn't actually 
+ * used in the image, so that we can use it as the transparency 
+ * index. Taken from gif.c */
+  
+static int find_unused_ia_colour (guchar *pixels,
+				  int numpixels,
+				  int* colors)
+{
+  int i;
+  gboolean ix_used[256];
+
+  g_print ("PNG: fuiac: Image claims to use %d/256 indices - finding free "
+	   "index...\n", (*colors));
+
+  for (i = 0; i < 256; i++)
+    {
+      ix_used[i] = (gboolean)FALSE;
+    }
+
+  for (i = 0; i < numpixels; i++)
+    {
+      /* If there is no alpha, then the index associated with 
+       * this pixel is taken */
+      if (pixels[i*2+1]) ix_used[pixels[i*2]] = (gboolean)TRUE;
+    }
+  
+  for (i = 255; i >= 0; i--)
+    {
+      if (ix_used[i] == (gboolean)FALSE)
+	{
+	  g_print ("PNG: Found unused colour index %d.\n", i);
+	  return i;
+	}
+    }
+
+  /* Couldn't find an unused colour index within the number of
+     bits per pixel we wanted.  Will have to increment the number
+     of colours in the image and assign a transparent pixel there. */
+  if ((*colors) < 256)
+    {
+      (*colors)++;
+      g_print ("PNG: 2nd pass - Increasing bounds and using colour index %d.\n"
+	       , (int) (*colors)-1);
+      return ((*colors)-1);
+    }
+  
+  g_message (_("PNG: Couldn't simply reduce colors further.\nSaving as opaque.\n"));
+  return (-1);
+}
+
 /*
  * 'query()' - Respond to a plug-in query...
  */
@@ -736,6 +791,8 @@
   time_t	cutime;         /* Time since epoch */
   struct tm	*gmt;		/* GMT broken down */
 
+  guchar remap [256];    /* Re-mapping for the palette */
+
  /*
   * PNG 0.89 and newer have a sane, forwards compatible constructor.
   * Some SGI IRIX users will not have a new enough version though
@@ -795,6 +852,14 @@
   info->bit_depth      = 8;
   info->interlace_type = pngvals.interlaced;
 
+ /* 
+  * Initialise remap[]
+  */
+  for (i = 0; i < 256; i ++)
+    {
+      remap[i] = i;
+    }
+
  /*
   * Set color type and remember bytes per pixel count 
   */
@@ -827,7 +892,7 @@
     case GIMP_INDEXEDA_IMAGE :
 	bpp		 = 2;
 	info->color_type = PNG_COLOR_TYPE_PALETTE;
-	respin_cmap (pp, info, image_ID); /* fix up transparency */
+	respin_cmap (pp, info, remap, image_ID, drawable); /* fix up transparency */
 	break;
     default:
         g_message ("%s\nImage type can't be saved as PNG", filename);
@@ -944,22 +1009,35 @@
 	num = end - begin;
 	
 	gimp_pixel_rgn_get_rect (&pixel_rgn, pixel, 0, begin, drawable->width, num);
-        if (info->valid & PNG_INFO_tRNS) {
-          for (i = 0; i < num; ++i) {
-	    fixed= pixels[i];
-            for (k = 0; k < drawable->width; ++k) {
-              fixed[k] = (fixed[k*2+1] > 127) ? fixed[k*2] + 1 : 0;
-            }
-          }
-       /* Forgot this case before, what if there are too many colors? */
-        } else if (info->valid & PNG_INFO_PLTE && bpp == 2) {
-          for (i = 0; i < num; ++i) {
-	    fixed= pixels[i];
-            for (k = 0; k < drawable->width; ++k) {
-              fixed[k] = fixed[k*2];
-            }
+        if (info->valid & PNG_INFO_tRNS) 
+          {
+            for (i = 0; i < num; ++i) 
+              {
+	        fixed = pixels[i];
+                for (k = 0; k < drawable->width; ++k) 
+                  {
+                    if (PNG_INFO_PLTE)
+                      fixed[k] = remap[fixed[2*k]];
+                    else
+                      fixed[k] = (fixed[k*2+1] > 127) ? 
+                                 fixed[k*2] + 1 : 
+                                 0;
+                     
+                  }
+              }
+            /* Forgot this case before, what if there are too many colors? */
+          } 
+        else if (info->valid & PNG_INFO_PLTE && bpp == 2) 
+          {
+            for (i = 0; i < num; ++i) 
+              {
+	        fixed = pixels[i];
+                for (k = 0; k < drawable->width; ++k) 
+                  {
+                    fixed[k] = fixed[k*2];
+                  }
+              }
           }
-        }
 	
 	png_write_rows (pp, pixels, num);
 	
@@ -995,40 +1073,105 @@
   gtk_widget_destroy (GTK_WIDGET (data));
 }
 
-/* respin_cmap actually reverse fills the colormap, so that empty entries
-   are left at the FRONT, this is only needed to reduce the size of the
-   tRNS chunk when saving GIF-like transparent images with colormaps */
-
-static void respin_cmap (png_structp pp, png_infop info, gint32 image_ID) {
+static void respin_cmap (png_structp pp, 
+                         png_infop info, 
+                         guchar *remap,
+                         gint32 image_ID,
+                         GimpDrawable *drawable) 
+{
   static const guchar trans[] = { 0 };
-  static guchar after[3 * 256];
   gint    colors;
   guchar *before;
+  gint transparent;
+  gint cols, rows;
+  GimpPixelRgn pixel_rgn;
+  guchar *pixels;
 
   before= gimp_image_get_cmap(image_ID, &colors);
 
+  cols = drawable->width;
+  rows = drawable->height;
+
+  gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0,
+                       drawable->width, drawable->height, 
+                       FALSE, FALSE);
+
+  pixels = (guchar *) g_malloc (drawable->width *
+                                drawable->height * 2);
+  
+  gimp_pixel_rgn_get_rect (&pixel_rgn, pixels, 0, 0,
+                           drawable->width, drawable->height);
+
+
+  /* Try to find an entry which isn't actually used in the
+     image, for a transparency index. */
+	  
+  transparent = find_unused_ia_colour(pixels,
+                                      drawable->width * drawable->height,
+                                      &colors);
+
 #if PNG_LIBPNG_VER > 99
-  if (colors < 256) { /* spare space in palette :) */
-    GimpRGB color;
+  if (transparent != -1)  /* we have a winner for a transparent 
+                           * index - do like gif2png and swap 
+                           * index 0 and index transparent */
+    {
+      png_color palette[256];
+      gint i;
+      
+      png_set_tRNS(pp, info, (png_bytep) trans, 1, NULL);
+      g_print ("PNG: Swapping index %d  and index 0 of \"before\" ", 
+          transparent );
+      g_print ("in \"after\".\n");
+
+      g_print ("Old palette:\n");
+      for(i=0; i < colors; i++)
+        g_print("Index %3d: (%3d, %3d, %3d)\n", i, 
+            before[3*i], before[3*i+1], before[3*i+2]);
+
+      /* Transform all pixels with a value = transparent to 
+       * 0 and vice versa to compensate for re-ordering in palette 
+       * due to png_set_tRNS() */
 
-    memcpy(after + 3, before, colors * 3);
+      remap[0] = transparent;
+      remap[transparent] = 0;
+      
+      /* Copy from index 0 to index transparent - 1 to index 1 to 
+       * transparent of after, then from transparent+1 to colors-1 
+       * unchanged, and finally from index transparent to index 0. */
+
+      g_print ("PNG: Setting index %d to transparent.\n", 
+          transparent);
+      g_print ("PNG: Copying from index 0 of \"before\" ");
+      g_print ("to index %d of \"after\".\n", transparent);
+      for(i = 0; i < colors; i++)
+        {
+          palette[i].red = before[3 * remap[i]];
+          palette[i].green = before[3 * remap[i] + 1];
+          palette[i].blue = before[3 * remap[i] + 2];
+        }
+      
+      g_print("Setting a palette of %d colours.\n", colors);
 
-    /* Apps with no natural background will use this instead, see
-       elsewhere for the bKGD chunk being written to use index 0 */
-    gimp_palette_get_background (&color);
-    gimp_rgb_get_uchar (&color, after+0, after+1, after+2);
-
-    /* One transparent palette entry, alpha == 0 */
-    png_set_tRNS(pp, info, (png_bytep) trans, 1, NULL);
-    png_set_PLTE(pp, info, (png_colorp) after, colors + 1);
-  } else {
-    png_set_PLTE(pp, info, (png_colorp) before, colors);
-  }
+      for(i=0; i < colors; i++)
+        g_print("Index %3d: (%3d, %3d, %3d)\n", i, 
+            palette[i].red, palette[i].green, palette[i].blue);
+
+      png_set_PLTE(pp, info, palette, colors);
+    } 
+  else 
+    {
+      /* Inform the user that we couldn't losslessly save the 
+       * transparency & just use the full palette */
+      g_message ( _("Couldn't losslessly save transparency, saving opacity instead.\n"));
+      png_set_PLTE (pp, info, (png_colorp) before, colors);
+    }
 #else
   info->valid	  |= PNG_INFO_PLTE;
   info->palette=     (png_colorp) before;
   info->num_palette= colors;
 #endif /* PNG_LIBPNG_VER > 99 */
+  
+  g_free (pixels);
 
 }
 
