[gdk-pixbuf] Allow saving 1-bit mono TIFFs used in faxes



commit 3cfe0a9e9a8724eb4358410428d3cf47fc983644
Author: Mukund Sivaraman <muks banu com>
Date:   Wed Oct 22 15:51:40 2014 -0400

    Allow saving 1-bit mono TIFFs used in faxes
    
    While working on a GTK+ 3 port of Hildon, I'm upstreaming some patches
    from Maemo's repositories. This one is repurposed from:
    http://maemo.gitorious.org/hildon/gtk/commit/005de8d5
    
      From: Christian Dywan <christian lanedo com>
      Date: Wed, 20 Jan 2010 11:22:29 +0100
      Subject: [PATCH] Fixes: NB#116221 - Sharing TIF image with Size Option
       Large makes the image much larger than the original one when shared
       to OVI or Flickr.
    
    This patch combined other bits which were removed, and the code was
    modified a little.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=578876

 gdk-pixbuf/gdk-pixbuf-io.c |   10 ++-
 gdk-pixbuf/io-tiff.c       |  236 ++++++++++++++++++++++++++++++++++++--------
 2 files changed, 203 insertions(+), 43 deletions(-)
---
diff --git a/gdk-pixbuf/gdk-pixbuf-io.c b/gdk-pixbuf/gdk-pixbuf-io.c
index d1d6cf8..63a0299 100644
--- a/gdk-pixbuf/gdk-pixbuf-io.c
+++ b/gdk-pixbuf/gdk-pixbuf-io.c
@@ -2404,9 +2404,13 @@ gdk_pixbuf_real_save_to_callback (GdkPixbuf         *pixbuf,
  * gdk_pixbuf_save (pixbuf, handle, "png", &error, "icc-profile", contents_encode, NULL);
  * ]|
  *
- * TIFF images recognize a "compression" option which acceps an integer value.
- * Among the codecs are 1 None, 2 Huffman, 5 LZW, 7 JPEG and 8 Deflate, see
- * the libtiff documentation and tiff.h for all supported codec values.
+ * TIFF images recognize: (1) a "bits-per-sample" option (integer) which
+ * can be either 1 for saving bi-level CCITTFAX4 images, or 8 for saving
+ * 8-bits per sample; (2) a "compression" option (integer) which can be
+ * 1 for no compression, 2 for Huffman, 5 for LZW, 7 for JPEG and 8 for
+ * DEFLATE (see the libtiff documentation and tiff.h for all supported
+ * codec values); (3) an "icc-profile" option (zero-terminated string)
+ * containing a base64 encoded ICC color profile.
  *
  * ICO images can be saved in depth 16, 24, or 32, by using the "depth"
  * parameter. When the ICO saver is given "x_hot" and "y_hot" parameters,
diff --git a/gdk-pixbuf/io-tiff.c b/gdk-pixbuf/io-tiff.c
index 262b1fb..d015cde 100644
--- a/gdk-pixbuf/io-tiff.c
+++ b/gdk-pixbuf/io-tiff.c
@@ -8,6 +8,8 @@
  *          Federico Mena-Quintero <federico gimp org>
  *          Jonathan Blandford <jrb redhat com>
  *          S�ren Sandmann <sandmann daimi au dk>
+ *          Christian Dywan <christian lanedo com>
+ *          Mukund Sivaraman <muks banu com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -85,6 +87,7 @@ tiff_image_parse (TIFF *tiff, TiffContext *context, GError **error)
        guchar *pixels = NULL;
        gint width, height, rowstride, bytes;
        GdkPixbuf *pixbuf;
+       guint16 bits_per_sample = 0;
        uint16 orientation = 0;
        uint16 transform = 0;
         uint16 codec;
@@ -173,6 +176,15 @@ tiff_image_parse (TIFF *tiff, TiffContext *context, GError **error)
                 return NULL;
         }
 
