|
| 1 | +""" |
| 2 | +The RGB color model is an additive color model in which red, green, and blue light |
| 3 | +are added together in various ways to reproduce a broad array of colors. The name |
| 4 | +of the model comes from the initials of the three additive primary colors, red, |
| 5 | +green, and blue. Meanwhile, the HSV representation models how colors appear under |
| 6 | +light. In it, colors are represented using three components: hue, saturation and |
| 7 | +(brightness-)value. This file provides functions for converting colors from one |
| 8 | +representation to the other. |
| 9 | +
|
| 10 | +(description adapted from https://en.wikipedia.org/wiki/RGB_color_model and |
| 11 | +https://en.wikipedia.org/wiki/HSL_and_HSV). |
| 12 | +""" |
| 13 | + |
| 14 | + |
| 15 | +def hsv_to_rgb(hue: float, saturation: float, value: float) -> list[int]: |
| 16 | + """ |
| 17 | + Conversion from the HSV-representation to the RGB-representation. |
| 18 | + Expected RGB-values taken from |
| 19 | + https://www.rapidtables.com/convert/color/hsv-to-rgb.html |
| 20 | +
|
| 21 | + >>> hsv_to_rgb(0, 0, 0) |
| 22 | + [0, 0, 0] |
| 23 | + >>> hsv_to_rgb(0, 0, 1) |
| 24 | + [255, 255, 255] |
| 25 | + >>> hsv_to_rgb(0, 1, 1) |
| 26 | + [255, 0, 0] |
| 27 | + >>> hsv_to_rgb(60, 1, 1) |
| 28 | + [255, 255, 0] |
| 29 | + >>> hsv_to_rgb(120, 1, 1) |
| 30 | + [0, 255, 0] |
| 31 | + >>> hsv_to_rgb(240, 1, 1) |
| 32 | + [0, 0, 255] |
| 33 | + >>> hsv_to_rgb(300, 1, 1) |
| 34 | + [255, 0, 255] |
| 35 | + >>> hsv_to_rgb(180, 0.5, 0.5) |
| 36 | + [64, 128, 128] |
| 37 | + >>> hsv_to_rgb(234, 0.14, 0.88) |
| 38 | + [193, 196, 224] |
| 39 | + >>> hsv_to_rgb(330, 0.75, 0.5) |
| 40 | + [128, 32, 80] |
| 41 | + """ |
| 42 | + if hue < 0 or hue > 360: |
| 43 | + raise Exception("hue should be between 0 and 360") |
| 44 | + |
| 45 | + if saturation < 0 or saturation > 1: |
| 46 | + raise Exception("saturation should be between 0 and 1") |
| 47 | + |
| 48 | + if value < 0 or value > 1: |
| 49 | + raise Exception("value should be between 0 and 1") |
| 50 | + |
| 51 | + chroma = value * saturation |
| 52 | + hue_section = hue / 60 |
| 53 | + second_largest_component = chroma * (1 - abs(hue_section % 2 - 1)) |
| 54 | + match_value = value - chroma |
| 55 | + |
| 56 | + if hue_section >= 0 and hue_section <= 1: |
| 57 | + red = round(255 * (chroma + match_value)) |
| 58 | + green = round(255 * (second_largest_component + match_value)) |
| 59 | + blue = round(255 * (match_value)) |
| 60 | + elif hue_section > 1 and hue_section <= 2: |
| 61 | + red = round(255 * (second_largest_component + match_value)) |
| 62 | + green = round(255 * (chroma + match_value)) |
| 63 | + blue = round(255 * (match_value)) |
| 64 | + elif hue_section > 2 and hue_section <= 3: |
| 65 | + red = round(255 * (match_value)) |
| 66 | + green = round(255 * (chroma + match_value)) |
| 67 | + blue = round(255 * (second_largest_component + match_value)) |
| 68 | + elif hue_section > 3 and hue_section <= 4: |
| 69 | + red = round(255 * (match_value)) |
| 70 | + green = round(255 * (second_largest_component + match_value)) |
| 71 | + blue = round(255 * (chroma + match_value)) |
| 72 | + elif hue_section > 4 and hue_section <= 5: |
| 73 | + red = round(255 * (second_largest_component + match_value)) |
| 74 | + green = round(255 * (match_value)) |
| 75 | + blue = round(255 * (chroma + match_value)) |
| 76 | + else: |
| 77 | + red = round(255 * (chroma + match_value)) |
| 78 | + green = round(255 * (match_value)) |
| 79 | + blue = round(255 * (second_largest_component + match_value)) |
| 80 | + |
| 81 | + return [red, green, blue] |
| 82 | + |
| 83 | + |
| 84 | +def rgb_to_hsv(red: int, green: int, blue: int) -> list[float]: |
| 85 | + """ |
| 86 | + Conversion from the RGB-representation to the HSV-representation. |
| 87 | + The tested values are the reverse values from the hsv_to_rgb-doctests. |
| 88 | + Function "approximately_equal_hsv" is needed because of small deviations due to |
| 89 | + rounding for the RGB-values. |
| 90 | +
|
| 91 | + >>> approximately_equal_hsv(rgb_to_hsv(0, 0, 0), [0, 0, 0]) |
| 92 | + True |
| 93 | + >>> approximately_equal_hsv(rgb_to_hsv(255, 255, 255), [0, 0, 1]) |
| 94 | + True |
| 95 | + >>> approximately_equal_hsv(rgb_to_hsv(255, 0, 0), [0, 1, 1]) |
| 96 | + True |
| 97 | + >>> approximately_equal_hsv(rgb_to_hsv(255, 255, 0), [60, 1, 1]) |
| 98 | + True |
| 99 | + >>> approximately_equal_hsv(rgb_to_hsv(0, 255, 0), [120, 1, 1]) |
| 100 | + True |
| 101 | + >>> approximately_equal_hsv(rgb_to_hsv(0, 0, 255), [240, 1, 1]) |
| 102 | + True |
| 103 | + >>> approximately_equal_hsv(rgb_to_hsv(255, 0, 255), [300, 1, 1]) |
| 104 | + True |
| 105 | + >>> approximately_equal_hsv(rgb_to_hsv(64, 128, 128), [180, 0.5, 0.5]) |
| 106 | + True |
| 107 | + >>> approximately_equal_hsv(rgb_to_hsv(193, 196, 224), [234, 0.14, 0.88]) |
| 108 | + True |
| 109 | + >>> approximately_equal_hsv(rgb_to_hsv(128, 32, 80), [330, 0.75, 0.5]) |
| 110 | + True |
| 111 | + """ |
| 112 | + if red < 0 or red > 255: |
| 113 | + raise Exception("red should be between 0 and 255") |
| 114 | + |
| 115 | + if green < 0 or green > 255: |
| 116 | + raise Exception("green should be between 0 and 255") |
| 117 | + |
| 118 | + if blue < 0 or blue > 255: |
| 119 | + raise Exception("blue should be between 0 and 255") |
| 120 | + |
| 121 | + float_red = red / 255 |
| 122 | + float_green = green / 255 |
| 123 | + float_blue = blue / 255 |
| 124 | + value = max(max(float_red, float_green), float_blue) |
| 125 | + chroma = value - min(min(float_red, float_green), float_blue) |
| 126 | + saturation = 0 if value == 0 else chroma / value |
| 127 | + |
| 128 | + if chroma == 0: |
| 129 | + hue = 0.0 |
| 130 | + elif value == float_red: |
| 131 | + hue = 60 * (0 + (float_green - float_blue) / chroma) |
| 132 | + elif value == float_green: |
| 133 | + hue = 60 * (2 + (float_blue - float_red) / chroma) |
| 134 | + else: |
| 135 | + hue = 60 * (4 + (float_red - float_green) / chroma) |
| 136 | + |
| 137 | + hue = (hue + 360) % 360 |
| 138 | + |
| 139 | + return [hue, saturation, value] |
| 140 | + |
| 141 | + |
| 142 | +def approximately_equal_hsv(hsv_1: list[float], hsv_2: list[float]) -> bool: |
| 143 | + """ |
| 144 | + Utility-function to check that two hsv-colors are approximately equal |
| 145 | +
|
| 146 | + >>> approximately_equal_hsv([0, 0, 0], [0, 0, 0]) |
| 147 | + True |
| 148 | + >>> approximately_equal_hsv([180, 0.5, 0.3], [179.9999, 0.500001, 0.30001]) |
| 149 | + True |
| 150 | + >>> approximately_equal_hsv([0, 0, 0], [1, 0, 0]) |
| 151 | + False |
| 152 | + >>> approximately_equal_hsv([180, 0.5, 0.3], [179.9999, 0.6, 0.30001]) |
| 153 | + False |
| 154 | + """ |
| 155 | + check_hue = abs(hsv_1[0] - hsv_2[0]) < 0.2 |
| 156 | + check_saturation = abs(hsv_1[1] - hsv_2[1]) < 0.002 |
| 157 | + check_value = abs(hsv_1[2] - hsv_2[2]) < 0.002 |
| 158 | + |
| 159 | + return check_hue and check_saturation and check_value |
0 commit comments