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 }