Add deterministic test image generator

This commit is contained in:
2026-07-27 15:45:01 +00:00
parent 388f27e160
commit e74bb67c95
+129
View File
@@ -0,0 +1,129 @@
import CoreGraphics
import Foundation
import ImageIO
import UniformTypeIdentifiers
/// テスト用の画像を決まった内容で作る。乱数は自前の線形合同法なので毎回同じ絵になる。
enum TestImages {
static let width = 640
static let height = 480
struct Generator {
private var state: UInt64
init(seed: UInt64) { state = seed &* 2_862_933_555_777_941_757 &+ 3_037_000_493 }
mutating func next() -> UInt64 {
state = state &* 6_364_136_223_846_793_005 &+ 1_442_695_040_888_963_407
return state
}
mutating func unit() -> Double {
Double(next() >> 11) / Double(1 << 53)
}
}
/// 色のついたブロックと円を敷き詰めた、構造のある絵を作る。
static func pattern(seed: UInt64) -> CGImage {
var generator = Generator(seed: seed)
let context = CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
)!
let columns = 8
let rows = 6
let cellWidth = CGFloat(width) / CGFloat(columns)
let cellHeight = CGFloat(height) / CGFloat(rows)
for row in 0..<rows {
for column in 0..<columns {
context.setFillColor(
red: generator.unit(),
green: generator.unit(),
blue: generator.unit(),
alpha: 1
)
context.fill(
CGRect(
x: CGFloat(column) * cellWidth,
y: CGFloat(row) * cellHeight,
width: cellWidth,
height: cellHeight
)
)
}
}
for _ in 0..<12 {
context.setFillColor(
red: generator.unit(),
green: generator.unit(),
blue: generator.unit(),
alpha: 0.85
)
let radius = 20 + generator.unit() * 60
context.fillEllipse(
in: CGRect(
x: generator.unit() * Double(width),
y: generator.unit() * Double(height),
width: radius,
height: radius
)
)
}
return context.makeImage()!
}
static func scaled(_ image: CGImage, factor: Double) -> CGImage {
let newWidth = max(1, Int(Double(image.width) * factor))
let newHeight = max(1, Int(Double(image.height) * factor))
let context = CGContext(
data: nil,
width: newWidth,
height: newHeight,
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
)!
context.interpolationQuality = .high
context.draw(image, in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
return context.makeImage()!
}
static func writeJPEG(_ image: CGImage, to url: URL, quality: Double) throws {
guard let destination = CGImageDestinationCreateWithURL(
url as CFURL, UTType.jpeg.identifier as CFString, 1, nil
) else {
throw TestImageError.cannotWrite(url)
}
CGImageDestinationAddImage(
destination,
image,
[kCGImageDestinationLossyCompressionQuality: quality] as CFDictionary
)
guard CGImageDestinationFinalize(destination) else {
throw TestImageError.cannotWrite(url)
}
}
static func makeTemporaryDirectory() throws -> URL {
let url = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent("PhotochkTests-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
return url
}
}
enum TestImageError: Error {
case cannotWrite(URL)
}