Skip to content

Commit 3ef45d1

Browse files
committed
Added basic flame styles for eye and pupil
1 parent b62af8a commit 3ef45d1

15 files changed

+398
-7
lines changed

Art/images/data_flame.png

1.88 KB
Loading

Art/images/eye_flame.png

1.61 KB
Loading

Art/images/pupil_flame.png

827 Bytes
Loading

Art/qrcode-designs.pcvd

23.3 KB
Binary file not shown.

Documentation/shape-configuration/eye-styles.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Eye shape options
22

3-
| Preview | Name | Class | Options |
3+
| Preview | Name | Class | Options |
44
|:-------------:|-----------|---------|---------|
55
| <a href="../../Art/images/eye_barsHorizontal.png"><img src="../../Art/images/eye_barsHorizontal.png" width="75" /></a> | __barsHorizontal__ | `QRCode.EyeShape.BarsHorizontal` | _none_ |
66
| <a href="../../Art/images/eye_barsVertical.png"><img src="../../Art/images/eye_barsVertical.png" width="75" /></a> | __barsVertical__ | `QRCode.EyeShape.BarsVertical` | _none_ |
@@ -16,6 +16,7 @@
1616
| <a href="../../Art/images/eye_eye.png"><img src="../../Art/images/eye_eye.png" width="75" /></a> | __eye__ | `QRCode.EyeShape.Eye` |__Flippable__<br/>• __Configurable eye corners__<br/> |
1717
| <a href="../../Art/images/eye_fabricScissors.png"><img src="../../Art/images/eye_fabricScissors.png" width="75" /></a> | __fabricScissors__ | `QRCode.EyeShape.FabricScissors` | _none_ |
1818
| <a href="../../Art/images/eye_fireball.png"><img src="../../Art/images/eye_fireball.png" width="75" /></a> | __fireball__ | `QRCode.EyeShape.Fireball` | _none_ |
19+
| <a href="../../Art/images/eye_flame.png"><img src="../../Art/images/eye_flame.png" width="75" /></a> | __flame__ | `QRCode.EyeShape.Flame` | _none_ |
1920
| <a href="../../Art/images/eye_headlight.png"><img src="../../Art/images/eye_headlight.png" width="75" /></a> | __headlight__ | `QRCode.EyeShape.Headlight` |__Flippable__<br/> |
2021
| <a href="../../Art/images/eye_leaf.png"><img src="../../Art/images/eye_leaf.png" width="75" /></a> | __leaf__ | `QRCode.EyeShape.Leaf` | _none_ |
2122
| <a href="../../Art/images/eye_peacock.png"><img src="../../Art/images/eye_peacock.png" width="75" /></a> | __peacock__ | `QRCode.EyeShape.Peacock` | _none_ |

Documentation/shape-configuration/pixel-styles.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
| <a href="../../Art/images/data_donut.png"><img src="../../Art/images/data_donut.png" width="75" /></a> | __donut__ | `QRCode.PixelShape.Donut` | _none_ |
1717
| <a href="../../Art/images/data_dripHorizontal.png"><img src="../../Art/images/data_dripHorizontal.png" width="75" /></a> | __dripHorizontal__ | `QRCode.PixelShape.DripHorizontal` | _none_ |
1818
| <a href="../../Art/images/data_dripVertical.png"><img src="../../Art/images/data_dripVertical.png" width="75" /></a> | __dripVertical__ | `QRCode.PixelShape.DripVertical` | _none_ |
19+
| <a href="../../Art/images/data_flame.png"><img src="../../Art/images/data_flame.png" width="75" /></a> | __flame__ | `QRCode.PixelShape.Flame` |__Pixel rotation__<br/>&nbsp;&nbsp;- Supports pixel rotation generator<br/>• __Pixel inset__<br/>&nbsp;&nbsp;- Supports pixel inset generator<br/> |
1920
| <a href="../../Art/images/data_flower.png"><img src="../../Art/images/data_flower.png" width="75" /></a> | __flower__ | `QRCode.PixelShape.Flower` |__Pixel rotation__<br/>&nbsp;&nbsp;- Supports pixel rotation generator<br/>• __Pixel inset__<br/>&nbsp;&nbsp;- Supports pixel inset generator<br/> |
2021
| <a href="../../Art/images/data_grid2x2.png"><img src="../../Art/images/data_grid2x2.png" width="75" /></a> | __grid2x2__ | `QRCode.PixelShape.Grid2x2` | _none_ |
2122
| <a href="../../Art/images/data_grid3x3.png"><img src="../../Art/images/data_grid3x3.png" width="75" /></a> | __grid3x3__ | `QRCode.PixelShape.Grid3x3` | _none_ |

