Schließt die compliance-kritische native Lücke (web hatte prepareImageForVision, nativ nicht). - ByokImagePrep.prepareForVision: ImageIO-Re-Encode → EXIF/GPS gestrippt + Downscale ≤2048px (Orientierung in Pixel gebacken, kein Metadaten-Dict). - ManaLLM.describeImage(rawImageData:…): Convenience, die den Strip selbst macht → Native-Konsument kann ihn nicht vergessen. - 3 Tests (Downscale-Cap verifiziert, Garbage→nil), swift test 39/39 grün. Native Vision/TTS-KONSUMENT bleibt Xcode (Tills Hand) — das hier ist das verifizierbare Fundament dafür. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
2.8 KiB
Swift
71 lines
2.8 KiB
Swift
import CoreGraphics
|
|
import Foundation
|
|
import ImageIO
|
|
@testable import ManaLLM
|
|
import Testing
|
|
import UniformTypeIdentifiers
|
|
|
|
/// Tests für die Vision-Bild-Vorbereitung (EXIF-Strip + Downscale).
|
|
/// Erzeugt In-Memory-JPEGs — netzwerk- und fixture-frei.
|
|
struct ByokImagePrepTests {
|
|
/// Einfarbiges JPEG der gewünschten Größe.
|
|
private func makeJpeg(width: Int, height: Int) -> Data {
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let context = CGContext(
|
|
data: nil,
|
|
width: width,
|
|
height: height,
|
|
bitsPerComponent: 8,
|
|
bytesPerRow: 0,
|
|
space: colorSpace,
|
|
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
|
|
)!
|
|
context.setFillColor(CGColor(red: 0.2, green: 0.6, blue: 0.3, alpha: 1))
|
|
context.fill(CGRect(x: 0, y: 0, width: width, height: height))
|
|
let image = context.makeImage()!
|
|
|
|
let output = NSMutableData()
|
|
let dest = CGImageDestinationCreateWithData(
|
|
output as CFMutableData, UTType.jpeg.identifier as CFString, 1, nil
|
|
)!
|
|
CGImageDestinationAddImage(dest, image, nil)
|
|
_ = CGImageDestinationFinalize(dest)
|
|
return output as Data
|
|
}
|
|
|
|
private func pixelSize(of data: Data) -> (width: Int, height: Int)? {
|
|
guard let src = CGImageSourceCreateWithData(data as CFData, nil),
|
|
let props = CGImageSourceCopyPropertiesAtIndex(src, 0, nil) as? [CFString: Any],
|
|
let w = props[kCGImagePropertyPixelWidth] as? Int,
|
|
let h = props[kCGImagePropertyPixelHeight] as? Int
|
|
else { return nil }
|
|
return (w, h)
|
|
}
|
|
|
|
@Test func downscalesLargeImageAndReturnsJpeg() throws {
|
|
let big = makeJpeg(width: 4000, height: 3000)
|
|
let prepared = ByokImagePrep.prepareForVision(big, maxEdge: 1024)
|
|
#expect(prepared != nil)
|
|
#expect(prepared?.mimeType == "image/jpeg")
|
|
let size = try pixelSize(of: #require(prepared?.data))
|
|
#expect(size != nil)
|
|
#expect(try max(#require(size?.width), #require(size?.height)) <= 1024)
|
|
#expect(try max(#require(size?.width), #require(size?.height)) > 0)
|
|
}
|
|
|
|
@Test func keepsSmallImageAndStripsToJpeg() throws {
|
|
let small = makeJpeg(width: 120, height: 90)
|
|
let prepared = ByokImagePrep.prepareForVision(small, maxEdge: 2048)
|
|
#expect(prepared != nil)
|
|
#expect(prepared?.mimeType == "image/jpeg")
|
|
// Kleiner als maxEdge → nicht hochskaliert.
|
|
let size = try pixelSize(of: #require(prepared?.data))
|
|
#expect(size != nil)
|
|
#expect(try max(#require(size?.width), #require(size?.height)) <= 120)
|
|
}
|
|
|
|
@Test func returnsNilForGarbageData() {
|
|
let prepared = ByokImagePrep.prepareForVision(Data([0x00, 0x01, 0x02, 0x03]))
|
|
#expect(prepared == nil)
|
|
}
|
|
}
|