Skip to content

Commit

Permalink
Added diamond pixel style
Browse files Browse the repository at this point in the history
  • Loading branch information
dagronf committed Jan 15, 2025
1 parent 4e0c2d2 commit ad05ac2
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 1 deletion.
Binary file added Art/images/data_diamond.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Art/paths.pcvd
Binary file not shown.
1 change: 1 addition & 0 deletions Documentation/shape-configuration/pixel-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| <a href="../../Art/images/data_circuit.png"><img src="../../Art/images/data_circuit.png" width="75" /></a> | __circuit__ | `QRCode.PixelShape.Circuit` | _none_ |
| <a href="../../Art/images/data_crt.png"><img src="../../Art/images/data_crt.png" width="75" /></a> | __crt__ | `QRCode.PixelShape.CRT` |__Pixel rotation__<br/>&nbsp;&nbsp;- Supports pixel rotation generator<br/>• __Pixel inset__<br/>&nbsp;&nbsp;- Supports pixel inset generator<br/> |
| <a href="../../Art/images/data_curvePixel.png"><img src="../../Art/images/data_curvePixel.png" width="75" /></a> | __curvePixel__ | `QRCode.PixelShape.CurvePixel` |__Corner radius__<br/> |
| <a href="../../Art/images/data_diamond.png"><img src="../../Art/images/data_diamond.png" width="75" /></a> | __diamond__ | `QRCode.PixelShape.Diamond` |__Pixel rotation__<br/>&nbsp;&nbsp;- Supports pixel rotation generator<br/>• __Pixel inset__<br/>&nbsp;&nbsp;- Supports pixel inset generator<br/> |
| <a href="../../Art/images/data_donut.png"><img src="../../Art/images/data_donut.png" width="75" /></a> | __donut__ | `QRCode.PixelShape.Donut` | _none_ |
| <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/> |
| <a href="../../Art/images/data_grid2x2.png"><img src="../../Art/images/data_grid2x2.png" width="75" /></a> | __grid2x2__ | `QRCode.PixelShape.Grid2x2` | _none_ |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ The 'shape' represents the way that each of the components are drawn
The data shape represents how the 'pixels' within the QR code are displayed. By default, this is a simple square,
however you can supply a `PixelShape` object to custom-draw the data. There are built-in generators for a variety of styles.

