Skip to content

Commit

Permalink
Use strokeWidth in Line, Polyline, QuadraticBezier
Browse files Browse the repository at this point in the history
  • Loading branch information
valeriyvan committed Oct 19, 2023
1 parent d1556dc commit f0493d4
Show file tree
Hide file tree
Showing 22 changed files with 112 additions and 63 deletions.
6 changes: 5 additions & 1 deletion Sources/geometrize-cli/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ struct GeometrizeOptions: ParsableArguments {
@Option(name: .shortAndLong, help: "Output file pathname.") var outputPath: String
@Option(name: [.customShort("t"), .long], help: "White space separated list of shapes to use geometrizing image.") var shapeTypes: String = "rectangle"
@Option(name: [.customShort("c"), .long], help: "The number of shapes to generate for the final output.") var shapeCount: UInt?
@Option(name: [.customShort("w"), .long], help: "Width of lines, polylines, bezier curves.") var lineWidth: UInt?
@Flag(name: .shortAndLong, help: "Verbose output.") var verbose: Bool = false
}

Expand Down Expand Up @@ -90,8 +91,11 @@ print("Using shapes: \(shapeTypes.map { "\(type(of: $0))".dropLast(5) /* drop .T

let shapeCount: Int = Int(options.shapeCount ?? 100)

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

let runnerOptions = ImageRunnerOptions(
shapeTypes: shapeTypes,
strokeWidth: strokeWidth,
alpha: 128,
shapeCount: 100,
maxShapeMutations: 100,
Expand All @@ -108,7 +112,7 @@ var runner = ImageRunner(targetBitmap: targetBitmap)
var shapeData: [ShapeResult] = []

// Hack to add a single background rectangle as the initial shape
let rect = Rectangle(x1: 0, y1: 0, x2: Double(targetBitmap.width), y2: Double(targetBitmap.height))
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
Expand Down
19 changes: 17 additions & 2 deletions Sources/geometrize/ImageRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public struct ImageRunnerOptions {
/// The shape types that the image runner shall use.
var shapeTypes: [Shape.Type]

/// Width of Line, Polyline, QuadraticBezier
var strokeWidth: Int

/// The alpha/opacity of the shapes (0-255).
var alpha: UInt8

Expand All @@ -53,8 +56,20 @@ public struct ImageRunnerOptions {
/// If zero or do not form a rectangle, the entire target image is used i.e. (0, 0, imageWidth, imageHeight).
var shapeBounds: ImageRunnerShapeBoundsOptions

public init(shapeTypes: [Shape.Type], alpha: UInt8, shapeCount: Int, maxShapeMutations: Int, seed: Int, maxThreads: Int, shapeBounds: ImageRunnerShapeBoundsOptions) {
public init(
shapeTypes: [Shape.Type],
strokeWidth: Int,
alpha: UInt8,
shapeCount: Int,
maxShapeMutations: Int,
seed: Int,
maxThreads: Int,
shapeBounds: ImageRunnerShapeBoundsOptions
) {
precondition(strokeWidth > 0)

self.shapeTypes = shapeTypes
self.strokeWidth = strokeWidth
self.alpha = alpha
self.shapeCount = shapeCount
self.maxShapeMutations = maxShapeMutations
Expand Down Expand Up @@ -98,7 +113,7 @@ public struct ImageRunner {
) -> ShapeResult? {
let types = options.shapeTypes

let shapeCreator: ShapeCreator = shapeCreator ?? makeDefaultShapeCreator(types: types)
let shapeCreator: ShapeCreator = shapeCreator ?? makeDefaultShapeCreator(types: types, strokeWidth: Double(options.strokeWidth))

model.setSeed(options.seed)

Expand Down
2 changes: 1 addition & 1 deletion Sources/geometrize/SVGExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct SVGExporter {

if shape.self is Line || shape.self is Polyline || shape.self is QuadraticBezier {
styles += strokeAttrib(color: color)
styles += " stroke-width=\"1\" fill=\"none\" "
styles += " stroke-width=\"\(Int(shape.strokeWidth))\" fill=\"none\" "
styles += strokeOpacityAttrib(color: color)
} else {
styles += fillAttrib(color: color)
Expand Down
4 changes: 2 additions & 2 deletions Sources/geometrize/ShapeCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ public typealias ShapeCreator = (inout SplitMix64) -> any Shape
/// - Parameters:
/// - types: The types of shapes to create.
/// - Returns: The default shape creator.
public func makeDefaultShapeCreator(types: [Shape.Type]) -> ShapeCreator {
public func makeDefaultShapeCreator(types: [Shape.Type], strokeWidth: Double) -> ShapeCreator {
return { generator in
let index = types.index(
types.startIndex,
offsetBy: Int._random(in: 0...types.count - 1, using: &generator)
)
let shapeType = types[index]
return shapeType.init()
return shapeType.init(strokeWidth: strokeWidth)
}
}
9 changes: 6 additions & 3 deletions Sources/geometrize/Shapes/Circle.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import Foundation

public final class Circle: Shape {
public var strokeWidth: Double
public var x: Double // x-coordinate.
public var y: Double // y-coordinate.
public var r: Double // Radius.

public init() {
public init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
x = 0.0
y = 0.0
r = 0.0
}

public init(x: Double, y: Double, r: Double) {
public init(strokeWidth: Double, x: Double, y: Double, r: Double) {
self.strokeWidth = strokeWidth
self.x = x
self.y = y
self.r = r
}

public func copy() -> Circle {
Circle(x: x, y: y, r: r)
Circle(strokeWidth: strokeWidth, x: x, y: y, r: r)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down
9 changes: 6 additions & 3 deletions Sources/geometrize/Shapes/Ellipse.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import Foundation

public final class Ellipse: Shape {
public var strokeWidth: Double
public var x: Double // x-coordinate.
public var y: Double // y-coordinate.
public var rx: Double // x-radius.
public var ry: Double // y-radius.

public init() {
public init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
x = 0.0
y = 0.0
rx = 0.0
ry = 0.0
}

public init(x: Double, y: Double, rx: Double, ry: Double) {
public init(strokeWidth: Double, x: Double, y: Double, rx: Double, ry: Double) {
self.strokeWidth = strokeWidth
self.x = x
self.y = y
self.rx = rx
self.ry = ry
}

public func copy() -> Ellipse {
Ellipse(x: x, y: y, rx: rx, ry: ry)
Ellipse(strokeWidth: strokeWidth, x: x, y: y, rx: rx, ry: ry)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down
11 changes: 7 additions & 4 deletions Sources/geometrize/Shapes/Line.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import Foundation

public final class Line: Shape {
public var strokeWidth: Double
public var x1, y1, x2, y2: Double

public init() {
public init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
x1 = 0.0
y1 = 0.0
x2 = 0.0
y2 = 0.0
}

public init(x1: Double, y1: Double, x2: Double, y2: Double) {
public init(strokeWidth: Double, x1: Double, y1: Double, x2: Double, y2: Double) {
self.strokeWidth = strokeWidth
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
}

public func copy() -> Line {
Line(x1: x1, y1: y1, x2: x2, y2: y2)
Line(strokeWidth: strokeWidth, x1: x1, y1: y1, x2: x2, y2: y2)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down Expand Up @@ -49,7 +52,7 @@ public final class Line: Shape {

public func rasterize(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>) -> [Scanline] {
let lines =
drawThickLine(from: Point(x: Int(x1), y: Int(y1)), to: Point(x: Int(x2), y: Int(y2)))
drawThickLine(from: Point(x: Int(x1), y: Int(y1)), to: Point(x: Int(x2), y: Int(y2)), thickness: Int(strokeWidth))
.compactMap {
Scanline(y: $0.y, x1: $0.x, x2: $0.x).trimmed(x: xRange, y: yRange)
}
Expand Down
11 changes: 7 additions & 4 deletions Sources/geometrize/Shapes/Polyline.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import Foundation

public final class Polyline: Shape {
public var strokeWidth: Double
public var points: [Point<Double>]

public init() {
public init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
points = []
}

public init(points: [Point<Double>]) {
public init(strokeWidth: Double, points: [Point<Double>]) {
self.strokeWidth = strokeWidth
self.points = points
}

public func copy() -> Polyline {
Polyline(points: points)
Polyline(strokeWidth: strokeWidth, points: points)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down Expand Up @@ -49,7 +52,7 @@ public final class Polyline: Shape {
for i in 0..<points.count {
let p0 = points[i]
let p1 = i < points.count - 1 ? points[i + 1] : p0
let points = drawThickLine(from: Point<Int>(p0), to: Point<Int>(p1))
let points = drawThickLine(from: Point<Int>(p0), to: Point<Int>(p1), thickness: Int(strokeWidth))
for point in points {
if !duplicates.contains(point) {
duplicates.insert(point)
Expand Down
11 changes: 7 additions & 4 deletions Sources/geometrize/Shapes/QuadraticBezier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import Foundation
import Algorithms

public final class QuadraticBezier: Shape {
public var strokeWidth: Double
public var x1: Double // First x-coordinate.
public var y1: Double // First y-coordinate.
public var x2: Double // Second x-coordinate.
public var y2: Double // Second y-coordinate.
public var cx: Double // Control point x-coordinate.
public var cy: Double // Control point y-coordinate.

public init() {
public init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
cx = 0.0
cy = 0.0
x1 = 0.0
Expand All @@ -18,7 +20,8 @@ public final class QuadraticBezier: Shape {
y2 = 0.0
}

public init(cx: Double, cy: Double, x1: Double, y1: Double, x2: Double, y2: Double) {
public init(strokeWidth: Double, cx: Double, cy: Double, x1: Double, y1: Double, x2: Double, y2: Double) {
self.strokeWidth = strokeWidth
self.cx = cx
self.cy = cy
self.x1 = x1
Expand All @@ -28,7 +31,7 @@ public final class QuadraticBezier: Shape {
}

public func copy() -> QuadraticBezier {
QuadraticBezier(cx: cx, cy: cy, x1: x1, y1: y1, x2: x2, y2: y2)
QuadraticBezier(strokeWidth: strokeWidth, cx: cx, cy: cy, x1: x1, y1: y1, x2: x2, y2: y2)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down Expand Up @@ -72,7 +75,7 @@ public final class QuadraticBezier: Shape {
// Prevent scanline overlap, it messes up the energy functions that rely on the scanlines not intersecting themselves
var duplicates: Set<Point<Int>> = Set()
for (from, to) in points.adjacentPairs() {
for point in drawThickLine(from: from, to: to) {
for point in drawThickLine(from: from, to: to, thickness: Int(strokeWidth)) {
if !duplicates.contains(point) {
duplicates.insert(point)
if let trimmed = Scanline(y: point.y, x1: point.x, x2: point.x).trimmed(x: xRange, y: yRange) {
Expand Down
11 changes: 7 additions & 4 deletions Sources/geometrize/Shapes/Rectangle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import Foundation

// Represents a rectangle.
public final class Rectangle: Shape {
public var strokeWidth: Double
public var x1, y1, x2, y2: Double

required public init() {
required public init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
x1 = 0.0
y1 = 0.0
x2 = 0.0
y2 = 0.0
}

public init(x1: Double, y1: Double, x2: Double, y2: Double) {
public init(strokeWidth: Double, x1: Double, y1: Double, x2: Double, y2: Double) {
self.strokeWidth = strokeWidth
self.x1 = x1
self.y1 = y1
self.x2 = x2
Expand All @@ -20,11 +23,11 @@ public final class Rectangle: Shape {

// Rectangle taking whole size of canvas
public convenience init(canvasWidth width: Int, height: Int) {
self.init(x1: 0.0, y1: 0.0, x2: Double(width), y2: Double(height))
self.init(strokeWidth: 1, x1: 0.0, y1: 0.0, x2: Double(width), y2: Double(height))
}

public func copy() -> Rectangle {
Rectangle(x1: x1, y1: y1, x2: x2, y2: y2)
Rectangle(strokeWidth: strokeWidth, x1: x1, y1: y1, x2: x2, y2: y2)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down
9 changes: 6 additions & 3 deletions Sources/geometrize/Shapes/RotatedEllipse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ import Foundation

// Represents a rotated ellipse.
public final class RotatedEllipse: Shape {
public var strokeWidth: Double
public var x: Double // x-coordinate.
public var y: Double // y-coordinate.
public var rx: Double // x-radius.
public var ry: Double // y-radius.
public var angleDegrees: Double // Rotation angle in degrees.

public required init() {
public required init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
x = 0.0
y = 0.0
rx = 0.0
ry = 0.0
angleDegrees = 0.0
}

public init(x: Double, y: Double, rx: Double, ry: Double, angleDegrees: Double) {
public init(strokeWidth: Double, x: Double, y: Double, rx: Double, ry: Double, angleDegrees: Double) {
self.strokeWidth = strokeWidth
self.x = x
self.y = y
self.rx = rx
Expand All @@ -25,7 +28,7 @@ public final class RotatedEllipse: Shape {
}

public func copy() -> RotatedEllipse {
RotatedEllipse(x: x, y: y, rx: rx, ry: ry, angleDegrees: angleDegrees)
RotatedEllipse(strokeWidth: strokeWidth, x: x, y: y, rx: rx, ry: ry, angleDegrees: angleDegrees)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down
9 changes: 6 additions & 3 deletions Sources/geometrize/Shapes/RotatedRectangle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ import Foundation

/// Represents a rotated rectangle.
public final class RotatedRectangle: Shape {
public var strokeWidth: Double
public var x1: Double
public var y1: Double
public var x2: Double
public var y2: Double
public var angleDegrees: Double

public required init() {
public required init(strokeWidth: Double) {
self.strokeWidth = strokeWidth
x1 = 0.0
y1 = 0.0
x2 = 0.0
y2 = 0.0
angleDegrees = 0.0
}

public init(x1: Double, y1: Double, x2: Double, y2: Double, angleDegrees: Double) {
public init(strokeWidth: Double, x1: Double, y1: Double, x2: Double, y2: Double, angleDegrees: Double) {
self.strokeWidth = strokeWidth
self.x1 = x1
self.y1 = y1
self.x2 = x2
Expand All @@ -25,7 +28,7 @@ public final class RotatedRectangle: Shape {
}

public func copy() -> RotatedRectangle {
RotatedRectangle(x1: x1, y1: y1, x2: x2, y2: y2, angleDegrees: angleDegrees)
RotatedRectangle(strokeWidth: strokeWidth, x1: x1, y1: y1, x2: x2, y2: y2, angleDegrees: angleDegrees)
}

public func setup(x xRange: ClosedRange<Int>, y yRange: ClosedRange<Int>, using generator: inout SplitMix64) {
Expand Down
4 changes: 3 additions & 1 deletion Sources/geometrize/Shapes/Shape.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Foundation

public protocol Shape: AnyObject, CustomStringConvertible {
init()
var strokeWidth: Double { get }

init(strokeWidth: Double)

func copy() -> Self

Expand Down
Loading

0 comments on commit f0493d4

Please sign in to comment.