* watchOS 8+
* catalyst 15+
-## The Tile view
+## The TileImage struct
If you'd like to do other things with the individual tiles, we also provide the
-Tile view, which is just a single tile.
+TileImage struct, which generates a CGImage.
-The tiles support the same properties as `PatternView` with the exception that
-`design` is a `TileDesign` and not a `Binding<TileDesign>`
+The tiles support similar properties as `PatternView` with the exception that
+
+* `design: TileDesign`: **required**, which design to use to tile the frame.
+* `pixelSize: CGFloat`: **defaults to 2.0**, the size of a pixel in the tile.
+* `foregroundColor: CGColor`: **defaults to black**, the foreground color.
+* `backgroundColor: CGColor`: **defaults to white**, the background color.
![Screenshots of the tiles showing the different overrides](./doc/images/tile_example.png)
--- /dev/null
+import CoreGraphics
+
+extension CGImage {
+ func resize(size:CGSize) -> CGImage? {
+ let width: Int = Int(size.width)
+ let height: Int = Int(size.height)
+
+ let bytesPerPixel = self.bitsPerPixel / self.bitsPerComponent
+ let destBytesPerRow = width * bytesPerPixel
+
+ guard let colorSpace = self.colorSpace else { return nil }
+ guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: self.bitsPerComponent, bytesPerRow: destBytesPerRow, space: colorSpace, bitmapInfo: self.alphaInfo.rawValue) else { return nil }
+
+ context.interpolationQuality = .none
+ context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height))
+
+ return context.makeImage()
+ }
+}
public var foregroundColor: Color
public var backgroundColor: Color
+ private let image: CGImage
private var patternSize: CGFloat {
pixelSize * 8.0;
}
-
+
public init(design: Binding<TileDesign>, pixelSize: CGFloat = 2.0, foregroundColor: Color = .black, backgroundColor: Color = .white) {
self._design = design
self.pixelSize = pixelSize
self.foregroundColor = foregroundColor
self.backgroundColor = backgroundColor
+
+ #if os(iOS) || os(watchOS) || os(tvOS)
+ let foregroundCGColor = UIColor(foregroundColor).cgColor
+ let backgroundCGColor = UIColor(backgroundColor).cgColor
+ #else
+ let foregroundCGColor = NSColor(foregroundColor).cgColor
+ let backgroundCGColor = NSColor(backgroundColor).cgColor
+ #endif
+
+ self.image = TileImage.image(design.wrappedValue, pixelSize: pixelSize, foregroundColor: foregroundCGColor, backgroundColor: backgroundCGColor)
}
-
+
public var body: some View {
GeometryReader { gr in
- VStack(spacing: 0) {
- ForEach(0 ..< 1 + Int(ceil(gr.size.height / patternSize)), id: \.self) { i in
- HStack(spacing: 0) {
- ForEach(0 ..< Int(ceil(gr.size.width / patternSize)), id: \.self) { j in
- Tile(design: design, pixelSize: pixelSize, foregroundColor: foregroundColor, backgroundColor: backgroundColor)
- }
- }
- }
- }
+ Image(image, scale: 1, label: Text("Test")).resizable(resizingMode: .tile)
}.drawingGroup()
}
}
+++ /dev/null
-import SwiftUI
-
-public struct Tile: View {
-
- public let design: TileDesign
- public var pixelSize: CGFloat = 2.0;
- public var foregroundColor: Color = .black
- public var backgroundColor: Color = .white
-
- private var pixels: [Int] {
- design.pixels()
- }
-
- public init(design: TileDesign, pixelSize: CGFloat = 2.0, foregroundColor: Color = .black, backgroundColor: Color = .white) {
- self.design = design
- self.pixelSize = pixelSize
- self.foregroundColor = foregroundColor
- self.backgroundColor = backgroundColor
- }
-
- public var body: some View {
- VStack(spacing: 0) {
- ForEach(0 ..< 8) { i in
- HStack(spacing: 0) {
- ForEach(0 ..< 8) { j in
- Rectangle()
- .frame(width: pixelSize, height: pixelSize)
- .foregroundColor(pixels[(i % 8) * 8 + j % 8] == 0
- ? foregroundColor
- : backgroundColor
- )
- }
- }
- }
- }
- }
-}
-
-struct Tile_Previews: PreviewProvider {
- static var previews: some View {
- VStack {
- Text("Default")
- Tile(design: .grid)
- Text("Color override")
- Tile(design: .balls, foregroundColor: .pink, backgroundColor: .cyan)
- Text("Pixel size override")
- Tile(design: .shingles, pixelSize: 8.0)
- }
- }
-}
case rhombus
case balls
- func pixels() -> [Int] {
+ func pixels() -> [UInt8] {
switch self {
case .grid:
return [
--- /dev/null
+import CoreGraphics
+
+struct TileImage {
+ static func image(_ design: TileDesign, pixelSize: CGFloat, foregroundColor: CGColor? = nil, backgroundColor: CGColor? = nil) -> CGImage {
+
+ let fg = foregroundColor ?? .init(red: 0, green: 0, blue: 0, alpha: 255)
+ let bg = backgroundColor ?? .init(red: 255, green: 255, blue: 255, alpha: 255)
+
+ // Convert the array to image data
+
+ let pixels: [UInt8] = design.pixels().map({ x in
+ var color = bg
+ if x == 0 {
+ color = fg
+ }
+ let components = color.components ?? [0,0,0,1]
+ return components.map({ c in
+ UInt8(round(c * 255))
+ })
+ }).reduce([], +)
+
+ // Generate the image
+
+ let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: pixels.count)
+ pointer.initialize(from: pixels, count: pixels.count)
+ let width = 8.0
+ let height = 8.0
+ let bitsPerComponent = 8
+ let componentCount = 4
+ let bitsPerPixel = bitsPerComponent * componentCount
+ let colorSpace = CGColorSpaceCreateDeviceRGB()
+
+ let bitmapInfo = CGBitmapInfo([.byteOrderDefault, CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)])
+ let provider = CGDataProvider(dataInfo: nil, data: pointer, size: Int(width * height * 4)) { _, _, _ in
+ return
+ }!
+
+ let image = CGImage(
+ width: Int(width),
+ height: Int(height),
+ bitsPerComponent: bitsPerComponent,
+ bitsPerPixel: bitsPerPixel,
+ bytesPerRow: Int(width) * componentCount,
+ space: colorSpace,
+ bitmapInfo: bitmapInfo,
+ provider: provider,
+ decode: nil, shouldInterpolate: false, intent: CGColorRenderingIntent.defaultIntent)!
+
+ return image.resize(size: CGSize(width: width * pixelSize, height: height * pixelSize)) ?? image
+ }
+}