load thumbnails for everything, async'ly on load

This commit is contained in:
Colin McMillen 2023-08-24 22:36:52 -04:00
parent cbea050334
commit 50b0e8d8e0
2 changed files with 31 additions and 12 deletions

View File

@ -29,12 +29,15 @@ public class Photo {
private static long touchCounter = 0; private static long touchCounter = 0;
private Texture texture; private Texture texture;
private Texture placeholder; private Texture placeholder;
private Texture thumbnailTexture;
private Image<Rgba32>? image = null; private Image<Rgba32>? image = null;
private Image<Rgba32>? thumbnail = null;
public Photo(string filename, Texture placeholder) { public Photo(string filename, Texture placeholder) {
Filename = filename; Filename = filename;
this.placeholder = placeholder; this.placeholder = placeholder;
texture = placeholder; texture = placeholder;
thumbnailTexture = placeholder;
DateTime creationTime = File.GetCreationTime(filename); // Local time. DateTime creationTime = File.GetCreationTime(filename); // Local time.
DateTimeOriginal = creationTime; DateTimeOriginal = creationTime;
@ -49,12 +52,18 @@ public class Photo {
// edit the image due to rotation (etc) and don't want to try generating // edit the image due to rotation (etc) and don't want to try generating
// a texture for it until that's already happened. // a texture for it until that's already happened.
LastTouch = touchCounter++; LastTouch = touchCounter++;
Image<Rgba32> tmp = await Image.LoadAsync<Rgba32>(Filename);
Util.RotateImageFromExif(tmp, Orientation);
image = tmp;
}
public async void LoadThumbnailAsync() {
DecoderOptions options = new DecoderOptions { DecoderOptions options = new DecoderOptions {
TargetSize = new Size(256, 256) TargetSize = new Size(256, 256)
}; };
Image<Rgba32> tmp = await Image.LoadAsync<Rgba32>(options, Filename); Image<Rgba32> tmp = await Image.LoadAsync<Rgba32>(options, Filename);
Util.RotateImageFromExif(tmp, Orientation); Util.RotateImageFromExif(tmp, Orientation);
image = tmp; thumbnail = tmp;
} }
public void Unload() { public void Unload() {
@ -273,16 +282,21 @@ public class Photo {
public Texture Texture() { public Texture Texture() {
LastTouch = touchCounter++; LastTouch = touchCounter++;
if (thumbnailTexture == placeholder && thumbnail != null) {
thumbnailTexture = new(thumbnail);
thumbnail.Dispose();
thumbnail = null;
}
if (texture == placeholder && image != null) { if (texture == placeholder && image != null) {
// The texture needs to be created on the GL thread, so we instantiate // 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 // it here (since this is called from OnRenderFrame), as long as the
// image is ready to go. // image is ready to go.
texture = new Texture(image); texture = new(image);
image.Dispose(); image.Dispose();
image = null; image = null;
Loaded = true; Loaded = true;
} }
return texture; return texture != placeholder ? texture : thumbnailTexture;
} }
public string Description() { public string Description() {

View File

@ -652,6 +652,8 @@ public class Game : GameWindow {
allPhotos.Sort(ComparePhotosByDate); allPhotos.Sort(ComparePhotosByDate);
photos = allPhotos; photos = allPhotos;
LoadThumbnailsAsync();
} }
private static int ComparePhotosByDate(Photo x, Photo y) { private static int ComparePhotosByDate(Photo x, Photo y) {
@ -669,11 +671,9 @@ public class Game : GameWindow {
} }
private void UnloadImages() { private void UnloadImages() {
return;
// Unload images that haven't been touched in a while. // Unload images that haven't been touched in a while.
// FIXME: keep around thumbnail-sized textures?
lock (loadedImagesLock) { lock (loadedImagesLock) {
while (loadedImages.Count > 100) { while (loadedImages.Count > 30) {
long earliestTime = long.MaxValue; long earliestTime = long.MaxValue;
Photo? earliest = null; Photo? earliest = null;
foreach (Photo photo in loadedImages) { 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. // Start loading any images that are in our window but not yet loaded.
int minLoadedImage = Math.Max(0, photoIndex - 30); int minLoadedImage = Math.Max(0, photoIndex - 10);
// int maxLoadedImage = Math.Min(photoIndex + 30, photos.Count - 1); int maxLoadedImage = Math.Min(photoIndex + 10, photos.Count - 1);
int maxLoadedImage = photos.Count - 1;
List<Photo> toLoad = new(); List<Photo> toLoad = new();
for (int i = minLoadedImage; i <= maxLoadedImage; i++) { for (int i = minLoadedImage; i <= maxLoadedImage; i++) {
lock (loadedImagesLock) { 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: // To find the JPEG compression level of a file from the command line:
// $ identify -verbose image.jpg | grep Quality: // $ identify -verbose image.jpg | grep Quality:
// FIXME: don't ExportPhotos() if another export is already active. // FIXME: don't ExportPhotos() if another export is already active.
@ -988,9 +993,9 @@ static class Program {
nwSettings.CurrentMonitor = bestMonitor.Handle; nwSettings.CurrentMonitor = bestMonitor.Handle;
nwSettings.Location = new Vector2i(bestMonitor.WorkArea.Min.X + 1, nwSettings.Location = new Vector2i(bestMonitor.WorkArea.Min.X + 1,
bestMonitor.WorkArea.Min.Y + 31); bestMonitor.WorkArea.Min.Y + 31);
// nwSettings.Size = new Vector2i(bestMonitor.WorkArea.Size.X - 2, nwSettings.Size = new Vector2i(bestMonitor.WorkArea.Size.X - 2,
// bestMonitor.WorkArea.Size.Y - 32); bestMonitor.WorkArea.Size.Y - 32);
nwSettings.Size = new Vector2i(1600, 900); // nwSettings.Size = new Vector2i(1600, 900);
nwSettings.MinimumSize = UiGeometry.MIN_WINDOW_SIZE; nwSettings.MinimumSize = UiGeometry.MIN_WINDOW_SIZE;
nwSettings.Title = "Totte"; nwSettings.Title = "Totte";
nwSettings.IsEventDriven = true; nwSettings.IsEventDriven = true;