Skip to content

Commit 4b5be0b

Browse files
committed
address review
1 parent fd15924 commit 4b5be0b

File tree

7 files changed

+34
-65
lines changed

7 files changed

+34
-65
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
node_modules
22
yarn.lock
33
.build
4+
/compose-icon
5+
/.build
6+
/xcuserdata

build-swift.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
2+
set -euo pipefail
23

34
# Build the Swift package for composing icons
45
echo "Building Swift package..."
56
cd imageComposition
6-
swift build --configuration release --product compose-icon
7+
xcrun swift build -c release --arch arm64 --arch x86_64 --product compose-icon
78

89
# Copy the built executable to the root directory for easy access
9-
cp .build/release/compose-icon ../compose-icon
10+
BIN_DIR="$(xcrun swift build -c release --show-bin-path)"
11+
cp "$BIN_DIR/compose-icon" ../compose-icon
1012

1113
echo "Swift executable built successfully: compose-icon"

compose-icon

-61.7 KB
Binary file not shown.

imageComposition/.swiftpm/xcode/xcuserdata/guigui.xcuserdatad/xcschemes/xcschememanagement.plist

Lines changed: 0 additions & 14 deletions
This file was deleted.

imageComposition/Sources/ComposeIcon/main.swift

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2020
func 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

5542
func 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
10896
guard 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

117105
guard 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

140128
guard 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
162150
guard 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
}

readme.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,6 @@ If either `license.txt`, `license.rtf`, or `sla.r` ([raw SLAResources file](http
5050

5151
`/usr/bin/rez` (from [Command Line Tools for Xcode](https://developer.apple.com/download/more/)) must be installed.
5252

53-
### DMG icon
54-
55-
[GraphicsMagick](http://www.graphicsmagick.org) is required to create the custom DMG icon that's based on the app icon and the macOS mounted device icon.
56-
57-
#### Steps using [Homebrew](https://brew.sh)
58-
59-
```sh
60-
brew install graphicsmagick imagemagick
61-
```
62-
6353
#### Icon example
6454

6555
Original icon → DMG icon

0 commit comments

Comments
 (0)