forked from fritob/Camper-Monitor
Seiten- und Heckansicht kamen als weiß gefüllte Vorlage ohne Alphakanal (Tools/California Side.png, Tools/California Back.png). Dafür ein neues Werkzeug ergänzt, das den weißen Grund in echte Transparenz übersetzt (mit weichem statt hartem Schwellwert, um Kanten nicht auszufransen), danach wie gewohnt durch make-vehicle-art.swift geschickt. California hatte bisher keine eigene Heckansicht und griff ersatzweise auf die von Vanster zurück – jetzt eigenes CaliforniaRear-Imageset, VehicleGraphicStyle.rearImageName entsprechend angepasst. Seiten- und Heckbild sind quadratisch (1254×1254), passend zu den bestehenden Seitenverhältnissen (sideAspect/rearAspect = 1) – keine Anpassung dort nötig. Build und Tests grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
63 lines
2.6 KiB
Swift
63 lines
2.6 KiB
Swift
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")
|