<img src="./Art/images/data_abstract.png" width="60" title="Abstract" /> <img src="./Art/images/data_arrow.png" width="60" title="Arrow" /> <img src="./Art/images/data_blob.png" width="60" title="Blob" /> <img src="./Art/images/data_circle.png" width="60" title="Circle" /> <img src="./Art/images/data_circuit.png" width="60" title="Circuit" /> <img src="./Art/images/data_crt.png" width="60" title="CRT" /> <img src="./Art/images/data_curvePixel.png" width="60" title="Curve pixel" /> <img src="./Art/images/data_donut.png" width="60" title="Donut" /> <img src="./Art/images/data_flower.png" width="60" title="Flower" /> <img src="./Art/images/data_grid2x2.png" width="60" title="2x2 Square Grid" /> <img src="./Art/images/data_grid3x3.png" width="60" title="3x3 Square Grid" /> <img src="./Art/images/data_grid4x4.png" width="60" title="4x4 Square Grid" /> <img src="./Art/images/data_heart.png" width="60" title="Heart" /> <img src="./Art/images/data_hexagon.png" width="60" title="Hexagon" /> <img src="./Art/images/data_horizontal.png" width="60" title="Horizontal bars" /> <img src="./Art/images/data_pointy.png" width="60" title="Pointy" /> <img src="./Art/images/data_razor.png" width="60" title="Razor" /> <img src="./Art/images/data_roundedEndIndent.png" width="60" title="Rounded end indent" /> <img src="./Art/images/data_roundedPath.png" width="60" title="Rounded path" /> <img src="./Art/images/data_roundedRect.png" width="60" title="Rounded rectangle" /> <img src="./Art/images/data_sharp.png" width="60" title="Sharp" /> <img src="./Art/images/data_shiny.png" width="60" title="Shiny" /> <img src="./Art/images/data_spikyCircle.png" width="60" title="Spiky Circle" /> <img src="./Art/images/data_square.png" width="60" title="Square" /> <img src="./Art/images/data_squircle.png" width="60" title="Squircle" /> <img src="./Art/images/data_star.png" width="60" title="Star" /> <img src="./Art/images/data_stitch.png" width="60" title="Stitch" /> <img src="./Art/images/data_vertical.png" width="60" title="Vertical bars" /> <img src="./Art/images/data_vortex.png" width="60" title="Vortex" /> <img src="./Art/images/data_wave.png" width="60" title="Wave" /> <img src="./Art/images/data_wex.png" width="60" title="Wex" />
<img src="./Art/images/data_abstract.png" width="60" title="Abstract" /> <img src="./Art/images/data_arrow.png" width="60" title="Arrow" /> <img src="./Art/images/data_blob.png" width="60" title="Blob" /> <img src="./Art/images/data_circle.png" width="60" title="Circle" /> <img src="./Art/images/data_circuit.png" width="60" title="Circuit" /> <img src="./Art/images/data_crt.png" width="60" title="CRT" /> <img src="./Art/images/data_curvePixel.png" width="60" title="Curve pixel" /> <img src="./Art/images/data_diamond.png" width="60" title="Diamond" /> <img src="./Art/images/data_donut.png" width="60" title="Donut" /> <img src="./Art/images/data_flower.png" width="60" title="Flower" /> <img src="./Art/images/data_grid2x2.png" width="60" title="2x2 Square Grid" /> <img src="./Art/images/data_grid3x3.png" width="60" title="3x3 Square Grid" /> <img src="./Art/images/data_grid4x4.png" width="60" title="4x4 Square Grid" /> <img src="./Art/images/data_heart.png" width="60" title="Heart" /> <img src="./Art/images/data_hexagon.png" width="60" title="Hexagon" /> <img src="./Art/images/data_horizontal.png" width="60" title="Horizontal bars" /> <img src="./Art/images/data_pointy.png" width="60" title="Pointy" /> <img src="./Art/images/data_razor.png" width="60" title="Razor" /> <img src="./Art/images/data_roundedEndIndent.png" width="60" title="Rounded end indent" /> <img src="./Art/images/data_roundedPath.png" width="60" title="Rounded path" /> <img src="./Art/images/data_roundedRect.png" width="60" title="Rounded rectangle" /> <img src="./Art/images/data_sharp.png" width="60" title="Sharp" /> <img src="./Art/images/data_shiny.png" width="60" title="Shiny" /> <img src="./Art/images/data_spikyCircle.png" width="60" title="Spiky Circle" /> <img src="./Art/images/data_square.png" width="60" title="Square" /> <img src="./Art/images/data_squircle.png" width="60" title="Squircle" /> <img src="./Art/images/data_star.png" width="60" title="Star" /> <img src="./Art/images/data_stitch.png" width="60" title="Stitch" /> <img src="./Art/images/data_vertical.png" width="60" title="Vertical bars" /> <img src="./Art/images/data_vortex.png" width="60" title="Vortex" /> <img src="./Art/images/data_wave.png" width="60" title="Wave" /> <img src="./Art/images/data_wex.png" width="60" title="Wex" />

[Pixel style configuration options](./Documentation/shape-configuration/pixel-styles.md)

Expand Down
1 change: 1 addition & 0 deletions Sources/QRCode/styles/data/QRCodePixelShapeFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import Foundation
QRCode.PixelShape.Stitch.self,
QRCode.PixelShape.Hexagon.self,
QRCode.PixelShape.Wex.self,
QRCode.PixelShape.Diamond.self,
].sorted(by: { a, b in a.Title < b.Title })

/// The default matrix to use when generating pixel sample images
Expand Down
217 changes: 217 additions & 0 deletions Sources/QRCode/styles/data/pixel/QRCodePixelShapeDiamond.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
//
// Copyright © 2025 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import CoreGraphics
import Foundation

