field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraMediaThumbnail.swift (2356B)


      1 import Foundation
      2 import ImageIO
      3 
      4 nonisolated enum TeraMediaThumbnailFailure: Error {
      5   case corrupt
      6   case resourceLimit
      7 }
      8 
      9 nonisolated enum TeraMediaThumbnail {
     10   /// The caller owns this task and its worker slot through decode completion.
     11   static func prepare(_ artifact: TeraVerifiedMediaArtifact,
     12                       limits: TeraMediaPresentationLimits) async throws -> CGImage
     13   {
     14     let task = Task.detached(priority: .utility) {
     15       try decode(artifact, limits: limits)
     16     }
     17     return try await withTaskCancellationHandler {
     18       try await task.value
     19     } onCancel: {
     20       task.cancel()
     21     }
     22   }
     23 
     24   private static func decode(_ artifact: TeraVerifiedMediaArtifact,
     25                              limits: TeraMediaPresentationLimits) throws -> CGImage
     26   {
     27     assert(!Thread.isMainThread, "Raster decoding must stay off the UI thread")
     28     try Task.checkCancellation()
     29     guard limits.accepts(byteCount: artifact.bytes.count, width: Int(artifact.width),
     30                          height: Int(artifact.height)) else { throw TeraMediaThumbnailFailure.resourceLimit }
     31     guard let source = CGImageSourceCreateWithData(artifact.bytes as CFData,
     32                                                    [kCGImageSourceShouldCache: false] as CFDictionary),
     33       let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any],
     34       let width = properties[kCGImagePropertyPixelWidth] as? Int,
     35       let height = properties[kCGImagePropertyPixelHeight] as? Int,
     36       width == Int(artifact.width), height == Int(artifact.height)
     37     else {
     38       throw TeraMediaThumbnailFailure.corrupt
     39     }
     40     guard limits.accepts(byteCount: artifact.bytes.count, width: width, height: height) else {
     41       throw TeraMediaThumbnailFailure.resourceLimit
     42     }
     43     let options: [CFString: Any] = [
     44       kCGImageSourceCreateThumbnailFromImageAlways: true,
     45       kCGImageSourceThumbnailMaxPixelSize: limits.thumbnailDimension,
     46       kCGImageSourceCreateThumbnailWithTransform: true,
     47       kCGImageSourceShouldCacheImmediately: true,
     48     ]
     49     guard let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary),
     50       image.width <= limits.thumbnailDimension, image.height <= limits.thumbnailDimension
     51     else {
     52       throw TeraMediaThumbnailFailure.corrupt
     53     }
     54     try Task.checkCancellation()
     55     return image
     56   }
     57 }