Skip to content

Commit dac9ece

Browse files
chore(preprod): Reimplement multiset icon parsing (#2889)
Previously in #2879 I added the ability for us to collect multiset icons and have them included in the ParsedAssets folder / assets.json. There were two problems: 1. we were only picking up the primary image of the set. For example, for a multiset called `AppIcon_Alt_Color_Red`, we'd only pick up the first image (`red-light-min.png`) when really it contains three, all of which we want to consider when analyzing for insights 2. We were saving the multiset image as the multiset object, rather than as an image, which led us to have to [add some undesirable logic in the launchpad service](https://github.com/getsentry/launchpad/pull/437/files#r2470576784) With these changes, we now get all the images in the multisets and we save them as images
1 parent 0a539c6 commit dac9ece

File tree

1 file changed

+91
-69
lines changed

1 file changed

+91
-69
lines changed

apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift

Lines changed: 91 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ typealias objectiveCMethodImp = @convention(c) (AnyObject, Selector, UnsafeRawPo
5050
AnyObject
5151
>?
5252

53+
private struct MultisizeSetInfo {
54+
let name: String
55+
let element: UInt
56+
let part: UInt
57+
let identifier: UInt
58+
let sizeIndexes: [(idiom: UInt, subtype: UInt)]
59+
}
60+
5361
enum AssetUtil {
5462
private static func idiomToString(_ idiom: UInt?) -> String? {
5563
guard let idiom = idiom else { return nil }
@@ -105,15 +113,62 @@ enum AssetUtil {
105113
let (structuredThemeStore, assetKeys) = initializeCatalog(from: file)
106114

107115
var images: [String: (cgImage: CGImage, format: String)] = [:]
108-
109-
for key in assetKeys {
116+
117+
// First pass: Build map of multisize sets and cache renditions for performance
118+
var multisizeSets: [MultisizeSetInfo] = []
119+
var renditionCache: [Int: NSObject] = [:]
120+
121+
for (index, key) in assetKeys.enumerated() {
110122
let keyList = unsafeBitCast(
111123
key.perform(Selector(("keyList"))),
112124
to: UnsafeMutableRawPointer.self
113125
)
114126
guard let rendition = createRendition(from: structuredThemeStore, keyList) else {
115127
continue
116128
}
129+
renditionCache[index] = rendition
130+
131+
let type = rendition.getUInt(forKey: "type") ?? 0
132+
if type == 1010 { // Multisize image set
133+
let renditionTypeName = rendition.perform(Selector(("name"))).takeUnretainedValue() as! String
134+
let keyElement = key.getUInt(forKey: "themeElement") ?? 0
135+
let keyPart = key.getUInt(forKey: "themePart") ?? 0
136+
let keyIdentifier = key.getUInt(forKey: "themeIdentifier") ?? 0
137+
138+
// Extract size indexes to identify which images belong to this set
139+
var sizeIndexes: [(idiom: UInt, subtype: UInt)] = []
140+
if rendition.responds(to: Selector(("sizeIndexes"))),
141+
let sizeIndexesResult = rendition.perform(Selector(("sizeIndexes"))),
142+
let sizeIndexesArray = sizeIndexesResult.takeUnretainedValue() as? NSArray {
143+
for sizeIndexObj in sizeIndexesArray {
144+
if let obj = sizeIndexObj as? NSObject {
145+
let idiom = obj.getUInt(forKey: "idiom") ?? 0
146+
let subtype = obj.getUInt(forKey: "subtype") ?? 0
147+
sizeIndexes.append((idiom: idiom, subtype: subtype))
148+
}
149+
}
150+
}
151+
152+
multisizeSets.append(MultisizeSetInfo(
153+
name: renditionTypeName,
154+
element: keyElement,
155+
part: keyPart,
156+
identifier: keyIdentifier,
157+
sizeIndexes: sizeIndexes
158+
))
159+
}
160+
}
161+
162+
// Second pass: Process all assets using cached renditions
163+
for (index, key) in assetKeys.enumerated() {
164+
guard let rendition = renditionCache[index] else {
165+
continue
166+
}
167+
168+
let keyList = unsafeBitCast(
169+
key.perform(Selector(("keyList"))),
170+
to: UnsafeMutableRawPointer.self
171+
)
117172

118173
let data = rendition.value(forKey: "_srcData") as! Data
119174
let length = UInt(data.count)
@@ -163,13 +218,7 @@ enum AssetUtil {
163218
var unslicedImage: CGImage?
164219

165220
if isMultisizeImageSet {
166-
// Look up the actual image rendition from the multisize set
167-
if let result = findImageForMultisizeSet(rendition, key, assetKeys, structuredThemeStore) {
168-
unslicedImage = result.image
169-
width = result.width
170-
height = result.height
171-
images[imageId] = (cgImage: unslicedImage!, format: "png")
172-
}
221+
continue
173222
} else {
174223
// Get image dimensions from regular rendition
175224
(width, height, unslicedImage) = resolveImageDimensions(rendition, isVector)
@@ -183,11 +232,19 @@ enum AssetUtil {
183232

184233
let idiomValue = key.getUInt(forKey: "themeIdiom")
185234
let colorSpaceID = rendition.getUInt(forKey: "colorSpaceID")
235+
236+
// Include multisize set name in the name field if it exists
237+
let finalName: String
238+
if let setName = findMultisizeSetName(key, in: multisizeSets) {
239+
finalName = "\(setName)/\(name)"
240+
} else {
241+
finalName = name
242+
}
186243

187244
let asset = AssetCatalogEntry(
188245
imageId: imageId,
189246
size: length,
190-
name: name,
247+
name: finalName,
191248
vector: isVector,
192249
width: width,
193250
height: height,
@@ -293,65 +350,6 @@ enum AssetUtil {
293350
}
294351
return true
295352
}
296-
297-
private static func findImageForMultisizeSet(
298-
_ rendition: NSObject,
299-
_ key: NSObject,
300-
_ assetKeys: [NSObject],
301-
_ structuredThemeStore: NSObject
302-
) -> (image: CGImage, width: Int, height: Int)? {
303-
// Get the sizeIndexes to find the actual image
304-
guard rendition.responds(to: Selector(("sizeIndexes"))),
305-
let sizeIndexesResult = rendition.perform(Selector(("sizeIndexes"))),
306-
let sizeIndexesArray = sizeIndexesResult.takeUnretainedValue() as? NSArray,
307-
sizeIndexesArray.count > 0 else {
308-
return nil
309-
}
310-
311-
// Get the first size index
312-
let sizeIndexObj = sizeIndexesArray.object(at: 0) as! NSObject
313-
314-
// Get the idiom and subtype from the size index
315-
let idiom = sizeIndexObj.getUInt(forKey: "idiom") ?? 0
316-
let subtype = sizeIndexObj.getUInt(forKey: "subtype") ?? 0
317-
let keyElement = key.getUInt(forKey: "themeElement") ?? 0
318-
319-
// Look for a rendition with matching idiom and subtype in the asset keys
320-
for otherKey in assetKeys {
321-
let otherKeyIdiom = otherKey.getUInt(forKey: "themeIdiom") ?? 0
322-
let otherKeySubtype = otherKey.getUInt(forKey: "themeSubtype") ?? 0
323-
let otherKeyElement = otherKey.getUInt(forKey: "themeElement") ?? 0
324-
325-
// Find a key with matching element, idiom, and subtype
326-
guard otherKeyElement == keyElement,
327-
otherKeyIdiom == idiom,
328-
otherKeySubtype == subtype else {
329-
continue
330-
}
331-
332-
let otherKeyList = unsafeBitCast(
333-
otherKey.perform(Selector(("keyList"))),
334-
to: UnsafeMutableRawPointer.self
335-
)
336-
337-
guard let imageRendition = createRendition(from: structuredThemeStore, otherKeyList) else {
338-
continue
339-
}
340-
341-
let renditionType = imageRendition.getUInt(forKey: "type") ?? 0
342-
343-
// Skip if this is another multisize set (type 1010)
344-
guard renditionType != 1010,
345-
let result = imageRendition.perform(Selector(("unslicedImage"))) else {
346-
continue
347-
}
348-
349-
let image = result.takeUnretainedValue() as! CGImage
350-
return (image, image.width, image.height)
351-
}
352-
353-
return nil
354-
}
355353

356354
private static func determineAssetType(_ key: NSObject) -> AssetType {
357355
let themeElement = key.getUInt(forKey: "themeElement") ?? 0
@@ -364,6 +362,30 @@ enum AssetUtil {
364362
}
365363
return .image
366364
}
365+
366+
private static func findMultisizeSetName(
367+
_ key: NSObject,
368+
in multisizeSets: [MultisizeSetInfo]
369+
) -> String? {
370+
let element = key.getUInt(forKey: "themeElement") ?? 0
371+
let identifier = key.getUInt(forKey: "themeIdentifier") ?? 0
372+
let idiom = key.getUInt(forKey: "themeIdiom") ?? 0
373+
let subtype = key.getUInt(forKey: "themeSubtype") ?? 0
374+
375+
for setInfo in multisizeSets {
376+
guard setInfo.element == element,
377+
setInfo.identifier == identifier else {
378+
continue
379+
}
380+
381+
for sizeIndex in setInfo.sizeIndexes {
382+
if sizeIndex.idiom == idiom && sizeIndex.subtype == subtype {
383+
return setInfo.name
384+
}
385+
}
386+
}
387+
return nil
388+
}
367389

368390
private static func resolveRenditionName(
369391
_ structuredThemeStore: NSObject,

0 commit comments

Comments
 (0)