Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces GeometrizingSequence #160

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 8 additions & 52 deletions Sources/geometrize-cli/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,62 +82,18 @@ let shapeCount: Int = Int(options.shapeCount ?? 100)

let strokeWidth: Int = Int(options.lineWidth ?? 1)

let runnerOptions = ImageRunnerOptions(
var shapeData: [ShapeResult] = []

let geometrizingSequence = GeometrizingSequence(
bitmap: targetBitmap,
shapeTypes: shapeTypes,
strokeWidth: strokeWidth,
alpha: 128,
shapeCount: 100,
maxShapeMutations: 100,
seed: 9001, // TODO: !!!
maxThreads: 5,
shapeBounds: ImageRunnerShapeBoundsOptions(
enabled: false,
xMinPercent: 0, yMinPercent: 0, xMaxPercent: 100, yMaxPercent: 100
)
iterations: 10,
shapesPerIteration: shapeCount / 10
)

var runner = ImageRunner(targetBitmap: targetBitmap)

var shapeData: [ShapeResult] = []

// Hack to add a single background rectangle as the initial shape
let rect = Rectangle(strokeWidth: 1, x1: 0, y1: 0, x2: Double(targetBitmap.width), y2: Double(targetBitmap.height))
shapeData.append(ShapeResult(score: 0, color: targetBitmap.averageColor(), shape: rect))

var counter = 0
// Here in shapeCount set count of shapes final image should have.
// Remember background is the first shape.
loop: while shapeData.count <= shapeCount {
if options.verbose {
print("Step \(counter)", terminator: "")
}
let stepResult = runner.step(
options: runnerOptions,
shapeCreator: nil,
energyFunction: defaultEnergyFunction,
addShapePrecondition: defaultAddShapePrecondition
)
switch stepResult {
case .success(let shapeResult):
shapeData.append(shapeResult)
if options.verbose {
print(", \(shapeResult.shape.description) added.", terminator: "")
}
case .match:
if options.verbose {
print(", geometrizing matched source image.", terminator: "")
}
break loop
case .failure:
if options.verbose {
print(", no shapes added.", terminator: "")
}
// TODO: should it break as well?
}
if options.verbose {
print(" Total count of shapes \(shapeData.count ).")
}
counter += 1
for iteration in geometrizingSequence {
shapeData.append(contentsOf: iteration)
}

do {
Expand Down
153 changes: 153 additions & 0 deletions Sources/geometrize/GeometrizingIterator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import Foundation

public struct GeometrizingIterator: IteratorProtocol {

private let originWidth: Int
private let originHeight: Int
private let shapeTypes: [Shape.Type]
private let iterations: Int
private let shapesPerIteration: Int

private var targetBitmap: Bitmap
private let width, height: Int // downscaled targetBitmap

private var iterationCounter: Int

private var shapeData: [ShapeResult] // TODO: remove

private let runnerOptions: ImageRunnerOptions
private var runner: ImageRunner

// Counts attempts to add shapes. Not all attempts to add shape result in adding a shape.
private var stepCounter: Int

private let verbose: Bool

public enum State {
case running
case succeeded
case succeededMatched
case failed
}

public var state: State

public init(
bitmap: Bitmap,
downscaleToMaxSize downscaleSize: Int = 500,
shapeTypes: [Shape.Type],
strokeWidth: Int,
iterations: Int,
shapesPerIteration: Int,
verbose: Bool = true
) {
self.shapeTypes = shapeTypes
self.iterations = iterations
self.shapesPerIteration = shapesPerIteration
self.verbose = verbose

targetBitmap = bitmap
originWidth = bitmap.width
originHeight = bitmap.height
let maxSize = max(originWidth, originHeight)
if maxSize > downscaleSize {
targetBitmap = bitmap.downsample(factor: maxSize / downscaleSize)
}

width = targetBitmap.width
height = targetBitmap.height

iterationCounter = 0

stepCounter = 0

// TODO: parameterize this
runnerOptions = ImageRunnerOptions(
shapeTypes: shapeTypes,
strokeWidth: strokeWidth,
alpha: 128,
shapeCount: 500, // TODO: ???
maxShapeMutations: 100,
seed: 9001, // TODO: !!!
maxThreads: 1,
shapeBounds: ImageRunnerShapeBoundsOptions(
enabled: false,
xMinPercent: 0, yMinPercent: 0, xMaxPercent: 100, yMaxPercent: 100
)
)

runner = ImageRunner(targetBitmap: targetBitmap)

shapeData = []

state = .running
}

public mutating func next() -> [ShapeResult]? { // swiftlint:disable:this cyclomatic_complexity function_body_length
guard state == .running else { return nil }

guard iterationCounter < iterations else {
state = .succeeded
return nil
}

var iterationShapeData: [ShapeResult] = []

if iterationCounter == 0 {
// Hack to add a single background rectangle as the initial shape
iterationShapeData.append(
ShapeResult(
score: 0,
color: targetBitmap.averageColor(),
shape: Rectangle(canvasWidth: targetBitmap.width, height: targetBitmap.height)
)
)
}

loop: while iterationShapeData.count <= shapesPerIteration {
if verbose {
print("Step \(stepCounter)", terminator: "")
}
let stepResult = runner.step(
options: runnerOptions,
energyFunction: defaultEnergyFunction,
addShapePrecondition: defaultAddShapePrecondition
)
switch stepResult {
case .success(let shapeResult):
iterationShapeData.append(shapeResult)
if verbose {
print(", \(shapeResult.shape.description) added.", terminator: "")
}
case .match:
if verbose {
print(", geometrizing matched source image.", terminator: "")
}
state = .succeededMatched
break loop
case .failure:
if verbose {
print(", no shapes added.", terminator: "")
}
state = .failed
break loop
}
if verbose {
print(" Total count of shapes \(shapeData.count ).")
}
stepCounter += 1
}

shapeData.append(contentsOf: iterationShapeData)

iterationCounter += 1

if verbose {
print("Iteration \(iterationCounter) complete, \(iterationShapeData.count) shapes in iteration, " +
"\(shapeData.count) shapes in total.")
}

return iterationShapeData
}

}
41 changes: 41 additions & 0 deletions Sources/geometrize/GeometrizingSequence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation

public struct GeometrizingSequence: Sequence {

public typealias Element = [ShapeResult]

let bitmap: Bitmap
let shapeTypes: [Shape.Type]
let strokeWidth: Int
let iterations: Int
let shapesPerIteration: Int
let verbose: Bool

public init(
bitmap: Bitmap,
shapeTypes: [Shape.Type],
strokeWidth: Int,
iterations: Int,
shapesPerIteration: Int,
verbose: Bool = false
) {
self.bitmap = bitmap
self.shapeTypes = shapeTypes
self.strokeWidth = strokeWidth
self.iterations = iterations
self.shapesPerIteration = shapesPerIteration
self.verbose = verbose
}

public func makeIterator() -> GeometrizingIterator {
GeometrizingIterator(
bitmap: bitmap,
shapeTypes: shapeTypes,
strokeWidth: strokeWidth,
iterations: iterations,
shapesPerIteration: shapesPerIteration,
verbose: verbose
)
}

}
3 changes: 1 addition & 2 deletions Sources/geometrize/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ struct State {
}

/// The score of the state, a measure of the improvement applying the state to the current bitmap will have.
// TODO: what is valid range of the score?
var score: Double
var score: Double // TODO: what is valid range of the score?

/// The alpha of the shape.
var alpha: UInt8
Expand Down