fix(mobile): render random widget images at the widget's display resolution (#32215)
* fix(mobile): render random widget images at the widget's display resolution * fix(mobile): cap widget image decode size for panoramas (cherry picked from commit 3d07c061a38275f8b0d406e1ecc67446497be0e2)
This commit is contained in:
@@ -20,7 +20,8 @@ struct ImageEntry: TimelineEntry {
|
||||
asset: Asset,
|
||||
dateOffset: Int,
|
||||
subtitle: String? = nil,
|
||||
forceFullColor: Bool = true
|
||||
forceFullColor: Bool = true,
|
||||
targetSize: CGSize? = nil
|
||||
)
|
||||
async throws -> Self
|
||||
{
|
||||
@@ -29,7 +30,7 @@ struct ImageEntry: TimelineEntry {
|
||||
value: dateOffset * 20,
|
||||
to: Date.now
|
||||
)!
|
||||
let image = try await api.fetchImage(asset: asset)
|
||||
let image = try await api.fetchImage(asset: asset, targetSize: targetSize)
|
||||
|
||||
return Self(
|
||||
date: entryDate,
|
||||
@@ -133,7 +134,8 @@ func generateRandomEntries(
|
||||
count: Int,
|
||||
filter: SearchFilter = Album.NONE.filter,
|
||||
subtitle: String? = nil,
|
||||
forceFullColor: Bool = true
|
||||
forceFullColor: Bool = true,
|
||||
targetSize: CGSize? = nil
|
||||
)
|
||||
async throws -> [ImageEntry]
|
||||
{
|
||||
@@ -150,7 +152,8 @@ func generateRandomEntries(
|
||||
asset: asset,
|
||||
dateOffset: dateOffset,
|
||||
subtitle: subtitle,
|
||||
forceFullColor: forceFullColor
|
||||
forceFullColor: forceFullColor,
|
||||
targetSize: targetSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ class ImmichAPI {
|
||||
return try JSONDecoder().decode([MemoryResult].self, from: data)
|
||||
}
|
||||
|
||||
func fetchImage(asset: Asset) async throws(FetchError) -> UIImage {
|
||||
func fetchImage(asset: Asset, targetSize: CGSize? = nil) async throws(FetchError) -> UIImage {
|
||||
let thumbnailParams = [URLQueryItem(name: "size", value: "preview"), URLQueryItem(name: "edited", value: "true")]
|
||||
let assetEndpoint = "/assets/" + asset.id + "/thumbnail"
|
||||
|
||||
@@ -245,10 +245,23 @@ class ImmichAPI {
|
||||
else {
|
||||
throw .invalidURL
|
||||
}
|
||||
var maxPixelSize = 512
|
||||
// Decoding to fill the widget, a panorama's long edge can exceed the memory limit.
|
||||
// To prevent this, cap the long side at 2x the widget.
|
||||
let maxDecodeMultiplier = 2
|
||||
|
||||
if let targetSize,
|
||||
let props = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
|
||||
let width = props[kCGImagePropertyPixelWidth] as? Int,
|
||||
let height = props[kCGImagePropertyPixelHeight] as? Int {
|
||||
let scale = max(targetSize.width / CGFloat(width), targetSize.height / CGFloat(height))
|
||||
maxPixelSize = Int(ceil(CGFloat(max(width, height)) * scale))
|
||||
maxPixelSize = min(maxPixelSize, Int(max(targetSize.width, targetSize.height)) * maxDecodeMultiplier)
|
||||
}
|
||||
|
||||
let decodeOptions: [NSString: Any] = [
|
||||
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
||||
kCGImageSourceThumbnailMaxPixelSize: 512,
|
||||
kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
|
||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||
]
|
||||
|
||||
@@ -262,6 +275,17 @@ class ImmichAPI {
|
||||
throw .fetchFailed
|
||||
}
|
||||
|
||||
if let targetSize {
|
||||
let ratio = targetSize.width / targetSize.height
|
||||
let cropWidth = min(CGFloat(thumbnail.width), CGFloat(thumbnail.height) * ratio)
|
||||
let cropHeight = min(CGFloat(thumbnail.height), CGFloat(thumbnail.width) / ratio)
|
||||
let x = (CGFloat(thumbnail.width) - cropWidth) / 2
|
||||
let y = (CGFloat(thumbnail.height) - cropHeight) / 2
|
||||
if let cropped = thumbnail.cropping(to: CGRect(x: x, y: y, width: cropWidth, height: cropHeight)) {
|
||||
return UIImage(cgImage: cropped)
|
||||
}
|
||||
}
|
||||
|
||||
return UIImage(cgImage: thumbnail)
|
||||
}
|
||||
|
||||
|
||||
@@ -113,13 +113,15 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
||||
// this must be a do/catch since we need to
|
||||
// distinguish between a network fail and an empty search
|
||||
do {
|
||||
let targetSize = CGSize(width: context.displaySize.width * UIScreen.main.scale, height: context.displaySize.height * UIScreen.main.scale)
|
||||
let search = try await generateRandomEntries(
|
||||
api: api,
|
||||
now: now,
|
||||
count: 12,
|
||||
filter: album.filter,
|
||||
subtitle: configuration.showAlbumName ? albumName : nil,
|
||||
forceFullColor: configuration.forceFullColor
|
||||
forceFullColor: configuration.forceFullColor,
|
||||
targetSize: targetSize
|
||||
)
|
||||
|
||||
// Load or save a cached asset for when network conditions are bad
|
||||
|
||||
Reference in New Issue
Block a user