导入 XuXuCheck 工程(iOS 干化学尿液试纸分析 App)
从桌面工程复制而来,作为本仓库的 Xuxucheck/ 子目录。 为控制仓库体积做了两处删减: - 移除 Frameworks/opencv2.xcframework 下的两个 visionOS 切片 (xros-arm64、xros-arm64-simulator,合计约 106 MB),并同步删掉该 xcframework 内 Info.plist 里对应的两条 AvailableLibraries 记录, 只保留 ios-arm64 与 ios-arm64_x86_64-simulator。 - 移除全部 .DS_Store 与 xcuserdata/。 已用 xcodebuild 从本目录完整编译验证:BUILD SUCCEEDED, ProcessXCFramework 以 --platform ios 成功选中框架,产物含 3699 个 cv:: 符号 (OpenCV 确实链入),嵌入 framework 3 个,与移除无用 pod 之后的状态一致。 仓库约 206 MB,其中 OpenCV 203 MB、其余源码与依赖约 3 MB。
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
Convenient extension for color array to work as a DynamicGradient.
|
||||
*/
|
||||
public extension Array where Element: DynamicColor {
|
||||
/**
|
||||
Gradient representation of the array.
|
||||
*/
|
||||
var gradient: DynamicGradient {
|
||||
return DynamicGradient(colors: self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// ContrastDisplayContext.swift
|
||||
// DynamicColorExample
|
||||
//
|
||||
// Created by Yannick LORIOT on 26/11/2016.
|
||||
// Copyright © 2016 Yannick LORIOT. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
extension DynamicColor {
|
||||
/**
|
||||
Used to describe the context of display of 2 colors.
|
||||
|
||||
Based on WCAG: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast
|
||||
*/
|
||||
public enum ContrastDisplayContext {
|
||||
/**
|
||||
A standard text in a normal context.
|
||||
*/
|
||||
case standard
|
||||
/**
|
||||
A large text in a normal context.
|
||||
You can look here for the definition of "large text":
|
||||
https://www.w3.org/TR/2008/REC-WCAG20-20081211/#larger-scaledef
|
||||
*/
|
||||
case standardLargeText
|
||||
/**
|
||||
A standard text in an enhanced context.
|
||||
Enhanced means that you want to be accessible (and AAA compliant in WCAG)
|
||||
*/
|
||||
case enhanced
|
||||
/**
|
||||
A large text in an enhanced context.
|
||||
Enhanced means that you want to be accessible (and AAA compliant in WCAG)
|
||||
You can look here for the definition of "large text":
|
||||
https://www.w3.org/TR/2008/REC-WCAG20-20081211/#larger-scaledef
|
||||
*/
|
||||
case enhancedLargeText
|
||||
|
||||
var minimumContrastRatio: CGFloat {
|
||||
switch self {
|
||||
case .standard:
|
||||
return 4.5
|
||||
case .standardLargeText:
|
||||
return 3.0
|
||||
case .enhanced:
|
||||
return 7.0
|
||||
case .enhancedLargeText:
|
||||
return 4.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: Deriving Colors
|
||||
|
||||
public extension DynamicColor {
|
||||
/**
|
||||
Creates and returns a color object with the hue rotated along the color wheel by the given amount.
|
||||
|
||||
- parameter amount: A float representing the number of degrees as ratio (usually between -360.0 degree and 360.0 degree).
|
||||
- returns: A DynamicColor object with the hue changed.
|
||||
*/
|
||||
final func adjustedHue(amount: CGFloat) -> DynamicColor {
|
||||
return HSL(color: self).adjustedHue(amount: amount).toDynamicColor()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns the complement of the color object.
|
||||
|
||||
This is identical to adjustedHue(180).
|
||||
|
||||
- returns: The complement DynamicColor.
|
||||
- seealso: adjustedHueColor:
|
||||
*/
|
||||
final func complemented() -> DynamicColor {
|
||||
return adjustedHue(amount: 180.0)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns a color object with the lightness increased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0. Default value is 0.2.
|
||||
- returns: A lighter DynamicColor.
|
||||
*/
|
||||
final func lighter(amount: CGFloat = 0.2) -> DynamicColor {
|
||||
return HSL(color: self).lighter(amount: amount).toDynamicColor()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns a color object with the lightness decreased by the given amount.
|
||||
|
||||
- parameter amount: Float between 0.0 and 1.0. Default value is 0.2.
|
||||
- returns: A darker DynamicColor.
|
||||
*/
|
||||
final func darkened(amount: CGFloat = 0.2) -> DynamicColor {
|
||||
return HSL(color: self).darkened(amount: amount).toDynamicColor()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns a color object with the saturation increased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0. Default value is 0.2.
|
||||
|
||||
- returns: A DynamicColor more saturated.
|
||||
*/
|
||||
final func saturated(amount: CGFloat = 0.2) -> DynamicColor {
|
||||
return HSL(color: self).saturated(amount: amount).toDynamicColor()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns a color object with the saturation decreased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0. Default value is 0.2.
|
||||
- returns: A DynamicColor less saturated.
|
||||
*/
|
||||
final func desaturated(amount: CGFloat = 0.2) -> DynamicColor {
|
||||
return HSL(color: self).desaturated(amount: amount).toDynamicColor()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns a color object converted to grayscale.
|
||||
|
||||
- returns: A grayscale DynamicColor.
|
||||
- seealso: desaturated:
|
||||
*/
|
||||
final func grayscaled(mode: GrayscalingMode = .lightness) -> DynamicColor {
|
||||
let (r, g, b, a) = self.toRGBAComponents()
|
||||
|
||||
let l: CGFloat
|
||||
switch mode {
|
||||
case .luminance:
|
||||
l = (0.299 * r) + (0.587 * g) + (0.114 * b)
|
||||
case .lightness:
|
||||
l = 0.5 * (max(r, g, b) + min(r, g, b))
|
||||
case .average:
|
||||
l = (1.0 / 3.0) * (r + g + b)
|
||||
case .value:
|
||||
l = max(r, g, b)
|
||||
}
|
||||
|
||||
return HSL(hue: 0.0, saturation: 0.0, lightness: l, alpha: a).toDynamicColor()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and return a color object where the red, green, and blue values are inverted, while the alpha channel is left alone.
|
||||
|
||||
- returns: An inverse (negative) of the original color.
|
||||
*/
|
||||
final func inverted() -> DynamicColor {
|
||||
let rgba = toRGBAComponents()
|
||||
|
||||
let invertedRed = 1.0 - rgba.r
|
||||
let invertedGreen = 1.0 - rgba.g
|
||||
let invertedBlue = 1.0 - rgba.b
|
||||
|
||||
return DynamicColor(red: invertedRed, green: invertedGreen, blue: invertedBlue, alpha: rgba.a)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: HSB Color Space
|
||||
|
||||
extension DynamicColor {
|
||||
// MARK: - Getting the HSB Components
|
||||
|
||||
/**
|
||||
Returns the HSB (hue, saturation, brightness) components.
|
||||
|
||||
- returns: The HSB components as a tuple (h, s, b).
|
||||
*/
|
||||
public final func toHSBComponents() -> (h: CGFloat, s: CGFloat, b: CGFloat) {
|
||||
var h: CGFloat = 0.0
|
||||
var s: CGFloat = 0.0
|
||||
var b: CGFloat = 0.0
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
getHue(&h, saturation: &s, brightness: &b, alpha: nil)
|
||||
|
||||
return (h: h, s: s, b: b)
|
||||
#elseif os(OSX)
|
||||
if isEqual(DynamicColor.black) {
|
||||
return (0.0, 0.0, 0.0)
|
||||
}
|
||||
else if isEqual(DynamicColor.white) {
|
||||
return (0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
getHue(&h, saturation: &s, brightness: &b, alpha: nil)
|
||||
|
||||
return (h: h, s: s, b: b)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
/**
|
||||
The hue component as CGFloat between 0.0 to 1.0.
|
||||
*/
|
||||
public final var hueComponent: CGFloat {
|
||||
return toHSBComponents().h
|
||||
}
|
||||
|
||||
/**
|
||||
The saturation component as CGFloat between 0.0 to 1.0.
|
||||
*/
|
||||
public final var saturationComponent: CGFloat {
|
||||
return toHSBComponents().s
|
||||
}
|
||||
|
||||
/**
|
||||
The brightness component as CGFloat between 0.0 to 1.0.
|
||||
*/
|
||||
public final var brightnessComponent: CGFloat {
|
||||
return toHSBComponents().b
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: HSL Color Space
|
||||
|
||||
extension DynamicColor {
|
||||
/**
|
||||
Initializes and returns a color object using the specified opacity and HSL component values.
|
||||
|
||||
- parameter hue: The hue component of the color object, specified as a value from 0.0 to 360.0 degree.
|
||||
- parameter saturation: The saturation component of the color object, specified as a value from 0.0 to 1.0.
|
||||
- parameter lightness: The lightness component of the color object, specified as a value from 0.0 to 1.0.
|
||||
- parameter alpha: The opacity value of the color object, specified as a value from 0.0 to 1.0. Default to 1.0.
|
||||
*/
|
||||
public convenience init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1) {
|
||||
let color = HSL(hue: hue, saturation: saturation, lightness: lightness, alpha: alpha).toDynamicColor()
|
||||
let components = color.toRGBAComponents()
|
||||
|
||||
self.init(red: components.r, green: components.g, blue: components.b, alpha: components.a)
|
||||
}
|
||||
|
||||
// MARK: - Getting the HSL Components
|
||||
|
||||
/**
|
||||
Returns the HSL (hue, saturation, lightness) components.
|
||||
|
||||
Notes that the hue value is between 0.0 and 360.0 degree.
|
||||
|
||||
- returns: The HSL components as a tuple (h, s, l).
|
||||
*/
|
||||
public final func toHSLComponents() -> (h: CGFloat, s: CGFloat, l: CGFloat) {
|
||||
let hsl = HSL(color: self)
|
||||
|
||||
return (hsl.h * 360.0, hsl.s, hsl.l)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: CIE L*a*b* Color Space
|
||||
|
||||
public extension DynamicColor {
|
||||
/**
|
||||
Initializes and returns a color object using CIE XYZ color space component values with an observer at 2° and a D65 illuminant.
|
||||
|
||||
Notes that values out of range are clipped.
|
||||
|
||||
- parameter L: The lightness, specified as a value from 0 to 100.0.
|
||||
- parameter a: The red-green axis, specified as a value from -128.0 to 127.0.
|
||||
- parameter b: The yellow-blue axis, specified as a value from -128.0 to 127.0.
|
||||
- parameter alpha: The opacity value of the color object, specified as a value from 0.0 to 1.0. Default to 1.0.
|
||||
*/
|
||||
convenience init(L: CGFloat, a: CGFloat, b: CGFloat, alpha: CGFloat = 1) {
|
||||
let clippedL = clip(L, 0.0, 100.0)
|
||||
let clippedA = clip(a, -128.0, 127.0)
|
||||
let clippedB = clip(b, -128.0, 127.0)
|
||||
|
||||
let normalized = { (c: CGFloat) -> CGFloat in
|
||||
pow(c, 3) > 0.008856 ? pow(c, 3) : (c - (16 / 116)) / 7.787
|
||||
}
|
||||
|
||||
let preY = (clippedL + 16.0) / 116.0
|
||||
let preX = (clippedA / 500.0) + preY
|
||||
let preZ = preY - (clippedB / 200.0)
|
||||
|
||||
let X = 95.05 * normalized(preX)
|
||||
let Y = 100.0 * normalized(preY)
|
||||
let Z = 108.9 * normalized(preZ)
|
||||
|
||||
self.init(X: X, Y: Y, Z: Z, alpha: alpha)
|
||||
}
|
||||
|
||||
// MARK: - Getting the L*a*b* Components
|
||||
|
||||
/**
|
||||
Returns the Lab (lightness, red-green axis, yellow-blue axis) components.
|
||||
It is based on the CIE XYZ color space with an observer at 2° and a D65 illuminant.
|
||||
|
||||
Notes that L values are between 0 to 100.0, a values are between -128 to 127.0 and b values are between -128 to 127.0.
|
||||
|
||||
- returns: The L*a*b* components as a tuple (L, a, b).
|
||||
*/
|
||||
final func toLabComponents() -> (L: CGFloat, a: CGFloat, b: CGFloat) {
|
||||
let normalized = { (c: CGFloat) -> CGFloat in
|
||||
c > 0.008856 ? pow(c, 1.0 / 3.0) : (7.787 * c) + (16.0 / 116.0)
|
||||
}
|
||||
|
||||
let xyz = toXYZComponents()
|
||||
let normalizedX = normalized(xyz.X / 95.05)
|
||||
let normalizedY = normalized(xyz.Y / 100.0)
|
||||
let normalizedZ = normalized(xyz.Z / 108.9)
|
||||
|
||||
let L = roundDecimal((116.0 * normalizedY) - 16.0, precision: 1000)
|
||||
let a = roundDecimal(500.0 * (normalizedX - normalizedY), precision: 1000)
|
||||
let b = roundDecimal(200.0 * (normalizedY - normalizedZ), precision: 1000)
|
||||
|
||||
return (L: L, a: a, b: b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: Mixing Colors
|
||||
|
||||
public extension DynamicColor {
|
||||
/**
|
||||
Mixes the given color object with the receiver.
|
||||
|
||||
Specifically, takes the average of each of the RGB components, optionally weighted by the given percentage.
|
||||
|
||||
- Parameter color: A color object to mix with the receiver.
|
||||
- Parameter weight: The weight specifies the amount of the given color object (between 0 and 1).
|
||||
The default value is 0.5, which means that half the given color and half the receiver color object should be used.
|
||||
0.25 means that a quarter of the given color object and three quarters of the receiver color object should be used.
|
||||
- Parameter colorspace: The color space used to mix the colors. By default it uses the RBG color space.
|
||||
- Returns: A color object corresponding to the two colors object mixed together.
|
||||
*/
|
||||
final func mixed(withColor color: DynamicColor, weight: CGFloat = 0.5, inColorSpace colorspace: DynamicColorSpace = .rgb) -> DynamicColor {
|
||||
let normalizedWeight = clip(weight, 0.0, 1.0)
|
||||
|
||||
switch colorspace {
|
||||
case .lab:
|
||||
return mixedLab(withColor: color, weight: normalizedWeight)
|
||||
case .hsl:
|
||||
return mixedHSL(withColor: color, weight: normalizedWeight)
|
||||
case .hsb:
|
||||
return mixedHSB(withColor: color, weight: normalizedWeight)
|
||||
case .rgb:
|
||||
return mixedRGB(withColor: color, weight: normalizedWeight)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns a color object corresponding to the mix of the receiver and an amount of white color, which increases lightness.
|
||||
|
||||
- Parameter amount: Float between 0.0 and 1.0. The default amount is equal to 0.2.
|
||||
- Returns: A lighter DynamicColor.
|
||||
*/
|
||||
final func tinted(amount: CGFloat = 0.2) -> DynamicColor {
|
||||
return mixed(withColor: .white, weight: amount)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates and returns a color object corresponding to the mix of the receiver and an amount of black color, which reduces lightness.
|
||||
|
||||
- Parameter amount: Float between 0.0 and 1.0. The default amount is equal to 0.2.
|
||||
- Returns: A darker DynamicColor.
|
||||
*/
|
||||
final func shaded(amount: CGFloat = 0.2) -> DynamicColor {
|
||||
return mixed(withColor: DynamicColor(red: 0, green: 0, blue: 0, alpha: 1), weight: amount)
|
||||
}
|
||||
|
||||
// MARK: - Convenient Internal Methods
|
||||
|
||||
func mixedLab(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor {
|
||||
let c1 = toLabComponents()
|
||||
let c2 = color.toLabComponents()
|
||||
|
||||
let L = c1.L + (weight * (c2.L - c1.L))
|
||||
let a = c1.a + (weight * (c2.a - c1.a))
|
||||
let b = c1.b + (weight * (c2.b - c1.b))
|
||||
let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent))
|
||||
|
||||
return DynamicColor(L: L, a: a, b: b, alpha: alpha)
|
||||
}
|
||||
|
||||
func mixedHSL(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor {
|
||||
let c1 = toHSLComponents()
|
||||
let c2 = color.toHSLComponents()
|
||||
|
||||
let h = c1.h + (weight * mixedHue(source: c1.h, target: c2.h))
|
||||
let s = c1.s + (weight * (c2.s - c1.s))
|
||||
let l = c1.l + (weight * (c2.l - c1.l))
|
||||
let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent))
|
||||
|
||||
return DynamicColor(hue: h, saturation: s, lightness: l, alpha: alpha)
|
||||
}
|
||||
|
||||
func mixedHSB(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor {
|
||||
let c1 = toHSBComponents()
|
||||
let c2 = color.toHSBComponents()
|
||||
|
||||
let h = c1.h + (weight * mixedHue(source: c1.h, target: c2.h))
|
||||
let s = c1.s + (weight * (c2.s - c1.s))
|
||||
let b = c1.b + (weight * (c2.b - c1.b))
|
||||
let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent))
|
||||
|
||||
return DynamicColor(hue: h, saturation: s, brightness: b, alpha: alpha)
|
||||
}
|
||||
|
||||
func mixedRGB(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor {
|
||||
let c1 = toRGBAComponents()
|
||||
let c2 = color.toRGBAComponents()
|
||||
|
||||
let red = c1.r + (weight * (c2.r - c1.r))
|
||||
let green = c1.g + (weight * (c2.g - c1.g))
|
||||
let blue = c1.b + (weight * (c2.b - c1.b))
|
||||
let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent))
|
||||
|
||||
return DynamicColor(red: red, green: green, blue: blue, alpha: alpha)
|
||||
}
|
||||
|
||||
func mixedHue(source: CGFloat, target: CGFloat) -> CGFloat {
|
||||
if target > source && target - source > 180.0 {
|
||||
return target - source + 360.0
|
||||
}
|
||||
else if target < source && source - target > 180.0 {
|
||||
return target + 360.0 - source
|
||||
}
|
||||
|
||||
return target - source
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: RGBA Color Space
|
||||
|
||||
public extension DynamicColor {
|
||||
/**
|
||||
Initializes and returns a color object using the specified opacity and RGB component values.
|
||||
|
||||
Notes that values out of range are clipped.
|
||||
|
||||
- Parameter r: The red component of the color object, specified as a value from 0.0 to 255.0.
|
||||
- Parameter g: The green component of the color object, specified as a value from 0.0 to 255.0.
|
||||
- Parameter b: The blue component of the color object, specified as a value from 0.0 to 255.0.
|
||||
- Parameter a: The opacity value of the color object, specified as a value from 0.0 to 255.0. The default value is 255.
|
||||
*/
|
||||
convenience init(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat = 255) {
|
||||
self.init(red: clip(r, 0, 255) / 255, green: clip(g, 0, 255) / 255, blue: clip(b, 0, 255) / 255, alpha: clip(a, 0, 255) / 255)
|
||||
}
|
||||
|
||||
// MARK: - Getting the RGBA Components
|
||||
|
||||
/**
|
||||
Returns the RGBA (red, green, blue, alpha) components.
|
||||
|
||||
- returns: The RGBA components as a tuple (r, g, b, a).
|
||||
*/
|
||||
final func toRGBAComponents() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
|
||||
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
|
||||
return (r, g, b, a)
|
||||
#elseif os(OSX)
|
||||
guard let rgbaColor = self.usingColorSpace(.deviceRGB) else {
|
||||
fatalError("Could not convert color to RGBA.")
|
||||
}
|
||||
|
||||
rgbaColor.getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
|
||||
return (r, g, b, a)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
/**
|
||||
The red component as CGFloat between 0.0 to 1.0.
|
||||
*/
|
||||
final var redComponent: CGFloat {
|
||||
return toRGBAComponents().r
|
||||
}
|
||||
|
||||
/**
|
||||
The green component as CGFloat between 0.0 to 1.0.
|
||||
*/
|
||||
final var greenComponent: CGFloat {
|
||||
return toRGBAComponents().g
|
||||
}
|
||||
|
||||
/**
|
||||
The blue component as CGFloat between 0.0 to 1.0.
|
||||
*/
|
||||
final var blueComponent: CGFloat {
|
||||
return toRGBAComponents().b
|
||||
}
|
||||
|
||||
/**
|
||||
The alpha component as CGFloat between 0.0 to 1.0.
|
||||
*/
|
||||
final var alphaComponent: CGFloat {
|
||||
return toRGBAComponents().a
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Setting the RGBA Components
|
||||
|
||||
/**
|
||||
Creates and returns a color object with the alpha increased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0.
|
||||
- returns: A color object with its alpha channel modified.
|
||||
*/
|
||||
final func adjustedAlpha(amount: CGFloat) -> DynamicColor {
|
||||
let components = toRGBAComponents()
|
||||
let normalizedAlpha = clip(components.a + amount, 0.0, 1.0)
|
||||
|
||||
return DynamicColor(red: components.r, green: components.g, blue: components.b, alpha: normalizedAlpha)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: CIE XYZ Color Space
|
||||
|
||||
public extension DynamicColor {
|
||||
/**
|
||||
Initializes and returns a color object using CIE XYZ color space component values with an observer at 2° and a D65 illuminant.
|
||||
|
||||
Notes that values out of range are clipped.
|
||||
|
||||
- parameter X: The mix of cone response curves, specified as a value from 0 to 95.05.
|
||||
- parameter Y: The luminance, specified as a value from 0 to 100.0.
|
||||
- parameter Z: The quasi-equal to blue stimulation, specified as a value from 0 to 108.9.
|
||||
- parameter alpha: The opacity value of the color object, specified as a value from 0.0 to 1.0. Default to 1.0.
|
||||
*/
|
||||
convenience init(X: CGFloat, Y: CGFloat, Z: CGFloat, alpha: CGFloat = 1) {
|
||||
let clippedX = clip(X, 0.0, 95.05) / 100.0
|
||||
let clippedY = clip(Y, 0.0, 100) / 100.0
|
||||
let clippedZ = clip(Z, 0.0, 108.9) / 100.0
|
||||
|
||||
let toRGB = { (c: CGFloat) -> CGFloat in
|
||||
let rgb = c > 0.0031308 ? 1.055 * pow(c, 1.0 / 2.4) - 0.055 : c * 12.92
|
||||
|
||||
return abs(roundDecimal(rgb, precision: 1000.0))
|
||||
}
|
||||
|
||||
let red = toRGB((clippedX * 3.2406) + (clippedY * -1.5372) + (clippedZ * -0.4986))
|
||||
let green = toRGB((clippedX * -0.9689) + (clippedY * 1.8758) + (clippedZ * 0.0415))
|
||||
let blue = toRGB((clippedX * 0.0557) + (clippedY * -0.2040) + (clippedZ * 1.0570))
|
||||
|
||||
self.init(red: red, green: green, blue: blue, alpha: alpha)
|
||||
}
|
||||
|
||||
// MARK: - Getting the XYZ Components
|
||||
|
||||
/**
|
||||
Returns the XYZ (mix of cone response curves, luminance, quasi-equal to blue stimulation) components with an observer at 2° and a D65 illuminant.
|
||||
|
||||
Notes that X values are between 0 to 95.05, Y values are between 0 to 100.0 and Z values are between 0 to 108.9.
|
||||
|
||||
- returns: The XYZ components as a tuple (X, Y, Z).
|
||||
*/
|
||||
final func toXYZComponents() -> (X: CGFloat, Y: CGFloat, Z: CGFloat) {
|
||||
let toSRGB = { (c: CGFloat) -> CGFloat in
|
||||
c > 0.04045 ? pow((c + 0.055) / 1.055, 2.4) : c / 12.92
|
||||
}
|
||||
|
||||
let rgba = toRGBAComponents()
|
||||
let red = toSRGB(rgba.r)
|
||||
let green = toSRGB(rgba.g)
|
||||
let blue = toSRGB(rgba.b)
|
||||
|
||||
let X = roundDecimal(((red * 0.4124) + (green * 0.3576) + (blue * 0.1805)) * 100.0, precision: 1000.0)
|
||||
let Y = roundDecimal(((red * 0.2126) + (green * 0.7152) + (blue * 0.0722)) * 100.0, precision: 1000.0)
|
||||
let Z = roundDecimal(((red * 0.0193) + (green * 0.1192) + (blue * 0.9505)) * 100.0, precision: 1000.0)
|
||||
|
||||
return (X: X, Y: Y, Z: Z)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
|
||||
/**
|
||||
Extension to manipulate colours easily.
|
||||
|
||||
It allows you to work hexadecimal strings and value, HSV and RGB components, derivating colours, and many more...
|
||||
*/
|
||||
public typealias DynamicColor = UIColor
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
|
||||
/**
|
||||
Extension to manipulate colours easily.
|
||||
|
||||
It allows you to work hexadecimal strings and value, HSV and RGB components, derivating colours, and many more...
|
||||
*/
|
||||
public typealias DynamicColor = NSColor
|
||||
#endif
|
||||
|
||||
public extension DynamicColor {
|
||||
// MARK: - Manipulating Hexa-decimal Values and Strings
|
||||
|
||||
/**
|
||||
Creates a color from an hex string (e.g. "#3498db"). The RGBA string are also supported (e.g. "#3498dbff").
|
||||
|
||||
If the given hex string is invalid the initialiser will create a black color.
|
||||
|
||||
- parameter hexString: A hexa-decimal color string representation.
|
||||
*/
|
||||
convenience init(hexString: String) {
|
||||
let hexString = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let scanner = Scanner(string: hexString)
|
||||
scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#")
|
||||
|
||||
var color: UInt64 = 0
|
||||
|
||||
if scanner.scanHexInt64(&color) {
|
||||
self.init(hex: color, useAlpha: hexString.count > 7)
|
||||
}
|
||||
else {
|
||||
self.init(hex: 0x000000)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a color from an hex integer (e.g. 0x3498db).
|
||||
|
||||
- parameter hex: A hexa-decimal UInt64 that represents a color.
|
||||
- parameter alphaChannel: If true the given hex-decimal UInt64 includes the alpha channel (e.g. 0xFF0000FF).
|
||||
*/
|
||||
convenience init(hex: UInt64, useAlpha alphaChannel: Bool = false) {
|
||||
let mask = UInt64(0xFF)
|
||||
let cappedHex = !alphaChannel && hex > 0xffffff ? 0xffffff : hex
|
||||
|
||||
let r = cappedHex >> (alphaChannel ? 24 : 16) & mask
|
||||
let g = cappedHex >> (alphaChannel ? 16 : 8) & mask
|
||||
let b = cappedHex >> (alphaChannel ? 8 : 0) & mask
|
||||
let a = alphaChannel ? cappedHex & mask : 255
|
||||
|
||||
let red = CGFloat(r) / 255.0
|
||||
let green = CGFloat(g) / 255.0
|
||||
let blue = CGFloat(b) / 255.0
|
||||
let alpha = CGFloat(a) / 255.0
|
||||
|
||||
self.init(red: red, green: green, blue: blue, alpha: alpha)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the color representation as hexadecimal string.
|
||||
|
||||
- returns: A string similar to this pattern "#f4003b".
|
||||
*/
|
||||
final func toHexString() -> String {
|
||||
return String(format: "#%06x", toHex())
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the color representation as an integer (without the alpha channel).
|
||||
|
||||
- returns: A UInt32 that represents the hexa-decimal color.
|
||||
*/
|
||||
final func toHex() -> UInt32 {
|
||||
let rgba = toRGBAComponents()
|
||||
|
||||
return roundToHex(rgba.r) << 16 | roundToHex(rgba.g) << 8 | roundToHex(rgba.b)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the RGBA color representation.
|
||||
|
||||
- returns: A UInt32 that represents the color as an RGBA value.
|
||||
*/
|
||||
func toRGBA() -> UInt32 {
|
||||
let rgba = toRGBAComponents()
|
||||
|
||||
return roundToHex(rgba.r) << 24 | roundToHex(rgba.g) << 16 | roundToHex(rgba.b) << 8 | roundToHex(rgba.a)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the AGBR color representation.
|
||||
|
||||
- returns: A UInt32 that represents the color as an AGBR value.
|
||||
*/
|
||||
func toAGBR() -> UInt32 {
|
||||
let rgba = toRGBAComponents()
|
||||
|
||||
return roundToHex(rgba.a) << 24 | roundToHex(rgba.b) << 16 | roundToHex(rgba.g) << 8 | roundToHex(rgba.r)
|
||||
}
|
||||
|
||||
// MARK: - Identifying and Comparing Colors
|
||||
|
||||
/**
|
||||
Returns a boolean value that indicates whether the receiver is equal to the given hexa-decimal string.
|
||||
|
||||
- parameter hexString: A hexa-decimal color number representation to be compared to the receiver.
|
||||
- returns: true if the receiver and the string are equals, otherwise false.
|
||||
*/
|
||||
func isEqual(toHexString hexString: String) -> Bool {
|
||||
return self.toHexString() == hexString
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a boolean value that indicates whether the receiver is equal to the given hexa-decimal integer.
|
||||
|
||||
- parameter hex: A UInt32 that represents the hexa-decimal color.
|
||||
- returns: true if the receiver and the integer are equals, otherwise false.
|
||||
*/
|
||||
func isEqual(toHex hex: UInt32) -> Bool {
|
||||
return self.toHex() == hex
|
||||
}
|
||||
|
||||
// MARK: - Querying Colors
|
||||
|
||||
/**
|
||||
Determines if the color object is dark or light.
|
||||
|
||||
It is useful when you need to know whether you should display the text in black or white.
|
||||
|
||||
- returns: A boolean value to know whether the color is light. If true the color is light, dark otherwise.
|
||||
*/
|
||||
func isLight() -> Bool {
|
||||
let components = toRGBAComponents()
|
||||
let brightness = ((components.r * 299.0) + (components.g * 587.0) + (components.b * 114.0)) / 1000.0
|
||||
|
||||
return brightness >= 0.5
|
||||
}
|
||||
|
||||
/**
|
||||
A float value representing the luminance of the current color. May vary from 0 to 1.0.
|
||||
|
||||
We use the formula described by W3C in WCAG 2.0. You can read more here: https://www.w3.org/TR/WCAG20/#relativeluminancedef.
|
||||
*/
|
||||
var luminance: CGFloat {
|
||||
let components = toRGBAComponents()
|
||||
|
||||
let componentsArray = [components.r, components.g, components.b].map { (val) -> CGFloat in
|
||||
guard val <= 0.03928 else { return pow((val + 0.055) / 1.055, 2.4) }
|
||||
|
||||
return val / 12.92
|
||||
}
|
||||
|
||||
return (0.2126 * componentsArray[0]) + (0.7152 * componentsArray[1]) + (0.0722 * componentsArray[2])
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a float value representing the contrast ratio between 2 colors.
|
||||
|
||||
We use the formula described by W3C in WCAG 2.0. You can read more here: https://www.w3.org/TR/WCAG20-TECHS/G18.html
|
||||
NB: the contrast ratio is a relative value. So the contrast between Color1 and Color2 is exactly the same between Color2 and Color1.
|
||||
|
||||
- returns: A CGFloat representing contrast value.
|
||||
*/
|
||||
func contrastRatio(with otherColor: DynamicColor) -> CGFloat {
|
||||
let otherLuminance = otherColor.luminance
|
||||
|
||||
let l1 = max(luminance, otherLuminance)
|
||||
let l2 = min(luminance, otherLuminance)
|
||||
|
||||
return (l1 + 0.05) / (l2 + 0.05)
|
||||
}
|
||||
|
||||
/**
|
||||
Indicates if two colors are contrasting, regarding W3C's WCAG 2.0 recommendations.
|
||||
|
||||
You can read it here: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast
|
||||
|
||||
The acceptable contrast ratio depends on the context of display. Most of the time, the default context (.Standard) is enough.
|
||||
|
||||
You can look at ContrastDisplayContext for more options.
|
||||
|
||||
- parameter otherColor: The other color to compare with.
|
||||
- parameter context: An optional context to determine the minimum acceptable contrast ratio. Default value is .Standard.
|
||||
|
||||
- returns: true is the contrast ratio between 2 colors exceed the minimum acceptable ratio.
|
||||
*/
|
||||
func isContrasting(with otherColor: DynamicColor, inContext context: ContrastDisplayContext = .standard) -> Bool {
|
||||
return self.contrastRatio(with: otherColor) > context.minimumContrastRatio
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
/**
|
||||
Object representing a gradient object. It allows you to manipulate colors inside different gradients and color spaces.
|
||||
*/
|
||||
final public class DynamicGradient {
|
||||
let colors: [DynamicColor]
|
||||
|
||||
/**
|
||||
Initializes and creates a gradient from a color array.
|
||||
|
||||
- Parameter colors: An array of colors.
|
||||
*/
|
||||
public init(colors: [DynamicColor]) {
|
||||
self.colors = colors
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the color palette of `amount` elements by grabbing equidistant colors.
|
||||
|
||||
- Parameter amount: An amount of colors to return. 2 by default.
|
||||
- Parameter colorspace: The color space used to mix the colors. By default it uses the RBG color space.
|
||||
- Returns: An array of DynamicColor objects with equi-distant space in the gradient.
|
||||
*/
|
||||
public func colorPalette(amount: UInt = 2, inColorSpace colorspace: DynamicColorSpace = .rgb) -> [DynamicColor] {
|
||||
guard amount > 0 && colors.count > 0 else {
|
||||
return []
|
||||
}
|
||||
|
||||
guard colors.count > 1 else {
|
||||
return (0 ..< amount).map { _ in colors[0] }
|
||||
}
|
||||
|
||||
let increment = 1.0 / CGFloat(amount - 1)
|
||||
|
||||
return (0 ..< amount).map { pickColorAt(scale: CGFloat($0) * increment, inColorSpace: colorspace) }
|
||||
}
|
||||
|
||||
/**
|
||||
Picks up and returns the color at the given scale by interpolating the colors.
|
||||
|
||||
For example, given this color array `[red, green, blue]` and a scale of `0.25` you will get a kaki color.
|
||||
|
||||
- Parameter scale: A float value between 0.0 and 1.0.
|
||||
- Parameter colorspace: The color space used to mix the colors. By default it uses the RBG color space.
|
||||
- Returns: A DynamicColor object corresponding to the color at the given scale.
|
||||
*/
|
||||
public func pickColorAt(scale: CGFloat, inColorSpace colorspace: DynamicColorSpace = .rgb) -> DynamicColor {
|
||||
guard colors.count > 1 else {
|
||||
return colors.first ?? .black
|
||||
}
|
||||
|
||||
let clippedScale = clip(scale, 0.0, 1.0)
|
||||
let positions = (0 ..< colors.count).map { CGFloat($0) / CGFloat(colors.count - 1) }
|
||||
|
||||
var color: DynamicColor = .black
|
||||
|
||||
for (index, position) in positions.enumerated() {
|
||||
guard clippedScale <= position else { continue }
|
||||
|
||||
guard clippedScale != 0.0 && clippedScale != 1.0 else {
|
||||
return colors[index]
|
||||
}
|
||||
|
||||
let previousPosition = positions[index - 1]
|
||||
let weight = (clippedScale - previousPosition) / (position - previousPosition)
|
||||
|
||||
color = colors[index - 1].mixed(withColor: colors[index], weight: weight, inColorSpace: colorspace)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return color
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
Defines the supported color spaces.
|
||||
*/
|
||||
public enum DynamicColorSpace {
|
||||
/// The RGB color space
|
||||
case rgb
|
||||
/// The HSL color space
|
||||
case hsl
|
||||
/// The HSB color space
|
||||
case hsb
|
||||
/// The Cie L*a*b* color space
|
||||
case lab
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
Defines the mode (i.e color space) used for grayscaling.
|
||||
|
||||
[More info](https://en.wikipedia.org/wiki/Lightness#Lightness_and_human_perception)
|
||||
*/
|
||||
public enum GrayscalingMode {
|
||||
/// XYZ luminance
|
||||
case luminance
|
||||
/// HSL lightness
|
||||
case lightness
|
||||
/// RGB average
|
||||
case average
|
||||
/// HSV value
|
||||
case value
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
/// Hue-saturation-lightness structure to make the color manipulation easier.
|
||||
internal struct HSL {
|
||||
/// Hue value between 0.0 and 1.0 (0.0 = 0 degree, 1.0 = 360 degree).
|
||||
var h: CGFloat = 0.0
|
||||
/// Saturation value between 0.0 and 1.0.
|
||||
var s: CGFloat = 0.0
|
||||
/// Lightness value between 0.0 and 1.0.
|
||||
var l: CGFloat = 0.0
|
||||
/// Alpha value between 0.0 and 1.0.
|
||||
var a: CGFloat = 1.0
|
||||
|
||||
// MARK: - Initializing HSL Colors
|
||||
|
||||
/**
|
||||
Initializes and creates a HSL color from the hue, saturation, lightness and alpha components.
|
||||
|
||||
- parameter h: The hue component of the color object, specified as a value between 0.0 and 360.0 degree.
|
||||
- parameter s: The saturation component of the color object, specified as a value between 0.0 and 1.0.
|
||||
- parameter l: The lightness component of the color object, specified as a value between 0.0 and 1.0.
|
||||
- parameter a: The opacity component of the color object, specified as a value between 0.0 and 1.0.
|
||||
*/
|
||||
init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1.0) {
|
||||
h = hue.truncatingRemainder(dividingBy: 360.0) / 360.0
|
||||
s = clip(saturation, 0.0, 1.0)
|
||||
l = clip(lightness, 0.0, 1.0)
|
||||
a = clip(alpha, 0.0, 1.0)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes and creates a HSL (hue, saturation, lightness) color from a DynamicColor object.
|
||||
|
||||
- parameter color: A DynamicColor object.
|
||||
*/
|
||||
init(color: DynamicColor) {
|
||||
let rgba = color.toRGBAComponents()
|
||||
|
||||
let maximum = max(rgba.r, max(rgba.g, rgba.b))
|
||||
let minimum = min(rgba.r, min(rgba.g, rgba.b))
|
||||
|
||||
let delta = maximum - minimum
|
||||
|
||||
h = 0.0
|
||||
s = 0.0
|
||||
l = (maximum + minimum) / 2.0
|
||||
|
||||
if delta != 0.0 {
|
||||
if l < 0.5 {
|
||||
s = delta / (maximum + minimum)
|
||||
}
|
||||
else {
|
||||
s = delta / (2.0 - maximum - minimum)
|
||||
}
|
||||
|
||||
if rgba.r == maximum {
|
||||
h = ((rgba.g - rgba.b) / delta) + (rgba.g < rgba.b ? 6.0 : 0.0)
|
||||
}
|
||||
else if rgba.g == maximum {
|
||||
h = ((rgba.b - rgba.r) / delta) + 2.0
|
||||
}
|
||||
else if rgba.b == maximum {
|
||||
h = ((rgba.r - rgba.g) / delta) + 4.0
|
||||
}
|
||||
}
|
||||
|
||||
h /= 6.0
|
||||
a = rgba.a
|
||||
}
|
||||
|
||||
// MARK: - Transforming HSL Color
|
||||
|
||||
/**
|
||||
Returns the DynamicColor representation from the current HSV color.
|
||||
|
||||
- returns: A DynamicColor object corresponding to the current HSV color.
|
||||
*/
|
||||
func toDynamicColor() -> DynamicColor {
|
||||
let (r, g, b, a) = rgbaComponents()
|
||||
|
||||
return DynamicColor(red: r, green: g, blue: b, alpha: a)
|
||||
}
|
||||
|
||||
/// Returns the RGBA components from the current HSV color.
|
||||
func rgbaComponents() -> (CGFloat, CGFloat, CGFloat, CGFloat) {
|
||||
let m2 = l <= 0.5 ? l * (s + 1.0) : (l + s) - (l * s)
|
||||
let m1 = (l * 2.0) - m2
|
||||
|
||||
let r = hueToRGB(m1: m1, m2: m2, h: h + (1.0 / 3.0))
|
||||
let g = hueToRGB(m1: m1, m2: m2, h: h)
|
||||
let b = hueToRGB(m1: m1, m2: m2, h: h - (1.0 / 3.0))
|
||||
|
||||
return (r, g, b, CGFloat(a))
|
||||
}
|
||||
|
||||
/// Hue to RGB helper function
|
||||
private func hueToRGB(m1: CGFloat, m2: CGFloat, h: CGFloat) -> CGFloat {
|
||||
let hue = moda(h, m: 1)
|
||||
|
||||
if hue * 6 < 1.0 {
|
||||
return m1 + ((m2 - m1) * hue * 6.0)
|
||||
}
|
||||
else if hue * 2.0 < 1.0 {
|
||||
return m2
|
||||
}
|
||||
else if hue * 3.0 < 1.9999 {
|
||||
return m1 + ((m2 - m1) * ((2.0 / 3.0) - hue) * 6.0)
|
||||
}
|
||||
|
||||
return m1
|
||||
}
|
||||
|
||||
// MARK: - Deriving the Color
|
||||
|
||||
/**
|
||||
Returns a color with the hue rotated along the color wheel by the given amount.
|
||||
|
||||
- parameter amount: A float representing the number of degrees as ratio (usually between -360.0 degree and 360.0 degree).
|
||||
- returns: A HSL color with the hue changed.
|
||||
*/
|
||||
func adjustedHue(amount: CGFloat) -> HSL {
|
||||
return HSL(hue: (h * 360.0) + amount, saturation: s, lightness: l, alpha: a)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a color with the lightness increased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0.
|
||||
- returns: A lighter HSL color.
|
||||
*/
|
||||
func lighter(amount: CGFloat) -> HSL {
|
||||
return HSL(hue: h * 360.0, saturation: s, lightness: l + amount, alpha: a)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a color with the lightness decreased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0.
|
||||
- returns: A darker HSL color.
|
||||
*/
|
||||
func darkened(amount: CGFloat) -> HSL {
|
||||
return lighter(amount: amount * -1.0)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a color with the saturation increased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0.
|
||||
- returns: A HSL color more saturated.
|
||||
*/
|
||||
func saturated(amount: CGFloat) -> HSL {
|
||||
return HSL(hue: h * 360.0, saturation: s + amount, lightness: l, alpha: a)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a color with the saturation decreased by the given amount.
|
||||
|
||||
- parameter amount: CGFloat between 0.0 and 1.0.
|
||||
- returns: A HSL color less saturated.
|
||||
*/
|
||||
func desaturated(amount: CGFloat) -> HSL {
|
||||
return saturated(amount: amount * -1.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
/**
|
||||
Clips the values in an interval.
|
||||
|
||||
Given an interval, values outside the interval are clipped to the interval
|
||||
edges. For example, if an interval of [0, 1] is specified, values smaller than
|
||||
0 become 0, and values larger than 1 become 1.
|
||||
|
||||
- Parameter v: The value to clipped.
|
||||
- Parameter minimum: The minimum edge value.
|
||||
- Parameter maximum: The maximum edgevalue.
|
||||
*/
|
||||
internal func clip<T: Comparable>(_ v: T, _ minimum: T, _ maximum: T) -> T {
|
||||
return max(min(v, maximum), minimum)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the absolute value of the modulo operation.
|
||||
|
||||
- Parameter x: The value to compute.
|
||||
- Parameter m: The modulo.
|
||||
*/
|
||||
internal func moda(_ x: CGFloat, m: CGFloat) -> CGFloat {
|
||||
return (x.truncatingRemainder(dividingBy: m) + m).truncatingRemainder(dividingBy: m)
|
||||
}
|
||||
|
||||
/**
|
||||
Rounds the given float to a given decimal precision.
|
||||
|
||||
- Parameter x: The value to round.
|
||||
- Parameter m: The precision. Default to 10000.
|
||||
*/
|
||||
internal func roundDecimal(_ x: CGFloat, precision: CGFloat = 10000.0) -> CGFloat {
|
||||
return CGFloat(Int(round(x * precision))) / precision
|
||||
}
|
||||
|
||||
internal func roundToHex(_ x: CGFloat) -> UInt32 {
|
||||
guard x > 0 else { return 0 }
|
||||
let rounded: CGFloat = round(x * 255.0)
|
||||
|
||||
return UInt32(rounded)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
public extension Color {
|
||||
// MARK: - Manipulating Hexa-decimal Values and Strings
|
||||
|
||||
/**
|
||||
Creates a color from an hex string (e.g. "#3498db"). The RGBA string are also supported (e.g. "#3498dbff").
|
||||
|
||||
If the given hex string is invalid the initialiser will create a black color.
|
||||
|
||||
- parameter hexString: A hexa-decimal color string representation.
|
||||
*/
|
||||
init(hexString: String) {
|
||||
let hexString = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let scanner = Scanner(string: hexString)
|
||||
scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#")
|
||||
|
||||
var color: UInt64 = 0
|
||||
|
||||
if scanner.scanHexInt64(&color) {
|
||||
self.init(hex: color, useOpacity: hexString.count > 7)
|
||||
}
|
||||
else {
|
||||
self.init(hex: 0x000000)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a color from an hex integer (e.g. 0x3498db).
|
||||
|
||||
- parameter hex: A hexa-decimal UInt64 that represents a color.
|
||||
- parameter opacityChannel: If true the given hex-decimal UInt64 includes the opacity channel (e.g. 0xFF0000FF).
|
||||
*/
|
||||
init(hex: UInt64, useOpacity opacityChannel: Bool = false) {
|
||||
let mask = UInt64(0xFF)
|
||||
let cappedHex = !opacityChannel && hex > 0xffffff ? 0xffffff : hex
|
||||
|
||||
let r = cappedHex >> (opacityChannel ? 24 : 16) & mask
|
||||
let g = cappedHex >> (opacityChannel ? 16 : 8) & mask
|
||||
let b = cappedHex >> (opacityChannel ? 8 : 0) & mask
|
||||
let o = opacityChannel ? cappedHex & mask : 255
|
||||
|
||||
let red = Double(r) / 255.0
|
||||
let green = Double(g) / 255.0
|
||||
let blue = Double(b) / 255.0
|
||||
let opacity = Double(o) / 255.0
|
||||
|
||||
self.init(red: red, green: green, blue: blue, opacity: opacity)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
/// Convert a DynamicColor to a SwiftUI color
|
||||
extension DynamicColor {
|
||||
/**
|
||||
Returns the Color from an Dynamic Color.
|
||||
|
||||
- returns: A Color (SwiftUI).
|
||||
*/
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
func toColor() -> Color {
|
||||
return Color(self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* DynamicColor
|
||||
*
|
||||
* Copyright 2015-present Yannick Loriot.
|
||||
* http://yannickloriot.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension HSL {
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
func toColor() -> Color {
|
||||
let (r, g, b, a) = rgbaComponents()
|
||||
|
||||
return SwiftUI.Color(red: Double(r), green: Double(g), blue: Double(b), opacity: Double(a))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user