Commit 3f12a8cc authored by Jim Nelson's avatar Jim Nelson

Callers to PhotoTransformer.get_pixbuf now must specify a scale, to discourage...

Callers to PhotoTransformer.get_pixbuf now must specify a scale, to discourage pipeline transformations of 
unscaled pixbufs.  Support added for retrieving pixbufs at screen size, which is adequate for most display 
operations.  Beefed-up the get_preview_pixbuf() method, which encourages using lower-quality but 
fully-transformed pixbufs when possible.
parent 1bccf59f
......@@ -255,6 +255,8 @@ public abstract class PageWindow : Gtk.Window {
// AppWindow also offers support for going into fullscreen mode. It handles the interface
// notifications Page is expecting when switching back and forth.
public abstract class AppWindow : PageWindow {
public const int DND_ICON_SCALE = 128;
private const string DATA_DIR = ".shotwell";
public static Gdk.Color BG_COLOR = parse_color("#444");
......
......@@ -55,7 +55,7 @@ class SlideshowPage : SinglePhotoPage {
public override void switched_to() {
base.switched_to();
set_pixbuf(thumbnail.get_photo().get_pixbuf());
set_pixbuf(thumbnail.get_photo().get_pixbuf(PhotoTransformer.SCREEN));
Timeout.add(CHECK_ADVANCE_MSEC, auto_advance);
timer.start();
......@@ -91,12 +91,7 @@ class SlideshowPage : SinglePhotoPage {
private void on_next_automatic() {
thumbnail = (Thumbnail) controller.get_next_item(thumbnail);
// Photo.get_pixbuf can optimize its pipeline with a scaled pixbuf, so ask for one that
// fits in the screen
Gdk.Screen screen = AppWindow.get_instance().window.get_screen();
int scale = int.max(screen.get_width(), screen.get_height());
set_pixbuf(thumbnail.get_photo().get_pixbuf(PhotoTransformer.EXCEPTION_NONE, scale));
set_pixbuf(thumbnail.get_photo().get_pixbuf(PhotoTransformer.SCREEN));
// reset the timer
timer.start();
......@@ -109,8 +104,8 @@ class SlideshowPage : SinglePhotoPage {
private void manual_advance(Thumbnail thumbnail) {
this.thumbnail = thumbnail;
// start with blown-up thumbnail
set_pixbuf(thumbnail.get_photo().get_thumbnail(ThumbnailCache.BIG_SCALE));
// start with blown-up preview
set_pixbuf(thumbnail.get_photo().get_preview_pixbuf(PhotoTransformer.SCREEN));
// schedule improvement to real photo
Idle.add(on_improvement);
......@@ -120,7 +115,7 @@ class SlideshowPage : SinglePhotoPage {
}
private bool on_improvement() {
set_pixbuf(thumbnail.get_photo().get_pixbuf());
set_pixbuf(thumbnail.get_photo().get_pixbuf(PhotoTransformer.SCREEN));
return false;
}
......@@ -484,7 +479,7 @@ public class CollectionPage : CheckerboardPage {
// set up icon using the "first" photo, although Sets are not ordered
if (icon == null)
icon = photo.get_thumbnail(ThumbnailCache.MEDIUM_SCALE);
icon = photo.get_preview_pixbuf(AppWindow.DND_ICON_SCALE);
debug("Prepared %s for export", file.get_path());
}
......
......@@ -492,7 +492,7 @@ public class CropTool : EditingTool {
public override Gdk.Pixbuf? get_unscaled_pixbuf(Photo 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.EXCEPTION_CROP) : null;
return photo.has_crop() ? photo.get_pixbuf(PhotoTransformer.Exception.CROP) : null;
}
private void prepare_gc(Gdk.GC default_gc, Gdk.Drawable drawable) {
......@@ -1435,7 +1435,7 @@ public class AdjustTool : EditingTool {
}
public override Gdk.Pixbuf? get_unscaled_pixbuf(Photo photo) {
return photo.get_pixbuf(PhotoTransformer.EXCEPTION_ADJUST);
return photo.get_pixbuf(PhotoTransformer.Exception.ADJUST);
}
......
......@@ -20,8 +20,7 @@ public class DirectoryItem : LayoutItem, EventSource {
assert(photo_id.is_valid());
Photo photo = Photo.fetch(photo_id);
Gdk.Pixbuf pixbuf = photo.get_thumbnail(SCALE);
pixbuf = scale_pixbuf(pixbuf, SCALE, INTERP);
Gdk.Pixbuf pixbuf = photo.get_preview_pixbuf(SCALE, INTERP);
set_image(pixbuf);
}
......
......@@ -839,7 +839,7 @@ public class ImportQueuePage : SinglePhotoPage {
}
private void on_imported(Photo photo) {
set_pixbuf(photo.get_pixbuf());
set_pixbuf(photo.get_pixbuf(PhotoTransformer.SCREEN));
progress_bytes += photo.get_filesize();
double pct = (progress_bytes <= total_bytes) ? (double) progress_bytes / (double) total_bytes
......
......@@ -206,8 +206,15 @@ public class Photo : PhotoTransformer, Queryable {
return new Gdk.Pixbuf.from_file(get_file().get_path());
}
public override Gdk.Pixbuf get_quick_pixbuf(int scale) throws Error {
return scale_pixbuf(get_thumbnail(scale), scale, Gdk.InterpType.BILINEAR);
public override Gdk.Pixbuf get_preview_pixbuf(int scale,
Gdk.InterpType interp = Gdk.InterpType.BILINEAR) throws Error {
Gdk.Pixbuf pixbuf = get_thumbnail(ThumbnailCache.BIG_SCALE);
int pixels = scale_to_pixels(scale);
if (pixels > 0)
pixbuf = scale_pixbuf(pixbuf, pixels, interp);
return pixbuf;
}
public override Exif.Data? get_exif() {
......@@ -459,7 +466,7 @@ public class Photo : PhotoTransformer, Queryable {
dest_exif.set_orientation(photo_table.get_orientation(photo_id));
dest_exif.commit();
} else {
Gdk.Pixbuf pixbuf = get_pixbuf();
Gdk.Pixbuf pixbuf = get_pixbuf(PhotoTransformer.UNSCALED);
pixbuf.save(dest_file.get_path(), "jpeg", "quality", EXPORT_JPEG_QUALITY.get_pct_text());
copy_exported_exif(original_exif, new PhotoExif(dest_file), Orientation.TOP_LEFT,
Dimensions.for_pixbuf(pixbuf));
......@@ -503,7 +510,7 @@ public class Photo : PhotoTransformer, Queryable {
// load transformed image for thumbnail generation
Gdk.Pixbuf pixbuf = null;
try {
pixbuf = get_pixbuf();
pixbuf = get_pixbuf(PhotoTransformer.SCREEN);
} catch (Error err) {
error("%s", err.message);
}
......
......@@ -170,18 +170,12 @@ public class PhotoPage : SinglePhotoPage {
// throw a resized large thumbnail up to get an image on the screen quickly,
// and when ready decode and display the full image
set_pixbuf(photo.get_thumbnail(ThumbnailCache.BIG_SCALE));
set_pixbuf(photo.get_preview_pixbuf(PhotoTransformer.SCREEN));
Idle.add(update_pixbuf);
}
private bool update_pixbuf() {
// Photo.get_pixbuf() can optimize its pipeline if given a scale to work with ... since
// SinglePhotoPage may need to resize its unscaled image thousands of times if the user
// resizes the window, get a scaled image large enough for the screen
Gdk.Screen screen = AppWindow.get_instance().window.get_screen();
int scale = int.max(screen.get_width(), screen.get_height());
set_pixbuf(photo.get_pixbuf(PhotoTransformer.EXCEPTION_NONE, scale));
set_pixbuf(photo.get_pixbuf(PhotoTransformer.SCREEN));
return false;
}
......@@ -253,8 +247,12 @@ public class PhotoPage : SinglePhotoPage {
}
// set up icon for drag-and-drop
Gdk.Pixbuf icon = photo.get_thumbnail(ThumbnailCache.MEDIUM_SCALE);
Gtk.drag_source_set_icon_pixbuf(canvas, icon);
try {
Gdk.Pixbuf icon = photo.get_preview_pixbuf(AppWindow.DND_ICON_SCALE);
Gtk.drag_source_set_icon_pixbuf(canvas, icon);
} catch (Error err) {
message("Unable to get drag-and-drop icon: %s", err.message);
}
debug("Prepared for export %s", file.get_path());
......
......@@ -9,14 +9,20 @@
// transformations to be stored persistently elsewhere or in memory until they're commited en
// masse to an image file.
public abstract class PhotoTransformer : Object {
public const int UNSCALED = 0;
public const int SCREEN = -1;
public const Jpeg.Quality EXPORT_JPEG_QUALITY = Jpeg.Quality.HIGH;
public const Gdk.InterpType EXPORT_INTERP = Gdk.InterpType.HYPER;
public const int EXCEPTION_NONE = 0;
public const int EXCEPTION_ORIENTATION = 1 << 0;
public const int EXCEPTION_CROP = 1 << 1;
public const int EXCEPTION_REDEYE = 1 << 2;
public const int EXCEPTION_ADJUST = 1 << 3;
public enum Exception {
NONE = 0,
ORIENTATION = 1 << 0,
CROP = 1 << 1,
REDEYE = 1 << 2,
ADJUST = 1 << 3,
ALL = 0xFFFFFFFF
}
private static PhotoTransformer cached_raw_instance = null;
private static Gdk.Pixbuf cached_raw = null;
......@@ -30,10 +36,20 @@ public abstract class PhotoTransformer : Object {
// Returns a raw, untransformed, unrotated, unscaled pixbuf from the source
public abstract Gdk.Pixbuf get_raw_pixbuf() throws Error;
// 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;
}
// Returns a fully transformed (and scaled, if specified) pixbuf from the source.
// Transformations may be excluded via the mask.
public Gdk.Pixbuf get_pixbuf(int exceptions = EXCEPTION_NONE, int scale = 0,
//
// 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 = Gdk.InterpType.HYPER) throws Error {
#if MEASURE_PIPELINE
Timer timer = new Timer();
......@@ -84,7 +100,7 @@ public abstract class PhotoTransformer : Object {
//
// redeye reduction
if ((exceptions & EXCEPTION_REDEYE) == 0) {
if ((exceptions & Exception.REDEYE) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
......@@ -97,7 +113,7 @@ public abstract class PhotoTransformer : Object {
}
// crop
if ((exceptions & EXCEPTION_CROP) == 0) {
if ((exceptions & Exception.CROP) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
......@@ -112,18 +128,19 @@ public abstract class PhotoTransformer : Object {
}
// scale
if (scale > 0) {
int scale_pixels = scale_to_pixels(scale);
if (scale_pixels > 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
pixbuf = scale_pixbuf(pixbuf, scale, interp);
pixbuf = scale_pixbuf(pixbuf, scale_pixels, interp);
#if MEASURE_PIPELINE
scale_time = timer.elapsed();
#endif
}
// color adjustment
if ((exceptions & EXCEPTION_ADJUST) == 0) {
if ((exceptions & Exception.ADJUST) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
......@@ -136,7 +153,7 @@ public abstract class PhotoTransformer : Object {
}
// orientation (all modifications are stored in unrotated coordinate system)
if ((exceptions & EXCEPTION_ORIENTATION) == 0) {
if ((exceptions & Exception.ORIENTATION) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
......@@ -157,12 +174,29 @@ public abstract class PhotoTransformer : Object {
return pixbuf;
}
// A quick pixbuf is one that can be quickly generated and scaled as a preview while the
// A preview pixbuf is one that can be quickly generated and scaled as a preview while the
// fully transformed pixbuf is built. It should be fully transformed for the user. If the
// subclass doesn't have one handy, PhotoTransformer will generate a usable one.
public virtual Gdk.Pixbuf get_quick_pixbuf(int scale) throws Error {
// as a fallback, return a small image that can be scaled quickly
return get_pixbuf(scale);
//
// Note that scale may be UNSCALED or SCREEN, and subclasses must support both. Use
// scale_to_pixels for conversion. 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 virtual Gdk.Pixbuf get_preview_pixbuf(int scale,
Gdk.InterpType interp = Gdk.InterpType.BILINEAR) throws Error {
// as a fallback, return a small image that can be scaled quickly ... cap the size for
// performance reasons
if (scale == UNSCALED || scale == SCREEN || scale > 400)
scale = 400;
Gdk.Pixbuf pixbuf = get_pixbuf(scale, Exception.NONE, interp);
// scale to what the user is asking for
int scale_pixels = scale_to_pixels(scale);
if (scale_pixels > 0)
pixbuf = scale_pixbuf(pixbuf, scale_pixels, interp);
return pixbuf;
}
//
......@@ -212,7 +246,7 @@ public abstract class PhotoTransformer : Object {
return;
}
Gdk.Pixbuf pixbuf = get_pixbuf();
Gdk.Pixbuf pixbuf = get_pixbuf(PhotoTransformer.UNSCALED);
Dimensions dim = Dimensions.for_pixbuf(pixbuf);
Dimensions scaled = dim.get_scaled_by_constraint(scale, constraint);
......
......@@ -122,6 +122,12 @@ bool coord_in_rectangle(int x, int y, Gdk.Rectangle rect) {
return (x >= rect.x && x < (rect.x + rect.width) && y >= rect.y && y <= (rect.y + rect.height));
}
int get_screen_scale() {
Gdk.Screen screen = AppWindow.get_instance().window.get_screen();
return int.max(screen.get_width(), screen.get_height());
}
namespace Jpeg {
public const uint8 MARKER_PREFIX = 0xFF;
......
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