public extension QRCode.PixelShape {
/// A diamond pixel shape
@objc(QRCodePixelShapeDiamond) class Diamond: NSObject, QRCodePixelShapeGenerator {
/// The generator name
@objc public static let Name: String = "diamond"
/// The generator title
@objc public static var Title: String { "Diamond" }

/// This pupil generator can be used when generating eye and pupil shapes
@objc public var canGenerateEyeAndPupilShapes: Bool { true }

/// Create
/// - Parameters:
/// - insetGenerator: The inset function to apply to each pixel
/// - insetFraction: The inset between each pixel
/// - rotationGenerator: The rotation function to apply to each pixel
/// - rotationFraction: The rotation fraction (-1.0 -> 1.0) to apply to the rotation of each pixel
@objc public init(
insetGenerator: QRCodePixelInsetGenerator = QRCode.PixelInset.Fixed(),
insetFraction: CGFloat = 0,
rotationGenerator: QRCodePixelRotationGenerator = QRCode.PixelRotation.Fixed(),
rotationFraction: CGFloat = 0
) {
self.common = CommonPixelGenerator(
pixelType: .diamond,
insetGenerator: insetGenerator,
insetFraction: insetFraction,
rotationGenerator: rotationGenerator,
rotationFraction: rotationFraction
)
super.init()
}

/// Create an instance of this path generator with the specified settings
@objc public static func Create(_ settings: [String: Any]?) -> any QRCodePixelShapeGenerator {
let insetFraction = DoubleValue(settings?[QRCode.SettingsKey.insetFraction, default: 0]) ?? 0
let rotationFraction = CGFloatValue(settings?[QRCode.SettingsKey.rotationFraction]) ?? 0.0

let generator: QRCodePixelInsetGenerator
if let s = settings?[QRCode.SettingsKey.insetGeneratorName] as? String {
generator = QRCode.PixelInset.generator(named: s) ?? QRCode.PixelInset.Fixed()
}
else {
// Backwards compatible
let useRandomInset = BoolValue(settings?[QRCode.SettingsKey.useRandomInset]) ?? false
generator = useRandomInset ? QRCode.PixelInset.Random() : QRCode.PixelInset.Fixed()
}

let rotationGenerator: QRCodePixelRotationGenerator
if let s = settings?[QRCode.SettingsKey.rotationGeneratorName] as? String {
rotationGenerator = QRCode.PixelRotation.generator(named: s) ?? QRCode.PixelRotation.Fixed()
}
else {
// Backwards compatible
let useRandomRotation = BoolValue(settings?[QRCode.SettingsKey.useRandomRotation]) ?? false
rotationGenerator = useRandomRotation ? QRCode.PixelRotation.Random() : QRCode.PixelRotation.Fixed()
}

return Diamond(
insetGenerator: generator,
insetFraction: insetFraction,
rotationGenerator: rotationGenerator,
rotationFraction: rotationFraction
)
}

/// Make a copy of the object
@objc public func copyShape() -> any QRCodePixelShapeGenerator {
return Diamond(
insetGenerator: self.common.insetGenerator.copyInsetGenerator(),
insetFraction: self.common.insetFraction,
rotationGenerator: self.common.rotationGenerator.copyRotationGenerator(),
rotationFraction: self.common.rotationFraction
)
}

/// Reset the generator back to defaults
@objc public func reset() {
self.common.insetGenerator = QRCode.PixelInset.Fixed()
self.common.insetFraction = 0
self.common.rotationGenerator = QRCode.PixelRotation.Fixed()
self.common.rotationFraction = 0
}

/// Generate a CGPath from the matrix contents
/// - Parameters:
/// - matrix: The matrix to generate
/// - size: The size of the resulting CGPath
/// - Returns: A path
public func generatePath(from matrix: BoolMatrix, size: CGSize) -> CGPath {
common.generatePath(from: matrix, size: size)
}

private let common: CommonPixelGenerator
}
}

// MARK: - Drawing

internal extension QRCode.PixelShape.Diamond {
// A 10x10 'pixel' representation of a diamond pixel
static func diamond10x10() -> CGPath { __diamondPath }
}

private let __diamondPath = CGPath.make { diamondPath in
diamondPath.move(to: CGPoint(x: 1, y: 1))
diamondPath.line(to: CGPoint(x: 10, y: 0))
diamondPath.line(to: CGPoint(x: 9, y: 9))
diamondPath.line(to: CGPoint(x: 0, y: 10))
diamondPath.line(to: CGPoint(x: 1, y: 1))
diamondPath.close()
}

// MARK: - Settings

