|
2 | 2 | using CoreGraphics; |
3 | 3 | using CoreMedia; |
4 | 4 | using Foundation; |
| 5 | +using ImageIO; |
5 | 6 | using OwlCore.Storage; |
6 | 7 | using SecureFolderFS.Maui.AppModels; |
7 | 8 | using SecureFolderFS.Maui.ServiceImplementation; |
@@ -51,33 +52,28 @@ private static async Task<IImageStream> GenerateImageThumbnailAsync(Stream strea |
51 | 52 | if (data is null) |
52 | 53 | throw new Exception("Failed to load image data."); |
53 | 54 |
|
54 | | - using var image = UIImage.LoadFromData(data); |
55 | | - if (image?.CGImage is null) |
56 | | - throw new Exception("Failed to load image."); |
| 55 | + using var source = CGImageSource.FromData(data); |
| 56 | + if (source is null) |
| 57 | + throw new Exception("Failed to create image source."); |
57 | 58 |
|
58 | | - // Apply EXIF orientation |
59 | | - var orientedImage = image.Orientation == UIImageOrientation.Up |
60 | | - ? image |
61 | | - : UIImage.FromImage(image.CGImage, 1.0f, image.Orientation); |
62 | | - |
63 | | - // Resize |
64 | | - var scale = Math.Min(maxSize / orientedImage.Size.Width, maxSize / orientedImage.Size.Height); |
65 | | - var newSize = new CGSize(orientedImage.Size.Width * scale, orientedImage.Size.Height * scale); |
66 | | - |
67 | | - UIGraphics.BeginImageContextWithOptions(newSize, false, 1.0f); |
68 | | - orientedImage.Draw(new CGRect(CGPoint.Empty, newSize)); |
69 | | - using var resizedImage = UIGraphics.GetImageFromCurrentImageContext(); |
70 | | - UIGraphics.EndImageContext(); |
71 | | - |
72 | | - if (resizedImage is null) |
73 | | - throw new Exception("Failed to resize image."); |
74 | | - |
75 | | - // Compress to JPEG |
76 | | - using var jpegData = resizedImage.AsJPEG(Constants.Browser.IMAGE_THUMBNAIL_QUALITY); |
| 59 | + var options = new CGImageThumbnailOptions |
| 60 | + { |
| 61 | + MaxPixelSize = (int)maxSize, |
| 62 | + ShouldAllowFloat = false, |
| 63 | + CreateThumbnailWithTransform = true, // handles EXIF orientation |
| 64 | + CreateThumbnailFromImageAlways = true |
| 65 | + }; |
| 66 | + |
| 67 | + using var cgImage = source.CreateThumbnail(0, options); |
| 68 | + if (cgImage is null) |
| 69 | + throw new Exception("Failed to create thumbnail."); |
| 70 | + |
| 71 | + using var image = UIImage.FromImage(cgImage); |
| 72 | + using var jpegData = image.AsJPEG(Constants.Browser.IMAGE_THUMBNAIL_QUALITY); |
77 | 73 | if (jpegData is null) |
78 | 74 | throw new FormatException("Failed to convert image to JPEG."); |
79 | 75 |
|
80 | | - var memoryStream = new MemoryStream(); |
| 76 | + var memoryStream = new MemoryStream((int)jpegData.Length); |
81 | 77 | await jpegData.AsStream().CopyToAsync(memoryStream).ConfigureAwait(false); |
82 | 78 | memoryStream.Position = 0L; |
83 | 79 |
|
@@ -115,10 +111,18 @@ private static async Task<IImageStream> GenerateVideoThumbnailAsync(Stream strea |
115 | 111 | }; |
116 | 112 |
|
117 | 113 | var actualTime = new CMTime((long)captureTime.TotalSeconds, 1); |
118 | | - var imageRef = generator.CopyCGImageAtTime(actualTime, out _, out var error); |
119 | | - if (imageRef is null || error != null) |
120 | | - throw new FormatException($"Failed to generate thumbnail: {error?.LocalizedDescription}"); |
| 114 | + var tcs = new TaskCompletionSource<CGImage>(TaskCreationOptions.RunContinuationsAsynchronously); |
| 115 | + var times = new[] { NSValue.FromCMTime(actualTime) }; |
| 116 | + |
| 117 | + generator.GenerateCGImagesAsynchronously(times, (_, image, _, _, error) => |
| 118 | + { |
| 119 | + if (error != null || image is null) |
| 120 | + tcs.TrySetException(new FormatException($"Failed to generate thumbnail: {error?.LocalizedDescription}")); |
| 121 | + else |
| 122 | + tcs.TrySetResult(image); |
| 123 | + }); |
121 | 124 |
|
| 125 | + using var imageRef = await tcs.Task.ConfigureAwait(false); |
122 | 126 | using var image = UIImage.FromImage(imageRef); |
123 | 127 | using var jpegData = image.AsJPEG(Constants.Browser.IMAGE_THUMBNAIL_QUALITY); |
124 | 128 | if (jpegData is null) |
|
0 commit comments