Skip to content

Commit d9a463a

Browse files
authored
Fix library optimization stalling on Intel macs (#268)
Fixes a bug where upgrade from v1.4.2 to v1.5.x would stall on Intel macs while running v8_convert_artwork_to_heic db migration due to slower HEIC hardware encoding support on Intel processors. Fixes #265
1 parent c784d1a commit d9a463a

1 file changed

Lines changed: 62 additions & 4 deletions

File tree

Utilities/ImageUtils.swift

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ enum ImageUtils {
1818
quality: CGFloat = 0.8,
1919
source: String? = nil
2020
) -> Data? {
21-
guard let image = NSImage(data: imageData),
22-
let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
21+
#if arch(x86_64)
22+
return compressImageIntel(from: imageData, maxDimension: maxDimension, quality: quality, source: source)
23+
#else
24+
guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil),
25+
let cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
2326
let context = source.map { " from \($0)" } ?? ""
2427
Logger.warning("Failed to create image\(context) (\(imageData.count) bytes)")
2528
return nil
@@ -64,6 +67,7 @@ enum ImageUtils {
6467
let logContext = source.map { " from \($0)" } ?? ""
6568
Logger.warning("HEIC encoding failed\(logContext), falling back to JPEG")
6669
return resizeImage(from: imageData, to: targetSize)
70+
#endif
6771
}
6872

6973
/// Encode a CGImage as HEIC data.
@@ -85,6 +89,25 @@ enum ImageUtils {
8589
return data as Data
8690
}
8791

92+
/// Encode a CGImage as JPEG data.
93+
static func encodeJPEG(_ cgImage: CGImage, quality: CGFloat = 0.8) -> Data? {
94+
let data = NSMutableData()
95+
guard let destination = CGImageDestinationCreateWithData(
96+
data as CFMutableData,
97+
UTType.jpeg.identifier as CFString,
98+
1,
99+
nil
100+
) else { return nil }
101+
CGImageDestinationAddImage(destination, cgImage, [
102+
kCGImageDestinationLossyCompressionQuality: quality
103+
] as CFDictionary)
104+
guard CGImageDestinationFinalize(destination) else {
105+
Logger.warning("Failed to encode image as JPEG")
106+
return nil
107+
}
108+
return data as Data
109+
}
110+
88111
/// Resize an image from Data to specified size and return as JPEG Data
89112
/// - Parameters:
90113
/// - imageData: Original image data
@@ -117,8 +140,7 @@ enum ImageUtils {
117140

118141
guard let resizedCG = context.makeImage() else { return nil }
119142

120-
let bitmapRep = NSBitmapImageRep(cgImage: resizedCG)
121-
return bitmapRep.representation(using: .jpeg, properties: [.compressionFactor: compressionFactor])
143+
return encodeJPEG(resizedCG, quality: CGFloat(compressionFactor))
122144
}
123145

124146
/// Extract dominant colors from image data using grid-based sampling with diversity selection.
@@ -391,6 +413,42 @@ enum ImageUtils {
391413
guard let cgImage = ctx.makeImage() else { return nil }
392414
return NSBitmapImageRep(cgImage: cgImage).representation(using: .jpeg, properties: [.compressionFactor: 0.85])
393415
}
416+
417+
// MARK: - Intel x86_64 fallback
418+
// Software HEVC encode (VCPHEVC) on Intel deadlocks under concurrent scans (issue #265).
419+
// Resize-and-JPEG bypasses the encoder entirely. Remove this block when Intel support is dropped.
420+
421+
#if arch(x86_64)
422+
private static func compressImageIntel(
423+
from imageData: Data,
424+
maxDimension: CGFloat,
425+
quality: CGFloat,
426+
source: String?
427+
) -> Data? {
428+
guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil),
429+
let props = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
430+
let width = props[kCGImagePropertyPixelWidth] as? CGFloat,
431+
let height = props[kCGImagePropertyPixelHeight] as? CGFloat else {
432+
let context = source.map { " from \($0)" } ?? ""
433+
Logger.warning("Failed to read image properties\(context) (\(imageData.count) bytes)")
434+
return nil
435+
}
436+
437+
var destWidth = width
438+
var destHeight = height
439+
if width > maxDimension || height > maxDimension {
440+
let scale = min(maxDimension / width, maxDimension / height)
441+
destWidth = (width * scale).rounded(.down)
442+
destHeight = (height * scale).rounded(.down)
443+
}
444+
445+
return resizeImage(
446+
from: imageData,
447+
to: NSSize(width: destWidth, height: destHeight),
448+
compressionFactor: Float(quality)
449+
)
450+
}
451+
#endif
394452
}
395453

396454
// MARK: - Color Cache Object

0 commit comments

Comments
 (0)