@@ -13,51 +13,39 @@ func cgContext(width: Int, height: Int) -> CGContext? {
1313 bitsPerComponent: 8 ,
1414 bytesPerRow: 0 ,
1515 space: CGColorSpaceCreateDeviceRGB ( ) ,
16- bitmapInfo: CGImageAlphaInfo . premultipliedLast . rawValue
16+ bitmapInfo: CGBitmapInfo . byteOrder32Little . rawValue | CGImageAlphaInfo . premultipliedFirst . rawValue
1717 )
1818}
1919
2020func perspectiveTransform( image: CGImage , width: Int , height: Int ) -> CGImage ? {
21- // Apply perspective transformation directly to the image
2221 let ciImage = CIImage ( cgImage: image)
2322 let filter = CIFilter . perspectiveTransform ( )
24-
23+
2524 let w = CGFloat ( width)
2625 let h = CGFloat ( height)
27-
28- // From original JS transformation: top gets narrower by 8% on each side
29- // CIFilter uses bottom-left origin
30- filter. setValue ( ciImage, forKey: kCIInputImageKey)
31- filter. topLeft = CGPoint ( x: w * 0.08 , y: h) // Top-left: inset 8%
32- filter. topRight = CGPoint ( x: w * 0.92 , y: h) // Top-right: inset to 92%
33- filter. bottomLeft = CGPoint ( x: 0 , y: 0 ) // Bottom-left: no change
34- filter. bottomRight = CGPoint ( x: w, y: 0 ) // Bottom-right: no change
35-
26+
27+ filter. inputImage = ciImage
28+ filter. topLeft = CGPoint ( x: w * 0.08 , y: h)
29+ filter. topRight = CGPoint ( x: w * 0.92 , y: h)
30+ filter. bottomLeft = CGPoint ( x: 0 , y: 0 )
31+ filter. bottomRight = CGPoint ( x: w, y: 0 )
32+
3633 guard let outputImage = filter. outputImage else { return nil }
37-
38- // Create context for the final image
39- guard let ctx = cgContext ( width: width, height: height) else { return nil }
40-
41- ctx. clear ( CGRect ( x: 0 , y: 0 , width: width, height: height) )
42-
34+
35+ let inputExtent = ciImage. extent
36+ let croppedImage = outputImage. cropped ( to: inputExtent)
37+
4338 let ciContext = CIContext ( )
44- guard let finalCGImage = ciContext. createCGImage ( outputImage, from: outputImage. extent) else { return nil }
45-
46- // Crop to original size if needed
47- if finalCGImage. width == width && finalCGImage. height == height {
48- return finalCGImage
49- } else {
50- let sourceRect = CGRect ( x: 0 , y: 0 , width: width, height: height)
51- return finalCGImage. cropping ( to: sourceRect)
52- }
39+ return ciContext. createCGImage ( croppedImage, from: inputExtent)
5340}
5441
5542func resizeImage( image: CGImage , width: Int , height: Int ) -> CGImage ? {
5643 guard let ctx = cgContext ( width: width, height: height) else { return nil }
5744
45+ ctx. interpolationQuality = . high
5846 ctx. clear ( CGRect ( x: 0 , y: 0 , width: width, height: height) )
5947 ctx. draw ( image, in: CGRect ( x: 0 , y: 0 , width: width, height: height) )
60-
48+
6149 return ctx. makeImage ( )
6250}
6351
@@ -75,10 +63,10 @@ func compositeImages(baseImage: CGImage, overlayImage: CGImage, offsetY: CGFloat
7563 let overlayHeight = overlayImage. height
7664
7765 // Center horizontally and vertically, then apply upward offset
78- // CoreGraphics uses bottom-left origin, so we need to flip Y coordinate
66+ // CoreGraphics uses bottom-left origin, positive offsetY moves overlay up
7967 let x = CGFloat ( width - overlayWidth) / 2.0
8068 let centerY = CGFloat ( height - overlayHeight) / 2.0
81- let y = centerY + offsetY // In CoreGraphics, positive Y moves up from bottom-left origin
69+ let y = centerY + offsetY
8270
8371
8472 // Draw overlay image
@@ -106,7 +94,7 @@ func saveImage(_ image: CGImage, to path: String) -> Bool {
10694
10795// Main program
10896guard CommandLine . arguments. count == 4 else {
109- print ( " Usage: compose-icon <app-icon-path> <mount-icon-path> <output-path> " )
97+ fputs ( " Usage: compose-icon <app-icon-path> <mount-icon-path> <output-path> \n " , stderr )
11098 exit ( 1 )
11199}
112100
@@ -116,7 +104,7 @@ let outputPath = CommandLine.arguments[3]
116104
117105guard let appImage = loadImage ( from: appIconPath) ,
118106 let mountImage = loadImage ( from: mountIconPath) else {
119- print ( " Error: Could not load input images " )
107+ fputs ( " Error: Could not load input images \n " , stderr )
120108 exit ( 1 )
121109}
122110
@@ -129,20 +117,20 @@ guard let transformedAppImage = perspectiveTransform(
129117 width: appIconSize. width,
130118 height: appIconSize. height
131119) else {
132- print ( " Error: Could not apply perspective transformation " )
120+ fputs ( " Error: Could not apply perspective transformation \n " , stderr )
133121 exit ( 1 )
134122}
135123
136124// Resize app icon to fit inside mount icon (from JS: width / 1.58, height / 1.82)
137- let resizedWidth = Int ( Double ( mountIconSize. width) / 1.58 )
138- let resizedHeight = Int ( Double ( mountIconSize. height) / 1.82 )
125+ let resizedWidth = Int ( ( Double ( mountIconSize. width) / 1.58 ) . rounded ( ) )
126+ let resizedHeight = Int ( ( Double ( mountIconSize. height) / 1.82 ) . rounded ( ) )
139127
140128guard let resizedAppImage = resizeImage (
141129 image: transformedAppImage,
142130 width: resizedWidth,
143131 height: resizedHeight
144132) else {
145- print ( " Error: Could not resize app image " )
133+ fputs ( " Error: Could not resize app image \n " , stderr )
146134 exit ( 1 )
147135}
148136
@@ -154,12 +142,12 @@ guard let composedImage = compositeImages(
154142 overlayImage: resizedAppImage,
155143 offsetY: offsetY
156144) else {
157- print ( " Error: Could not composite images " )
145+ fputs ( " Error: Could not composite images \n " , stderr )
158146 exit ( 1 )
159147}
160148
161149// Save result
162150guard saveImage ( composedImage, to: outputPath) else {
163- print ( " Error: Could not save output image " )
151+ fputs ( " Error: Could not save output image \n " , stderr )
164152 exit ( 1 )
165153}
0 commit comments