[shotwell] Support reading WEBP



commit f032a58dca391b1833c6ea70785bb3b63abc68c7
Author: Jens Georg <mail jensge org>
Date:   Wed Aug 30 21:46:55 2017 +0200

    Support reading WEBP
    
    https://bugzilla.gnome.org/show_bug.cgi?id=717880
    
    Requires a gexiv2 linked against exiv2 0.26 which currently works in the
    flatpak and on F28, but NOT on Debian/Ubuntu 18.04

 meson.build                     |   3 +
 src/meson.build                 |   3 +-
 src/photos/PhotoFileFormat.vala |  18 ++-
 src/photos/WebPSupport.vala     | 240 ++++++++++++++++++++++++++++++++++++++++
 vapi/libwebp.vapi               |   5 +
 vapi/libwebpdemux.vapi          |  43 +++++++
 6 files changed, 309 insertions(+), 3 deletions(-)
---
diff --git a/meson.build b/meson.build
index 64932421..a4bb64b0 100644
--- a/meson.build
+++ b/meson.build
@@ -57,6 +57,9 @@ libraw = dependency('libraw', version : '>= 0.13.2')
 libexif = dependency('libexif', version : '>= 0.6.16')
 unity = dependency('unity', required : false)
 
+webpdemux = dependency('libwebpdemux')
+webp = dependency('libwebp')
+
 unity_available = false
 if unity.found() and get_option('unity-support')
   unity_available = true
diff --git a/src/meson.build b/src/meson.build
index ed0280de..4f501b42 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -32,7 +32,7 @@ endif
 
 shotwell_deps = [gio, gee, sqlite, gtk, sqlite, posix, gphoto2,
                  gstreamer_pbu, gio_unix, gudev, gexiv2, gmodule,
-                 libraw, libexif, sw_plugin]
+                 libraw, libexif, sw_plugin, webpdemux, webp]
 if unity_available
     shotwell_deps += [unity]
 endif