Documentation/shape-configuration/pupil-styles.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
| <a href="../../Art/images/pupil_edges.png"><img src="../../Art/images/pupil_edges.png" width="75" /></a> | __edges__ | `QRCode.PixelShape.Edges` |__Corner radius__<br/> |
2222
| <a href="../../Art/images/pupil_explode.png"><img src="../../Art/images/pupil_explode.png" width="75" /></a> | __explode__ | `QRCode.PixelShape.Explode` | _none_ |
2323
| <a href="../../Art/images/pupil_fabricScissors.png"><img src="../../Art/images/pupil_fabricScissors.png" width="75" /></a> | __fabricScissors__ | `QRCode.PixelShape.FabricScissors` | _none_ |
24+
| <a href="../../Art/images/pupil_flame.png"><img src="../../Art/images/pupil_flame.png" width="75" /></a> | __flame__ | `QRCode.PixelShape.Flame` | _none_ |
2425
| <a href="../../Art/images/pupil_forest.png"><img src="../../Art/images/pupil_forest.png" width="75" /></a> | __forest__ | `QRCode.PixelShape.Forest` |__Flippable__<br/> |
2526
| <a href="../../Art/images/pupil_hexagonLeaf.png"><img src="../../Art/images/pupil_hexagonLeaf.png" width="75" /></a> | __hexagonLeaf__ | `QRCode.PixelShape.HexagonLeaf` |__Flippable__<br/> |
2627
| <a href="../../Art/images/pupil_leaf.png"><img src="../../Art/images/pupil_leaf.png" width="75" /></a> | __leaf__ | `QRCode.PixelShape.Leaf` |__Flippable__<br/> |

README.md

Lines changed: 3 additions & 3 deletions
Large diffs are not rendered by default.

Sources/QRCode/styles/data/QRCodePixelShapeFactory.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import CoreGraphics
2121
import Foundation
2222

