Skip to content

Commit

Permalink
Refactored and simplified hex color (from int and string) initializat…
Browse files Browse the repository at this point in the history
…ion (#33)
  • Loading branch information
viktorasl authored and thii committed Jul 21, 2016
1 parent a097451 commit 3338595
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 31 deletions.
65 changes: 35 additions & 30 deletions Sources/SwiftHEXColors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -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.
Expand All @@ -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<String.Index>(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<String.Index>(hex.startIndex.advancedBy(2) ..< hex.startIndex.advancedBy(4)))
let blueHex = hex.substringWithRange(Range<String.Index>(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
Expand Down Expand Up @@ -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
}
}
}
59 changes: 58 additions & 1 deletion Tests/SwiftHEXColorsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 3338595

Please sign in to comment.