[simple-scan] Add support for lossy webp image



commit 699a4698cfd9492f4ca5bd94ead482f6627f8c1c
Author: Stéphane Fillion <stphanef3724 gmail com>
Date:   Thu May 18 02:13:31 2017 -0400

    Add support for lossy webp image

 README.md           |    2 +-
 meson.build         |    2 +
 src/app-window.vala |   25 +++++++++-
 src/book.vala       |    5 ++
 src/libwebp.vapi    |   55 ++++++++++++++++++++++
 src/libwebpmux.vapi |  128 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/meson.build     |    4 ++
 src/page.vala       |   39 +++++++++++++++
 8 files changed, 256 insertions(+), 4 deletions(-)
---
diff --git a/README.md b/README.md
index 7609fc1..2034383 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ https://launchpad.net/simple-scan
 
 Install the dependencies (on Ubuntu/Debian):
 ```
-$ sudo apt install bzr meson valac libgtk-3-dev libgusb-dev libcolord-dev libpackagekit-glib2-dev 
libsane-dev gettext itstool
+$ sudo apt install bzr meson valac libgtk-3-dev libgusb-dev libcolord-dev libpackagekit-glib2-dev 
libwebp-dev libsane-dev gettext itstool
 ```
 
 Get the source:
diff --git a/meson.build b/meson.build
index cceab24..e7d960f 100644
--- a/meson.build
+++ b/meson.build
@@ -27,6 +27,8 @@ gdk_pixbuf_dep = dependency ('gdk-pixbuf-2.0')
 gusb_dep = dependency ('gusb', version: '>= 0.2.7')
 colord_dep = dependency ('colord', required: false)
 packagekit_dep = dependency ('packagekit-glib2', required: false)
+webp_dep = dependency ('libwebp', required: false)
+webpmux_dep = dependency ('libwebpmux', required: false)
 sane_dep = dependency ('sane-backends')
 msgfmt = find_program  ('msgfmt')
 itstool = find_program  ('itstool')
diff --git a/src/app-window.vala b/src/app-window.vala
index 747edce..16b2241 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -317,6 +317,9 @@ public class AppWindow : Gtk.ApplicationWindow
                                 _("Image Files"));
         filter.add_mime_type ("image/jpeg");
         filter.add_mime_type ("image/png");
+#if HAVE_WEBP
+        filter.add_mime_type ("image/webp");
+#endif
         filter.add_mime_type ("application/pdf");
         save_dialog.add_filter (filter);
         filter = new Gtk.FileFilter ();
@@ -345,12 +348,20 @@ public class AppWindow : Gtk.ApplicationWindow
                              0, _("PNG (lossless)"),
                              1, ".png",
                              -1);
+#if HAVE_WEBP
+        file_type_store.append (out iter);
+        file_type_store.set (iter,
+                             /* Save dialog: Label for sabing in WEBP format */
+                             0, _("WebP (compressed)"),
+                             1, ".webp",
+                             -1);
+#endif
 
         var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
         box.visible = true;
         save_dialog.set_extra_widget (box);
 
-        /* Label in save dialog beside combo box to choose file format (PDF, JPEG, PNG) */
+        /* Label in save dialog beside combo box to choose file format (PDF, JPEG, PNG, WEBP) */
         var label = new Gtk.Label (_("File format:"));
         label.visible = true;
         box.pack_start (label, false, false, 0);
@@ -396,8 +407,8 @@ public class AppWindow : Gtk.ApplicationWindow
             filename = filename + extension;
             save_dialog.set_current_name (filename);
 
-            /* Quality only applicable for JPEG */
-            quality_scale.visible = quality_label.visible = extension == ".jpg";
+            /* Quality not applicable to PNG */
+            quality_scale.visible = quality_label.visible = (extension != ".png");
         });
 
         string? uri = null;
@@ -424,7 +435,11 @@ public class AppWindow : Gtk.ApplicationWindow
             /* Check the file(s) don't already exist */
             var files = new List<File> ();
             var format = uri_to_format (uri);
+#if HAVE_WEBP
+            if (format == "jpeg" || format == "png" || format == "webp")
+#else
             if (format == "jpeg" || format == "png")
+#endif
             {
                 for (var j = 0; j < book.n_pages; j++)
                     files.append (book.make_indexed_file (uri, j));
@@ -474,6 +489,10 @@ public class AppWindow : Gtk.ApplicationWindow
             return "pdf";
         else if (uri_lower.has_suffix (".png"))
             return "png";
+#if HAVE_WEBP
+        else if (uri_lower.has_suffix (".webp"))
+            return "webp";
+#endif
         else
             return "jpeg";
     }
diff --git a/src/book.vala b/src/book.vala
index a6d1b04..1abc0b1 100644
--- a/src/book.vala
+++ b/src/book.vala
@@ -569,11 +569,16 @@ public class Book
         {
         case "jpeg":
         case "png":
+#if HAVE_WEBP
+        case "webp":
+#endif
             save_multi_file (type, quality, file);
             break;
         case "pdf":
             save_pdf (file, quality);
             break;
+        default:
+            throw new FileError.INVAL ("Unknown file type: %s".printf (type));
         }
     }
 }
