Commit f032a58d authored by Jens Georg's avatar Jens Georg

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
parent 5a0c773e
......@@ -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
......
......@@ -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',
......
......@@ -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() {
......@@ -142,6 +143,9 @@ 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());
}
......
/* 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;
}
}
[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);
}
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);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment