Skip to content

Commit

Permalink
Merge pull request #1 from spacenation/hsv
Browse files Browse the repository at this point in the history
HSV and RGB colors
  • Loading branch information
ay42 authored Apr 26, 2020
2 parents 31e80eb + 89d3423 commit 4013a66
Show file tree
Hide file tree
Showing 15 changed files with 191 additions and 27 deletions.
12 changes: 6 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import PackageDescription

let package = Package(
name: "Color",
name: "Colors",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "Color",
targets: ["Color"]),
name: "Colors",
targets: ["Colors"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
Expand All @@ -19,10 +19,10 @@ let package = Package(
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "Color",
name: "Colors",
dependencies: []),
.testTarget(
name: "ColorTests",
dependencies: ["Color"]),
name: "ColorsTests",
dependencies: ["Colors"]),
]
)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
## Color
## Colors
8 changes: 0 additions & 8 deletions Sources/Color/Color.swift → Sources/Colors/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,4 @@ public struct Color: Codable, Equatable {

self.init(hue: hue, saturation: saturation, brightness: brightness)
}

public func maxComponentOffset(with color: Color) -> Double {
return [
self.hue - color.hue,
self.saturation - color.saturation,
self.brightness - color.brightness
].map { abs($0) }.max() ?? 0.0
}
}
7 changes: 7 additions & 0 deletions Sources/Colors/HSV/HSVColor+ComponentOffset.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

public extension HSVColor {
func maxComponentOffset(with color: HSVColor) -> UInt8 {
[self.hue - color.hue, self.saturation - color.saturation, self.value - color.value].max() ?? 0
}
}
21 changes: 21 additions & 0 deletions Sources/Colors/HSV/HSVColor+DominantColors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

extension Collection where Element == HSVColor {
public func dominantColor(saturationRange: ClosedRange<UInt8> = UInt8.min...UInt8.max, valueUnitRange: ClosedRange<UInt8> = UInt8.min...UInt8.max) -> HSVColor? {
let filteredColors = filter { saturationRange.contains($0.saturation) && valueUnitRange.contains($0.value) }
let colorBins = Dictionary(grouping: filteredColors) { $0.hue }

guard colorBins.count != 0 else { return nil }

if let dominantBin = colorBins.max(by: { $0.key > $1.key }) {
let elementsCount: Int = dominantBin.value.count
let saturationSum: Int = dominantBin.value.reduce(0) { $0 + Int($1.saturation) }
let valueSum: Int = dominantBin.value.reduce(0) { $0 + Int($1.value) }
let averageSaturation = UInt8(saturationSum / elementsCount)
let averageValue = UInt8(valueSum / elementsCount)
return HSVColor(hue: dominantBin.key, saturation: averageSaturation, value: averageValue)
} else {
return nil
}
}
}
43 changes: 43 additions & 0 deletions Sources/Colors/HSV/HSVColor+RGB.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Foundation

extension HSVColor {
public init(red: UInt8, green: UInt8, blue: UInt8) {
var hueDegrees: Float = 0
var unitSaturation: Float
var unitValue: Float

let unitRed = Float(red) / Float(UInt8.max)
let unitGreen = Float(green) / Float(UInt8.max)
let unitBlue = Float(blue) / Float(UInt8.max)

let unitMax = max(unitRed, unitGreen, unitBlue)
let unitMin = min(unitRed, unitGreen, unitBlue)
let unitDelta = unitMax - unitMin

if unitMax == 0 {
unitSaturation = 0
} else {
unitSaturation = (unitMax - unitMin) / unitMax
}

unitValue = unitMax

if unitDelta == 0 {
hueDegrees = 0
} else if unitMax == unitRed {
hueDegrees = 60 * ((unitGreen - unitBlue) / unitDelta)
} else if unitMax == unitGreen {
hueDegrees = 60 * ((unitBlue - unitRed) / unitDelta + 2)
} else if unitMax == unitBlue {
hueDegrees = 60 * ((unitRed - unitGreen) / unitDelta + 4)
}

if hueDegrees < 0 {
hueDegrees += 360
}

let unitHue = hueDegrees / 360.0

self.init(hueUnit: unitHue, saturationUnit: unitSaturation, valueUnit: unitValue)
}
}
32 changes: 32 additions & 0 deletions Sources/Colors/HSV/HSVColor+Unit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

public extension HSVColor {
/// Initializes a new Color struct with the provided HSV color parameters
///
/// - parameter hueUnit: The 360 degrees hue represented as Float from 0.0 to 1.0
/// - parameter saturationUnit: The saturation represented as Float from 0.0 to 1.0
/// - parameter valueUnit: The value represented as Float from 0.0 to 1.0
/// - returns: `HSVColor` struct
init(hueUnit: Float, saturationUnit: Float, valueUnit: Float) {
self.hue = UInt8(Float(UInt8.max) * max(0, min(hueUnit, 1)))
self.saturation = UInt8(Float(UInt8.max) * max(0, min(saturationUnit, 1)))
self.value = UInt8(Float(UInt8.max) * max(0, min(valueUnit, 1)))
}
}

public extension HSVColor {
var unitHue: Float {
get { Float(hue) / Float(UInt8.max) }
set { hue = UInt8(Float(UInt8.max) * max(0, min(newValue, 1))) }
}

var unitSaturation: Float {
get { Float(saturation) / Float(UInt8.max) }
set { saturation = UInt8(Float(UInt8.max) * max(0, min(newValue, 1))) }
}

var unitValue: Float {
get { Float(value) / Float(UInt8.max) }
set { value = UInt8(Float(UInt8.max) * max(0, min(newValue, 1))) }
}
}
8 changes: 8 additions & 0 deletions Sources/Colors/HSV/HSVColor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

public struct HSVColor: Hashable, Codable {
public var hue: UInt8
public var saturation: UInt8
public var value: UInt8
}

File renamed without changes.
32 changes: 32 additions & 0 deletions Sources/Colors/RGB/RGBColor+Unit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

public extension RGBColor {
/// Initializes a new Color struct with the provided RGB color parameters
///
/// - parameter redUnit: Red component represented as Float from 0.0 to 1.0
/// - parameter greenUnit: Green component represented as Float from 0.0 to 1.0
/// - parameter blueUnit: The blue component represented as Float from 0.0 to 1.0
/// - returns: `RGBColor` struct
init(redUnit: Float, greenUnit: Float, blueUnit: Float) {
self.red = UInt8(Float(UInt8.max) * max(0, min(redUnit, 1)))
self.green = UInt8(Float(UInt8.max) * max(0, min(greenUnit, 1)))
self.blue = UInt8(Float(UInt8.max) * max(0, min(blueUnit, 1)))
}
}

public extension RGBColor {
var unitRed: Float {
get { Float(red) / Float(UInt8.max) }
set { red = UInt8(Float(UInt8.max) * max(0, min(newValue, 1))) }
}

var unitGreen: Float {
get { Float(green) / Float(UInt8.max) }
set { green = UInt8(Float(UInt8.max) * max(0, min(newValue, 1))) }
}

var unitBlue: Float {
get { Float(blue) / Float(UInt8.max) }
set { blue = UInt8(Float(UInt8.max) * max(0, min(newValue, 1))) }
}
}
7 changes: 7 additions & 0 deletions Sources/Colors/RGB/RGBColor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

public struct RGBColor: Hashable, Codable {
public var red: UInt8
public var green: UInt8
public var blue: UInt8
}
11 changes: 0 additions & 11 deletions Tests/ColorTests/ColorTests.swift

This file was deleted.

19 changes: 19 additions & 0 deletions Tests/ColorsTests/HSVColorTests/HSVColorDominantColorsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import XCTest
@testable import Colors

final class HSVColorDominantColorsTests: XCTestCase {
func testHSVColorDominantColorsTests() {
let colors = [
HSVColor(hue: 1, saturation: 128, value: 255),
HSVColor(hue: 2, saturation: 255, value: 255),
HSVColor(hue: 1, saturation: 126, value: 0),
HSVColor(hue: 1, saturation: 0, value: 0)
]

XCTAssert(colors.dominantColor(saturationRange: 50...255) == HSVColor(hue: 1, saturation: 127, value: 127))
}

static var allTests = [
("testHSVColorDominantColorsTests", testHSVColorDominantColorsTests),
]
}
14 changes: 14 additions & 0 deletions Tests/ColorsTests/HSVColorTests/HSVColorRGBTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import XCTest
@testable import Colors

final class HSVColorTests: XCTestCase {
func testHSVColorRGBInitTests() {
XCTAssert(HSVColor(hue: 0, saturation: 0, value: 0) == HSVColor(red: 0, green: 0, blue: 0))
XCTAssert(HSVColor(hue: 0, saturation: 0, value: 127) == HSVColor(red: 127, green: 127, blue: 127))
XCTAssert(HSVColor(hue: 0, saturation: 0, value: 255) == HSVColor(red: 255, green: 255, blue: 255))
}

static var allTests = [
("testHSVColorRGBInitTests", testHSVColorRGBInitTests),
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(ColorTests.allTests),
testCase(HSVColorTests.allTests),
]
}
#endif

0 comments on commit 4013a66

Please sign in to comment.