From: Ruben Beltran del Rio Date: Fri, 28 Apr 2023 20:23:36 +0000 (+0200) Subject: Use CoreGraphics to draw instead of SwifTUI X-Git-Tag: 2.0.0 X-Git-Url: https://git.r.bdr.sh/rbdr/patterns/commitdiff_plain Use CoreGraphics to draw instead of SwifTUI --- diff --git a/README.md b/README.md index 1a862f6..900c4b2 100644 --- a/README.md +++ b/README.md @@ -97,12 +97,16 @@ PatternView(design: $design) * 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` +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) diff --git a/Sources/Patterns/CGImage+resize.swift b/Sources/Patterns/CGImage+resize.swift new file mode 100644 index 0000000..72ef00e --- /dev/null +++ b/Sources/Patterns/CGImage+resize.swift @@ -0,0 +1,19 @@ +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() + } +} diff --git a/Sources/Patterns/PatternView.swift b/Sources/Patterns/PatternView.swift index be78bde..d4ed37b 100644 --- a/Sources/Patterns/PatternView.swift +++ b/Sources/Patterns/PatternView.swift @@ -7,28 +7,31 @@ public struct PatternView: View { public var foregroundColor: Color public var backgroundColor: Color + private let image: CGImage private var patternSize: CGFloat { pixelSize * 8.0; } - + public init(design: Binding, 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() } } diff --git a/Sources/Patterns/Tile.swift b/Sources/Patterns/Tile.swift deleted file mode 100644 index 9c39640..0000000 --- a/Sources/Patterns/Tile.swift +++ /dev/null @@ -1,50 +0,0 @@ -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) - } - } -} diff --git a/Sources/Patterns/TileDesign.swift b/Sources/Patterns/TileDesign.swift index 5541102..84a8b00 100644 --- a/Sources/Patterns/TileDesign.swift +++ b/Sources/Patterns/TileDesign.swift @@ -13,7 +13,7 @@ public enum TileDesign: CaseIterable { case rhombus case balls - func pixels() -> [Int] { + func pixels() -> [UInt8] { switch self { case .grid: return [ diff --git a/Sources/Patterns/TileImage.swift b/Sources/Patterns/TileImage.swift new file mode 100644 index 0000000..4ef84da --- /dev/null +++ b/Sources/Patterns/TileImage.swift @@ -0,0 +1,51 @@ +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.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 + } +}