/* Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ public abstract class GdkReader : PhotoFileReader { protected GdkReader(string filepath, PhotoFileFormat file_format) { base (filepath, file_format); } 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 { return new Gdk.Pixbuf.from_file(get_filepath()); } public override Gdk.Pixbuf scaled_read(Dimensions full, Dimensions scaled) throws Error { Gdk.Pixbuf result = null; /* if we encounter a situation where there are two orders of magnitude or more of difference between the full image size and the scaled size, and if the full image size has five or more decimal digits of precision, Gdk.Pixbuf.from_file_at_scale( ) can fail due to what appear to be floating-point round-off issues. This isn't surprising, since 32-bit floats only have 6-7 decimal digits of precision in their mantissa. In this case, we prefetch the image at a larger scale and then downsample it to the desired scale as a post-process step. This short-circuits Gdk.Pixbuf's buggy scaling code. */ if (((full.width > 9999) || (full.height > 9999)) && ((scaled.width < 100) || (scaled.height < 100))) { Dimensions prefetch_dimensions = full.get_scaled_by_constraint(1000, ScaleConstraint.DIMENSIONS); result = new Gdk.Pixbuf.from_file_at_scale(get_filepath(), prefetch_dimensions.width, prefetch_dimensions.height, false); result = result.scale_simple(scaled.width, scaled.height, Gdk.InterpType.HYPER); } else { result = new Gdk.Pixbuf.from_file_at_scale(get_filepath(), scaled.width, scaled.height, false); } return result; } } public abstract class GdkSniffer : PhotoFileSniffer { private DetectedPhotoInformation detected = null; private bool size_ready = false; private bool area_prepared = false; protected GdkSniffer(File file, PhotoFileSniffer.Options options) { base (file, options); } public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error { detected = new DetectedPhotoInformation(); Gdk.PixbufLoader pixbuf_loader = new Gdk.PixbufLoader(); pixbuf_loader.size_prepared.connect(on_size_prepared); pixbuf_loader.area_prepared.connect(on_area_prepared); // 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) { // 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); for (;;) { size_t bytes_read = fins.read(buffer, null); if (bytes_read <= 0) break; count += bytes_read; if (calc_md5) md5_checksum.update(buffer, bytes_read); // keep parsing the image until the size is discovered if (!size_ready || !area_prepared) pixbuf_loader.write(buffer[0:bytes_read]); // if not searching for anything else, exit if (!calc_md5 && size_ready && area_prepared) break; } // PixbufLoader throws an error if you close it with an incomplete image, so trap this try { pixbuf_loader.close(); } catch (Error err) { } if (fins != null) fins.close(null); if (calc_md5) detected.md5 = md5_checksum.get_string(); // if size and area are not ready, treat as corrupted file (entire file was read) is_corrupted = !size_ready || !area_prepared; return detected; } private void on_size_prepared(Gdk.PixbufLoader loader, int width, int height) { detected.image_dim = Dimensions(width, height); size_ready = true; } private void on_area_prepared(Gdk.PixbufLoader pixbuf_loader) { Gdk.Pixbuf? pixbuf = pixbuf_loader.get_pixbuf(); if (pixbuf == null) return; detected.colorspace = pixbuf.get_colorspace(); detected.channels = pixbuf.get_n_channels(); detected.bits_per_channel = pixbuf.get_bits_per_sample(); unowned Gdk.PixbufFormat format = pixbuf_loader.get_format(); detected.format_name = format.get_name(); debug("Pixbuf detected format name: %s", detected.format_name); detected.file_format = PhotoFileFormat.from_pixbuf_name(detected.format_name); area_prepared = true; } }