diff --git a/src/libwebp.vapi b/src/libwebp.vapi
new file mode 100644
index 0000000..74bfdd5
--- /dev/null
+++ b/src/libwebp.vapi
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 Stéphane Fillion
+ * Authors: Stéphane Fillion <stphanef3724 gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace WebP
+{
+    // Returns the size of the compressed data (pointed to by *output), or 0 if
+    // an error occurred. The compressed data must be released by the caller
+    // using the call 'free(*output)'.
+    // These functions compress using the lossy format, and the quality_factor
+    // can go from 0 (smaller output, lower quality) to 100 (best quality,
+    // larger output).
+    [CCode (cheader_filename = "webp/encode.h", cname = "WebPEncodeRGB")]
+    private size_t _encode_rgb ([CCode (array_length = false)] uint8[] rgb,
+                                int width,
+                                int height,
+                                int stride,
+                                float quality_factor,
+                                [CCode (array_length = false)] out uint8[] output);
+    [CCode (cname = "vala_encode_rgb")]
+    public uint8[] encode_rgb (uint8[] rgb, int width, int height, int stride, float quality_factor)
+    {
+        uint8[] output;
+        size_t length;
+        length = _encode_rgb (rgb, width, height, stride, quality_factor, out output);
+        output.length = (int) length;
+        return output;
+    }
+
+    // These functions are the equivalent of the above, but compressing in a
+    // lossless manner. Files are usually larger than lossy format, but will
+    // not suffer any compression loss.
+    [CCode (cheader_filename = "webp/encode.h", cname = "WebPEncodeLosslessRGB")]
+    private size_t _encode_lossless_rgb ([CCode (array_length = false)] uint8[] rgb,
+                                         int width,
+                                         int height,
+                                         int stride,
+                                         [CCode (array_length = false)] out uint8[] output);
+    [CCode (cname = "vala_encode_lossless_rgb")]
+    public uint8[] encode_lossless_rgb (uint8[] rgb, int width, int height, int stride)
+    {
+        uint8[] output;
+        size_t length;
+        length = _encode_lossless_rgb (rgb, width, height, stride, out output);
+        output.length = (int) length;
+        return output;
+    }
+}
diff --git a/src/libwebpmux.vapi b/src/libwebpmux.vapi
new file mode 100644
index 0000000..f2461a2
--- /dev/null
+++ b/src/libwebpmux.vapi
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 Stéphane Fillion
+ * Authors: Stéphane Fillion <stphanef3724 gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace WebP
+{
+    // Error codes
+    [CCode (cheader_filename = "webp/mux.h", cname = "WebPMuxError", cprefix = "WEBP_MUX_", has_type_id = 
false)]
+    public enum MuxError
+    {
+        OK                 =  1,
+        NOT_FOUND          =  0,
+        INVALID_ARGUMENT   = -1,
+        BAD_DATA           = -2,
+        MEMORY_ERROR       = -3,
+        NOT_ENOUGH_DATA    = -4
+    }
+
+    // Data type used to describe 'raw' data, e.g., chunk data
+    // (ICC profile, metadata) and WebP compressed image data.
+    [CCode (cheader_filename = "webp/mux.h", cname = "WebPData", destroy_function = "", has_type_id = false)]
+    private struct Data
+    {
+        [CCode (array_length = false)] unowned uint8[] bytes;
+        size_t size;
+    }
+
+    // main opaque object.
+    [CCode (cheader_filename = "webp/mux.h", cname = "WebPMux", free_function = "WebPMuxDelete")]
+    [Compact]
+    public class Mux
+    {
+        // Creates an empty mux object.
+        // Returns:
+        //   A pointer to the newly created empty mux object.
+        //   Or NULL in case of memory error.
+        [CCode (cname = "WebPMuxNew")]
+        public static Mux? new_mux ();
+
+        // Sets the (non-animated and non-fragmented) image in the mux object.
+        // Note: Any existing images (including frames/fragments) will be removed.
+        // Parameters:
+        //   mux - (in/out) object in which the image is to be set
+        //   bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image
+        //               WebP file (non-animated and non-fragmented)
+        //   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+        //               object and value 0 indicates data will NOT be copied.
+        // Returns:
+        //   WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL.
+        //   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+        //   WEBP_MUX_OK - on success.
+        [CCode (cname = "WebPMuxSetImage")]
+        private MuxError _set_image (Data bitstream, bool copy_data);
+        [CCode (cname = "vala_set_image")]
+        public MuxError set_image (uint8[] bitstream, bool copy_data)
+        {
+                Data data;
+                data.bytes = bitstream;
+                data.size = bitstream.length;
+                return _set_image (data, copy_data);
+        }
+
+        // Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object.
+        // Any existing chunk(s) with the same id will be removed.
+        // Parameters:
+        //   mux - (in/out) object to which the chunk is to be added
+        //   fourcc - (in) a character array containing the fourcc of the given chunk;
+        //                 e.g., "ICCP", "XMP ", "EXIF" etc.
+        //   chunk_data - (in) the chunk data to be added
+        //   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+        //               object and value 0 indicates data will NOT be copied.
+        // Returns:
+        //   WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
+        //                               or if fourcc corresponds to an image chunk.
+        //   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+        //   WEBP_MUX_OK - on success.
+        [CCode (cname = "WebPMuxSetChunk")]
+        private MuxError _set_chunk ([CCode (array_length = false)] uchar[] fourcc,
+                                     Data chunk_data,
+                                     bool copy_data);
+        [CCode (cname = "vala_set_chunk")]
+        public MuxError set_chunk (string fourcc, uint8[] chunk_data, bool copy_data)
+        requires (fourcc.length == 4)
+        {
+            Data data;
+            data.bytes = chunk_data;
+            data.size = chunk_data.length;
+            return _set_chunk ((uchar[]) fourcc, data, copy_data);
+        }
+
+        // Assembles all chunks in WebP RIFF format and returns in 'assembled_data'.
+        // This function also validates the mux object.
+        // Note: The content of 'assembled_data' will be ignored and overwritten.
+        // Also, the content of 'assembled_data' is allocated using malloc(), and NOT
+        // owned by the 'mux' object. It MUST be deallocated by the caller by calling
+        // WebPDataClear(). It's always safe to call WebPDataClear() upon return,
+        // even in case of error.
+        // Parameters:
+        //   mux - (in/out) object whose chunks are to be assembled
+        //   assembled_data - (out) assembled WebP data
+        // Returns:
+        //   WEBP_MUX_BAD_DATA - if mux object is invalid.
+        //   WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL.
+        //   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+        //   WEBP_MUX_OK - on success.
+        [CCode (cname = "WebPMuxAssemble")]
+        private MuxError _assemble (out Data assembled_data);
+        [CCode (cname = "vala_assemble")]
+        public MuxError assemble (out uint8[] assembled_data)
+        {
+            Data data;
+            MuxError mux_error;
+            unowned uint8[] out_array;
+            mux_error = _assemble (out data);
+            out_array = data.bytes;
+            out_array.length = (int) data.size;
+            assembled_data = out_array;
+            return mux_error;
+        }
+    }
+}
diff --git a/src/meson.build b/src/meson.build
index ed09864..f0083f9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,6 +12,10 @@ if packagekit_dep.found ()
     vala_args += [ '-D', 'HAVE_PACKAGEKIT' ]
     dependencies += packagekit_dep
 endif
