| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- /*
- * 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
- }
- }
|