diff --git a/Sources/SwiftHEXColors.swift b/Sources/SwiftHEXColors.swift index 4b28afa..4c85f0e 100644 --- a/Sources/SwiftHEXColors.swift +++ b/Sources/SwiftHEXColors.swift @@ -28,6 +28,12 @@ typealias SWColor = NSColor #endif +private extension Int { + func duplicate4bits() -> Int { + return (self << 4) + self + } +} + /// An extension of UIColor (on iOS) or NSColor (on OSX) providing HEX color handling. public extension SWColor { /** @@ -40,6 +46,18 @@ public extension SWColor { self.init(hexString: hexString, alpha: 1.0) } + private convenience init?(hex3: Int, alpha: Float) { + self.init(red: CGFloat( ((hex3 & 0xF00) >> 8).duplicate4bits() ) / 255.0, + green: CGFloat( ((hex3 & 0x0F0) >> 4).duplicate4bits() ) / 255.0, + blue: CGFloat( ((hex3 & 0x00F) >> 0).duplicate4bits() ) / 255.0, alpha: CGFloat(alpha)) + } + + private convenience init?(hex6: Int, alpha: Float) { + self.init(red: CGFloat( (hex6 & 0xFF0000) >> 16 ) / 255.0, + green: CGFloat( (hex6 & 0x00FF00) >> 8 ) / 255.0, + blue: CGFloat( (hex6 & 0x0000FF) >> 0 ) / 255.0, alpha: CGFloat(alpha)) + } + /** Create non-autoreleased color with in the given hex string and alpha. @@ -54,33 +72,18 @@ public extension SWColor { if hex.hasPrefix("#") { hex = hex.substringFromIndex(hex.startIndex.advancedBy(1)) } - - if (hex.rangeOfString("(^[0-9A-Fa-f]{6}$)|(^[0-9A-Fa-f]{3}$)", options: .RegularExpressionSearch) != nil) { - - // Deal with 3 character Hex strings - if hex.characters.count == 3 { - let redHex = hex.substringToIndex(hex.startIndex.advancedBy(1)) - let greenHex = hex.substringWithRange(Range(hex.startIndex.advancedBy(1) ..< hex.startIndex.advancedBy(2))) - let blueHex = hex.substringFromIndex(hex.startIndex.advancedBy(2)) - - hex = redHex + redHex + greenHex + greenHex + blueHex + blueHex - } - - let redHex = hex.substringToIndex(hex.startIndex.advancedBy(2)) - let greenHex = hex.substringWithRange(Range(hex.startIndex.advancedBy(2) ..< hex.startIndex.advancedBy(4))) - let blueHex = hex.substringWithRange(Range(hex.startIndex.advancedBy(4) ..< hex.startIndex.advancedBy(6))) - - var redInt: CUnsignedInt = 0 - var greenInt: CUnsignedInt = 0 - var blueInt: CUnsignedInt = 0 - - NSScanner(string: redHex).scanHexInt(&redInt) - NSScanner(string: greenHex).scanHexInt(&greenInt) - NSScanner(string: blueHex).scanHexInt(&blueInt) - - self.init(red: CGFloat(redInt) / 255.0, green: CGFloat(greenInt) / 255.0, blue: CGFloat(blueInt) / 255.0, alpha: CGFloat(alpha)) + + guard let hexVal = Int(hex, radix: 16) else { + self.init() + return nil } - else { + + switch hex.characters.count { + case 3: + self.init(hex3: hexVal, alpha: alpha) + case 6: + self.init(hex6: hexVal, alpha: alpha) + default: // Note: // The swift 1.1 compiler is currently unable to destroy partially initialized classes in all cases, // so it disallows formation of a situation where it would have to. We consider this a bug to be fixed @@ -108,9 +111,11 @@ public extension SWColor { - returns: color with the given hex value and alpha */ public convenience init?(hex: Int, alpha: Float) { - var hexString = String(format: "%2X", hex) - let leadingZerosString = String(count: 6 - hexString.characters.count, repeatedValue: Character("0")) - hexString = leadingZerosString + hexString - self.init(hexString: hexString as String , alpha: alpha) + if (0x000000 ... 0xFFFFFF) ~= hex { + self.init(hex6: hex , alpha: alpha) + } else { + self.init() + return nil + } } } diff --git a/Tests/SwiftHEXColorsTests.swift b/Tests/SwiftHEXColorsTests.swift index 161af10..54c3c27 100644 --- a/Tests/SwiftHEXColorsTests.swift +++ b/Tests/SwiftHEXColorsTests.swift @@ -23,8 +23,65 @@ import XCTest @testable import SwiftHEXColors -class SwiftHEXColorsTests: XCTestCase { +private let epsilon: CGFloat = 0.001 +private func ~=(c1: SWColor, c2: SWColor) -> Bool { + var a1: CGFloat = 0.0 + var r1: CGFloat = 0.0 + var g1: CGFloat = 0.0 + var b1: CGFloat = 0.0 + + c1.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) + + var a2: CGFloat = 0.0 + var r2: CGFloat = 0.0 + var g2: CGFloat = 0.0 + var b2: CGFloat = 0.0 + + c2.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) + + for (c1, c2) in [(r1, r2), (g1, g2), (b1, b2), (a1, a2)] { + if abs(c1 - c2) >= epsilon { + return false + } + } + return true +} +class SwiftHEXColorsTests: XCTestCase { + + func testThat12bitColorIsInited() { + let color = SWColor(hexString: "#D4A", alpha: 0.88) + let expected = SWColor(red: 221.0 / 255.0, green: 68.0 / 255.0, blue: 170.0 / 255.0, alpha: 0.88) + XCTAssertTrue(color! ~= expected) + } + + func testThat24bitColorIsInited() { + let color = SWColor(hexString: "#81DAB9", alpha: 0.88) + let expected = SWColor(red: 129.0 / 255.0, green: 218.0 / 255.0, blue: 185.0 / 255.0, alpha: 0.88) + XCTAssertTrue(color! ~= expected) + } + + func testThat16bitColorIsNil() { + let color = SWColor(hexString: "#78A2", alpha: 0.33) + XCTAssertNil(color) + } + + func testThatNotHexSymbolProducesNil() { + let color = SWColor(hexString: "#FFF&FF", alpha: 0.88) + XCTAssertNil(color) + } + + func testThatTooBigIntColorIsNil() { + let color = SWColor(hex: 0xFFFFFFD, alpha: 0.2) + XCTAssertNil(color) + } + + func testThatHexIntColorIsInited() { + let color = SWColor(hex: 0x94D88A, alpha: 0.3) + let expected = SWColor(red: 148.0 / 255.0, green: 216.0 / 255.0, blue: 138.0 / 255.0, alpha: 0.3) + XCTAssertTrue(color! ~= expected) + } + // is alpha equals to 1 by default after init func testAlphaInHexStringInit() { let hexBlackColor = SWColor(hexString: "000000")