Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b23e96615e | ||
|
|
0b3bf5e815 |
@@ -26,11 +26,10 @@ enum VehicleGraphicStyle: String, CaseIterable, Identifiable, Codable, Sendable
|
||||
}
|
||||
}
|
||||
|
||||
/// Für California liegt noch keine eigene Heckansicht vor, deshalb
|
||||
/// vorerst die von Vanster.
|
||||
var rearImageName: String {
|
||||
switch self {
|
||||
case .vanster, .california: return "VansterRear"
|
||||
case .vanster: return "VansterRear"
|
||||
case .california: return "CaliforniaRear"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,5 +38,22 @@ sips --resampleHeight 600 heckansicht.png --out /tmp/rear.png
|
||||
Jedes Fahrzeug-Grafikset (siehe `VehicleGraphicStyle`) hat sein eigenes
|
||||
Bildpaar `<Name>Side`/`<Name>Rear`. Hat die Vorlage schon einen
|
||||
transparenten Hintergrund, geht die Quelldatei direkt ins Skript. Liegt sie
|
||||
auf weißem Grund, muss der Rand vorher weg – sonst wird die ganze Fläche
|
||||
mitgefärbt.
|
||||
auf weißem Grund, muss der Rand vorher weg – siehe `remove-white-background.swift`.
|
||||
|
||||
## remove-white-background.swift
|
||||
|
||||
Vorstufe zu `make-vehicle-art.swift`, wenn die Vorlage auf weißem statt
|
||||
transparentem Grund liegt (kein Alphakanal). Ohne diesen Schritt hielte
|
||||
`make-vehicle-art` die ganze Fläche für Zeichnung und färbte den Hintergrund
|
||||
mit ein.
|
||||
|
||||
Reines Weiß wird voll durchsichtig, alles andere bleibt stehen; ein weicher
|
||||
Übergang statt einer harten Schwelle vermeidet gezackte Kanten an
|
||||
kantengeglätteten Linien.
|
||||
|
||||
```bash
|
||||
swiftc -O -o /tmp/remove-white-background Tools/remove-white-background.swift
|
||||
swiftc -O -o /tmp/make-vehicle-art Tools/make-vehicle-art.swift
|
||||
/tmp/remove-white-background seitenansicht.png /tmp/seite-transparent.png
|
||||
/tmp/make-vehicle-art /tmp/seite-transparent.png VanControl/Assets.xcassets/CaliforniaSide.imageset/CaliforniaSide.png
|
||||
```
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import AppKit
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
// Vorlagen kommen manchmal auf weißem statt transparentem Grund. Ohne
|
||||
// Alphakanal hielte `make-vehicle-art.swift` die ganze Fläche für Zeichnung
|
||||
// und färbte den Hintergrund mit ein. Dieses Werkzeug macht daraus einen
|
||||
// echten Alphakanal: reines Weiß wird voll durchsichtig, alles andere bleibt
|
||||
// stehen. Ein weicher Übergang statt einer harten Schwelle verhindert
|
||||
// gezackte Kanten an den (leicht kantengeglätteten) Linien.
|
||||
|
||||
let arguments = CommandLine.arguments
|
||||
guard arguments.count >= 3,
|
||||
let source = NSImage(contentsOfFile: arguments[1]),
|
||||
let image = source.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
||||
print("Quelle nicht lesbar"); exit(1)
|
||||
}
|
||||
|
||||
let width = image.width, height = image.height
|
||||
let bytesPerRow = width * 4
|
||||
var pixels = [UInt8](repeating: 0, count: bytesPerRow * height)
|
||||
guard let context = CGContext(data: &pixels, width: width, height: height,
|
||||
bitsPerComponent: 8, bytesPerRow: bytesPerRow,
|
||||
space: CGColorSpaceCreateDeviceRGB(),
|
||||
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
|
||||
print("Puffer nicht anlegbar"); exit(1)
|
||||
}
|
||||
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
|
||||
|
||||
/// Unterhalb davon gilt ein Pixel als reiner Hintergrund, oberhalb als
|
||||
/// sicher Zeichnung. Dazwischen wird linear zwischen 0 und voller Deckkraft
|
||||
/// überblendet, damit Kanten nicht ausgefranst wirken.
|
||||
let whiteFloor = 230.0
|
||||
let whiteCeiling = 250.0
|
||||
|
||||
for index in stride(from: 0, to: pixels.count, by: 4) {
|
||||
// Quelle ist opak (kein Alphakanal), also stehen hier die Rohfarben.
|
||||
let r = Double(pixels[index])
|
||||
let g = Double(pixels[index + 1])
|
||||
let b = Double(pixels[index + 2])
|
||||
let whiteness = min(r, g, b)
|
||||
|
||||
let t = min(1, max(0, (whiteness - whiteFloor) / (whiteCeiling - whiteFloor)))
|
||||
let alpha = 1 - t
|
||||
guard alpha > 0.004 else {
|
||||
pixels[index] = 0; pixels[index + 1] = 0; pixels[index + 2] = 0; pixels[index + 3] = 0
|
||||
continue
|
||||
}
|
||||
// Premultipliziert ablegen.
|
||||
pixels[index] = UInt8(r * alpha)
|
||||
pixels[index + 1] = UInt8(g * alpha)
|
||||
pixels[index + 2] = UInt8(b * alpha)
|
||||
pixels[index + 3] = UInt8(alpha * 255)
|
||||
}
|
||||
|
||||
guard let result = context.makeImage() else { print("Bild leer"); exit(1) }
|
||||
let rep = NSBitmapImageRep(cgImage: result)
|
||||
guard let png = rep.representation(using: .png, properties: [:]) else {
|
||||
print("PNG nicht erzeugbar"); exit(1)
|
||||
}
|
||||
try png.write(to: URL(fileURLWithPath: arguments[2]))
|
||||
print("\(arguments[2]): fertig")
|
||||
@@ -496,8 +496,10 @@
|
||||
AA0000000000000000000014 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-App2-Testflight";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-App1-Testflight";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
@@ -529,8 +531,10 @@
|
||||
AA0000000000000000000015 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-App2-Testflight";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-App1-Testflight";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-App1-Testflight-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-App1-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-App2-Testflight-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-App2-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 326 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "CaliforniaRear.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 },
|
||||
"properties" : { "template-rendering-intent" : "template" }
|
||||
}
|
||||
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 323 KiB |