From 858f80f7a1db4635995ad73ee409791d585c40d6 Mon Sep 17 00:00:00 2001 From: Eric Yuan Date: Sun, 7 Jul 2024 12:22:57 +1200 Subject: [PATCH 1/7] Add RGBA enum to DOM.Color --- SwiftDraw/DOM.Color.swift | 1 + SwiftDraw/LayerTree.Color.swift | 10 ++++++++++ SwiftDraw/Parser.XML.Color.swift | 18 ++++++++++++++++++ SwiftDraw/XML.Formatter.SVG.swift | 3 +++ 4 files changed, 32 insertions(+) diff --git a/SwiftDraw/DOM.Color.swift b/SwiftDraw/DOM.Color.swift index 2c3f345..1c4c7a9 100644 --- a/SwiftDraw/DOM.Color.swift +++ b/SwiftDraw/DOM.Color.swift @@ -39,6 +39,7 @@ extension DOM { case rgbf(DOM.Float, DOM.Float, DOM.Float) case p3(DOM.Float, DOM.Float, DOM.Float) case hex(UInt8, UInt8, UInt8) + case rgba(UInt8, UInt8, UInt8, DOM.Float) // see: https://www.w3.org/TR/SVG11/types.html#ColorKeywords enum Keyword: String { diff --git a/SwiftDraw/LayerTree.Color.swift b/SwiftDraw/LayerTree.Color.swift index 289c89c..5b8bf73 100644 --- a/SwiftDraw/LayerTree.Color.swift +++ b/SwiftDraw/LayerTree.Color.swift @@ -74,6 +74,8 @@ extension LayerTree.Color { b: Float(b), a: 1.0, space: .p3) + case let .rgba(r, g, b, a): + return LayerTree.Color(r, g, b, a) } } @@ -84,6 +86,14 @@ extension LayerTree.Color { a: 1.0, space: .srgb) } + + init(_ r: UInt8, _ g: UInt8, _ b: UInt8, _ a: DOM.Float) { + self = .rgba(r: Float(r)/255.0, + g: Float(g)/255.0, + b: Float(b)/255.0, + a: a, + space: .srgb) + } var isOpaque: Bool { switch self { diff --git a/SwiftDraw/Parser.XML.Color.swift b/SwiftDraw/Parser.XML.Color.swift index cbc1e39..a61d8a6 100644 --- a/SwiftDraw/Parser.XML.Color.swift +++ b/SwiftDraw/Parser.XML.Color.swift @@ -49,6 +49,8 @@ extension XMLParser { return .color(c) } else if let url = try parseURLSelector(data: data) { return .url(url) + } else if let c = try parseColorRGBA(data: data) { + return .color(c) } throw Error.invalid @@ -173,4 +175,20 @@ extension XMLParser { return .hex(r, g, b) } + + private func parseColorRGBA(data: String) throws -> DOM.Color? { + var scanner = XMLParser.Scanner(text: data) + try scanner.scanString("rgba(") + + let r = try scanner.scanUInt8() + scanner.scanStringIfPossible(",") + let g = try scanner.scanUInt8() + scanner.scanStringIfPossible(",") + let b = try scanner.scanUInt8() + scanner.scanStringIfPossible(",") + let a = try scanner.scanFloat() // Opacity + try scanner.scanString(")") + + return .rgba(r, g, b, a) + } } diff --git a/SwiftDraw/XML.Formatter.SVG.swift b/SwiftDraw/XML.Formatter.SVG.swift index 0a8273b..50e36e8 100644 --- a/SwiftDraw/XML.Formatter.SVG.swift +++ b/SwiftDraw/XML.Formatter.SVG.swift @@ -309,6 +309,9 @@ extension XML.Formatter { let gg = String(format: "%02X", g) let bb = String(format: "%02X", b) return "#\(rr)\(gg)\(bb)" + case let .rgba(r, g, b, a): + let aa = String(format: "%.2f", a) + return "rgba(\(r), \(g), \(b), \(aa))" } } From 2258006560f29e44b5b77fd82f1c45ea86b92586 Mon Sep 17 00:00:00 2001 From: Eric Yuan Date: Sun, 7 Jul 2024 12:26:45 +1200 Subject: [PATCH 2/7] Support alpha when rendering --- SwiftDraw/LayerTree.Color.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SwiftDraw/LayerTree.Color.swift b/SwiftDraw/LayerTree.Color.swift index 5b8bf73..424a1d9 100644 --- a/SwiftDraw/LayerTree.Color.swift +++ b/SwiftDraw/LayerTree.Color.swift @@ -110,14 +110,14 @@ extension LayerTree.Color { switch self { case .none: return .none - case let .rgba(r: r, g: g, b: b, a: _, space): + case let .rgba(r: r, g: g, b: b, a: a, space): return .rgba(r: r, g: g, b: b, - a: alpha, + a: alpha * a, space: space) - case .gray(white: let w, a: _): - return .gray(white: w, a: alpha) + case .gray(white: let w, a: let a): + return .gray(white: w, a: alpha * a) } } From f4bfedb06f7f602d86d6c155ee89780519dac8bb Mon Sep 17 00:00:00 2001 From: Eric Yuan Date: Sun, 7 Jul 2024 12:37:25 +1200 Subject: [PATCH 3/7] Add the sample --- Examples/Sources/ViewController.swift | 2 +- Samples.bundle/rgba.svg | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Samples.bundle/rgba.svg diff --git a/Examples/Sources/ViewController.swift b/Examples/Sources/ViewController.swift index dcd3b5b..28c2ed5 100644 --- a/Examples/Sources/ViewController.swift +++ b/Examples/Sources/ViewController.swift @@ -65,7 +65,7 @@ class ViewController: UIViewController { override func loadView() { let imageView = UIImageView(frame: UIScreen.main.bounds) - imageView.image = SVG(named: "units-cm.svg", in: .samples)?.rasterize() + imageView.image = SVG(named: "rgba.svg", in: .samples)?.rasterize() imageView.contentMode = .scaleAspectFit imageView.backgroundColor = .white self.view = imageView diff --git a/Samples.bundle/rgba.svg b/Samples.bundle/rgba.svg new file mode 100644 index 0000000..894aa8e --- /dev/null +++ b/Samples.bundle/rgba.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From ac3a5bb418a3df0a0d481233f93e1e5084133544 Mon Sep 17 00:00:00 2001 From: Eric Yuan Date: Sun, 7 Jul 2024 12:41:49 +1200 Subject: [PATCH 4/7] Refine the sample --- Samples.bundle/rgba.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Samples.bundle/rgba.svg b/Samples.bundle/rgba.svg index 894aa8e..4c66a99 100644 --- a/Samples.bundle/rgba.svg +++ b/Samples.bundle/rgba.svg @@ -1,5 +1,5 @@ - + - \ No newline at end of file + From cb8740385760aaf3e3808c3b1729bfcf4283c887 Mon Sep 17 00:00:00 2001 From: Eric Yuan Date: Sun, 7 Jul 2024 14:10:47 +1200 Subject: [PATCH 5/7] Refactor alpha into current types Rather than creating a new value, added the support for existing color types to conform with CSS color specification. --- Samples.bundle/rgba.svg | 2 +- SwiftDraw/DOM.Color.swift | 5 +-- SwiftDraw/LayerTree.Color.swift | 10 ++--- SwiftDraw/Parser.XML.Color.swift | 70 +++++++++++++++++++++---------- SwiftDraw/XML.Formatter.SVG.swift | 13 +++--- 5 files changed, 61 insertions(+), 39 deletions(-) diff --git a/Samples.bundle/rgba.svg b/Samples.bundle/rgba.svg index 4c66a99..7b3c286 100644 --- a/Samples.bundle/rgba.svg +++ b/Samples.bundle/rgba.svg @@ -1,5 +1,5 @@ - + diff --git a/SwiftDraw/DOM.Color.swift b/SwiftDraw/DOM.Color.swift index 1c4c7a9..65ddb3b 100644 --- a/SwiftDraw/DOM.Color.swift +++ b/SwiftDraw/DOM.Color.swift @@ -35,11 +35,10 @@ extension DOM { case none case currentColor case keyword(Keyword) - case rgbi(UInt8, UInt8, UInt8) - case rgbf(DOM.Float, DOM.Float, DOM.Float) + case rgbi(UInt8, UInt8, UInt8, DOM.Float) + case rgbf(DOM.Float, DOM.Float, DOM.Float, DOM.Float) case p3(DOM.Float, DOM.Float, DOM.Float) case hex(UInt8, UInt8, UInt8) - case rgba(UInt8, UInt8, UInt8, DOM.Float) // see: https://www.w3.org/TR/SVG11/types.html#ColorKeywords enum Keyword: String { diff --git a/SwiftDraw/LayerTree.Color.swift b/SwiftDraw/LayerTree.Color.swift index 424a1d9..e7b45bb 100644 --- a/SwiftDraw/LayerTree.Color.swift +++ b/SwiftDraw/LayerTree.Color.swift @@ -58,15 +58,15 @@ extension LayerTree.Color { case let .keyword(c): let rgbi = c.rgbi return LayerTree.Color(rgbi.0, rgbi.1, rgbi.2) - case let .rgbi(r, g, b): - return LayerTree.Color(r, g, b) + case let .rgbi(r, g, b, a): + return LayerTree.Color(r, g, b, Float(a)) case let .hex(r, g, b): return LayerTree.Color(r, g, b) - case let .rgbf(r, g, b): + case let .rgbf(r, g, b, a): return .rgba(r: Float(r), g: Float(g), b: Float(b), - a: 1.0, + a: Float(a), space: .srgb) case let .p3(r, g, b): return .rgba(r: Float(r), @@ -74,8 +74,6 @@ extension LayerTree.Color { b: Float(b), a: 1.0, space: .p3) - case let .rgba(r, g, b, a): - return LayerTree.Color(r, g, b, a) } } diff --git a/SwiftDraw/Parser.XML.Color.swift b/SwiftDraw/Parser.XML.Color.swift index a61d8a6..5369145 100644 --- a/SwiftDraw/Parser.XML.Color.swift +++ b/SwiftDraw/Parser.XML.Color.swift @@ -91,6 +91,17 @@ extension XMLParser { return try parseColorRGBi(data: data) } + private func parseColorRGBA(data: String) throws -> DOM.Color? { + var scanner = XMLParser.Scanner(text: data) + guard scanner.scanStringIfPossible("rgba(") else { return nil } + + if let c = try? parseColorRGBAf(data: data) { + return c + } + + return try parseColorRGBAi(data: data) + } + private func parseURLSelector(data: String) throws -> DOM.URL? { var scanner = XMLParser.Scanner(text: data) guard (try? scanner.scanString("url(")) == true else { @@ -109,32 +120,63 @@ extension XMLParser { return url } - private func parseColorRGBi(data: String) throws -> DOM.Color { + private func parseIntColor(data: String, withAlpha: Bool) throws -> DOM.Color { var scanner = XMLParser.Scanner(text: data) - try scanner.scanString("rgb(") + try scanner.scanString(withAlpha ? "rgba(" : "rgb(") let r = try scanner.scanUInt8() scanner.scanStringIfPossible(",") let g = try scanner.scanUInt8() scanner.scanStringIfPossible(",") let b = try scanner.scanUInt8() + var a: Float = 1.0 + + if withAlpha { + scanner.scanStringIfPossible(",") + a = try scanner.scanFloat() // Opacity + } + try scanner.scanString(")") - return .rgbi(r, g, b) + return .rgbi(r, g, b, a) } - private func parseColorRGBf(data: String) throws -> DOM.Color { + private func parseColorRGBi(data: String) throws -> DOM.Color { + return try parseIntColor(data: data, withAlpha: false) + } + + private func parseColorRGBAi(data: String) throws -> DOM.Color { + return try parseIntColor(data: data, withAlpha: true) + } + + private func parsePercentageColor(data: String, withAlpha: Bool) throws -> DOM.Color { var scanner = XMLParser.Scanner(text: data) - try scanner.scanString("rgb(") + try scanner.scanString(withAlpha ? "rgba(" : "rgb(") let r = try scanner.scanPercentage() scanner.scanStringIfPossible(",") let g = try scanner.scanPercentage() scanner.scanStringIfPossible(",") let b = try scanner.scanPercentage() + + var a: Float = 1.0 + if withAlpha { + scanner.scanStringIfPossible(",") + a = try scanner.scanFloat() // Opacity + } + try scanner.scanString(")") - return .rgbf(r, g, b) + return .rgbf(r, g, b, a) + } + + private func parseColorRGBf(data: String) throws -> DOM.Color { + return try parsePercentageColor(data: data, withAlpha: false) + } + + private func parseColorRGBAf(data: String) throws -> DOM.Color { + return try parsePercentageColor(data: data, withAlpha: true) } + private func parseColorP3(data: String) throws -> DOM.Color? { var scanner = XMLParser.Scanner(text: data) @@ -175,20 +217,4 @@ extension XMLParser { return .hex(r, g, b) } - - private func parseColorRGBA(data: String) throws -> DOM.Color? { - var scanner = XMLParser.Scanner(text: data) - try scanner.scanString("rgba(") - - let r = try scanner.scanUInt8() - scanner.scanStringIfPossible(",") - let g = try scanner.scanUInt8() - scanner.scanStringIfPossible(",") - let b = try scanner.scanUInt8() - scanner.scanStringIfPossible(",") - let a = try scanner.scanFloat() // Opacity - try scanner.scanString(")") - - return .rgba(r, g, b, a) - } } diff --git a/SwiftDraw/XML.Formatter.SVG.swift b/SwiftDraw/XML.Formatter.SVG.swift index 50e36e8..e99154e 100644 --- a/SwiftDraw/XML.Formatter.SVG.swift +++ b/SwiftDraw/XML.Formatter.SVG.swift @@ -295,13 +295,15 @@ extension XML.Formatter { return "currentColor" case let .keyword(k): return k.rawValue - case let .rgbi(r, g, b): - return "rgb(\(r), \(g), \(b))" - case let .rgbf(r, g, b): + case let .rgbi(r, g, b, a): + let aa = String(format: "%.2f", a) + return "rgb(\(r), \(g), \(b), \(aa)" + case let .rgbf(r, g, b, a): let rr = String(format: "%.0f", r * 100) let gg = String(format: "%.0f", g * 100) let bb = String(format: "%.0f", b * 100) - return "rgb(\(rr)%, \(gg)%, \(bb)%)" + let aa = String(format: "%.2f", a) + return "rgb(\(rr)%, \(gg)%, \(bb)%, \(aa)" case let .p3(r, g, b): return "color(display-p3 \(r), \(g), \(b))" case let .hex(r, g, b): @@ -309,9 +311,6 @@ extension XML.Formatter { let gg = String(format: "%02X", g) let bb = String(format: "%02X", b) return "#\(rr)\(gg)\(bb)" - case let .rgba(r, g, b, a): - let aa = String(format: "%.2f", a) - return "rgba(\(r), \(g), \(b), \(aa))" } } From 502ef1df9e906ca7707f37f6eb28b7d183576104 Mon Sep 17 00:00:00 2001 From: Eric Yuan Date: Sun, 7 Jul 2024 19:16:00 +1200 Subject: [PATCH 6/7] Update tests --- SwiftDrawTests/LayerTree.BuilderTests.swift | 2 +- SwiftDrawTests/LayerTree.ColorTests.swift | 6 +++--- SwiftDrawTests/NSImage+ImageTests.swift | 4 ++-- SwiftDrawTests/Parser.XML.ColorTests.swift | 23 +++++++++++++++------ SwiftDrawTests/ValueParserTests.swift | 6 ++++-- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/SwiftDrawTests/LayerTree.BuilderTests.swift b/SwiftDrawTests/LayerTree.BuilderTests.swift index 4721e5a..261f89d 100644 --- a/SwiftDrawTests/LayerTree.BuilderTests.swift +++ b/SwiftDrawTests/LayerTree.BuilderTests.swift @@ -106,7 +106,7 @@ final class LayerTreeBuilderTests: XCTestCase { func testStrokeAttributes() { var state = LayerTree.Builder.State() - state.stroke = .color(.rgbf(1.0, 0.0, 0.0)) + state.stroke = .color(.rgbf(1.0, 0.0, 0.0, 1.0)) state.strokeOpacity = 0.5 state.strokeWidth = 5.0 state.strokeLineCap = .square diff --git a/SwiftDrawTests/LayerTree.ColorTests.swift b/SwiftDrawTests/LayerTree.ColorTests.swift index fcea174..af5af70 100644 --- a/SwiftDrawTests/LayerTree.ColorTests.swift +++ b/SwiftDrawTests/LayerTree.ColorTests.swift @@ -119,9 +119,9 @@ final class LayerTreeColorTests: XCTestCase { let none = DOM.Color.none let black = DOM.Color.keyword(.black) let white = DOM.Color.keyword(.white) - let red = DOM.Color.rgbi(255, 0, 0) - let green = DOM.Color.rgbi(0, 255, 0) - let blue = DOM.Color.rgbi(0, 0, 255) + let red = DOM.Color.rgbi(255, 0, 0, 1.0) + let green = DOM.Color.rgbi(0, 255, 0, 1.0) + let blue = DOM.Color.rgbi(0, 0, 255, 1.0) XCTAssertEqual(Color(none), .none) XCTAssertEqual(Color(black), .srgb(r: 0.0, g: 0.0, b: 0.0, a: 1.0)) diff --git a/SwiftDrawTests/NSImage+ImageTests.swift b/SwiftDrawTests/NSImage+ImageTests.swift index bd1f4aa..f2804dc 100644 --- a/SwiftDrawTests/NSImage+ImageTests.swift +++ b/SwiftDrawTests/NSImage+ImageTests.swift @@ -72,8 +72,8 @@ private extension SVG { let svg = DOM.SVG(width: 2, height: 2) svg.childElements.append(DOM.Rect(x: 0, y: 0, width: 1, height: 1)) svg.childElements.append(DOM.Rect(x: 1, y: 1, width: 1, height: 1)) - svg.childElements[0].attributes.fill = .color(DOM.Color.rgbi(255, 0, 0)) - svg.childElements[1].attributes.fill = .color(DOM.Color.rgbi(0, 0, 255)) + svg.childElements[0].attributes.fill = .color(DOM.Color.rgbi(255, 0, 0, 1.0)) + svg.childElements[1].attributes.fill = .color(DOM.Color.rgbi(0, 0, 255, 1.0)) return SVG(dom: svg, options: .default) } } diff --git a/SwiftDrawTests/Parser.XML.ColorTests.swift b/SwiftDrawTests/Parser.XML.ColorTests.swift index f258e7f..4224199 100644 --- a/SwiftDrawTests/Parser.XML.ColorTests.swift +++ b/SwiftDrawTests/Parser.XML.ColorTests.swift @@ -63,16 +63,27 @@ final class ParserColorTests: XCTestCase { func testColorRGBi() { // integer 0-255 - XCTAssertEqual(try XMLParser().parseColor("rgb(0,1,2)"), .rgbi(0, 1, 2)) - XCTAssertEqual(try XMLParser().parseColor(" rgb( 0 , 1 , 2) "), .rgbi(0, 1, 2)) - XCTAssertEqual(try XMLParser().parseColor("rgb(255,100,78)"), .rgbi(255, 100, 78)) + XCTAssertEqual(try XMLParser().parseColor("rgb(0,1,2)"), .rgbi(0, 1, 2, 1.0)) + XCTAssertEqual(try XMLParser().parseColor(" rgb( 0 , 1 , 2) "), .rgbi(0, 1, 2, 1.0)) + XCTAssertEqual(try XMLParser().parseColor("rgb(255,100,78)"), .rgbi(255, 100, 78, 1.0)) } func testColorRGBf() { // percentage 0-100% - XCTAssertEqual(try XMLParser().parseColor("rgb(0,1%,99%)"), .rgbf(0.0, 0.01, 0.99)) - XCTAssertEqual(try XMLParser().parseColor("rgb( 0%, 52% , 100%) "), .rgbf(0.0, 0.52, 1.0)) - XCTAssertEqual(try XMLParser().parseColor("rgb(75%,25%,7%)"), .rgbf(0.75, 0.25, 0.07)) + XCTAssertEqual(try XMLParser().parseColor("rgb(0,1%,99%)"), .rgbf(0.0, 0.01, 0.99, 1.0)) + XCTAssertEqual(try XMLParser().parseColor("rgb( 0%, 52% , 100%) "), .rgbf(0.0, 0.52, 1.0, 1.0)) + XCTAssertEqual(try XMLParser().parseColor("rgb(75%,25%,7%)"), .rgbf(0.75, 0.25, 0.07, 1.0)) + } + + func testColorRGBA() { + // integer 0-255 + XCTAssertEqual(try XMLParser().parseColor("rgba(0,1,2,0.5)"), .rgbi(0, 1, 2, 0.5)) + XCTAssertEqual(try XMLParser().parseColor(" rgba( 0 , 1 , 2, 0.6) "), .rgbi(0, 1, 2, 0.6)) + XCTAssertEqual(try XMLParser().parseColor("rgba(255,100,78,0.7)"), .rgbi(255, 100, 78, 0.7)) + // percentage 0-100% + XCTAssertEqual(try XMLParser().parseColor("rgba(0,1%,99%,0.5)"), .rgbf(0.0, 0.01, 0.99, 0.5)) + XCTAssertEqual(try XMLParser().parseColor("rgba( 0%, 52% , 100%, 0.6) "), .rgbf(0.0, 0.52, 1.0, 0.6)) + XCTAssertEqual(try XMLParser().parseColor("rgba(75%,25%,7%,0.7)"), .rgbf(0.75, 0.25, 0.07, 0.7)) } func testColorHex() { diff --git a/SwiftDrawTests/ValueParserTests.swift b/SwiftDrawTests/ValueParserTests.swift index 5139ccb..bdb699e 100644 --- a/SwiftDrawTests/ValueParserTests.swift +++ b/SwiftDrawTests/ValueParserTests.swift @@ -111,8 +111,10 @@ final class ValueParserTests: XCTestCase { XCTAssertEqual(try parser.parseFill("black"), .color(.keyword(.black))) XCTAssertEqual(try parser.parseFill("red"), .color(.keyword(.red))) - XCTAssertEqual(try parser.parseFill("rgb(10,20,30)"), .color(.rgbi(10, 20, 30))) - XCTAssertEqual(try parser.parseFill("rgb(10%,20%,100%)"), .color(.rgbf(0.1, 0.2, 1.0))) + XCTAssertEqual(try parser.parseFill("rgb(10,20,30)"), .color(.rgbi(10, 20, 30, 1.0))) + XCTAssertEqual(try parser.parseFill("rgb(10%,20%,100%)"), .color(.rgbf(0.1, 0.2, 1.0, 1.0))) + XCTAssertEqual(try parser.parseFill("rgba(10, 20, 30, 0.5)"), .color(.rgbi(10, 20, 30, 0.5))) + XCTAssertEqual(try parser.parseFill("rgba(10%,20%,100%,0.6)"), .color(.rgbf(0.1, 0.2, 1.0, 0.6))) XCTAssertEqual(try parser.parseFill("#AAFF00"), .color(.hex(170, 255, 0))) XCTAssertEqual(try parser.parseFill("url(#test)"), .url(URL(string: "#test")!)) From b5a1ba7c2c44dc65275596b7474aeb24c0174aa9 Mon Sep 17 00:00:00 2001 From: Eric Yuan Date: Sun, 7 Jul 2024 19:23:32 +1200 Subject: [PATCH 7/7] Fix color encoding --- SwiftDraw/XML.Formatter.SVG.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/SwiftDraw/XML.Formatter.SVG.swift b/SwiftDraw/XML.Formatter.SVG.swift index e99154e..f2b7d99 100644 --- a/SwiftDraw/XML.Formatter.SVG.swift +++ b/SwiftDraw/XML.Formatter.SVG.swift @@ -296,14 +296,22 @@ extension XML.Formatter { case let .keyword(k): return k.rawValue case let .rgbi(r, g, b, a): - let aa = String(format: "%.2f", a) - return "rgb(\(r), \(g), \(b), \(aa)" + if a == 1.0 { + return "rgb(\(r), \(g), \(b))" + } else { + let aa = String(format: "%.2f", a) + return "rgba(\(r), \(g), \(b), \(aa))" + } case let .rgbf(r, g, b, a): let rr = String(format: "%.0f", r * 100) let gg = String(format: "%.0f", g * 100) let bb = String(format: "%.0f", b * 100) - let aa = String(format: "%.2f", a) - return "rgb(\(rr)%, \(gg)%, \(bb)%, \(aa)" + if a == 1.0 { + return "rgb(\(rr)%, \(gg)%, \(bb)%)" + } else { + let aa = String(format: "%.2f", a) + return "rgba(\(rr)%, \(gg)%, \(bb)%, \(aa))" + } case let .p3(r, g, b): return "color(display-p3 \(r), \(g), \(b))" case let .hex(r, g, b):