[simple-scan] Add support for lossy webp image
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [simple-scan] Add support for lossy webp image
- Date: Fri, 26 May 2017 03:29:46 +0000 (UTC)
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]