79 lines
2.9 KiB
Swift
79 lines
2.9 KiB
Swift
import CoreGraphics
|
|
import Foundation
|
|
|
|
/// P1 helper: scan a vertical seam between two adjacent integer-aligned patches.
|
|
/// Any pixel that is not exactly `left` and not exactly `right` is mixed
|
|
/// (anti-aliased / interpolated) — P1 fail.
|
|
|
|
struct IntegrityReport: Equatable {
|
|
var boundaryMixed: Int
|
|
var boundarySamples: Int
|
|
var pass: Bool
|
|
var note: String
|
|
}
|
|
|
|
enum PixelIntegrity {
|
|
static func scanVerticalSeam(
|
|
image: CGImage,
|
|
seamX: Int,
|
|
y0: Int,
|
|
y1: Int,
|
|
left: (UInt8, UInt8, UInt8),
|
|
right: (UInt8, UInt8, UInt8)
|
|
) -> IntegrityReport {
|
|
let width = image.width
|
|
let height = image.height
|
|
let xL = seamX - 1
|
|
let xR = seamX
|
|
if xL < 0 || xR >= width {
|
|
return IntegrityReport(boundaryMixed: 0, boundarySamples: 0, pass: false, note: "Seam out of bounds")
|
|
}
|
|
guard let data = rasterRGBA(image) else {
|
|
return IntegrityReport(boundaryMixed: 0, boundarySamples: 0, pass: false, note: "Unable to read pixels")
|
|
}
|
|
var mixed = 0
|
|
var samples = 0
|
|
let bpr = width * 4
|
|
let yEnd = min(y1, height)
|
|
let yStart = max(y0, 0)
|
|
for y in yStart..<yEnd {
|
|
let iL = y * bpr + xL * 4
|
|
let iR = y * bpr + xR * 4
|
|
samples += 2
|
|
if data[iL] != left.0 || data[iL + 1] != left.1 || data[iL + 2] != left.2 { mixed += 1 }
|
|
if data[iR] != right.0 || data[iR + 1] != right.1 || data[iR + 2] != right.2 { mixed += 1 }
|
|
}
|
|
return IntegrityReport(
|
|
boundaryMixed: mixed,
|
|
boundarySamples: samples,
|
|
pass: mixed == 0 && samples > 0,
|
|
note: mixed == 0
|
|
? "Zero mixed pixels on the red|green seam — no interpolation."
|
|
: "\(mixed) mixed pixels along the seam — raster was resampled or antialiased."
|
|
)
|
|
}
|
|
|
|
static func rasterRGBA(_ image: CGImage) -> [UInt8]? {
|
|
let width = image.width
|
|
let height = image.height
|
|
let bpr = width * 4
|
|
guard let buffer = NSMutableData(length: bpr * height) else { return nil }
|
|
let info = CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.noneSkipLast.rawValue
|
|
guard let ctx = CGContext(
|
|
data: buffer.mutableBytes,
|
|
width: width,
|
|
height: height,
|
|
bitsPerComponent: 8,
|
|
bytesPerRow: bpr,
|
|
space: image.colorSpace ?? CGColorSpaceCreateDeviceRGB(),
|
|
bitmapInfo: info
|
|
) else { return nil }
|
|
ctx.interpolationQuality = .none
|
|
ctx.setShouldAntialias(false)
|
|
ctx.setAllowsAntialiasing(false)
|
|
ctx.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
|
|
let ptr = buffer.bytes.assumingMemoryBound(to: UInt8.self)
|
|
return Array(UnsafeBufferPointer(start: ptr, count: buffer.length))
|
|
}
|
|
}
|