HSL.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /*
  2. * DynamicColor
  3. *
  4. * Copyright 2015-present Yannick Loriot.
  5. * http://yannickloriot.com
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a copy
  8. * of this software and associated documentation files (the "Software"), to deal
  9. * in the Software without restriction, including without limitation the rights
  10. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. * copies of the Software, and to permit persons to whom the Software is
  12. * furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in
  15. * all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. * THE SOFTWARE.
  24. *
  25. */
  26. #if os(iOS) || os(tvOS) || os(watchOS)
  27. import UIKit
  28. #elseif os(OSX)
  29. import AppKit
  30. #endif
  31. /// Hue-saturation-lightness structure to make the color manipulation easier.
  32. internal struct HSL {
  33. /// Hue value between 0.0 and 1.0 (0.0 = 0 degree, 1.0 = 360 degree).
  34. var h: CGFloat = 0.0
  35. /// Saturation value between 0.0 and 1.0.
  36. var s: CGFloat = 0.0
  37. /// Lightness value between 0.0 and 1.0.
  38. var l: CGFloat = 0.0
  39. /// Alpha value between 0.0 and 1.0.
  40. var a: CGFloat = 1.0
  41. // MARK: - Initializing HSL Colors
  42. /**
  43. Initializes and creates a HSL color from the hue, saturation, lightness and alpha components.
  44. - parameter h: The hue component of the color object, specified as a value between 0.0 and 360.0 degree.
  45. - parameter s: The saturation component of the color object, specified as a value between 0.0 and 1.0.
  46. - parameter l: The lightness component of the color object, specified as a value between 0.0 and 1.0.
  47. - parameter a: The opacity component of the color object, specified as a value between 0.0 and 1.0.
  48. */
  49. init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1.0) {
  50. h = hue.truncatingRemainder(dividingBy: 360.0) / 360.0
  51. s = clip(saturation, 0.0, 1.0)
  52. l = clip(lightness, 0.0, 1.0)
  53. a = clip(alpha, 0.0, 1.0)
  54. }
  55. /**
  56. Initializes and creates a HSL (hue, saturation, lightness) color from a DynamicColor object.
  57. - parameter color: A DynamicColor object.
  58. */
  59. init(color: DynamicColor) {
  60. let rgba = color.toRGBAComponents()
  61. let maximum = max(rgba.r, max(rgba.g, rgba.b))
  62. let minimum = min(rgba.r, min(rgba.g, rgba.b))
  63. let delta = maximum - minimum
  64. h = 0.0
  65. s = 0.0
  66. l = (maximum + minimum) / 2.0
  67. if delta != 0.0 {
  68. if l < 0.5 {
  69. s = delta / (maximum + minimum)
  70. }
  71. else {
  72. s = delta / (2.0 - maximum - minimum)
  73. }
  74. if rgba.r == maximum {
  75. h = ((rgba.g - rgba.b) / delta) + (rgba.g < rgba.b ? 6.0 : 0.0)
  76. }
  77. else if rgba.g == maximum {
  78. h = ((rgba.b - rgba.r) / delta) + 2.0
  79. }
  80. else if rgba.b == maximum {
  81. h = ((rgba.r - rgba.g) / delta) + 4.0
  82. }
  83. }
  84. h /= 6.0
  85. a = rgba.a
  86. }
  87. // MARK: - Transforming HSL Color
  88. /**
  89. Returns the DynamicColor representation from the current HSV color.
  90. - returns: A DynamicColor object corresponding to the current HSV color.
  91. */
  92. func toDynamicColor() -> DynamicColor {
  93. let m2 = l <= 0.5 ? l * (s + 1.0) : (l + s) - (l * s)
  94. let m1 = (l * 2.0) - m2
  95. let r = hueToRGB(m1: m1, m2: m2, h: h + (1.0 / 3.0))
  96. let g = hueToRGB(m1: m1, m2: m2, h: h)
  97. let b = hueToRGB(m1: m1, m2: m2, h: h - (1.0 / 3.0))
  98. return DynamicColor(red: r, green: g, blue: b, alpha: CGFloat(a))
  99. }
  100. /// Hue to RGB helper function
  101. private func hueToRGB(m1: CGFloat, m2: CGFloat, h: CGFloat) -> CGFloat {
  102. let hue = moda(h, m: 1)
  103. if hue * 6 < 1.0 {
  104. return m1 + ((m2 - m1) * hue * 6.0)
  105. }
  106. else if hue * 2.0 < 1.0 {
  107. return m2
  108. }
  109. else if hue * 3.0 < 1.9999 {
  110. return m1 + ((m2 - m1) * ((2.0 / 3.0) - hue) * 6.0)
  111. }
  112. return m1
  113. }
  114. // MARK: - Deriving the Color
  115. /**
  116. Returns a color with the hue rotated along the color wheel by the given amount.
  117. - parameter amount: A float representing the number of degrees as ratio (usually between -360.0 degree and 360.0 degree).
  118. - returns: A HSL color with the hue changed.
  119. */
  120. func adjustedHue(amount: CGFloat) -> HSL {
  121. return HSL(hue: (h * 360.0) + amount, saturation: s, lightness: l, alpha: a)
  122. }
  123. /**
  124. Returns a color with the lightness increased by the given amount.
  125. - parameter amount: CGFloat between 0.0 and 1.0.
  126. - returns: A lighter HSL color.
  127. */
  128. func lighter(amount: CGFloat) -> HSL {
  129. return HSL(hue: h * 360.0, saturation: s, lightness: l + amount, alpha: a)
  130. }
  131. /**
  132. Returns a color with the lightness decreased by the given amount.
  133. - parameter amount: CGFloat between 0.0 and 1.0.
  134. - returns: A darker HSL color.
  135. */
  136. func darkened(amount: CGFloat) -> HSL {
  137. return lighter(amount: amount * -1.0)
  138. }
  139. /**
  140. Returns a color with the saturation increased by the given amount.
  141. - parameter amount: CGFloat between 0.0 and 1.0.
  142. - returns: A HSL color more saturated.
  143. */
  144. func saturated(amount: CGFloat) -> HSL {
  145. return HSL(hue: h * 360.0, saturation: s + amount, lightness: l, alpha: a)
  146. }
  147. /**
  148. Returns a color with the saturation decreased by the given amount.
  149. - parameter amount: CGFloat between 0.0 and 1.0.
  150. - returns: A HSL color less saturated.
  151. */
  152. func desaturated(amount: CGFloat) -> HSL {
  153. return saturated(amount: amount * -1.0)
  154. }
  155. }