Commit d5309be8 authored by Jim Nelson's avatar Jim Nelson

#257: Removed 64-pixel thumbnails. Do not think at this time we need to have...

#257: Removed 64-pixel thumbnails.  Do not think at this time we need to have screen-sized thumbnails.  #621 
and #677: Applying color transformations is much speedier.  Pipeline only runs once, to generate thumbnails, and 
thumbnails are now generated in the idle queue.
parent 08c2cad0
......@@ -49,10 +49,10 @@ SRC_FILES = \
LibraryWindow.vala \
CameraTable.vala \
DirectWindow.vala \
Properties.vala \
CustomComponents.vala \
Config.vala \
Event.vala
Properties.vala \
CustomComponents.vala \
Config.vala \
Event.vala
VAPI_FILES = \
libexif.vapi \
......@@ -170,7 +170,7 @@ cleantemps:
package:
$(MAKE) dist
cp $(DIST_TAR_GZ) ../$(PACKAGE_ORIG_GZ)
cp $(DIST_TAR_GZ) $(PACKAGE_ORIG_GZ)
rm -f $(DIST_TAR_GZ)
rm -f $(DIST_TAR_BZ2)
......
......@@ -420,6 +420,7 @@ public class CollectionPage : CheckerboardPage {
private int scale = Thumbnail.DEFAULT_SCALE;
private bool improval_scheduled = false;
private bool reschedule_improval = false;
private bool layout_refresh_scheduled = false;
private Gee.ArrayList<File> drag_items = new Gee.ArrayList<File>();
private bool thumbs_resized = false;
private Gee.HashMap<LibraryPhoto, Thumbnail> thumbnail_map =
......@@ -702,10 +703,12 @@ public class CollectionPage : CheckerboardPage {
return;
photo.removed += on_photo_removed;
photo.thumbnail_altered += on_thumbnail_altered;
photo.altered += on_photo_altered;
Thumbnail thumbnail = new Thumbnail(photo, scale);
thumbnail.display_title(display_titles());
thumbnail.low_quality_thumbnail += schedule_thumbnail_improval;
thumbnail.geometry_changed += schedule_layout_refresh;
add_item(thumbnail);
thumbnail_map.set(photo, thumbnail);
......@@ -725,15 +728,8 @@ public class CollectionPage : CheckerboardPage {
slideshow_button.sensitive = (get_count() > 0);
}
private void on_thumbnail_altered(LibraryPhoto photo) {
private void on_photo_altered(LibraryPhoto photo) {
queryable_altered(photo);
// the thumbnail is only going to reload a low-quality interp, so schedule improval
schedule_thumbnail_improval();
// since the geometry might have changed, refresh the layout
if (is_in_view())
refresh();
}
private Thumbnail? get_thumbnail_for_photo(LibraryPhoto photo) {
......@@ -774,11 +770,6 @@ public class CollectionPage : CheckerboardPage {
foreach (LayoutItem item in get_items())
((Thumbnail) item).resize(scale);
if (is_in_view()) {
refresh();
schedule_thumbnail_improval();
}
}
private void schedule_thumbnail_improval() {
......@@ -814,6 +805,25 @@ public class CollectionPage : CheckerboardPage {
return false;
}
private void schedule_layout_refresh() {
if (!is_in_view())
return;
if (layout_refresh_scheduled)
return;
Idle.add_full(Priority.HIGH, background_refresh);
layout_refresh_scheduled = true;
}
private bool background_refresh() {
refresh();
layout_refresh_scheduled = false;
return false;
}
private void on_file_menu() {
set_item_sensitive("/CollectionMenuBar/FileMenu/Export", get_selected_count() > 0);
}
......@@ -949,7 +959,7 @@ public class CollectionPage : CheckerboardPage {
foreach (LayoutItem item in get_selected()) {
LibraryPhoto photo = ((Thumbnail) item).get_photo();
photo.removed -= on_photo_removed;
photo.thumbnail_altered -= on_thumbnail_altered;
photo.altered -= on_photo_altered;
photo.remove(result == Gtk.ResponseType.NO);
......@@ -963,17 +973,10 @@ public class CollectionPage : CheckerboardPage {
}
private void do_rotations(Gee.Iterable<LayoutItem> c, Rotation rotation) {
bool rotation_performed = false;
foreach (LayoutItem item in c) {
LibraryPhoto photo = ((Thumbnail) item).get_photo();
photo.rotate(rotation);
rotation_performed = true;
}
// geometry could've changed
if (rotation_performed)
refresh();
}
private void on_rotate_clockwise() {
......@@ -989,17 +992,10 @@ public class CollectionPage : CheckerboardPage {
}
private void on_revert() {
bool revert_performed = false;
foreach (LayoutItem item in get_selected()) {
LibraryPhoto photo = ((Thumbnail) item).get_photo();
photo.remove_all_transformations();
revert_performed = true;
}
// geometry could change
if (revert_performed)
refresh();
}
private void on_slideshow() {
......
......@@ -1193,8 +1193,9 @@ namespace AutoEnhance {
const float SHADOW_AGGRESSIVENESS_MUL = 0.5f;
const int EMPIRICAL_DARK = 50;
public void create_auto_enhance_adjustments(Gdk.Pixbuf pixbuf,
out PixelTransformation[] adjustments) {
public PixelTransformation[] create_auto_enhance_adjustments(Gdk.Pixbuf pixbuf) {
PixelTransformation[] adjustments = new PixelTransformation[SupportedAdjustments.NUM];
IntensityHistogram analysis_histogram = new IntensityHistogram(pixbuf);
/* compute the percentage of pixels in the image that fall into the shadow range --
this measures "of the pixels in the image, how many of them are in shadow?" */
......@@ -1255,6 +1256,8 @@ public void create_auto_enhance_adjustments(Gdk.Pixbuf pixbuf,
new ExposureTransformation(0.0f);
adjustments[SupportedAdjustments.SATURATION] =
new SaturationTransformation(0.0f);
return adjustments;
}
}
......@@ -324,7 +324,7 @@ public abstract class EditingTool {
public signal void deactivated();
public signal void applied(Gdk.Pixbuf? new_pixbuf);
public signal void applied(Gdk.Pixbuf? new_pixbuf, bool needs_improvement);
public signal void cancelled();
......@@ -616,9 +616,14 @@ public class CropTool : EditingTool {
// store the new crop
canvas.get_photo().set_crop(crop);
// crop the current pixbuf and offer it to the editing host
Gdk.Pixbuf cropped = new Gdk.Pixbuf.subpixbuf(canvas.get_scaled_pixbuf(), scaled_crop.left,
scaled_crop.top, scaled_crop.get_width(), scaled_crop.get_height());
// signal application; we don't have the cropped image, so the host needs to fetch it
applied(null);
// signal host; we have a cropped image, but it will be scaled upward, and so a better one
// should be fetched
applied(cropped, true);
}
private void update_cursor(int x, int y) {
......@@ -1140,7 +1145,7 @@ public class RedeyeTool : EditingTool {
}
private void on_close() {
applied(current_pixbuf);
applied(current_pixbuf, false);
}
private void on_canvas_resize() {
......@@ -1511,7 +1516,7 @@ public class AdjustTool : EditingTool {
AppWindow.get_instance().set_normal_cursor();
applied(draw_to_pixbuf);
applied(draw_to_pixbuf, false);
}
private void update_transformations(PixelTransformation[] new_transformations) {
......
......@@ -5,17 +5,27 @@
*/
class EventDirectoryItem : LayoutItem, EventSource {
public const int SCALE =
ThumbnailCache.MEDIUM_SCALE + ((ThumbnailCache.BIG_SCALE - ThumbnailCache.MEDIUM_SCALE) / 2);
public const int SCALE = ThumbnailCache.Size.MEDIUM.get_scale()
+ ((ThumbnailCache.Size.BIG.get_scale() - ThumbnailCache.Size.MEDIUM.get_scale()) / 2);
public Event event;
private bool is_exposed = false;
private LibraryPhoto primary_photo;
private Dimensions image_dim = Dimensions();
public EventDirectoryItem(Event event) {
this.event = event;
event.altered += update_display;
// monitor the primary photo's thumbnail
primary_photo = event.get_primary_photo();
primary_photo.thumbnail_altered += on_thumbnail_altered;
update_display();
// stash the image size for when it's not being displayed
image_dim = primary_photo.get_dimensions().get_scaled(SCALE);
// monitor the event for changes
event.altered += on_event_altered;
}
public time_t get_start_time() {
......@@ -38,9 +48,56 @@ class EventDirectoryItem : LayoutItem, EventSource {
return event.get_total_filesize();
}
public override void exposed() {
if (is_exposed)
return;
is_exposed = true;
update_display();
}
public override void unexposed() {
if (!is_exposed)
return;
is_exposed = false;
clear_image(image_dim.width, image_dim.height);
}
private void on_event_altered() {
// check if the primary photo changed
LibraryPhoto new_primary = event.get_primary_photo();
if (!new_primary.equals(primary_photo)) {
// disconnect and connect signal
primary_photo.thumbnail_altered -= on_thumbnail_altered;
primary_photo = new_primary;
primary_photo.thumbnail_altered += on_thumbnail_altered;
// update image dimensions
image_dim = primary_photo.get_dimensions().get_scaled(SCALE);
}
if (is_exposed)
update_display();
else
clear_image(image_dim.width, image_dim.height);
}
private void on_thumbnail_altered() {
// get new dimensions
image_dim = primary_photo.get_dimensions().get_scaled(SCALE);
if (is_exposed)
update_thumbnail_display();
}
private void update_display() {
set_title(event.get_name());
set_image(event.get_primary_photo().get_preview_pixbuf(Scaling.for_best_fit(SCALE)));
update_thumbnail_display();
}
private void update_thumbnail_display() {
set_image(primary_photo.get_preview_pixbuf(Scaling.for_best_fit(SCALE)));
}
}
......
......@@ -1389,7 +1389,7 @@ public class LibraryPhoto : TransformablePhoto {
private static Gee.HashMap<int64?, LibraryPhoto> photo_map = null;
private bool generate_thumbnails = true;
private bool block_thumbnail_generation = false;
public signal void thumbnail_altered();
......@@ -1469,23 +1469,29 @@ public class LibraryPhoto : TransformablePhoto {
notifier.removed(this);
}
private bool generate_thumbnails() {
// load transformed image for thumbnail generation
Gdk.Pixbuf pixbuf = null;
try {
pixbuf = get_pixbuf(Scaling.for_best_fit(ThumbnailCache.Size.BIG.get_scale()));
} catch (Error err) {
error("%s", err.message);
}
ThumbnailCache.import(get_photo_id(), pixbuf, true);
// fire signal that thumbnails have changed
thumbnail_altered();
return false;
}
private override void altered () {
// the exportable file is now not in sync with transformed photo
remove_exportable_file();
if (generate_thumbnails) {
// load transformed image for thumbnail generation
Gdk.Pixbuf pixbuf = null;
try {
pixbuf = get_pixbuf(Scaling.for_screen());
} catch (Error err) {
error("%s", err.message);
}
ThumbnailCache.import(get_photo_id(), pixbuf, true);
// fire signal that thumbnails have changed
thumbnail_altered();
}
// generate new thumbnails in the background
if (!block_thumbnail_generation)
Idle.add_full(Priority.LOW, generate_thumbnails);
base.altered();
......@@ -1493,26 +1499,26 @@ public class LibraryPhoto : TransformablePhoto {
}
public override Gdk.Pixbuf get_preview_pixbuf(Scaling scaling) {
Gdk.Pixbuf pixbuf = get_thumbnail(ThumbnailCache.BIG_SCALE);
Gdk.Pixbuf pixbuf = get_thumbnail(ThumbnailCache.Size.BIG);
return scaling.perform_on_pixbuf(pixbuf, Gdk.InterpType.NEAREST);
}
public override void rotate(Rotation rotation) {
// block thumbnail generation for this operation; taken care of below
generate_thumbnails = false;
block_thumbnail_generation = true;
base.rotate(rotation);
generate_thumbnails = true;
block_thumbnail_generation = false;
// because rotations are (a) common and available everywhere in the app, (b) the user expects
// a level of responsiveness not necessarily required by other modifications, (c) can be
// performed on multiple images simultaneously, and (d) can't cache a lot of full-sized
// pixbufs for rotate-and-scale ops, perform the rotation directly on the already-modified
// thumbnails.
foreach (int scale in ThumbnailCache.SCALES) {
Gdk.Pixbuf thumbnail = ThumbnailCache.fetch(get_photo_id(), scale);
foreach (ThumbnailCache.Size size in ThumbnailCache.ALL_SIZES) {
Gdk.Pixbuf thumbnail = ThumbnailCache.fetch(get_photo_id(), size);
thumbnail = rotation.perform(thumbnail);
ThumbnailCache.replace(get_photo_id(), scale, thumbnail);
ThumbnailCache.replace(get_photo_id(), size, thumbnail);
}
thumbnail_altered();
......
......@@ -131,6 +131,14 @@ public abstract class EditingHostPage : SinglePhotoPage {
return photo;
}
public override void switched_to() {
base.switched_to();
// check if the photo altered while away
if (pixbuf_scaling == null)
replace_photo(photo);
}
public override void switching_from() {
base.switching_from();
......@@ -151,7 +159,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
protected void replace_photo(TransformablePhoto new_photo) {
// if it's the same Photo object and the scaling hasn't changed, there's nothing to do
// otherwise, need to reload the image for the proper scaling
if (new_photo == photo && (pixbuf_scaling == null || pixbuf_scaling.equals(get_canvas_scaling())))
if (new_photo == photo && pixbuf_scaling != null && pixbuf_scaling.equals(get_canvas_scaling()))
return;
// only check if okay if there's something to replace
......@@ -217,7 +225,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
// message loop run to get the real pixbuf on-screen. If no transformations, don't bother.
// TODO: Allow viewing the original while a tool is activated.
if (original == null && photo.has_transformations() && current_tool == null)
Idle.add(fetch_original);
Idle.add_full(Priority.LOW, fetch_original);
return false;
}
......@@ -300,7 +308,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
default_repaint();
}
private void deactivate_tool(Gdk.Pixbuf? new_pixbuf = null) {
private void deactivate_tool(Gdk.Pixbuf? new_pixbuf = null, bool needs_improvement = false) {
if (current_tool == null)
return;
......@@ -317,15 +325,22 @@ public abstract class EditingHostPage : SinglePhotoPage {
// display the (possibly) new photo
Gdk.Pixbuf replacement = null;
if (new_pixbuf != null)
if (new_pixbuf != null) {
replacement = new_pixbuf;
else if (cancel_editing_pixbuf != null)
} else if (cancel_editing_pixbuf != null) {
replacement = cancel_editing_pixbuf;
else
needs_improvement = false;
} else {
replacement = photo.get_pixbuf(get_canvas_scaling());
needs_improvement = false;
}
set_pixbuf(replacement);
cancel_editing_pixbuf = null;
// if this is a rough pixbuf, schedule an improvement
if (needs_improvement)
Idle.add(update_pixbuf);
// return to fast interpolation for viewing
set_default_interp(FAST_INTERP);
......@@ -613,12 +628,15 @@ public abstract class EditingHostPage : SinglePhotoPage {
current_editing_toggle.active = false;
}
private void on_tool_applied(Gdk.Pixbuf? new_pixbuf) {
private void on_tool_applied(Gdk.Pixbuf? new_pixbuf, bool needs_improvement) {
// if the tool didn't supply a pixbuf, it's relying on the host to generate it from Photo
Gdk.Pixbuf final_pixbuf = (new_pixbuf != null) ? new_pixbuf
: photo.get_pixbuf(get_canvas_scaling());
Gdk.Pixbuf final_pixbuf = new_pixbuf;
if (final_pixbuf == null) {
final_pixbuf = photo.get_pixbuf(get_canvas_scaling());
needs_improvement = false;
}
deactivate_tool(final_pixbuf);
deactivate_tool(final_pixbuf, needs_improvement);
}
private void on_tool_cancelled() {
......@@ -654,10 +672,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
"transform histogram");
}
PixelTransformation[] transformations =
new PixelTransformation[SupportedAdjustments.NUM];
AutoEnhance.create_auto_enhance_adjustments(pixbuf, out transformations);
PixelTransformation[] transformations = AutoEnhance.create_auto_enhance_adjustments(pixbuf);
/* if the current tool is the adjust tool, then don't commit to the database --
just set the slider values in the adjust dialog and force it to repaint
......@@ -672,7 +687,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
AppWindow.get_instance().set_normal_cursor();
quick_update_pixbuf();
update_pixbuf();
}
private void place_tool_window() {
......
......@@ -19,6 +19,10 @@ public class Thumbnail : LayoutItem, PhotoSource {
private bool thumb_exposed = false;
private Gdk.InterpType interp = LOW_QUALITY_INTERP;
public signal void low_quality_thumbnail();
public signal void geometry_changed();
public Thumbnail(LibraryPhoto photo, int scale = DEFAULT_SCALE) {
this.photo = photo;
this.scale = scale;
......@@ -52,9 +56,12 @@ public class Thumbnail : LayoutItem, PhotoSource {
interp = LOW_QUALITY_INTERP;
set_image(pixbuf);
low_quality_thumbnail();
} else {
clear_image(dim.width, dim.height);
}
geometry_changed();
}
public void resize(int new_scale) {
......@@ -98,6 +105,7 @@ public class Thumbnail : LayoutItem, PhotoSource {
interp = LOW_QUALITY_INTERP;
set_image(pixbuf);
low_quality_thumbnail();
thumb_exposed = true;
}
......@@ -128,7 +136,7 @@ public class Thumbnail : LayoutItem, PhotoSource {
}
public uint64 get_filesize() {
return photo.get_filesize();
return photo.get_filesize();
}
}
......@@ -5,36 +5,43 @@
*/
public class ThumbnailCache : Object {
public const Gdk.InterpType DEFAULT_INTERP = Gdk.InterpType.HYPER;
public const int DEFAULT_JPEG_QUALITY = 90;
public const int MAX_INMEMORY_DATA_SIZE = 256 * 1024;
public const Gdk.InterpType DEFAULT_INTERP = Gdk.InterpType.BILINEAR;
public const Jpeg.Quality DEFAULT_QUALITY = Jpeg.Quality.HIGH;
public const int MAX_INMEMORY_DATA_SIZE = 512 * 1024;
public const int BIG_SCALE = 360;
public const int MEDIUM_SCALE = 128;
public const int SMALL_SCALE = 64;
public enum Size {
BIG = 360,
MEDIUM = 128;
public int get_scale() {
return (int) this;
}
public static Size get_best_size(int scale) {
return scale <= MEDIUM.get_scale() ? MEDIUM : BIG;
}
}
public const int[] SCALES = { BIG_SCALE, MEDIUM_SCALE, SMALL_SCALE };
public static const Size[] ALL_SIZES = { Size.BIG, Size.MEDIUM };
public const ulong KBYTE = 1024;
public const ulong MBYTE = 1024 * KBYTE;
public const ulong MAX_BIG_CACHED_BYTES = 25 * MBYTE;
public const ulong MAX_MEDIUM_CACHED_BYTES = 25 * MBYTE;
public const ulong MAX_SMALL_CACHED_BYTES = 10 * MBYTE;
public const ulong MAX_BIG_CACHED_BYTES = 40 * MBYTE;
public const ulong MAX_MEDIUM_CACHED_BYTES = 30 * MBYTE;
private static ThumbnailCache big = null;
private static ThumbnailCache medium = null;
private static ThumbnailCache small = null;
private static int cycle_fetched_thumbnails = 0;
private static int cycle_overflow_thumbnails = 0;
private static bool debug_scheduled = false;
private File cache_dir;
private int scale;
private Size size;
private ulong max_cached_bytes;
private Gdk.InterpType interp;
private string jpeg_quality;
private Jpeg.Quality quality;
private Gee.HashMap<int64?, ImageData> cache_map = new Gee.HashMap<int64?, ImageData>(
int64_hash, int64_equal, direct_equal);
private Gee.ArrayList<int64?> cache_lru = new Gee.ArrayList<int64?>(int64_equal);
......@@ -42,24 +49,20 @@ public class ThumbnailCache : Object {
private ThumbnailCacheTable cache_table;
private PhotoTable photo_table = new PhotoTable();
private ThumbnailCache(int scale, ulong max_cached_bytes, Gdk.InterpType interp = DEFAULT_INTERP,
int jpeg_quality = DEFAULT_JPEG_QUALITY) {
assert(scale != 0);
assert((jpeg_quality >= 0) && (jpeg_quality <= 100));
this.cache_dir = AppWindow.get_data_subdir("thumbs", "thumbs%d".printf(scale));
this.scale = scale;
private ThumbnailCache(Size size, ulong max_cached_bytes, Gdk.InterpType interp = DEFAULT_INTERP,
Jpeg.Quality quality = DEFAULT_QUALITY) {
this.cache_dir = AppWindow.get_data_subdir("thumbs", "thumbs%d".printf(size.get_scale()));
this.size = size;
this.max_cached_bytes = max_cached_bytes;
this.interp = interp;
this.jpeg_quality = "%d".printf(jpeg_quality);
this.cache_table = new ThumbnailCacheTable(scale);
this.quality = quality;
this.cache_table = new ThumbnailCacheTable(size.get_scale());
}
// Doing this because static construct {} not working nor new'ing in the above statement
public static void init() {
big = new ThumbnailCache(BIG_SCALE, MAX_BIG_CACHED_BYTES);
medium = new ThumbnailCache(MEDIUM_SCALE, MAX_MEDIUM_CACHED_BYTES);
small = new ThumbnailCache(SMALL_SCALE, MAX_SMALL_CACHED_BYTES);
big = new ThumbnailCache(Size.BIG, MAX_BIG_CACHED_BYTES);
medium = new ThumbnailCache(Size.MEDIUM, MAX_MEDIUM_CACHED_BYTES);
}
public static void terminate() {
......@@ -71,9 +74,6 @@ public class ThumbnailCache : Object {
medium._import(photo_id, original, force);
spin_event_loop();
small._import(photo_id, original, force);
spin_event_loop();
}
public static void remove(PhotoID photo_id) {
......@@ -82,38 +82,32 @@ public class ThumbnailCache : Object {
medium._remove(photo_id);
spin_event_loop();
small._remove(photo_id);
spin_event_loop();
}
public static Gdk.Pixbuf? fetch(PhotoID photo_id, int scale) {
if (scale > MEDIUM_SCALE) {
Size size = Size.get_best_size(scale);
if (size == Size.BIG) {
return big._fetch(photo_id);
} else if(scale > SMALL_SCALE) {
return medium._fetch(photo_id);
} else {
return small._fetch(photo_id);
assert(size == Size.MEDIUM);
return medium._fetch(photo_id);
}
}
public static void replace(PhotoID photo_id, int scale, Gdk.Pixbuf replacement) {
public static void replace(PhotoID photo_id, Size size, Gdk.Pixbuf replacement) {
ThumbnailCache cache = null;
switch (scale) {
case SMALL_SCALE:
cache = small;
switch (size) {
case Size.BIG:
cache = big;
break;
case MEDIUM_SCALE:
case Size.MEDIUM:
cache = medium;
break;
case BIG_SCALE:
cache = big;
break;
default:
error("Unknown scale %d", scale);
error("Unknown thumbnail size %d", size.get_scale());
break;
}
......@@ -121,7 +115,7 @@ public class ThumbnailCache : Object {
}
public static bool exists(PhotoID photo_id) {
return big._exists(photo_id) && medium._exists(photo_id) && small._exists(photo_id);
return big._exists(photo_id) && medium._exists(photo_id);
}
private class ImageData {
......@@ -182,15 +176,6 @@ public class ThumbnailCache : Object {
cycle_fetched_thumbnails++;
schedule_debug();
int filesize = cache_table.get_filesize(photo_id);
if(filesize > MAX_INMEMORY_DATA_SIZE) {
// too big to store in memory, so return the pixbuf straight from disk
debug("Persistant thumbnail [%lld] %s too large to cache in memory, loading straight from disk",
photo_id.id, file.get_path());
return pixbuf;
}
// stash in memory for next time
store_in_memory(photo_id, pixbuf);
......@@ -214,7 +199,7 @@ public class ThumbnailCache : Object {
photo_id.id, file.get_path());
// scale according to cache's parameters
Gdk.Pixbuf scaled = scale_pixbuf(original, scale, interp);
Gdk.Pixbuf scaled = scale_pixbuf(original, size.get_scale(), interp);
// save scaled image as JPEG
int filesize = -1;
......@@ -242,7 +227,7 @@ public class ThumbnailCache : Object {
remove_from_memory(photo_id);
// scale to cache's parameters
Gdk.Pixbuf scaled = scale_pixbuf(original, scale, interp);
Gdk.Pixbuf scaled = scale_pixbuf(original, size.get_scale(), interp);
// save scaled image as JPEG
int filesize = -1;
......@@ -296,6 +281,14 @@ public class ThumbnailCache : Object {
remove_from_memory(photo_id);
ImageData data = new ImageData(thumbnail);
// see if this is too large to keep in memory
if(data.bytes > MAX_INMEMORY_DATA_SIZE) {
debug("Persistant thumbnail [%lld] too large to cache in memory", photo_id.id);
return;
}
cache_map.set(photo_id.id, data);
cache_lru.insert(0, photo_id.id);
......@@ -310,14 +303,15 @@ public class ThumbnailCache : Object {
cache_lru.remove_at(index);
data = cache_map.get(id);
assert(data.bytes <= cached_bytes);
cached_bytes -= data.bytes;