diff --git a/Shared/Models/VehicleGraphicStyle.swift b/Shared/Models/VehicleGraphicStyle.swift index 571387d..b41ecb2 100644 --- a/Shared/Models/VehicleGraphicStyle.swift +++ b/Shared/Models/VehicleGraphicStyle.swift @@ -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" } } diff --git a/Tools/README.md b/Tools/README.md index 7fec113..1be09c0 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -38,5 +38,22 @@ sips --resampleHeight 600 heckansicht.png --out /tmp/rear.png Jedes Fahrzeug-Grafikset (siehe `VehicleGraphicStyle`) hat sein eigenes Bildpaar `Side`/`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 +``` diff --git a/Tools/remove-white-background.swift b/Tools/remove-white-background.swift new file mode 100644 index 0000000..f361bcf --- /dev/null +++ b/Tools/remove-white-background.swift @@ -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") diff --git a/VanControl/Assets.xcassets/CaliforniaRear.imageset/CaliforniaRear.png b/VanControl/Assets.xcassets/CaliforniaRear.imageset/CaliforniaRear.png new file mode 100644 index 0000000..a06e444 Binary files /dev/null and b/VanControl/Assets.xcassets/CaliforniaRear.imageset/CaliforniaRear.png differ diff --git a/VanControl/Assets.xcassets/CaliforniaRear.imageset/Contents.json b/VanControl/Assets.xcassets/CaliforniaRear.imageset/Contents.json new file mode 100644 index 0000000..90e4a2e --- /dev/null +++ b/VanControl/Assets.xcassets/CaliforniaRear.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { + "filename" : "CaliforniaRear.png", + "idiom" : "universal" + } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { "template-rendering-intent" : "template" } +} diff --git a/VanControl/Assets.xcassets/CaliforniaSide.imageset/CaliforniaSide.png b/VanControl/Assets.xcassets/CaliforniaSide.imageset/CaliforniaSide.png index 57a56c2..69e25ff 100644 Binary files a/VanControl/Assets.xcassets/CaliforniaSide.imageset/CaliforniaSide.png and b/VanControl/Assets.xcassets/CaliforniaSide.imageset/CaliforniaSide.png differ