diff --git a/Photo.cs b/Photo.cs index 8941f9c..c3149f1 100644 --- a/Photo.cs +++ b/Photo.cs @@ -29,12 +29,15 @@ public class Photo { private static long touchCounter = 0; private Texture texture; private Texture placeholder; + private Texture thumbnailTexture; private Image? image = null; + private Image? thumbnail = null; public Photo(string filename, Texture placeholder) { Filename = filename; this.placeholder = placeholder; texture = placeholder; + thumbnailTexture = placeholder; DateTime creationTime = File.GetCreationTime(filename); // Local time. DateTimeOriginal = creationTime; @@ -49,12 +52,18 @@ public class Photo { // edit the image due to rotation (etc) and don't want to try generating // a texture for it until that's already happened. LastTouch = touchCounter++; + Image tmp = await Image.LoadAsync(Filename); + Util.RotateImageFromExif(tmp, Orientation); + image = tmp; + } + + public async void LoadThumbnailAsync() { DecoderOptions options = new DecoderOptions { TargetSize = new Size(256, 256) }; Image tmp = await Image.LoadAsync(options, Filename); Util.RotateImageFromExif(tmp, Orientation); - image = tmp; + thumbnail = tmp; } public void Unload() { @@ -273,16 +282,21 @@ public class Photo { public Texture Texture() { LastTouch = touchCounter++; + if (thumbnailTexture == placeholder && thumbnail != null) { + thumbnailTexture = new(thumbnail); + thumbnail.Dispose(); + thumbnail = null; + } if (texture == placeholder && image != null) { // The texture needs to be created on the GL thread, so we instantiate // it here (since this is called from OnRenderFrame), as long as the // image is ready to go. - texture = new Texture(image); + texture = new(image); image.Dispose(); image = null; Loaded = true; } - return texture; + return texture != placeholder ? texture : thumbnailTexture; } public string Description() { diff --git a/Program.cs b/Program.cs index 4169425..8901152 100644 --- a/Program.cs +++ b/Program.cs @@ -652,6 +652,8 @@ public class Game : GameWindow { allPhotos.Sort(ComparePhotosByDate); photos = allPhotos; + + LoadThumbnailsAsync(); } private static int ComparePhotosByDate(Photo x, Photo y) { @@ -669,11 +671,9 @@ public class Game : GameWindow { } private void UnloadImages() { - return; // Unload images that haven't been touched in a while. - // FIXME: keep around thumbnail-sized textures? lock (loadedImagesLock) { - while (loadedImages.Count > 100) { + while (loadedImages.Count > 30) { long earliestTime = long.MaxValue; Photo? earliest = null; foreach (Photo photo in loadedImages) { @@ -704,9 +704,8 @@ public class Game : GameWindow { } } // Start loading any images that are in our window but not yet loaded. - int minLoadedImage = Math.Max(0, photoIndex - 30); - // int maxLoadedImage = Math.Min(photoIndex + 30, photos.Count - 1); - int maxLoadedImage = photos.Count - 1; + int minLoadedImage = Math.Max(0, photoIndex - 10); + int maxLoadedImage = Math.Min(photoIndex + 10, photos.Count - 1); List toLoad = new(); for (int i = minLoadedImage; i <= maxLoadedImage; i++) { lock (loadedImagesLock) { @@ -722,6 +721,12 @@ public class Game : GameWindow { } } + private async void LoadThumbnailsAsync() { + foreach (Photo p in allPhotos) { + await Task.Run( () => { p.LoadThumbnailAsync(); }); + } + } + // To find the JPEG compression level of a file from the command line: // $ identify -verbose image.jpg | grep Quality: // FIXME: don't ExportPhotos() if another export is already active. @@ -988,9 +993,9 @@ static class Program { nwSettings.CurrentMonitor = bestMonitor.Handle; nwSettings.Location = new Vector2i(bestMonitor.WorkArea.Min.X + 1, bestMonitor.WorkArea.Min.Y + 31); - // nwSettings.Size = new Vector2i(bestMonitor.WorkArea.Size.X - 2, - // bestMonitor.WorkArea.Size.Y - 32); - nwSettings.Size = new Vector2i(1600, 900); + nwSettings.Size = new Vector2i(bestMonitor.WorkArea.Size.X - 2, + bestMonitor.WorkArea.Size.Y - 32); + // nwSettings.Size = new Vector2i(1600, 900); nwSettings.MinimumSize = UiGeometry.MIN_WINDOW_SIZE; nwSettings.Title = "Totte"; nwSettings.IsEventDriven = true;