@@ -76,6 +76,7 @@ executable('shotwell',
             'photos/RawSupport.vala',
             'photos/PngSupport.vala',
             'photos/TiffSupport.vala',
+            'photos/WebPSupport.vala',
             'plugins/Plugins.vala',
             'plugins/StandardHostInterface.vala',
             'plugins/ManifestWidget.vala',
diff --git a/src/photos/PhotoFileFormat.vala b/src/photos/PhotoFileFormat.vala
index e642008e..94ca7521 100644
--- a/src/photos/PhotoFileFormat.vala
+++ b/src/photos/PhotoFileFormat.vala
@@ -58,12 +58,13 @@ public enum PhotoFileFormat {
     TIFF,
     BMP,
     GIF,
+    WEBP,
     UNKNOWN;
     
     // This is currently listed in the order of detection, that is, the file is examined from
     // left to right.  (See PhotoFileInterrogator.)
     public static PhotoFileFormat[] get_supported() {
-        return { JFIF, RAW, PNG, TIFF, BMP, GIF };
+        return { JFIF, RAW, PNG, TIFF, BMP, GIF, WEBP };
     }
     
     public static PhotoFileFormat[] get_writeable() {
@@ -141,7 +142,10 @@ public enum PhotoFileFormat {
 
             case GIF:
                 return 5;
-            
+
+            case WEBP:
+                return 6;
+
             case UNKNOWN:
             default:
                 return -1;
@@ -169,6 +173,9 @@ public enum PhotoFileFormat {
             case 5:
                 return GIF;
                             
+            case 6:
+                return WEBP;
+
             default:
                 return UNKNOWN;
         }
@@ -249,6 +256,10 @@ public enum PhotoFileFormat {
                 Photos.GifFileFormatDriver.init();
                 break;
 
+            case WEBP:
+                Photos.WebpFileFormatDriver.init();
+                break;
+
             default:
                 error("Unsupported file format %s", this.to_string());
         }
@@ -274,6 +285,9 @@ public enum PhotoFileFormat {
             case GIF:
                 return Photos.GifFileFormatDriver.get_instance();
 
+            case WEBP:
+                return Photos.WebpFileFormatDriver.get_instance();
+
             default:
                 error("Unsupported file format %s", this.to_string());
         }
diff --git a/src/photos/WebPSupport.vala b/src/photos/WebPSupport.vala
new file mode 100644
index 00000000..093f196c
--- /dev/null
+++ b/src/photos/WebPSupport.vala
@@ -0,0 +1,240 @@
+/* Copyright 2016 Software Freedom Conservancy Inc.
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+namespace Photos {
+
+public class WebpFileFormatDriver : PhotoFileFormatDriver {
+    private static WebpFileFormatDriver instance = null;
+
+    public static void init() {
+        instance = new WebpFileFormatDriver();
+        WebpFileFormatProperties.init();
+    }
+
+    public static WebpFileFormatDriver get_instance() {
+        return instance;
+    }
+
+    public override PhotoFileFormatProperties get_properties() {
+        return WebpFileFormatProperties.get_instance();
+    }
+
+    public override PhotoFileReader create_reader(string filepath) {
+        return new WebpReader(filepath);
+    }
+
+    public override PhotoMetadata create_metadata() {
+        return new PhotoMetadata();
+    }
+
+    public override bool can_write_image() {
+        return false;
+    }
+
+    public override bool can_write_metadata() {
+        return true;
+    }
+
+    public override PhotoFileWriter? create_writer(string filepath) {
+        return null;
+    }
+
+    public override PhotoFileMetadataWriter? create_metadata_writer(string filepath) {
+        return new WebpMetadataWriter(filepath);
+    }
+
+    public override PhotoFileSniffer create_sniffer(File file, PhotoFileSniffer.Options options) {
+        return new WebpSniffer(file, options);
+    }
+}
+
+private class WebpFileFormatProperties : PhotoFileFormatProperties {
+    private static string[] KNOWN_EXTENSIONS = {
+        "webp"
+    };
+
+    private static string[] KNOWN_MIME_TYPES = {
+        "image/webp"
+    };
+
+    private static WebpFileFormatProperties instance = null;
+
+    public static void init() {
+        instance = new WebpFileFormatProperties();
+    }
+
+    public static WebpFileFormatProperties get_instance() {
+        return instance;
+    }
+
+    public override PhotoFileFormat get_file_format() {
+        return PhotoFileFormat.WEBP;
+    }
+
+    public override PhotoFileFormatFlags get_flags() {
+        return PhotoFileFormatFlags.NONE;
+    }
+
+    public override string get_default_extension() {
+        return "webp";
+    }
+
+    public override string get_user_visible_name() {
+        return _("WebP");
+    }
+
+    public override string[] get_known_extensions() {
+        return KNOWN_EXTENSIONS;
+    }
+
+    public override string get_default_mime_type() {
+        return KNOWN_MIME_TYPES[0];
+    }
+
+    public override string[] get_mime_types() {
+        return KNOWN_MIME_TYPES;
+    }
+}
+
+private class WebpSniffer : PhotoFileSniffer {
+    private DetectedPhotoInformation detected = null;
+
+    public WebpSniffer(File file, PhotoFileSniffer.Options options) {
+        base (file, options);
+        detected = new DetectedPhotoInformation();
+    }
+
+    public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error {
+        is_corrupted = false;
+
+        if (!is_webp(file))
+            return null;
+
+         // valac chokes on the ternary operator here
+        Checksum? md5_checksum = null;
+        if (calc_md5)
+            md5_checksum = new Checksum(ChecksumType.MD5);
+
+        detected.metadata = new PhotoMetadata();
+        try {
+            detected.metadata.read_from_file(file);
+        } catch (Error err) {
+            debug("Failed to load meta-data from file: %s", err.message);
+            // no metadata detected
+            detected.metadata = null;
+        }
+
+        if (calc_md5 && detected.metadata != null) {
+            detected.exif_md5 = detected.metadata.exif_hash();
+            detected.thumbnail_md5 = detected.metadata.thumbnail_hash();
+        }
+
+        // if no MD5, don't read as much, as the needed info will probably be gleaned
+        // in the first 8K to 16K
+        uint8[] buffer = calc_md5 ? new uint8[64 * 1024] : new uint8[8 * 1024];
+        size_t count = 0;
+
+        // loop through until all conditions we're searching for are met
+        FileInputStream fins = file.read(null);
+        var ba = new ByteArray();
+        for (;;) {
+            size_t bytes_read = fins.read(buffer, null);
+            if (bytes_read <= 0)
+                break;
+
+            ba.append(buffer[0:bytes_read]);
+
+            count += bytes_read;
+
+            if (calc_md5)
+                md5_checksum.update(buffer, bytes_read);
+
+            WebP.Data d = WebP.Data();
+            d.bytes = ba.data;
+
+            WebP.ParsingState state;
+            var demux = new WebP.Demuxer.partial(d, out state);
+
+            if (state == WebP.ParsingState.PARSE_ERROR) {
+                is_corrupted = true;
+                break;
+            }
+
+            if (state > WebP.ParsingState.PARSED_HEADER) {
+                detected.file_format = PhotoFileFormat.WEBP;
+                detected.format_name = "WebP";
+                detected.channels = 4;
+                detected.bits_per_channel = 8;
+                detected.image_dim.width = (int) demux.get(WebP.FormatFeature.CANVAS_WIDTH);
+                detected.image_dim.height = (int) demux.get(WebP.FormatFeature.CANVAS_HEIGHT);
+
+                // if not searching for anything else, exit
+                if (!calc_md5)
+                    break;
+            }
+        }
+
+        if (fins != null)
+            fins.close(null);
+
+        if (calc_md5)
+            detected.md5 = md5_checksum.get_string();
+
+        return detected;
+    }
+}
+
+private class WebpReader : PhotoFileReader {
+    public WebpReader(string filepath) {
+        base (filepath, PhotoFileFormat.WEBP);
+    }
+
+    public override PhotoMetadata read_metadata() throws Error {
+        PhotoMetadata metadata = new PhotoMetadata();
+        metadata.read_from_file(get_file());
+
+        return metadata;
+    }
+
+    public override Gdk.Pixbuf unscaled_read() throws Error {
+        uint8[] buffer;
+
+        FileUtils.get_data(this.get_filepath(), out buffer);
+        int width, height;
+        var pixdata = WebP.DecodeRGBA(buffer, out width, out height);
+        pixdata.length = width * height * 4;
+
+        return new Gdk.Pixbuf.from_data(pixdata, Gdk.Colorspace.RGB, true, 8, width, height, width * 4);
+    }
+}
+
+private class WebpMetadataWriter : PhotoFileMetadataWriter {
+    public WebpMetadataWriter(string filepath) {
+        base (filepath, PhotoFileFormat.TIFF);
+    }
+
+    public override void write_metadata(PhotoMetadata metadata) throws Error {
+        metadata.write_to_file(get_file());
+    }
+}
+
+public bool is_webp(File file, Cancellable? cancellable = null) throws Error {
+    var ins = file.read();
+
+    uint8 buffer[12];
+    try {
+        ins.read(buffer, null);
+        if (buffer[0] == 'R' && buffer[1] == 'I' && buffer[2] == 'F' && buffer[3] == 'F' &&
+            buffer[8] == 'W' && buffer[9] == 'E' && buffer[10] == 'B' && buffer[11] == 'P')
+            return true;
+    } catch (Error error) {
+        debug ("Failed to read from file %s: %s", file.get_path (), error.message);
+    }
+
+    return false;
+}
+
+}
diff --git a/vapi/libwebp.vapi b/vapi/libwebp.vapi
new file mode 100644
index 00000000..a19fbcf2
--- /dev/null
+++ b/vapi/libwebp.vapi
@@ -0,0 +1,5 @@
+[CCode (cheader_filename = "webp/decode.h")]
+namespace WebP {
+    [CCode (array_length = false, cname="WebPDecodeRGBA")]
+    public static uint8[] DecodeRGBA([CCode (array_length_pos=1)]uint8[] data, out int width, out int 
height);
+}
diff --git a/vapi/libwebpdemux.vapi b/vapi/libwebpdemux.vapi
new file mode 100644
index 00000000..7612b427
--- /dev/null
+++ b/vapi/libwebpdemux.vapi
@@ -0,0 +1,43 @@
+namespace WebP {
+    [CCode (has_type_id = false)]
+    public struct Data {
+        [CCode (array_length_cname = "size")]
+        public unowned uint8[] bytes;
+
+        public size_t size;
+
+        [CCode (cname = "WebPDataClear")]
+        public void clear();
+    }
+
+    [CCode (cprefix = "WEBP_DEMUX_", cname = "WebPDemuxState")]
+    public enum ParsingState {
+        PARSE_ERROR,
+        PARSING_HEADER,
+        PARSED_HEADER,
+        DONE
+    }
+
+    [CCode (cprefix = "WEBP_FF_")]
+    public enum FormatFeature {
+        FORMAT_FLAGS,
+        CANVAS_WIDTH,
+        CANVAS_HEIGHT,
+        LOOP_COUNT,
+        BACKGROUND_COLOR,
+        FRAME_COUNT
+    }
+
+    [Compact]
+    [CCode (free_function = "WebPDemuxDelete", cname = "WebPDemuxer", cheader_filename = "webp/demux.h", 
has_type_id = false)]
+    public class Demuxer {
+        [CCode (cname="WebPDemux")]
+        public Demuxer(Data data);
+
+        [CCode (cname="WebPDemuxPartial")]
+        public Demuxer.partial(Data data, out ParsingState state);
+
+        [CCode (cname="WebPDemuxGetI")]
+        public uint32 get(FormatFeature feature);
+    }
+}


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