public extension QRCode.PixelShape.Diamond {
/// Returns true if the shape supports setting a value for the specified key, false otherwise
@objc func supportsSettingValue(forKey key: String) -> Bool {
return key == QRCode.SettingsKey.insetFraction
|| key == QRCode.SettingsKey.insetGeneratorName
|| key == QRCode.SettingsKey.rotationFraction
|| key == QRCode.SettingsKey.rotationGeneratorName
}

/// Returns the current settings for the shape
@objc func settings() -> [String : Any] {
var result: [String: Any] = [
QRCode.SettingsKey.insetGeneratorName: self.common.insetGenerator.name,
QRCode.SettingsKey.insetFraction: self.common.insetFraction,
QRCode.SettingsKey.rotationGeneratorName: self.common.rotationGenerator.name,
QRCode.SettingsKey.rotationFraction: self.common.rotationFraction,
]
if self.common.insetGenerator is QRCode.PixelInset.Random {
// Backwards compatibility
result[QRCode.SettingsKey.useRandomInset] = true
}
if self.common.rotationGenerator is QRCode.PixelRotation.Random {
// Backwards compatibility
result[QRCode.SettingsKey.useRandomRotation] = true
}
return result
}

/// Set a configuration value for a particular setting string
@objc func setSettingValue(_ value: Any?, forKey key: String) -> Bool {
if key == QRCode.SettingsKey.insetFraction {
return self.common.setInsetFractionValue(value)
}
else if key == QRCode.SettingsKey.insetGeneratorName {
return self.common.setInsetGenerator(named: value)
}
else if key == QRCode.SettingsKey.rotationFraction {
return self.common.setRotationFraction(value)
}
else if key == QRCode.SettingsKey.rotationGeneratorName {
return self.common.setRotationGenerator(named: value)
}
else if key == QRCode.SettingsKey.useRandomRotation {
// backwards compatibility
let which = BoolValue(value) ?? false
return self.common.setRotationGenerator(named: which ? QRCode.PixelRotation.Random() : QRCode.PixelRotation.Fixed())
}
else if key == QRCode.SettingsKey.useRandomInset {
// backwards compatibility
let which = BoolValue(value) ?? false
return self.common.setInsetGenerator(which ? QRCode.PixelInset.Random() : QRCode.PixelInset.Fixed())
}
return false
}
}

// MARK: - Pixel creation conveniences

public extension QRCodePixelShapeGenerator where Self == QRCode.PixelShape.Diamond {
/// Create a diamond pixel generator
/// - Parameters:
/// - insetGenerator: The inset generator
/// - insetFraction: The inset between each pixel
/// - rotationGenerator: The rotation generator
/// - rotationFraction: The rotation fraction (-1.0 -> 1.0) to apply to the rotation of each pixel
/// - Returns: A pixel generator
@inlinable static func diamond(
insetGenerator: QRCodePixelInsetGenerator = QRCode.PixelInset.Fixed(),
insetFraction: CGFloat = 0,
rotationGenerator: QRCodePixelRotationGenerator = QRCode.PixelRotation.Fixed(),
rotationFraction: CGFloat = 0
) -> QRCodePixelShapeGenerator {
QRCode.PixelShape.Diamond(
insetGenerator: insetGenerator,
insetFraction: insetFraction,
rotationGenerator: rotationGenerator,
rotationFraction: rotationFraction
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ internal extension QRCode.PixelShape {
case stitch
case hexagon
case wex
case diamond
static var availableTypes: [String] = Self.allCases.map { $0.rawValue }
}

Expand Down Expand Up @@ -219,6 +220,16 @@ internal extension QRCode.PixelShape {
let sq = Arrow.arrow10x10()
path.addPath(sq, transform: transform)
}
else if self.pixelType == .diamond {
let transform = CGAffineTransform(scaleX: ri.width / 10, y: ri.width / 10)
.concatenating(CGAffineTransform(
translationX: xoff + (CGFloat(col) * dm) + insetValue,
y: yoff + (CGFloat(row) * dm) + insetValue
))
.concatenating(rotateTransform)
let sq = Diamond.diamond10x10()
path.addPath(sq, transform: transform)
}
else if self.pixelType == .wave {
let transform = CGAffineTransform(scaleX: ri.width / 10, y: ri.width / 10)
.concatenating(CGAffineTransform(
Expand Down

0 comments on commit ad05ac2

Please sign in to comment.