Commit 8f35cba8 authored by Jim Nelson's avatar Jim Nelson

Fixed scaled load-and-decode to properly scale images which are re-oriented...

Fixed scaled load-and-decode to properly scale images which are re-oriented and/or have a crop.  Assertions in 
the code guarantee that the pixbuf should not be rescaled once it leaves the pipeline.
parent 4153a882
......@@ -94,8 +94,8 @@ public struct Box {
double x_scale, y_scale;
get_dimensions().get_scale_factors(scaled, out x_scale, out y_scale);
int l = (int) Math.round(left * x_scale);
int t = (int) Math.round(top * y_scale);
int l = (int) Math.round((double) left * x_scale);
int t = (int) Math.round((double) top * y_scale);
// fix-up to match the scaled dimensions
int r = l + scaled.width - 1;
......@@ -107,15 +107,15 @@ public struct Box {
return box;
}
public Box get_scaled_proportional(Dimensions orig, Dimensions scaled) {
public Box get_scaled_similar(Dimensions original, Dimensions scaled) {
double x_scale, y_scale;
orig.get_scale_factors(scaled, out x_scale, out y_scale);
int l = (int) Math.round(left * x_scale);
int t = (int) Math.round(top * y_scale);
int r = (int) Math.round(right * x_scale);
int b = (int) Math.round(bottom * y_scale);
original.get_scale_factors(scaled, out x_scale, out y_scale);
int l = (int) Math.round((double) left * x_scale);
int t = (int) Math.round((double) top * y_scale);
int r = (int) Math.round((double) right * x_scale);
int b = (int) Math.round((double) bottom * y_scale);
// catch rounding errors
if (r >= scaled.width)
r = scaled.width - 1;
......@@ -314,7 +314,7 @@ public struct Box {
}
public string to_string() {
return "%d,%d %d,%d".printf(left, top, right, bottom);
return "%d,%d %d,%d (%s)".printf(left, top, right, bottom, get_dimensions().to_string());
}
private static bool in_zone(double pos, int zone) {
......
......@@ -131,7 +131,7 @@ class SlideshowPage : SinglePhotoPage {
base.switched_to();
// since the canvas might not be ready at this point, start with screen-sized photo
set_pixbuf(thumbnail.get_photo().get_pixbuf(TransformablePhoto.SCREEN));
set_pixbuf(thumbnail.get_photo().get_pixbuf(Scaling.for_screen()));
// start the auto-advance timer
Timeout.add(CHECK_ADVANCE_MSEC, auto_advance);
......@@ -159,7 +159,7 @@ class SlideshowPage : SinglePhotoPage {
return false;
Thumbnail next = (Thumbnail) controller.get_next_item(thumbnail);
next_pixbuf = next.get_photo().get_pixbuf(get_canvas_scale());
next_pixbuf = next.get_photo().get_pixbuf(get_canvas_scaling());
return false;
}
......@@ -192,7 +192,7 @@ class SlideshowPage : SinglePhotoPage {
Gdk.Pixbuf pixbuf = next_pixbuf;
if (pixbuf == null) {
warning("Slideshow prefetch was not ready");
pixbuf = thumbnail.get_photo().get_pixbuf(get_canvas_scale());
next_pixbuf = thumbnail.get_photo().get_pixbuf(get_canvas_scaling());
}
set_pixbuf(pixbuf);
......@@ -212,7 +212,7 @@ class SlideshowPage : SinglePhotoPage {
this.thumbnail = thumbnail;
// start with blown-up preview
set_pixbuf(thumbnail.get_photo().get_preview_pixbuf(get_canvas_scale()));
set_pixbuf(thumbnail.get_photo().get_preview_pixbuf(get_canvas_scaling()));
// schedule improvement to real photo
Idle.add(on_improvement);
......@@ -225,7 +225,7 @@ class SlideshowPage : SinglePhotoPage {
}
private bool on_improvement() {
set_pixbuf(thumbnail.get_photo().get_pixbuf(get_canvas_scale()));
set_pixbuf(thumbnail.get_photo().get_pixbuf(get_canvas_scaling()));
return false;
}
......@@ -655,7 +655,7 @@ public class CollectionPage : CheckerboardPage {
// set up icon using the "first" photo, although Sets are not ordered
if (icon == null)
icon = photo.get_preview_pixbuf(AppWindow.DND_ICON_SCALE);
icon = photo.get_preview_pixbuf(Scaling.for_best_fit(AppWindow.DND_ICON_SCALE));
debug("Prepared %s for export", file.get_path());
}
......
......@@ -100,15 +100,10 @@ public class ThemeLoader {
return into;
}
public static Gdk.Pixbuf load_icon(string source_file) {
public static Gdk.Pixbuf load_icon(string source_basename) {
populate_theme_params();
Gdk.Pixbuf loaded_pixbuf = null;
try {
loaded_pixbuf = new Gdk.Pixbuf.from_file(source_file);
} catch (Error e) {
error("ThemeLoader: load_icon: couldn't read icon data from disk");
}
Gdk.Pixbuf loaded_pixbuf = Resources.get_icon(source_basename, 0);
/* Sweep through the icon image data loaded from disk and determine how many
unique colors are in it. We do this with the aid of a HashSet. */
......@@ -210,7 +205,7 @@ public class RGBHistogramManipulator : Gtk.DrawingArea {
private RGBHistogram histogram = null;
private int left_nub_position = 0;
private int right_nub_position = 255;
private Gdk.Pixbuf nub_pixbuf = ThemeLoader.load_icon("icons/drag_nub.png");
private Gdk.Pixbuf nub_pixbuf = ThemeLoader.load_icon("drag_nub.png");
private bool is_left_nub_tracking = false;
private bool is_right_nub_tracking = false;
private int track_start_x = 0;
......
......@@ -61,7 +61,16 @@ public struct Dimensions {
public string to_string() {
return "%dx%d".printf(width, height);
}
public bool equals(Dimensions dim) {
return (width == dim.width && height == dim.height);
}
// sometimes a pixel or two is okay
public bool approx_equals(Dimensions dim, int fudge = 1) {
return (width - dim.width).abs() <= fudge && (height - dim.height).abs() <= fudge;
}
public Dimensions get_scaled(int scale) {
assert(scale > 0);
......@@ -100,11 +109,11 @@ public struct Dimensions {
// TODO: Surely this can be done by examining dimensions to avoid double calculations.
scaled.width = viewport.width;
double ratio = (double) viewport.width / (double) width;
scaled.height = (int) ((double) height * ratio);
scaled.height = (int) Math.round((double) height * ratio);
if (scaled.height > viewport.height) {
scaled.height = viewport.height;
ratio = (double) viewport.height / (double) height;
scaled.width = (int) ((double) width * ratio);
scaled.width = (int) Math.round((double) width * ratio);
}
assert(scaled.height <= viewport.height);
......@@ -118,14 +127,25 @@ public struct Dimensions {
get_scale_factors(scaled, out x_scale, out y_scale);
Gdk.Rectangle scaled_rect = Gdk.Rectangle();
scaled_rect.x = (int) (rect.x * x_scale);
scaled_rect.y = (int) (rect.y * y_scale);
scaled_rect.width = (int) (rect.width * x_scale);
scaled_rect.height = (int) (rect.height * y_scale);
scaled_rect.x = (int) Math.round((double) rect.x * x_scale);
scaled_rect.y = (int) Math.round((double) rect.y * y_scale);
scaled_rect.width = (int) Math.round((double) rect.width * x_scale);
scaled_rect.height = (int) Math.round((double) rect.height * y_scale);
return scaled_rect;
}
// Returns the current dimensions scaled in a similar proportion as the two suppled dimensions
public Dimensions get_scaled_similar(Dimensions original, Dimensions scaled) {
double x_scale, y_scale;
original.get_scale_factors(scaled, out x_scale, out y_scale);
double scale = double.min(x_scale, y_scale);
return Dimensions((int) Math.round((double) width * scale),
(int) Math.round((double) height * scale));
}
public Dimensions get_scaled_by_width(int scale) {
double ratio = (double) scale / (double) width;
......@@ -159,3 +179,137 @@ public struct Dimensions {
}
}
public struct Scaling {
private const int NO_SCALE = 0;
private const int SCREEN = -1;
private ScaleConstraint constraint;
private int scale;
private Dimensions viewport;
private Scaling(ScaleConstraint constraint, int scale, Dimensions viewport) {
this.constraint = constraint;
this.scale = scale;
this.viewport = viewport;
}
public static Scaling for_original() {
return Scaling(ScaleConstraint.ORIGINAL, NO_SCALE, Dimensions());
}
public static Scaling for_screen() {
return Scaling(ScaleConstraint.DIMENSIONS, SCREEN, Dimensions());
}
public static Scaling for_best_fit(int pixels) {
assert(pixels > 0);
return Scaling(ScaleConstraint.DIMENSIONS, pixels, Dimensions());
}
public static Scaling for_viewport(Dimensions viewport) {
assert(viewport.has_area());
return Scaling(ScaleConstraint.DIMENSIONS, NO_SCALE, viewport);
}
public static Scaling for_widget(Gtk.Widget widget) {
Dimensions viewport = Dimensions.for_allocation(widget.allocation);
assert(viewport.has_area());
return Scaling(ScaleConstraint.DIMENSIONS, NO_SCALE, viewport);
}
private static int get_screen_scale() {
Gdk.Screen screen = AppWindow.get_instance().window.get_screen();
return int.max(screen.get_width(), screen.get_height());
}
private int scale_to_pixels() {
if (scale == SCREEN)
return get_screen_scale();
return (scale >= 0) ? scale : 0;
}
public bool is_unscaled() {
return constraint == ScaleConstraint.ORIGINAL;
}
public bool is_best_fit(Dimensions original, out int pixels) {
if (constraint == ScaleConstraint.ORIGINAL || scale == NO_SCALE)
return false;
pixels = scale_to_pixels();
assert(pixels > 0);
return true;
}
public bool is_best_fit_dimensions(Dimensions original, out Dimensions scaled) {
int pixels;
if (!is_best_fit(original, out pixels))
return false;
scaled = original.get_scaled(pixels);
return true;
}
public bool is_for_viewport(Dimensions original, out Dimensions scaled) {
if (constraint == ScaleConstraint.ORIGINAL || scale != NO_SCALE)
return false;
assert(viewport.has_area());
scaled = original.get_scaled_proportional(viewport);
return true;
}
public Dimensions get_scaled_dimensions(Dimensions original) {
if (is_unscaled())
return original;
Dimensions scaled;
if (is_best_fit_dimensions(original, out scaled))
return scaled;
bool is_viewport = is_for_viewport(original, out scaled);
assert(is_viewport);
return scaled;
}
public Gdk.Pixbuf perform_on_pixbuf(Gdk.Pixbuf pixbuf, Gdk.InterpType interp) {
if (is_unscaled())
return pixbuf;
Dimensions pixbuf_dim = Dimensions.for_pixbuf(pixbuf);
int pixels;
if (is_best_fit(pixbuf_dim, out pixels))
return scale_pixbuf(pixbuf, pixels, interp);
Dimensions scaled;
bool is_viewport = is_for_viewport(pixbuf_dim, out scaled);
assert(is_viewport);
return resize_pixbuf(pixbuf, scaled, interp);
}
public string to_string() {
if (constraint == ScaleConstraint.ORIGINAL)
return "scaling: UNSCALED";
else if (scale != NO_SCALE)
return "scaling: best-fit (%d pixels)".printf(scale_to_pixels());
else
return "scaling: viewport %s".printf(viewport.to_string());
}
public bool equals(Scaling scaling) {
return (constraint == scaling.constraint) && (scale == scaling.scale)
&& viewport.equals(scaling.viewport);
}
}
......@@ -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_display_pixbuf(TransformablePhoto photo) {
public virtual Gdk.Pixbuf? get_display_pixbuf(Scaling scaling, TransformablePhoto photo) {
return null;
}
......@@ -467,7 +467,7 @@ public class CropTool : EditingTool {
// scale the crop to the scaled photo's size ... the scaled crop is maintained in
// coordinates not relative to photo's position on canvas
scaled_crop = crop.get_scaled_proportional(uncropped_dim,
scaled_crop = crop.get_scaled_similar(uncropped_dim,
Dimensions.for_rectangle(canvas.get_scaled_pixbuf_position()));
base.activate(canvas);
......@@ -489,11 +489,11 @@ public class CropTool : EditingTool {
return crop_tool_window;
}
public override Gdk.Pixbuf? get_display_pixbuf(TransformablePhoto photo) {
public override Gdk.Pixbuf? get_display_pixbuf(Scaling scaling, 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(TransformablePhoto.SCREEN,
TransformablePhoto.Exception.CROP) : null;
return photo.has_crop() ? photo.get_pixbuf(scaling, TransformablePhoto.Exception.CROP)
: null;
}
private void prepare_gc(Gdk.GC default_gc, Gdk.Drawable drawable) {
......@@ -531,10 +531,10 @@ public class CropTool : EditingTool {
Dimensions uncropped_dim = canvas.get_photo().get_uncropped_dimensions();
// rescale to full crop
Box crop = scaled_crop.get_scaled_proportional(old_dim, uncropped_dim);
Box crop = scaled_crop.get_scaled_similar(old_dim, uncropped_dim);
// rescale back to new size
scaled_crop = crop.get_scaled_proportional(uncropped_dim, new_dim);
scaled_crop = crop.get_scaled_similar(uncropped_dim, new_dim);
prepare_visuals(scaled);
}
......@@ -600,7 +600,7 @@ public class CropTool : EditingTool {
private void on_crop_apply() {
// up-scale scaled crop to photo's dimensions
Box crop = scaled_crop.get_scaled_proportional(
Box crop = scaled_crop.get_scaled_similar(
Dimensions.for_rectangle(canvas.get_scaled_pixbuf_position()),
canvas.get_photo().get_uncropped_dimensions());
......@@ -1448,8 +1448,8 @@ public class AdjustTool : EditingTool {
canvas.paint_pixbuf(draw_to_pixbuf);
}
public override Gdk.Pixbuf? get_display_pixbuf(TransformablePhoto photo) {
return photo.get_pixbuf(TransformablePhoto.SCREEN, TransformablePhoto.Exception.ADJUST);
public override Gdk.Pixbuf? get_display_pixbuf(Scaling scaling, TransformablePhoto photo) {
return photo.get_pixbuf(scaling, TransformablePhoto.Exception.ADJUST);
}
private void on_cancel() {
......
......@@ -19,7 +19,7 @@ public class DirectoryItem : LayoutItem, EventSource {
assert(photo_id.is_valid());
LibraryPhoto photo = LibraryPhoto.fetch(photo_id);
Gdk.Pixbuf pixbuf = photo.get_preview_pixbuf(SCALE);
Gdk.Pixbuf pixbuf = photo.get_preview_pixbuf(Scaling.for_best_fit(SCALE));
set_image(pixbuf);
}
......
......@@ -843,7 +843,7 @@ public class ImportQueuePage : SinglePhotoPage {
}
private void on_imported(LibraryPhoto photo) {
set_pixbuf(photo.get_pixbuf(get_canvas_scale()));
set_pixbuf(photo.get_pixbuf(get_canvas_scaling()));
progress_bytes += photo.get_filesize();
double pct = (progress_bytes <= total_bytes) ? (double) progress_bytes / (double) total_bytes
......
......@@ -226,6 +226,10 @@ public enum Orientation {
return dim;
}
}
public Dimensions derotate_dimensions(Dimensions dim) {
return rotate_dimensions(dim);
}
public Gdk.Pixbuf rotate_pixbuf(owned Gdk.Pixbuf pixbuf) {
switch (this) {
......@@ -401,6 +405,7 @@ public enum Orientation {
return derotated;
}
// space is the unrotated dimensions the point is rotating with
public Box rotate_box(Dimensions space, Box box) {
Gdk.Point top_left, bottom_right;
box.get_points(out top_left, out bottom_right);
......@@ -411,6 +416,7 @@ public enum Orientation {
return Box.from_points(top_left, bottom_right);
}
// space is the unrotated dimensions the point is return to
public Box derotate_box(Dimensions space, Box box) {
Gdk.Point top_left, bottom_right;
box.get_points(out top_left, out bottom_right);
......
......@@ -1260,8 +1260,8 @@ public abstract class SinglePhotoPage : Page {
return pixmap_dim;
}
public int get_canvas_scale() {
return int.max(canvas.allocation.width, canvas.allocation.height);
public Scaling get_canvas_scaling() {
return Scaling.for_widget(viewport);
}
public Gdk.Pixbuf? get_unscaled_pixbuf() {
......@@ -1378,7 +1378,7 @@ public abstract class SinglePhotoPage : Page {
// rescale if canvas rescaled or better quality is requested
if (scaled == null || interp != repaint_interp) {
scaled = unscaled.scale_simple(scaled_pos.width, scaled_pos.height, repaint_interp);
scaled = resize_pixbuf(unscaled, Dimensions.for_rectangle(scaled_pos), repaint_interp);
UpdateReason reason = UpdateReason.RESIZED_CANVAS;
if (new_photo)
......
......@@ -97,9 +97,6 @@ public enum ImportResult {
// 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.BILINEAR;
public const Jpeg.Quality EXPORT_JPEG_QUALITY = Jpeg.Quality.HIGH;
......@@ -119,11 +116,11 @@ public abstract class TransformablePhoto: PhotoBase {
ADJUST = 1 << 3,
ALL = 0xFFFFFFFF;
public bool includes(Exception exception) {
public bool prohibits(Exception exception) {
return ((this & exception) != 0);
}
public bool excludes(Exception exception) {
public bool allows(Exception exception) {
return ((this & exception) == 0);
}
}
......@@ -137,7 +134,6 @@ public abstract class TransformablePhoto: PhotoBase {
private static PhotoID cached_photo_id = PhotoID();
private static Gdk.Pixbuf cached_raw = null;
private static int cached_scale = 0;
// because fetching individual items from the database is high-overhead, store all of
// the photo row in memory
......@@ -735,31 +731,139 @@ public abstract class TransformablePhoto: PhotoBase {
}
// Pixbuf generation
// Returns dimensions for the pixbuf at various stages of the pipeline. raw is for the pixbuf
// with no scaling. scaled_image is the dimensions of the image after a scaled load-and-decode.
// scaled_viewport is the dimensions of the image sized according to the scaling parameter.
// scaled_image and scaled_viewport may be different if the photo is cropped.
//
// Returns true if scaling is to occur, false otherwise. If false, scaled_image will be set to
// the raw image dimensions and scaled_to_viewport will be the dimensions of the image scaled
// to the Scaling viewport.
private bool calculate_pixbuf_dimensions(Scaling scaling, Exception exceptions,
out Dimensions scaled_image, out Dimensions scaled_to_viewport) {
Dimensions raw = get_raw_dimensions();
if (scaling.is_unscaled()) {
scaled_image = raw;
scaled_to_viewport = raw;
return false;
}
Orientation orientation = get_orientation();
// If no crop, the scaled_image is simply raw scaled to fit into the viewport. Otherwise,
// the image is scaled enough so the cropped region fits the viewport.
// Returns a raw, untransformed, unrotated, unscaled pixbuf directly from the source
public Gdk.Pixbuf load_raw_pixbuf(int scale) throws Error {
int pixels = scale_to_pixels(scale);
string path = get_file().get_path();
scaled_image = Dimensions();
scaled_to_viewport = Dimensions();
if (exceptions.allows(Exception.CROP)) {
Box crop;
if (get_raw_crop(out crop)) {
// rotate the crop and raw space accordingly ... order is important here, rotate_box
// works with the unrotated dimensions in space
Dimensions rotated_raw = raw;
if (exceptions.allows(Exception.ORIENTATION)) {
crop = orientation.rotate_box(raw, crop);
rotated_raw = orientation.rotate_dimensions(raw);
}
// scale the rotated crop to fit in the viewport
Box scaled_crop = crop.get_scaled(scaling.get_scaled_dimensions(crop.get_dimensions()));
// the viewport size is the size of the scaled crop
scaled_to_viewport = scaled_crop.get_dimensions();
// only scale the image if the crop is larger than the viewport
if (crop.get_width() <= scaled_crop.get_width()
&& crop.get_height() <= scaled_crop.get_height()) {
scaled_image = raw;
scaled_to_viewport = crop.get_dimensions();
return false;
}
// resize the total pixbuf so the crop slices directly from the scaled pixbuf,
// with no need for resizing thereafter. The decoded size is determined by the
// proportion of the actual size to the crop size
scaled_image = rotated_raw.get_scaled_similar(crop.get_dimensions(),
scaled_crop.get_dimensions());
// derotate, as the loader knows nothing about orientation
if (exceptions.allows(Exception.ORIENTATION))
scaled_image = orientation.derotate_dimensions(scaled_image);
}
}
// if scaled_image not set, merely scale the raw pixbuf
if (!scaled_image.has_area()) {
// rotate for the scaler
Dimensions rotated_raw = raw;
if (exceptions.allows(Exception.ORIENTATION))
rotated_raw = orientation.rotate_dimensions(raw);
scaled_image = scaling.get_scaled_dimensions(rotated_raw);
scaled_to_viewport = scaled_image;
return (pixels > 0) ? new Gdk.Pixbuf.from_file_at_size(path, pixels, pixels)
: new Gdk.Pixbuf.from_file(path);
// derotate the scaled dimensions, as the loader knows nothing about orientation
if (exceptions.allows(Exception.ORIENTATION))
scaled_image = orientation.derotate_dimensions(scaled_image);
}
// do not scale up
if (scaled_image.width >= raw.width && scaled_image.height >= raw.height) {
scaled_image = raw;
return false;
}
assert(scaled_image.has_area());
assert(scaled_to_viewport.has_area());
return true;
}
// Converts a scale parameter for get_pixbuf or get_preview_pixbuf into an actual pixel
// count to proportionally scale to. Returns 0 (UNSCALED) if an unscaled pixbuf is specified
// (or a bad value).
public static int scale_to_pixels(int scale) {
if (scale == SCREEN)
return get_screen_scale();
// Returns a raw, untransformed, unrotated pixbuf directly from the source. Scaling provides
// asked for a scaled-down image, which has certain performance benefits if the resized
// JPEG is scaled down by a factor of a power of two (one-half, one-fourth, etc.).
private Gdk.Pixbuf load_raw_pixbuf(Scaling scaling, Exception exceptions) throws Error {
string path = get_file().get_path();
// no scaling, load and get out
if (scaling.is_unscaled()) {
debug("LOAD_RAW_PIXBUF UNSCALED: requested");
return new Gdk.Pixbuf.from_file(path);
}
// Need the dimensions of the image to load
Dimensions scaled_image, scaled_to_viewport;
bool is_scaled = calculate_pixbuf_dimensions(scaling, exceptions, out scaled_image,
out scaled_to_viewport);
if (!is_scaled) {
debug("LOAD_RAW_PIXBUF UNSCALED: scaling unavailable");
return new Gdk.Pixbuf.from_file(path);
}
Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file_at_size(path, scaled_image.width,
scaled_image.height);
debug("LOAD_RAW_PIXBUF %s: %s -> %s (actual: %s)", scaling.to_string(),
get_raw_dimensions().to_string(), scaled_image.to_string(),
Dimensions.for_pixbuf(pixbuf).to_string());
assert(scaled_image.approx_equals(Dimensions.for_pixbuf(pixbuf)));
return (scale >= 0) ? scale : 0;
return pixbuf;
}
// This find the best method possible to load-and-decode the photo's pixbuf, using caches
// and scaled decodes whenever possible. This pixbuf is untransformed, unrotated, and unscaled
// and scaled decodes whenever possible. This pixbuf is untransformed and unrotated.
// no_copy should only be set to true if the user specifically knows no transformations will
// be made on the returned pixbuf.
private Gdk.Pixbuf get_raw_pixbuf(int scale, bool no_copy) throws Error {
private Gdk.Pixbuf get_raw_pixbuf(Scaling scaling, Exception exceptions, bool no_copy) throws Error {
#if MEASURE_PIPELINE
Timer timer = new Timer();
Timer total_timer = new Timer();
......@@ -768,21 +872,35 @@ public abstract class TransformablePhoto: PhotoBase {
total_timer.start();
#endif
Gdk.Pixbuf pixbuf = null;
string method = null;
// check if a cached pixbuf is available to use, to avoid load-and-decode
if (cached_raw != null && cached_scale == scale && cached_photo_id.id == row.photo_id.id) {
if (cached_raw != null && cached_photo_id.id == row.photo_id.id) {
// verify that the scaled image required for this request matches the dimensions of
// the one in the cache
Dimensions scaled_image, scaled_to_viewport;
calculate_pixbuf_dimensions(scaling, exceptions, out scaled_image, out scaled_to_viewport);
if (scaled_image.approx_equals(Dimensions.for_pixbuf(cached_raw))) {
method = "USING CACHED";
#if MEASURE_PIPELINE
timer.start();
timer.start();
#endif
pixbuf = (no_copy) ? cached_raw : cached_raw.copy();
pixbuf = (no_copy) ? cached_raw : cached_raw.copy();
#if MEASURE_PIPELINE
pixbuf_copy_time = timer.elapsed();
pixbuf_copy_time = timer.elapsed();
#endif
} else {
} else {
method = "CACHE BLOWN";
}
}
if (pixbuf == null) {
method = "LOADING";
#if MEASURE_PIPELINE
timer.start();
#endif
pixbuf = load_raw_pixbuf(scale);
pixbuf = load_raw_pixbuf(scaling, exceptions);
#if MEASURE_PIPELINE
load_and_decode_time = timer.elapsed();
......@@ -791,15 +909,15 @@ public abstract class TransformablePhoto: PhotoBase {
// stash in the cache
cached_photo_id = row.photo_id;
cached_raw = (no_copy) ? pixbuf : pixbuf.copy();
cached_scale = scale;
#if MEASURE_PIPELINE
pixbuf_copy_time = timer.elapsed();
#endif
}
#if MEASURE_PIPELINE
debug("GET_RAW_PIXBUF (%d): load_and_decode=%lf pixbuf_copy=%lf total=%lf",
scale, load_and_decode_time, pixbuf_copy_time, total_timer.elapsed());
debug("GET_RAW_PIXBUF (%s) %s (%s): load_and_decode=%lf pixbuf_copy=%lf total=%lf", method,
to_string(), scaling.to_string(), load_and_decode_time, pixbuf_copy_time,
total_timer.elapsed());
#endif
return pixbuf;
......@@ -807,7 +925,7 @@ public abstract class TransformablePhoto: PhotoBase {
// Returns a raw, untransformed, scaled pixbuf from the source that has been rotated
// according to its original EXIF settings
public Gdk.Pixbuf get_original_pixbuf(int scale, Gdk.InterpType interp = DEFAULT_INTERP) throws Error {
public Gdk.Pixbuf get_original_pixbuf(Scaling scaling) throws Error {
#if MEASURE_PIPELINE
Timer timer = new Timer();
Timer total_timer = new Timer();
......@@ -818,7 +936,7 @@ public abstract class TransformablePhoto: PhotoBase {
// load-and-decode and scale
// no copy made because the pixbuf goes unmodified in this pipeline
Gdk.Pixbuf pixbuf = get_raw_pixbuf(scale, true);
Gdk.Pixbuf pixbuf = get_raw_pixbuf(scaling, Exception.NONE, true);
// orientation
#if MEASURE_PIPELINE
......@@ -828,29 +946,29 @@ public abstract class TransformablePhoto: PhotoBase {
#if MEASURE_PIPELINE
orientation_time = timer.elapsed();
debug("ORIGINAL PIPELINE: orientation=%lf total=%lf", orientation_time,
total_timer.elapsed());
debug("ORIGINAL PIPELINE %s (%s): orientation=%lf total=%lf", to_string(), scaling.to_string(),
orientation_time, total_timer.elapsed());
#endif
return pixbuf;
}