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,
|
asset: Asset,
|
||||||
dateOffset: Int,
|
dateOffset: Int,
|
||||||
subtitle: String? = nil,
|
subtitle: String? = nil,
|
||||||
forceFullColor: Bool = true
|
forceFullColor: Bool = true,
|
||||||
|
targetSize: CGSize? = nil
|
||||||
)
|
)
|
||||||
async throws -> Self
|
async throws -> Self
|
||||||
{
|
{
|
||||||
@@ -29,7 +30,7 @@ struct ImageEntry: TimelineEntry {
|
|||||||
value: dateOffset * 20,
|
value: dateOffset * 20,
|
||||||
to: Date.now
|
to: Date.now
|
||||||
)!
|
)!
|
||||||
let image = try await api.fetchImage(asset: asset)
|
let image = try await api.fetchImage(asset: asset, targetSize: targetSize)
|
||||||
|
|
||||||
return Self(
|
return Self(
|
||||||
date: entryDate,
|
date: entryDate,
|
||||||
@@ -133,7 +134,8 @@ func generateRandomEntries(
|
|||||||
count: Int,
|
count: Int,
|
||||||
filter: SearchFilter = Album.NONE.filter,
|
filter: SearchFilter = Album.NONE.filter,
|
||||||
subtitle: String? = nil,
|
subtitle: String? = nil,
|
||||||
forceFullColor: Bool = true
|
forceFullColor: Bool = true,
|
||||||
|
targetSize: CGSize? = nil
|
||||||
)
|
)
|
||||||
async throws -> [ImageEntry]
|
async throws -> [ImageEntry]
|
||||||
{
|
{
|
||||||
@@ -150,7 +152,8 @@ func generateRandomEntries(
|
|||||||
asset: asset,
|
asset: asset,
|
||||||
dateOffset: dateOffset,
|
dateOffset: dateOffset,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
forceFullColor: forceFullColor
|
forceFullColor: forceFullColor,
|
||||||
|
targetSize: targetSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ class ImmichAPI {
|
|||||||
return try JSONDecoder().decode([MemoryResult].self, from: data)
|
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 thumbnailParams = [URLQueryItem(name: "size", value: "preview"), URLQueryItem(name: "edited", value: "true")]
|
||||||
let assetEndpoint = "/assets/" + asset.id + "/thumbnail"
|
let assetEndpoint = "/assets/" + asset.id + "/thumbnail"
|
||||||
|
|
||||||
@@ -245,10 +245,23 @@ class ImmichAPI {
|
|||||||
else {
|
else {
|
||||||
throw .invalidURL
|
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] = [
|
let decodeOptions: [NSString: Any] = [
|
||||||
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
||||||
kCGImageSourceThumbnailMaxPixelSize: 512,
|
kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
|
||||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -262,6 +275,17 @@ class ImmichAPI {
|
|||||||
throw .fetchFailed
|
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)
|
return UIImage(cgImage: thumbnail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,13 +113,15 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
|||||||
// this must be a do/catch since we need to
|
// this must be a do/catch since we need to
|
||||||
// distinguish between a network fail and an empty search
|
// distinguish between a network fail and an empty search
|
||||||
do {
|
do {
|
||||||
|
let targetSize = CGSize(width: context.displaySize.width * UIScreen.main.scale, height: context.displaySize.height * UIScreen.main.scale)
|
||||||
let search = try await generateRandomEntries(
|
let search = try await generateRandomEntries(
|
||||||
api: api,
|
api: api,
|
||||||
now: now,
|
now: now,
|
||||||
count: 12,
|
count: 12,
|
||||||
filter: album.filter,
|
filter: album.filter,
|
||||||
subtitle: configuration.showAlbumName ? albumName : nil,
|
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
|
// Load or save a cached asset for when network conditions are bad
|
||||||
|
|||||||
Reference in New Issue
Block a user