Commit 2be1e084 authored by Jim Nelson's avatar Jim Nelson

Moved PhotoTransformer into TransformablePhoto, which is now properly in the...

Moved PhotoTransformer into TransformablePhoto, which is now properly in the inheritance tree.  Also prepped 
PhotoPage for using TransformablePhoto rather than Photo (which is now LibraryPhoto), so it can be reused for 
direct edit mode.
parent deee14d6
......@@ -47,8 +47,7 @@ SRC_FILES = \
EditingTools.vala \
Queryable.vala \
LibraryWindow.vala \
CameraTable.vala \
PhotoTransformer.vala
CameraTable.vala
VAPI_FILES = \
libexif.vapi \
......
......@@ -16,8 +16,8 @@ public abstract class BatchImportJob {
public class BatchImport {
public const int IMPORT_DIRECTORY_DEPTH = 3;
private class DateComparator : Comparator<Photo> {
public override int64 compare(Photo photo_a, Photo photo_b) {
private class DateComparator : Comparator<LibraryPhoto> {
public override int64 compare(LibraryPhoto photo_a, LibraryPhoto photo_b) {
return photo_a.get_exposure_time() - photo_b.get_exposure_time();
}
}
......@@ -120,7 +120,7 @@ public class BatchImport {
private string name;
private uint64 total_bytes;
private BatchImport ref_holder = null;
private SortedList<Photo> success = null;
private SortedList<LibraryPhoto> success = null;
private Gee.ArrayList<string> failed = null;
private Gee.ArrayList<string> skipped = null;
private ImportID import_id = ImportID();
......@@ -144,13 +144,13 @@ public class BatchImport {
public signal void starting();
// Called for each Photo imported to the system
public signal void imported(Photo photo);
public signal void imported(LibraryPhoto photo);
// Called when a job fails. import_complete will also be called at the end of the batch
public signal void import_job_failed(ImportResult result, BatchImportJob job, File? file);
// Called at the end of the batched jobs; this will be signalled exactly once for the batch
public signal void import_complete(ImportID import_id, SortedList<Photo> photos_by_date,
public signal void import_complete(ImportID import_id, SortedList<LibraryPhoto> photos_by_date,
Gee.ArrayList<string> failed, Gee.ArrayList<string> skipped);
public string get_name() {
......@@ -179,7 +179,7 @@ public class BatchImport {
private bool perform_import() {
starting();
success = new SortedList<Photo>(new DateComparator());
success = new SortedList<LibraryPhoto>(new DateComparator());
failed = new Gee.ArrayList<string>();
skipped = new Gee.ArrayList<string>();
import_id = (new PhotoTable()).generate_import_id();
......@@ -324,8 +324,8 @@ public class BatchImport {
import = copied;
}
Photo photo;
ImportResult result = Photo.import(import, import_id, out photo);
LibraryPhoto photo;
ImportResult result = LibraryPhoto.import(import, import_id, out photo);
if (result != ImportResult.SUCCESS) {
if (copy_to_library) {
try {
......
......@@ -55,7 +55,7 @@ class SlideshowPage : SinglePhotoPage {
public override void switched_to() {
base.switched_to();
set_pixbuf(thumbnail.get_photo().get_pixbuf(PhotoTransformer.SCREEN));
set_pixbuf(thumbnail.get_photo().get_pixbuf(TransformablePhoto.SCREEN));
Timeout.add(CHECK_ADVANCE_MSEC, auto_advance);
timer.start();
......@@ -91,7 +91,7 @@ class SlideshowPage : SinglePhotoPage {
private void on_next_automatic() {
thumbnail = (Thumbnail) controller.get_next_item(thumbnail);
set_pixbuf(thumbnail.get_photo().get_pixbuf(PhotoTransformer.SCREEN));
set_pixbuf(thumbnail.get_photo().get_pixbuf(TransformablePhoto.SCREEN));
// reset the timer
timer.start();
......@@ -105,7 +105,7 @@ class SlideshowPage : SinglePhotoPage {
this.thumbnail = thumbnail;
// start with blown-up preview
set_pixbuf(thumbnail.get_photo().get_preview_pixbuf(PhotoTransformer.SCREEN));
set_pixbuf(thumbnail.get_photo().get_preview_pixbuf(TransformablePhoto.SCREEN));
// schedule improvement to real photo
Idle.add(on_improvement);
......@@ -115,7 +115,7 @@ class SlideshowPage : SinglePhotoPage {
}
private bool on_improvement() {
set_pixbuf(thumbnail.get_photo().get_pixbuf(PhotoTransformer.SCREEN));
set_pixbuf(thumbnail.get_photo().get_pixbuf(TransformablePhoto.SCREEN));
return false;
}
......@@ -164,7 +164,7 @@ class SlideshowPage : SinglePhotoPage {
}
public override Gee.Iterable<Queryable>? get_queryables() {
Gee.ArrayList<Photo> photo_array_list = new Gee.ArrayList<Photo>();
Gee.ArrayList<LibraryPhoto> photo_array_list = new Gee.ArrayList<LibraryPhoto>();
photo_array_list.add(thumbnail.get_photo());
return photo_array_list;
}
......@@ -466,7 +466,7 @@ public class CollectionPage : CheckerboardPage {
// files first
Gdk.Pixbuf icon = null;
foreach (LayoutItem item in get_selected()) {
Photo photo = ((Thumbnail) item).get_photo();
LibraryPhoto photo = ((Thumbnail) item).get_photo();
File file = null;
try {
......@@ -513,10 +513,14 @@ public class CollectionPage : CheckerboardPage {
drag_items.clear();
foreach (LayoutItem item in get_selected()) {
((Thumbnail) item).get_photo().export_failed();
}
return false;
}
public void add_photo(Photo photo) {
public void add_photo(LibraryPhoto photo) {
// search for duplicates
if (get_thumbnail_for_photo(photo) != null)
return;
......@@ -532,7 +536,7 @@ public class CollectionPage : CheckerboardPage {
slideshow_button.sensitive = true;
}
private void on_photo_removed(Photo photo) {
private void on_photo_removed(LibraryPhoto photo) {
Thumbnail found = get_thumbnail_for_photo(photo);
if (found != null)
remove_item(found);
......@@ -540,7 +544,7 @@ public class CollectionPage : CheckerboardPage {
slideshow_button.sensitive = (get_count() > 0);
}
private void on_thumbnail_altered(Photo photo) {
private void on_thumbnail_altered(LibraryPhoto photo) {
// the thumbnail is only going to reload a low-quality interp, so schedule improval
schedule_thumbnail_improval();
......@@ -549,7 +553,7 @@ public class CollectionPage : CheckerboardPage {
refresh();
}
private Thumbnail? get_thumbnail_for_photo(Photo photo) {
private Thumbnail? get_thumbnail_for_photo(LibraryPhoto photo) {
foreach (LayoutItem item in get_items()) {
Thumbnail thumbnail = (Thumbnail) item;
if (thumbnail.get_photo().equals(photo))
......@@ -639,7 +643,7 @@ public class CollectionPage : CheckerboardPage {
}
private void on_export() {
Gee.ArrayList<Photo> export_list = new Gee.ArrayList<Photo>();
Gee.ArrayList<LibraryPhoto> export_list = new Gee.ArrayList<LibraryPhoto>();
foreach (LayoutItem item in get_selected())
export_list.add(((Thumbnail) item).get_photo());
......@@ -656,7 +660,7 @@ public class CollectionPage : CheckerboardPage {
// handle the single-photo case
if (export_list.size == 1) {
Photo photo = export_list.get(0);
LibraryPhoto photo = export_list.get(0);
File save_as = ExportUI.choose_file(photo.get_file());
if (save_as == null)
......@@ -681,7 +685,7 @@ public class CollectionPage : CheckerboardPage {
AppWindow.get_instance().set_busy_cursor();
foreach (Photo photo in export_list) {
foreach (LibraryPhoto photo in export_list) {
File save_as = export_dir.get_child(photo.get_file().get_basename());
if (save_as.query_exists(null)) {
if (!ExportUI.query_overwrite(save_as))
......@@ -712,7 +716,7 @@ public class CollectionPage : CheckerboardPage {
private bool can_revert_selected() {
foreach (LayoutItem item in get_selected()) {
Photo photo = ((Thumbnail) item).get_photo();
LibraryPhoto photo = ((Thumbnail) item).get_photo();
if (photo.has_transformations())
return true;
}
......@@ -766,7 +770,7 @@ public class CollectionPage : CheckerboardPage {
// in on_photo_removed being called, which we don't want in this case is because it will
// remove from the list while iterating, so disconnect the signals and do the work here
foreach (LayoutItem item in get_selected()) {
Photo photo = ((Thumbnail) item).get_photo();
LibraryPhoto photo = ((Thumbnail) item).get_photo();
photo.removed -= on_photo_removed;
photo.thumbnail_altered -= on_thumbnail_altered;
......@@ -782,7 +786,7 @@ public class CollectionPage : CheckerboardPage {
private void do_rotations(Gee.Iterable<LayoutItem> c, Rotation rotation) {
bool rotation_performed = false;
foreach (LayoutItem item in c) {
Photo photo = ((Thumbnail) item).get_photo();
LibraryPhoto photo = ((Thumbnail) item).get_photo();
photo.rotate(rotation);
rotation_performed = true;
......@@ -808,7 +812,7 @@ public class CollectionPage : CheckerboardPage {
private void on_revert() {
bool revert_performed = false;
foreach (LayoutItem item in get_selected()) {
Photo photo = ((Thumbnail) item).get_photo();
LibraryPhoto photo = ((Thumbnail) item).get_photo();
photo.remove_all_transformations();
revert_performed = true;
......
......@@ -92,18 +92,18 @@ public bool verify_databases(out string app_version) {
// verify photo table
foreach (PhotoID photo_id in photo_ids) {
Photo photo = Photo.fetch(photo_id);
LibraryPhoto photo = LibraryPhoto.fetch(photo_id);
switch (photo.check_currency()) {
case Photo.Currency.CURRENT:
case LibraryPhoto.Currency.CURRENT:
// do nothing
break;
case Photo.Currency.DIRTY:
case LibraryPhoto.Currency.DIRTY:
message("Time or filesize changed on %s, reimporting ...", photo.to_string());
photo.update();
break;
case Photo.Currency.GONE:
case LibraryPhoto.Currency.GONE:
message("Unable to locate %s: Removing from photo library", photo.to_string());
photo.remove(true);
break;
......
......@@ -82,13 +82,13 @@ public abstract class EditingToolWindow : Gtk.Window {
public abstract class PhotoCanvas {
private Gtk.Window container;
private Gdk.Window drawing_window;
private Photo photo;
private TransformablePhoto photo;
private Gdk.GC default_gc;
private Gdk.Drawable drawable;
private Gdk.Pixbuf scaled;
private Gdk.Rectangle scaled_position;
public PhotoCanvas(Gtk.Window container, Gdk.Window drawing_window, Photo photo,
public PhotoCanvas(Gtk.Window container, Gdk.Window drawing_window, TransformablePhoto photo,
Gdk.GC default_gc, Gdk.Drawable drawable, Gdk.Pixbuf scaled, Gdk.Rectangle scaled_position) {
this.container = container;
this.drawing_window = drawing_window;
......@@ -170,7 +170,7 @@ public abstract class PhotoCanvas {
return active_rect;
}
public Photo get_photo() {
public TransformablePhoto get_photo() {
return photo;
}
......@@ -359,7 +359,7 @@ public abstract class EditingTool {
//
// Note this this method doesn't need to be returning the "proper" pixbuf on-the-fly (i.e.
// a pixbuf with unsaved tool edits in it). That can be handled in the paint() virtual method.
public virtual Gdk.Pixbuf? get_unscaled_pixbuf(Photo photo) {
public virtual Gdk.Pixbuf? get_display_pixbuf(TransformablePhoto photo) {
return null;
}
......@@ -489,11 +489,11 @@ public class CropTool : EditingTool {
return crop_tool_window;
}
public override Gdk.Pixbuf? get_unscaled_pixbuf(Photo photo) {
public override Gdk.Pixbuf? get_display_pixbuf(TransformablePhoto photo) {
// show the uncropped photo for editing, but return null if no crop so the current pixbuf
// is used
return photo.has_crop() ? photo.get_pixbuf(PhotoTransformer.SCREEN,
PhotoTransformer.Exception.CROP) : null;
return photo.has_crop() ? photo.get_pixbuf(TransformablePhoto.SCREEN,
TransformablePhoto.Exception.CROP) : null;
}
private void prepare_gc(Gdk.GC default_gc, Gdk.Drawable drawable) {
......@@ -1435,8 +1435,8 @@ public class AdjustTool : EditingTool {
canvas.paint_pixbuf(draw_to_pixbuf);
}
public override Gdk.Pixbuf? get_unscaled_pixbuf(Photo photo) {
return photo.get_pixbuf(PhotoTransformer.SCREEN, PhotoTransformer.Exception.ADJUST);
public override Gdk.Pixbuf? get_display_pixbuf(TransformablePhoto photo) {
return photo.get_pixbuf(TransformablePhoto.SCREEN, TransformablePhoto.Exception.ADJUST);
}
......
......@@ -5,7 +5,6 @@
*/
public class DirectoryItem : LayoutItem, EventSource {
public const Gdk.InterpType INTERP = Gdk.InterpType.BILINEAR;
public const int SCALE =
ThumbnailCache.MEDIUM_SCALE + ((ThumbnailCache.BIG_SCALE - ThumbnailCache.MEDIUM_SCALE) / 2);
......@@ -19,8 +18,8 @@ public class DirectoryItem : LayoutItem, EventSource {
PhotoID photo_id = event_table.get_primary_photo(event_id);
assert(photo_id.is_valid());
Photo photo = Photo.fetch(photo_id);
Gdk.Pixbuf pixbuf = photo.get_preview_pixbuf(SCALE, INTERP);
LibraryPhoto photo = LibraryPhoto.fetch(photo_id);
Gdk.Pixbuf pixbuf = photo.get_preview_pixbuf(SCALE);
set_image(pixbuf);
}
......@@ -35,9 +34,9 @@ public class DirectoryItem : LayoutItem, EventSource {
public Gee.Iterable<PhotoSource> get_photos() {
Gee.ArrayList<PhotoID?> photo_ids = (new PhotoTable()).get_event_photos(event_id);
Gee.ArrayList<Photo> photos = new Gee.ArrayList<Photo>();
Gee.ArrayList<LibraryPhoto> photos = new Gee.ArrayList<LibraryPhoto>();
foreach (PhotoID photo_id in photo_ids) {
photos.add(Photo.fetch(photo_id));
photos.add(LibraryPhoto.fetch(photo_id));
}
return photos;
}
......
......@@ -60,8 +60,8 @@ class ImportPreview : LayoutItem, PhotoSource {
return dimensions;
}
public Exif.Data get_exif() {
return exif;
public Exif.Data? get_exif() {
return exif;
}
}
......@@ -838,8 +838,8 @@ public class ImportQueuePage : SinglePhotoPage {
current_batch = batch_import;
}
private void on_imported(Photo photo) {
set_pixbuf(photo.get_pixbuf(PhotoTransformer.SCREEN));
private void on_imported(LibraryPhoto photo) {
set_pixbuf(photo.get_pixbuf(TransformablePhoto.SCREEN));
progress_bytes += photo.get_filesize();
double pct = (progress_bytes <= total_bytes) ? (double) progress_bytes / (double) total_bytes
......@@ -850,7 +850,7 @@ public class ImportQueuePage : SinglePhotoPage {
}
private void on_import_complete(BatchImport batch_import, ImportID import_id,
SortedList<Photo> imported, Gee.ArrayList<string> failed, Gee.ArrayList<string> skipped) {
SortedList<LibraryPhoto> imported, Gee.ArrayList<string> failed, Gee.ArrayList<string> skipped) {
assert(batch_import == current_batch);
current_batch = null;
......
......@@ -127,10 +127,10 @@ public class LibraryWindow : AppWindow {
add_parent_page(events_directory_page);
add_orphan_page(photo_page);
// create Photo objects for all photos in the database and load into the Photos page
// create LibraryPhoto objects for all photos in the database and load into the Photos page
Gee.ArrayList<PhotoID?> photo_ids = photo_table.get_photos();
foreach (PhotoID photo_id in photo_ids) {
Photo photo = Photo.fetch(photo_id);
LibraryPhoto photo = LibraryPhoto.fetch(photo_id);
photo.removed += on_photo_removed;
collection_page.add_photo(photo);
......@@ -364,7 +364,7 @@ public class LibraryWindow : AppWindow {
}
}
public void photo_imported(Photo photo) {
public void photo_imported(LibraryPhoto photo) {
// want to know when it's removed from the system for cleanup
photo.removed += on_photo_removed;
......@@ -373,7 +373,7 @@ public class LibraryWindow : AppWindow {
collection_page.refresh();
}
public void batch_import_complete(SortedList<Photo> imported_photos) {
public void batch_import_complete(SortedList<LibraryPhoto> imported_photos) {
debug("Processing imported photos to create events ...");
// walk through photos, splitting into events based on criteria
......@@ -381,7 +381,7 @@ public class LibraryWindow : AppWindow {
time_t current_event_start = 0;
EventID current_event_id = EventID();
EventPage current_event_page = null;
foreach (Photo photo in imported_photos) {
foreach (LibraryPhoto photo in imported_photos) {
time_t exposure_time = photo.get_exposure_time();
if (exposure_time == 0) {
......@@ -448,7 +448,7 @@ public class LibraryWindow : AppWindow {
}
}
private void on_photo_removed(Photo photo) {
private void on_photo_removed(LibraryPhoto photo) {
PhotoID photo_id = photo.get_photo_id();
// update event's primary photo if this is the one; remove event if no more photos in it
......@@ -573,7 +573,7 @@ public class LibraryWindow : AppWindow {
Gee.ArrayList<PhotoID?> photo_ids = photo_table.get_event_photos(event_id);
foreach (PhotoID photo_id in photo_ids)
event_page.add_photo(Photo.fetch(photo_id));
event_page.add_photo(LibraryPhoto.fetch(photo_id));
insert_child_page_sorted(events_directory_page.get_marker(), event_page,
new CompareEventPage(get_events_sort()));
......
......@@ -4,6 +4,556 @@
* See the COPYING file in this distribution.
*/
// PhotoBase is the base class for all objects which represent, in some form or another, an image
// or photo.
public abstract class PhotoBase : Object, Queryable, PhotoSource {
public PhotoBase() {
}
// Queryable
public abstract string get_name();
// PhotoSource
public abstract time_t get_exposure_time();
public abstract Dimensions get_dimensions();
public abstract uint64 get_filesize();
public abstract Exif.Data? get_exif();
}
// TransformablePhoto is an abstract class that allows for applying transformations on-the-fly to a
// particular photo without modifying the backing image file. The interface allows for
// transformations to be stored persistently elsewhere or in memory until they're commited en
// masse to an image file.
public abstract class TransformablePhoto: PhotoBase {
public const int UNSCALED = 0;
public const int SCREEN = -1;
public const Gdk.InterpType DEFAULT_INTERP = Gdk.InterpType.HYPER;
public const Jpeg.Quality EXPORT_JPEG_QUALITY = Jpeg.Quality.HIGH;
public const Gdk.InterpType EXPORT_INTERP = Gdk.InterpType.HYPER;
public enum Exception {
NONE = 0,
ORIENTATION = 1 << 0,
CROP = 1 << 1,
REDEYE = 1 << 2,
ADJUST = 1 << 3,
ALL = 0xFFFFFFFF
}
private static TransformablePhoto cached_raw_instance = null;
private static Gdk.Pixbuf cached_raw = null;
private File file;
// the subclass is responsible for firing this signal whenever a transformation is added
// or removed
public signal void altered();
public TransformablePhoto(File file) {
this.file = file;
}
public File get_file() {
return file;
}
// Transformation storage and exporting
public abstract Dimensions get_raw_dimensions();
public abstract bool has_transformations();
public abstract void remove_all_transformations();
public abstract Orientation get_orientation();
public abstract void set_orientation(Orientation orientation);
public virtual void rotate(Rotation rotation) {
Orientation orientation = get_orientation();
orientation = orientation.perform(rotation);
set_orientation(orientation);
}
public virtual bool has_crop() {
Box crop;
return get_raw_crop(out crop);
}
// This returns the crop against the coordinate system of the unrotated photo.
public abstract bool get_raw_crop(out Box crop);
// This sets the crop against the coordinate system of the unrotated photo.
public abstract void set_raw_crop(Box crop);
public abstract float get_color_adjustment(ColorTransformationKind kind);
public abstract void set_color_adjustments(Gee.ArrayList<ColorTransformationInstance?> adjustments);
// All instances are against the coordinate system of the unrotated photo.
public abstract void add_raw_redeye_instance(RedeyeInstance redeye);
// All instances are against the coordinate system of the unscaled, unrotated photo.
public abstract RedeyeInstance[] get_raw_redeye_instances();
// Returns a File that can be used for exporting ... this file should persist for a reasonable
// amount of time, as drag-and-drop exports can conclude long after the DnD source has seen
// the end of the transaction. ... However, if failure is detected, export_failed() will be
// called, and the file can be removed if necessary.
public abstract File generate_exportable() throws Error;
// Called when an export has failed; the object can use this to delete the exportable file
// from generate_exportable() if necessary
public abstract void export_failed();
// Pixbuf generation
// Returns a raw, untransformed, unrotated, unscaled pixbuf from the source
public Gdk.Pixbuf get_raw_pixbuf() throws Error {
return new Gdk.Pixbuf.from_file(file.get_path());
}
// Converts a scale parameter for get_pixbuf or get_preview_pixbuf into an actual pixel
// count to proportionally scale to. Returns 0 if an unscaled pixbuf is specified.
public static int scale_to_pixels(int scale) {
return (scale == SCREEN) ? get_screen_scale() : scale;
}
// A preview pixbuf is one that can be quickly generated and scaled as a preview while the
// fully transformed pixbuf is built. It is fully transformed.
//
// Note that scale may be UNSCALED or SCREEN. UNSCALED is not
// considered a performance-killer for this method, although the quality of the pixbuf may be
// quite poor compared to the actual unscaled transformed pixbuf.
public abstract Gdk.Pixbuf get_preview_pixbuf(int scale) throws Error;
// Returns a fully transformed (and scaled, if specified) pixbuf from the source.
// Transformations may be excluded via the mask.
//
// Set scale to UNSCALED for unscaled pixbuf or SCREEN for a pixbuf scaled to the screen size
// (which can be scaled further, with some loss). Note that UNSCALED can be extremely expensive,
// and it's far better to specify an appropriate scale.
public Gdk.Pixbuf get_pixbuf(int scale, Exception exceptions = Exception.NONE,
Gdk.InterpType interp = DEFAULT_INTERP) throws Error {
#if MEASURE_PIPELINE
Timer timer = new Timer();
Timer total_timer = new Timer();
double load_and_decode_time = 0.0, pixbuf_copy_time = 0.0, redeye_time = 0.0,
adjustment_time = 0.0, crop_time = 0.0, orientation_time = 0.0, scale_time = 0.0;
total_timer.start();
#endif
Gdk.Pixbuf pixbuf = null;
//
// Image load-and-decode
//
if (cached_raw != null && cached_raw_instance == this) {
// used the cached raw pixbuf for this instance, which is merely the decoded pixbuf
// (no transformations)
#if MEASURE_PIPELINE
timer.start();
#endif
pixbuf = cached_raw.copy();
#if MEASURE_PIPELINE
pixbuf_copy_time = timer.elapsed();
#endif
} else {
#if MEASURE_PIPELINE
timer.start();
#endif
pixbuf = get_raw_pixbuf();
#if MEASURE_PIPELINE
load_and_decode_time = timer.elapsed();
#endif
// stash for next time
#if MEASURE_PIPELINE
timer.start();
#endif
cached_raw = pixbuf.copy();
#if MEASURE_PIPELINE
pixbuf_copy_time = timer.elapsed();
#endif
cached_raw_instance = this;
}
//
// Image transformation pipeline
//
// redeye reduction
if ((exceptions & Exception.REDEYE) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
RedeyeInstance[] redeye_instances = get_raw_redeye_instances();
foreach (RedeyeInstance instance in redeye_instances)
pixbuf = do_redeye(pixbuf, instance);
#if MEASURE_PIPELINE
redeye_time = timer.elapsed();
#endif
}
// crop
if ((exceptions & Exception.CROP) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
Box crop;
if (get_raw_crop(out crop))
pixbuf = new Gdk.Pixbuf.subpixbuf(pixbuf, crop.left, crop.top, crop.get_width(),
crop.get_height());
#if MEASURE_PIPELINE
crop_time = timer.elapsed();
#endif
}
// scale
int scale_pixels = scale_to_pixels(scale);
if (scale_pixels > 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
pixbuf = scale_pixbuf(pixbuf, scale_pixels, interp);
#if MEASURE_PIPELINE
scale_time = timer.elapsed();
#endif
}
// color adjustment
if ((exceptions & Exception.ADJUST) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
ColorTransformation composite_transform = get_composite_transformation();
if (!composite_transform.is_identity())
ColorTransformation.transform_pixbuf(composite_transform, pixbuf);
#if MEASURE_PIPELINE
adjustment_time = timer.elapsed();
#endif
}
// orientation (all modifications are stored in unrotated coordinate system)
if ((exceptions & Exception.ORIENTATION) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
pixbuf = get_orientation().rotate_pixbuf(pixbuf);
#if MEASURE_PIPELINE
orientation_time = timer.elapsed();
#endif
}
#if MEASURE_PIPELINE
double total_time = total_timer.elapsed();
debug("Pipeline: load_and_decode=%lf pixbuf_copy=%lf redeye=%lf crop=%lf scale=%lf adjustment=%lf orientation=%lf total=%lf",
load_and_decode_time, pixbuf_copy_time, redeye_time, crop_time, scale_time, adjustment_time,
orientation_time, total_time);
#endif
return pixbuf;
}
//
// File export
//
public static void copy_exported_exif(Exif.Data source, PhotoExif dest, Orientation orientation,
Dimensions dim) throws Error {
dest.set_exif(source);
dest.set_dimensions(dim);
dest.set_orientation(orientation);
dest.remove_all_tags(Exif.Tag.RELATED_IMAGE_WIDTH);
dest.remove_all_tags(Exif.Tag.RELATED_IMAGE_LENGTH);
dest.remove_thumbnail();
dest.commit();
}
// Writes a file appropriate for export meeting the specified parameters.
//
// TODO: Lossless transformations, especially for mere rotations of JFIF files.
public void export(File dest_file, int scale, ScaleConstraint constraint,
Jpeg.Quality quality) throws Error {
if (constraint == ScaleConstraint.ORIGINAL) {
// generate a raw exportable file and copy that
File exportable = generate_exportable();
try {
exportable.copy(dest_file, FileCopyFlags.OVERWRITE | FileCopyFlags.ALL_METADATA,
null, null);
} catch (Error err) {
export_failed();
throw err;
}
return;
}
Gdk.Pixbuf pixbuf = get_pixbuf(UNSCALED);
Dimensions dim = Dimensions.for_pixbuf(pixbuf);
Dimensions scaled = dim.get_scaled_by_constraint(scale, constraint);
// only scale if necessary ... although scale_simple probably catches this, it's an easy
// check to avoid image loss
if (dim.width != scaled.width || dim.height != scaled.height)
pixbuf = pixbuf.scale_simple(scaled.width, scaled.height, EXPORT_INTERP);
try {
pixbuf.save(dest_file.get_path(), "jpeg", "quality", quality.get_pct_text());
Exif.Data exif = get_exif();
if (exif != null)
copy_exported_exif(exif, new PhotoExif(dest_file), Orientation.TOP_LEFT, scaled);
} catch (Error err) {
export_failed();