diff --git a/Sources/geometrize-cli/main.swift b/Sources/geometrize-cli/main.swift index 3db1028..bc123fc 100644 --- a/Sources/geometrize-cli/main.swift +++ b/Sources/geometrize-cli/main.swift @@ -110,25 +110,25 @@ while shapeData.count <= shapeCount { if options.verbose { print("Step \(counter)", terminator: "") } - let shapes = runner.step( + let shapeResult = runner.step( options: runnerOptions, shapeCreator: nil, energyFunction: defaultEnergyFunction, addShapePrecondition: defaultAddShapePrecondition ) - if shapes.isEmpty { + if let shapeResult { + shapeData.append(shapeResult) if options.verbose { - print(", no shapes added.", terminator: "") + print(", \(shapeResult.shape.description) added.", terminator: "") } } else { if options.verbose { - print(", \(shapes.map(\.shape).map(\.description).joined(separator: ", ")) added.", terminator: "") + print(", no shapes added.", terminator: "") } } if options.verbose { print(" Total count of shapes \(shapeData.count ).") } - shapeData.append(contentsOf: shapes) counter += 1 } diff --git a/Sources/geometrize/GeometrizeModelHillClimb.swift b/Sources/geometrize/GeometrizeModelHillClimb.swift index 56a4a9a..16b4998 100644 --- a/Sources/geometrize/GeometrizeModelHillClimb.swift +++ b/Sources/geometrize/GeometrizeModelHillClimb.swift @@ -40,7 +40,8 @@ class GeometrizeModelHillClimb: GeometrizeModelBase { return [state] } - /// Steps the primitive optimization/fitting algorithm. + /// Concurrently runs several optimization sessions tying improving image geometrization by adding a shape to it + /// and returns result of the best optimization or nil if improvement of image wasn't found. /// - Parameters: /// - shapeCreator: A function that will produce the shapes. /// - alpha: The alpha of the shape. @@ -49,8 +50,7 @@ class GeometrizeModelHillClimb: GeometrizeModelBase { /// - maxThreads: The maximum number of threads to use during this step. /// - energyFunction: A function to calculate the energy. /// - addShapePrecondition: A function to determine whether to accept a shape. - /// - Returns: A vector containing data about the shapes added to the model in this step. - /// This may be empty if no shape that improved the image could be found. + /// - Returns: Returns `ShapeResult` representing a shape added to improve image or nil if improvement wasn't found. func step( // swiftlint:disable:this function_parameter_count shapeCreator: () -> any Shape, alpha: UInt8, @@ -59,7 +59,7 @@ class GeometrizeModelHillClimb: GeometrizeModelBase { maxThreads: Int, energyFunction: @escaping EnergyFunction, addShapePrecondition: @escaping ShapeAcceptancePreconditionFunction = defaultAddShapePrecondition - ) -> [ShapeResult] { + ) -> ShapeResult? { let states: [State] = getHillClimbState( shapeCreator: shapeCreator, @@ -90,14 +90,13 @@ class GeometrizeModelHillClimb: GeometrizeModelBase { let newScore: Double = differencePartial(target: targetBitmap, before: before, after: currentBitmap, score: lastScore, lines: lines) guard addShapePrecondition(lastScore, newScore, shape, lines, color, before, currentBitmap, targetBitmap) else { currentBitmap = before - return [] + return nil } // Improvement - set new baseline and return the new shape lastScore = newScore - let result: ShapeResult = ShapeResult(score: lastScore, color: color, shape: shape) - return [result] + return ShapeResult(score: lastScore, color: color, shape: shape) } /// Sets the seed that the random number generators of this model use. diff --git a/Sources/geometrize/ImageRunner.swift b/Sources/geometrize/ImageRunner.swift index 8ad265a..5340be6 100644 --- a/Sources/geometrize/ImageRunner.swift +++ b/Sources/geometrize/ImageRunner.swift @@ -82,19 +82,20 @@ public struct ImageRunner { model = GeometrizeModelHillClimb(target: targetBitmap, initial: initialBitmap) } - /// Updates the internal model once. + /// Makes one step of geometrization trying to add a shape to image improving its geometrization. /// - Parameters: /// - options: Various configurable settings for doing the step e.g. the shape types to consider. /// - shapeCreator: An optional function for creating and mutating shapes. /// - energyFunction: A function to calculate the energy. /// - addShapePrecondition: A function to determine whether to accept a shape. - /// - Returns: A vector containing data about the shapes just added to the internal model. + /// - Returns: a ShapeResult representing a shape added to image or nil if a shape improving image + /// geometrization wasn't found. public mutating func step( options: ImageRunnerOptions, shapeCreator: (() -> any Shape)? = nil, energyFunction: @escaping EnergyFunction, addShapePrecondition: @escaping ShapeAcceptancePreconditionFunction - ) -> [ShapeResult] { + ) -> ShapeResult? { let (xMin, yMin, xMax, yMax) = mapShapeBoundsToImage(options: options.shapeBounds, image: model.getTarget()) let types = options.shapeTypes @@ -102,7 +103,7 @@ public struct ImageRunner { model.setSeed(options.seed) - let result: [ShapeResult] = model.step( + return model.step( shapeCreator: shapeCreator, alpha: options.alpha, shapeCount: options.shapeCount, @@ -111,8 +112,6 @@ public struct ImageRunner { energyFunction: energyFunction, addShapePrecondition: addShapePrecondition ) - - return result } public var currentBitmap: Bitmap { diff --git a/Tests/geometrizeTests/ImageRunnerTests.swift b/Tests/geometrizeTests/ImageRunnerTests.swift index 67ffd6f..3fdc339 100644 --- a/Tests/geometrizeTests/ImageRunnerTests.swift +++ b/Tests/geometrizeTests/ImageRunnerTests.swift @@ -43,9 +43,11 @@ final class ImageRunnerTests: XCTestCase { var counter = 0 while shapeData.count <= 1000 /* Here set count of shapes final image should have. Remember background is the first shape. */ { print("Step \(counter)", terminator: "") - let shapes = runner.step(options: options, shapeCreator: nil, energyFunction: defaultEnergyFunction, addShapePrecondition: defaultAddShapePrecondition) - print(", \(shapes.isEmpty ? "no" : String(shapes.count)) shape(s) was/were added. Total count of shapes \(shapeData.count ).") - shapeData.append(contentsOf: shapes) + let shapeResult = runner.step(options: options, shapeCreator: nil, energyFunction: defaultEnergyFunction, addShapePrecondition: defaultAddShapePrecondition) + if let shapeResult { + shapeData.append(shapeResult) + } + print(", \(shapeResult == nil ? "no" : "1") shape was added. Total count of shapes \(shapeData.count ).") counter += 1 }