+        /* Save the bits per sample as an option since pixbufs are
+           expected to be always 8 bits per sample. */
+        TIFFGetField (tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
+        if (bits_per_sample > 0) {
+                gchar str[5];
+                g_snprintf (str, sizeof (str), "%d", bits_per_sample);
+                gdk_pixbuf_set_option (pixbuf, "bits-per-sample", str);
+        }
+
        /* Set the "orientation" key associated with this image. libtiff 
           orientation handling is odd, so further processing is required
           by higher-level functions based on this tag. If the embedded
@@ -591,6 +603,44 @@ free_save_context (TiffSaveContext *context)
         g_free (context);
 }
 
+static void
+copy_gray_row (gint     *dest,
+               guchar   *src,
+               gint      width,
+               gboolean  has_alpha)
+{
+        gint i;
+        guchar *p;
+
+        p = src;
+        for (i = 0; i < width; i++) {
+                int pr, pg, pb, pv;
+
+                pr = *p++;
+                pg = *p++;
+                pb = *p++;
+
+                if (has_alpha) {
+                        int pa = *p++;
+
+                        /* Premul alpha to simulate it */
+                        if (pa > 0) {
+                                pr = pr * pa / 255;
+                                pg = pg * pa / 255;
+                                pb = pb * pa / 255;
+                        } else {
+                                pr = pg = pb = 0;
+                        }
+                }
+
+                /* Calculate value MAX(MAX(r,g),b) */
+                pv = pr > pg ? pr : pg;
+                pv = pv > pb ? pv : pb;
+
+                *dest++ = pv;
+        }
+}
+
 static gboolean
 gdk_pixbuf__tiff_image_save_to_callback (GdkPixbufSaveFunc   save_func,
                                          gpointer            user_data,
@@ -601,14 +651,16 @@ gdk_pixbuf__tiff_image_save_to_callback (GdkPixbufSaveFunc   save_func,
 {
         TIFF *tiff;
         gint width, height, rowstride;
+        const gchar *bits_per_sample = NULL;
+        long bps;
+        const gchar *compression = NULL;
         guchar *pixels;
         gboolean has_alpha;
         gushort alpha_samples[1] = { EXTRASAMPLE_UNASSALPHA };
         int y;
         TiffSaveContext *context;
         gboolean retval;
-        guchar *icc_profile = NULL;
-        gsize icc_profile_size = 0;
+        const gchar *icc_profile = NULL;
 
         tiff_set_handlers ();
 
@@ -638,9 +690,6 @@ gdk_pixbuf__tiff_image_save_to_callback (GdkPixbufSaveFunc   save_func,
 
         TIFFSetField (tiff, TIFFTAG_IMAGEWIDTH, width);
         TIFFSetField (tiff, TIFFTAG_IMAGELENGTH, height);
-        TIFFSetField (tiff, TIFFTAG_BITSPERSAMPLE, 8);
-        TIFFSetField (tiff, TIFFTAG_SAMPLESPERPIXEL, has_alpha ? 4 : 3);
-        TIFFSetField (tiff, TIFFTAG_ROWSPERSTRIP, height);
 
         /* libtiff supports a number of 'codecs' such as:
            1 None, 2 Huffman, 5 LZW, 7 JPEG, 8 Deflate, see tiff.h */
@@ -648,48 +697,156 @@ gdk_pixbuf__tiff_image_save_to_callback (GdkPixbufSaveFunc   save_func,
             guint i = 0;
 
             while (keys[i]) {
-                if (g_str_equal (keys[i], "compression")) {
-                    guint16 codec = strtol (values[i], NULL, 0);
-                    if (TIFFIsCODECConfigured (codec))
-                        TIFFSetField (tiff, TIFFTAG_COMPRESSION, codec);
-                    else {
-                        g_set_error_literal (error,
-                                             GDK_PIXBUF_ERROR,
-                                             GDK_PIXBUF_ERROR_FAILED,
-                                             _("TIFF compression doesn't refer to a valid codec."));
-                        retval = FALSE;
-                        goto cleanup;
-                    }
-                } else if (g_str_equal (keys[i], "icc-profile")) {
+                    if (g_str_equal (keys[i], "bits-per-sample"))
+                            bits_per_sample = values[i];
+                    else if (g_str_equal (keys[i], "compression"))
+                            compression = values[i];
+                    else if (g_str_equal (keys[i], "icc-profile"))
+                            icc_profile = values[i];
+                   i++;
+            }
+        }
+
+        /* Use 8 bits per sample by default, if none was recorded or
+           specified. */
+        if (!bits_per_sample)
+                bits_per_sample = "8";
+
+        /* Use DEFLATE compression (8) by default, if none was recorded
+           or specified. */
+        if (!compression)
+                compression = "8";
+
+        /* libtiff supports a number of 'codecs' such as:
+           1 None, 2 Huffman, 5 LZW, 7 JPEG, 8 Deflate, see tiff.h */
+        guint16 codec = strtol (compression, NULL, 0);
+
+        if (TIFFIsCODECConfigured (codec))
+                TIFFSetField (tiff, TIFFTAG_COMPRESSION, codec);
+        else {
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_FAILED,
+                                     _("TIFF compression doesn't refer to a valid codec."));
+                retval = FALSE;
+                goto cleanup;
+        }
+
+        /* We support 1-bit or 8-bit saving */
+        bps = atol (bits_per_sample);
+        if (bps == 1) {
+                TIFFSetField (tiff, TIFFTAG_BITSPERSAMPLE, 1);
+                TIFFSetField (tiff, TIFFTAG_SAMPLESPERPIXEL, 1);
+                TIFFSetField (tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
+                TIFFSetField (tiff, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4);
+        } else if (bps == 8) {
+                TIFFSetField (tiff, TIFFTAG_BITSPERSAMPLE, 8);
+                TIFFSetField (tiff, TIFFTAG_SAMPLESPERPIXEL, has_alpha ? 4 : 3);
+                TIFFSetField (tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+
+                if (has_alpha)
+                        TIFFSetField (tiff, TIFFTAG_EXTRASAMPLES, 1, alpha_samples);
+
+                if (icc_profile != NULL) {
+                        guchar *icc_profile_buf;
+                        gsize icc_profile_size;
+
                         /* decode from base64 */
-                        icc_profile = g_base64_decode (values[i], &icc_profile_size);
+                        icc_profile_buf = g_base64_decode (icc_profile, &icc_profile_size);
                         if (icc_profile_size < 127) {
-                            g_set_error (error,
-                                         GDK_PIXBUF_ERROR,
-                                         GDK_PIXBUF_ERROR_BAD_OPTION,
-                                         _("Color profile has invalid length %d."),
-                                         (gint)icc_profile_size);
-                            retval = FALSE;
-                            goto cleanup;
+                                g_set_error (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_BAD_OPTION,
+                                             _("Color profile has invalid length %d."),
+                                             (gint) icc_profile_size);
+                                retval = FALSE;
+                                g_free (icc_profile_buf);
+                                goto cleanup;
                         }
-                }
-                i++;
-            }
-        }
 
-        if (has_alpha)
-                TIFFSetField (tiff, TIFFTAG_EXTRASAMPLES, 1, alpha_samples);
+                        TIFFSetField (tiff, TIFFTAG_ICCPROFILE, icc_profile_size, icc_profile_buf);
+                        g_free (icc_profile_buf);
+                }
+        } else {
+                /* The passed bits-per-sample is not supported. */
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_FAILED,
+                                     _("TIFF bits-per-sample doesn't contain a supported value."));
+                retval = FALSE;
+                goto cleanup;
+         }
 
-        TIFFSetField (tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
-        TIFFSetField (tiff, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);        
+        TIFFSetField (tiff, TIFFTAG_ROWSPERSTRIP, height);
+        TIFFSetField (tiff, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);
         TIFFSetField (tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
 
-        if (icc_profile != NULL)
-                TIFFSetField (tiff, TIFFTAG_ICCPROFILE, icc_profile_size, icc_profile);
+        if (bps == 1) {
+                guchar *mono_row;
+                gint *dith_row_1, *dith_row_2, *dith_row_tmp;
+
+                dith_row_1 = g_new (gint, width);
+                dith_row_2 = g_new (gint, width);
+                mono_row = g_malloc ((width + 7) / 8);
+
+                copy_gray_row (dith_row_1, pixels, width, has_alpha);
+
+                for (y = 0; y < height; y++) {
+                        guint x;
+                        gint *p;
+
+                        memset (mono_row, 0, (width + 7) / 8);
 
-        for (y = 0; y < height; y++) {
-                if (TIFFWriteScanline (tiff, pixels + y * rowstride, y, 0) == -1)
-                        break;
+                        if (y > 0) {
+                                dith_row_tmp = dith_row_1;
+                                dith_row_1 = dith_row_2;
+                                dith_row_2 = dith_row_tmp;
+                        }
+
+                        if (y < (height - 1))
+                                copy_gray_row (dith_row_2, pixels + ((y + 1) * rowstride), width, has_alpha);
+
+                        p = dith_row_1;
+                        for (x = 0; x < width; x++) {
+                                gint p_old, p_new, quant_error;
+
+                                /* Apply Floyd-Steinberg dithering */
+
+                                p_old = *p++;
+
+                                if (p_old > 127)
+                                        p_new = 255;
+                                else
+                                        p_new = 0;
+
+                                quant_error = p_old - p_new;
+                                if (x < (width - 1))
+                                        dith_row_1[x + 1] += 7 * quant_error / 16;
+                                if (y < (height - 1)) {
+                                        if (x > 0)
+                                                dith_row_2[x - 1] += 3 * quant_error / 16;
+
+                                        dith_row_2[x] += 5 * quant_error / 16;
+
+                                        if (x < (width - 1))
+                                                dith_row_2[x + 1] += quant_error / 16;
+                                }
+
+                                if (p_new > 127)
+                                        mono_row[x / 8] |= (0x1 << (7 - (x % 8)));
+                        }
+
+                        if (TIFFWriteScanline (tiff, mono_row, y, 0) == -1)
+                                break;
+                }
+                g_free (mono_row);
+                g_free (dith_row_1);
+                g_free (dith_row_2);
+        } else {
+                for (y = 0; y < height; y++) {
+                        if (TIFFWriteScanline (tiff, pixels + y * rowstride, y, 0) == -1)
+                                break;
+                }
         }
 
         if (y < height) {
@@ -708,7 +865,6 @@ gdk_pixbuf__tiff_image_save_to_callback (GdkPixbufSaveFunc   save_func,
         retval = save_func (context->buffer, context->used, error, user_data);
 
 cleanup:
-        g_free (icc_profile);
         free_save_context (context);
         return retval;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]