+if webp_dep.found () and (not colord_dep.found () or webpmux_dep.found ()) # Webpmux only required if colord
+    vala_args += [ '-D', 'HAVE_WEBP' ]
+    dependencies += [ webp_dep, webpmux_dep ]
+endif
 
 simple_scan = executable ('simple-scan',
                           [ 'config.vapi',
diff --git a/src/page.vala b/src/page.vala
index 8936187..47c142f 100644
--- a/src/page.vala
+++ b/src/page.vala
@@ -676,6 +676,45 @@ public class Page
                 keys[2] = null;
             writer.save (image, "png", keys, values);
         }
+#if HAVE_WEBP
+        else if (strcmp (type, "webp") == 0)
+        {
+            var webp_data = WebP.encode_rgb (image.get_pixels (),
+                                             image.get_width (),
+                                             image.get_height (),
+                                             image.get_rowstride (),
+                                             (float) quality);
+#if HAVE_COLORD
+            WebP.MuxError mux_error;
+            var mux = WebP.Mux.new_mux ();
+            uint8[] output;
+
+            mux_error = mux.set_image (webp_data, false);
+            debug ("mux.set_image: %s", mux_error.to_string ());
+
+            if (icc_profile_data != null)
+            {
+                mux_error = mux.set_chunk ("ICCP", icc_profile_data.data, false);
+                debug ("mux.set_chunk: %s", mux_error.to_string ());
+                if (mux_error != WebP.MuxError.OK)
+                    warning ("icc profile data not saved in %s", file.get_basename ());
+            }
+
+            mux_error = mux.assemble (out output);
+            debug ("mux.assemble: %s", mux_error.to_string ());
+            if (mux_error != WebP.MuxError.OK)
+                throw new FileError.FAILED (_("Unable to encode %s").printf (file.get_basename ()));
+
+            stream.write_all (output, null);
+#else
+
+            if (webp_data.length == 0)
+                throw new FileError.FAILED (_("Unable to encode %s").printf (file.get_basename ()));
+
+            stream.write_all (webp_data, null);
+#endif
+        }
+#endif
         else
             throw new FileError.INVAL ("Unknown file type: %s".printf (type));
     }


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