2323
/// A data shape factory
24-
@objc public final class QRCodePixelShapeFactory: NSObject, Sendable {
24+
@objc public final class QRCodePixelShapeFactory: NSObject {
2525
/// Shared data shape factory
2626
@objc public static let shared = QRCodePixelShapeFactory()
2727

@@ -101,6 +101,7 @@ import Foundation
101101
QRCode.PixelShape.Crosshatch.self,
102102
QRCode.PixelShape.DripVertical.self,
103103
QRCode.PixelShape.DripHorizontal.self,
104+
QRCode.PixelShape.Flame.self,
104105
].sorted(by: { a, b in a.Title < b.Title })
105106

106107
/// The default matrix to use when generating pixel sample images
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
//
2+
// Copyright © 2025 Darren Ford. All rights reserved.
3+
//
4+
// MIT license
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7+
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
8+
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
9+
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in all copies or substantial
12+
// portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
15+
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
16+
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18+
//
19+
20+
import CoreGraphics
21+
import Foundation
22+
23+
public extension QRCode.PixelShape {
24+
/// A flame style pixel to match the flame style eye
25+
@objc(QRCodePixelShapeFlame) class Flame: NSObject, QRCodePixelShapeGenerator {
26+
/// The generator name
27+
@objc public static let Name: String = "flame"
28+
/// The generator title
29+
@objc public static var Title: String { "Flame" }
30+
31+
/// This pupil generator can be used when generating eye and pupil shapes
32+
@objc public var canGenerateEyeAndPupilShapes: Bool { true }
33+
34+
/// Create
35+
/// - Parameters:
36+
/// - insetGenerator: The inset function to apply to each pixel
37+
/// - insetFraction: The inset between each pixel
38+
/// - rotationGenerator: The rotation function to apply to each pixel
39+
/// - rotationFraction: The rotation fraction (-1.0 -> 1.0) to apply to the rotation of each pixel
40+
@objc public init(
41+
insetGenerator: QRCodePixelInsetGenerator = QRCode.PixelInset.Fixed(),
42+
insetFraction: CGFloat = 0,
43+
rotationGenerator: QRCodePixelRotationGenerator = QRCode.PixelRotation.Fixed(),
44+
rotationFraction: CGFloat = 0
45+
) {
46+
self.common = CommonPixelGenerator(
47+
pixelType: .flame,
48+
insetGenerator: insetGenerator,
49+
insetFraction: insetFraction,
50+
rotationGenerator: rotationGenerator,
51+
rotationFraction: rotationFraction
52+
)
53+
super.init()
54+
}
55+
56+
/// Create an instance of this path generator with the specified settings
57+
@objc public static func Create(_ settings: [String: Any]?) -> any QRCodePixelShapeGenerator {
58+
let insetFraction = DoubleValue(settings?[QRCode.SettingsKey.insetFraction, default: 0]) ?? 0
59+
let rotationFraction = CGFloatValue(settings?[QRCode.SettingsKey.rotationFraction]) ?? 0.0
60+
61+
let generator: QRCodePixelInsetGenerator
62+
if let s = settings?[QRCode.SettingsKey.insetGeneratorName] as? String {
63+
generator = QRCode.PixelInset.generator(named: s) ?? QRCode.PixelInset.Fixed()
64+
}
65+
else {
66+
// Backwards compatible
67+
let useRandomInset = BoolValue(settings?[QRCode.SettingsKey.useRandomInset]) ?? false
68+
generator = useRandomInset ? QRCode.PixelInset.Random() : QRCode.PixelInset.Fixed()
69+
}
70+
71+
let rotationGenerator: QRCodePixelRotationGenerator
72+
if let s = settings?[QRCode.SettingsKey.rotationGeneratorName] as? String {
73+
rotationGenerator = QRCode.PixelRotation.generator(named: s) ?? QRCode.PixelRotation.Fixed()
74+
}
75+
else {
76+
// Backwards compatible
77+
let useRandomRotation = BoolValue(settings?[QRCode.SettingsKey.useRandomRotation]) ?? false
78+
rotationGenerator = useRandomRotation ? QRCode.PixelRotation.Random() : QRCode.PixelRotation.Fixed()
79+
}
80+
81+
return Flame(
82+
insetGenerator: generator,
83+
insetFraction: insetFraction,
84+
rotationGenerator: rotationGenerator,
85+
rotationFraction: rotationFraction
86+
)
87+
}
88+
89+
/// Make a copy of the object
90+
@objc public func copyShape() -> any QRCodePixelShapeGenerator {
91+
return Flame(
92+
insetGenerator: self.common.insetGenerator.copyInsetGenerator(),
93+
insetFraction: self.common.insetFraction,
94+
rotationGenerator: self.common.rotationGenerator.copyRotationGenerator(),
95+
rotationFraction: self.common.rotationFraction
96+
)
97+
}
98+
99+
/// Reset the generator back to defaults
100+
@objc public func reset() {
101+
self.common.insetGenerator = QRCode.PixelInset.Fixed()
102+
self.common.insetFraction = 0
103+
self.common.rotationGenerator = QRCode.PixelRotation.Fixed()
104+
self.common.rotationFraction = 0
105+
}
106+
107+
/// Generate a CGPath from the matrix contents
108+
/// - Parameters:
109+
/// - matrix: The matrix to generate
110+
/// - size: The size of the resulting CGPath
111+
/// - Returns: A path
112+
public func generatePath(from matrix: BoolMatrix, size: CGSize) -> CGPath {
113+
common.generatePath(from: matrix, size: size)
114+
}
115+
116+
private let common: CommonPixelGenerator
117+
}
118+
}
119+
120+
// MARK: - Drawing
121+
122+
internal extension QRCode.PixelShape.Flame {
123+
// A 10x10 'pixel' representation of a flame pixel
124+
static func flame10x10() -> CGPath { generatedPixelPath__ }
125+
}
126+
127+
private let generatedPixelPath__ = CGPath.make { flamepixelpathPath in
128+
flamepixelpathPath.move(to: CGPoint(x: 1, y: 1))
129+
flamepixelpathPath.curve(to: CGPoint(x: 2, y: 0), controlPoint1: CGPoint(x: 1, y: 0.45), controlPoint2: CGPoint(x: 1.45, y: 0))
130+
flamepixelpathPath.line(to: CGPoint(x: 9, y: 0))
131+
flamepixelpathPath.curve(to: CGPoint(x: 10, y: 1), controlPoint1: CGPoint(x: 9.55, y: 0), controlPoint2: CGPoint(x: 10, y: 0.45))
132+
flamepixelpathPath.line(to: CGPoint(x: 10, y: 8))
133+
flamepixelpathPath.curve(to: CGPoint(x: 9, y: 9), controlPoint1: CGPoint(x: 10, y: 8.55), controlPoint2: CGPoint(x: 9.55, y: 9))
134+
flamepixelpathPath.line(to: CGPoint(x: 1, y: 10))
135+
flamepixelpathPath.curve(to: CGPoint(x: 0, y: 9), controlPoint1: CGPoint(x: 0.45, y: 10), controlPoint2: CGPoint(x: 0, y: 9.55))
136+
flamepixelpathPath.line(to: CGPoint(x: 1, y: 1))
137+
flamepixelpathPath.close()
138+
}.flipped()
139+
140+
// MARK: - Settings
141+
142+
public extension QRCode.PixelShape.Flame {
143+
/// Returns true if the shape supports setting a value for the specified key, false otherwise
144+
@objc func supportsSettingValue(forKey key: String) -> Bool {
145+
return key == QRCode.SettingsKey.insetFraction
146+
|| key == QRCode.SettingsKey.insetGeneratorName
147+
|| key == QRCode.SettingsKey.rotationFraction
148+
|| key == QRCode.SettingsKey.rotationGeneratorName
149+
}
150+
151+
/// Returns the current settings for the shape
152+
@objc func settings() -> [String : Any] {
153+
var result: [String: Any] = [
154+
QRCode.SettingsKey.insetGeneratorName: self.common.insetGenerator.name,
155+
QRCode.SettingsKey.insetFraction: self.common.insetFraction,
156+
QRCode.SettingsKey.rotationGeneratorName: self.common.rotationGenerator.name,
157+
QRCode.SettingsKey.rotationFraction: self.common.rotationFraction,
158+
]
159+
if self.common.insetGenerator is QRCode.PixelInset.Random {
160+
// Backwards compatibility
161+
result[QRCode.SettingsKey.useRandomInset] = true
162+
}
163+
if self.common.rotationGenerator is QRCode.PixelRotation.Random {
164+
// Backwards compatibility
165+
result[QRCode.SettingsKey.useRandomRotation] = true
166+
}
167+
return result
168+
}
169+
170+
/// Set a configuration value for a particular setting string
171+
@objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool {
172+
if key == QRCode.SettingsKey.insetFraction {
173+
return self.common.setInsetFractionValue(value)
174+
}
175+
else if key == QRCode.SettingsKey.insetGeneratorName {
176+
return self.common.setInsetGenerator(named: value)
177+
}
178+
else if key == QRCode.SettingsKey.rotationFraction {
179+
return self.common.setRotationFraction(value)
180+
}
181+
else if key == QRCode.SettingsKey.rotationGeneratorName {
182+
return self.common.setRotationGenerator(named: value)
183+
}
184+
else if key == QRCode.SettingsKey.useRandomRotation {
185+
// backwards compatibility
186+
let which = BoolValue(value) ?? false
187+
return self.common.setRotationGenerator(named: which ? QRCode.PixelRotation.Random() : QRCode.PixelRotation.Fixed())
188+
}
189+
else if key == QRCode.SettingsKey.useRandomInset {
190+
// backwards compatibility
191+
let which = BoolValue(value) ?? false
192+
return self.common.setInsetGenerator(which ? QRCode.PixelInset.Random() : QRCode.PixelInset.Fixed())
193+
}
194+
return false
195+
}
196+
}
197+
198+
// MARK: - Pixel creation conveniences
199+
200+
public extension QRCodePixelShapeGenerator where Self == QRCode.PixelShape.Flame {
201+
/// Create a flame pixel generator
202+
/// - Parameters:
203+
/// - insetGenerator: The inset generator
204+
/// - insetFraction: The inset between each pixel
205+
/// - rotationGenerator: The rotation generator
206+
/// - rotationFraction: The rotation fraction (-1.0 -> 1.0) to apply to the rotation of each pixel
207+
/// - Returns: A pixel generator
208+
@inlinable static func flame(
209+
insetGenerator: QRCodePixelInsetGenerator = QRCode.PixelInset.Fixed(),
210+
insetFraction: CGFloat = 0,
211+
rotationGenerator: QRCodePixelRotationGenerator = QRCode.PixelRotation.Fixed(),
212+
rotationFraction: CGFloat = 0
213+
) -> QRCodePixelShapeGenerator {
214+
QRCode.PixelShape.Flame(
215+
insetGenerator: insetGenerator,
216+
insetFraction: insetFraction,
217+
rotationGenerator: rotationGenerator,
218+
rotationFraction: rotationFraction
219+
)
220+
}
221+
}

0 commit comments

Comments
 (0)