doryen-extra 0.2.1

Doryen-extra aims to be a loose re-implementation of the utility features from the popular roguelike library named libtcod
Documentation
/* BSD 3-Clause License
 *
 * Copyright © 2019, Alexander Krivács Schrøder <alexschrod@gmail.com>.
 * Copyright © 2008-2019, Jice and the libtcod contributors.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

//! # Color representation and operations.
//!
//! A color is defined by its red, green and blue components in the range 0 to 255.
//! You can use the following predefined colors (hover over a color to see its full name and
//! RGB values:
//!
//! <table class="color">
//! <tbody><tr><td></td><th colspan="8">STANDARD COLORS</th></tr>
//! <tr><td></td><td>DESATURATED</td><td>LIGHTEST</td><td>LIGHTER</td><td>LIGHT</td><td>NORMAL</td><td>DARK</td><td>DARKER</td><td>DARKEST</td></tr>
//! <tr><td>RED</td><td title="DESATURATED_RED (128, 64, 64)" style="background-color: rgb(128, 64, 64); --darkreader-inline-bgcolor:#663333;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_RED (255, 191, 191)" style="background-color: rgb(255, 191, 191); --darkreader-inline-bgcolor:#590000;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_RED (255, 166, 166)" style="background-color: rgb(255, 166, 166); --darkreader-inline-bgcolor:#680000;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_RED (255, 115, 115)" style="background-color: rgb(255, 115, 115); --darkreader-inline-bgcolor:#870000;" data-darkreader-inline-bgcolor=""></td><td title="RED (255, 0, 0)" style="background-color: rgb(255, 0, 0); --darkreader-inline-bgcolor:#cc0000;" data-darkreader-inline-bgcolor=""></td><td title="DARK_RED (191, 0, 0)" style="background-color: rgb(191, 0, 0); --darkreader-inline-bgcolor:#bf0000;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_RED (128, 0, 0)" style="background-color: rgb(128, 0, 0); --darkreader-inline-bgcolor:#800000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_RED (64, 0, 0)" style="background-color: rgb(64, 0, 0); --darkreader-inline-bgcolor:#400000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>FLAME</td><td title="DESATURATED_FLAME (128, 80, 64)" style="background-color: rgb(128, 80, 64); --darkreader-inline-bgcolor:#664033;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_FLAME (255, 207, 191)" style="background-color: rgb(255, 207, 191); --darkreader-inline-bgcolor:#591600;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_FLAME (255, 188, 166)" style="background-color: rgb(255, 188, 166); --darkreader-inline-bgcolor:#681a00;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_FLAME (255, 149, 115)" style="background-color: rgb(255, 149, 115); --darkreader-inline-bgcolor:#872100;" data-darkreader-inline-bgcolor=""></td><td title="FLAME (255, 63, 0)" style="background-color: rgb(255, 63, 0); --darkreader-inline-bgcolor:#cc3200;" data-darkreader-inline-bgcolor=""></td><td title="DARK_FLAME (191, 47, 0)" style="background-color: rgb(191, 47, 0); --darkreader-inline-bgcolor:#bf2f00;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_FLAME (128, 32, 0)" style="background-color: rgb(128, 32, 0); --darkreader-inline-bgcolor:#802000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_FLAME (64, 16, 0)" style="background-color: rgb(64, 16, 0); --darkreader-inline-bgcolor:#401000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>ORANGE</td><td title="DESATURATED_ORANGE (128, 96, 64)" style="background-color: rgb(128, 96, 64); --darkreader-inline-bgcolor:#664d33;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_ORANGE (255, 223, 191)" style="background-color: rgb(255, 223, 191); --darkreader-inline-bgcolor:#592d00;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_ORANGE (255, 210, 166)" style="background-color: rgb(255, 210, 166); --darkreader-inline-bgcolor:#683400;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_ORANGE (255, 185, 115)" style="background-color: rgb(255, 185, 115); --darkreader-inline-bgcolor:#874300;" data-darkreader-inline-bgcolor=""></td><td title="ORANGE (255, 127, 0)" style="background-color: rgb(255, 127, 0); --darkreader-inline-bgcolor:#cc6600;" data-darkreader-inline-bgcolor=""></td><td title="DARK_ORANGE (191, 95, 0)" style="background-color: rgb(191, 95, 0); --darkreader-inline-bgcolor:#bf5f00;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_ORANGE (128, 64, 0)" style="background-color: rgb(128, 64, 0); --darkreader-inline-bgcolor:#804000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_ORANGE (64, 32, 0)" style="background-color: rgb(64, 32, 0); --darkreader-inline-bgcolor:#402000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>AMBER</td><td title="DESATURATED_AMBER (128, 112, 64)" style="background-color: rgb(128, 112, 64); --darkreader-inline-bgcolor:#665933;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_AMBER (255, 239, 191)" style="background-color: rgb(255, 239, 191); --darkreader-inline-bgcolor:#594300;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_AMBER (255, 233, 166)" style="background-color: rgb(255, 233, 166); --darkreader-inline-bgcolor:#684f00;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_AMBER (255, 220, 115)" style="background-color: rgb(255, 220, 115); --darkreader-inline-bgcolor:#876500;" data-darkreader-inline-bgcolor=""></td><td title="AMBER (255, 191, 0)" style="background-color: rgb(255, 191, 0); --darkreader-inline-bgcolor:#cc9900;" data-darkreader-inline-bgcolor=""></td><td title="DARK_AMBER (191, 143, 0)" style="background-color: rgb(191, 143, 0); --darkreader-inline-bgcolor:#bf8f00;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_AMBER (128, 96, 0)" style="background-color: rgb(128, 96, 0); --darkreader-inline-bgcolor:#806000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_AMBER (64, 48, 0)" style="background-color: rgb(64, 48, 0); --darkreader-inline-bgcolor:#403000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>YELLOW</td><td title="DESATURATED_YELLOW (128, 128, 64)" style="background-color: rgb(128, 128, 64); --darkreader-inline-bgcolor:#666633;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_YELLOW (255, 255, 191)" style="background-color: rgb(255, 255, 191); --darkreader-inline-bgcolor:#595900;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_YELLOW (255, 255, 166)" style="background-color: rgb(255, 255, 166); --darkreader-inline-bgcolor:#686800;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_YELLOW (255, 255, 115)" style="background-color: rgb(255, 255, 115); --darkreader-inline-bgcolor:#878700;" data-darkreader-inline-bgcolor=""></td><td title="YELLOW (255, 255, 0)" style="background-color: rgb(255, 255, 0); --darkreader-inline-bgcolor:#cccc00;" data-darkreader-inline-bgcolor=""></td><td title="DARK_YELLOW (191, 191, 0)" style="background-color: rgb(191, 191, 0); --darkreader-inline-bgcolor:#bfbf00;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_YELLOW (128, 128, 0)" style="background-color: rgb(128, 128, 0); --darkreader-inline-bgcolor:#808000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_YELLOW (64, 64, 0)" style="background-color: rgb(64, 64, 0); --darkreader-inline-bgcolor:#404000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>LIME</td><td title="DESATURATED_LIME (112, 128, 64)" style="background-color: rgb(112, 128, 64); --darkreader-inline-bgcolor:#596633;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_LIME (239, 255, 191)" style="background-color: rgb(239, 255, 191); --darkreader-inline-bgcolor:#435900;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_LIME (233, 255, 166)" style="background-color: rgb(233, 255, 166); --darkreader-inline-bgcolor:#4f6800;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_LIME (220, 255, 115)" style="background-color: rgb(220, 255, 115); --darkreader-inline-bgcolor:#658700;" data-darkreader-inline-bgcolor=""></td><td title="LIME (191, 255, 0)" style="background-color: rgb(191, 255, 0); --darkreader-inline-bgcolor:#99cc00;" data-darkreader-inline-bgcolor=""></td><td title="DARK_LIME (143, 191, 0)" style="background-color: rgb(143, 191, 0); --darkreader-inline-bgcolor:#8fbf00;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_LIME (96, 128, 0)" style="background-color: rgb(96, 128, 0); --darkreader-inline-bgcolor:#608000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_LIME (48, 64, 0)" style="background-color: rgb(48, 64, 0); --darkreader-inline-bgcolor:#304000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>CHARTREUSE</td><td title="DESATURATED_CHARTREUSE (96, 128, 64)" style="background-color: rgb(96, 128, 64); --darkreader-inline-bgcolor:#4d6633;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_CHARTREUSE (223, 255, 191)" style="background-color: rgb(223, 255, 191); --darkreader-inline-bgcolor:#2d5900;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_CHARTREUSE (210, 255, 166)" style="background-color: rgb(210, 255, 166); --darkreader-inline-bgcolor:#346800;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_CHARTREUSE (185, 255, 115)" style="background-color: rgb(185, 255, 115); --darkreader-inline-bgcolor:#448700;" data-darkreader-inline-bgcolor=""></td><td title="CHARTREUSE (127, 255, 0)" style="background-color: rgb(127, 255, 0); --darkreader-inline-bgcolor:#66cc00;" data-darkreader-inline-bgcolor=""></td><td title="DARK_CHARTREUSE (95, 191, 0)" style="background-color: rgb(95, 191, 0); --darkreader-inline-bgcolor:#5fbf00;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_CHARTREUSE (64, 128, 0)" style="background-color: rgb(64, 128, 0); --darkreader-inline-bgcolor:#408000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_CHARTREUSE (32, 64, 0)" style="background-color: rgb(32, 64, 0); --darkreader-inline-bgcolor:#204000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>GREEN</td><td title="DESATURATED_GREEN (64, 128, 64)" style="background-color: rgb(64, 128, 64); --darkreader-inline-bgcolor:#336633;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_GREEN (191, 255, 191)" style="background-color: rgb(191, 255, 191); --darkreader-inline-bgcolor:#005900;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_GREEN (166, 255, 166)" style="background-color: rgb(166, 255, 166); --darkreader-inline-bgcolor:#006800;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_GREEN (115, 255, 115)" style="background-color: rgb(115, 255, 115); --darkreader-inline-bgcolor:#008700;" data-darkreader-inline-bgcolor=""></td><td title="GREEN (0, 255, 0)" style="background-color: rgb(0, 255, 0); --darkreader-inline-bgcolor:#00cc00;" data-darkreader-inline-bgcolor=""></td><td title="DARK_GREEN (0, 191, 0)" style="background-color: rgb(0, 191, 0); --darkreader-inline-bgcolor:#00bf00;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_GREEN (0, 128, 0)" style="background-color: rgb(0, 128, 0); --darkreader-inline-bgcolor:#008000;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_GREEN (0, 64, 0)" style="background-color: rgb(0, 64, 0); --darkreader-inline-bgcolor:#004000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>SEA</td><td title="DESATURATED_SEA (64, 128, 96)" style="background-color: rgb(64, 128, 96); --darkreader-inline-bgcolor:#33664d;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_SEA (191, 255, 223)" style="background-color: rgb(191, 255, 223); --darkreader-inline-bgcolor:#00592d;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_SEA (166, 255, 210)" style="background-color: rgb(166, 255, 210); --darkreader-inline-bgcolor:#006834;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_SEA (115, 255, 185)" style="background-color: rgb(115, 255, 185); --darkreader-inline-bgcolor:#008744;" data-darkreader-inline-bgcolor=""></td><td title="SEA (0, 255, 127)" style="background-color: rgb(0, 255, 127); --darkreader-inline-bgcolor:#00cc66;" data-darkreader-inline-bgcolor=""></td><td title="DARK_SEA (0, 191, 95)" style="background-color: rgb(0, 191, 95); --darkreader-inline-bgcolor:#00bf5f;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_SEA (0, 128, 64)" style="background-color: rgb(0, 128, 64); --darkreader-inline-bgcolor:#008040;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_SEA (0, 64, 32)" style="background-color: rgb(0, 64, 32); --darkreader-inline-bgcolor:#004020;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>TURQUOISE</td><td title="DESATURATED_TURQUOISE (64, 128, 112)" style="background-color: rgb(64, 128, 112); --darkreader-inline-bgcolor:#336659;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_TURQUOISE (191, 255, 239)" style="background-color: rgb(191, 255, 239); --darkreader-inline-bgcolor:#005943;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_TURQUOISE (166, 255, 233)" style="background-color: rgb(166, 255, 233); --darkreader-inline-bgcolor:#00684f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_TURQUOISE (115, 255, 220)" style="background-color: rgb(115, 255, 220); --darkreader-inline-bgcolor:#008765;" data-darkreader-inline-bgcolor=""></td><td title="TURQUOISE (0, 255, 191)" style="background-color: rgb(0, 255, 191); --darkreader-inline-bgcolor:#00cc99;" data-darkreader-inline-bgcolor=""></td><td title="DARK_TURQUOISE (0, 191, 143)" style="background-color: rgb(0, 191, 143); --darkreader-inline-bgcolor:#00bf8f;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_TURQUOISE (0, 128, 96)" style="background-color: rgb(0, 128, 96); --darkreader-inline-bgcolor:#008060;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_TURQUOISE (0, 64, 48)" style="background-color: rgb(0, 64, 48); --darkreader-inline-bgcolor:#004030;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>CYAN</td><td title="DESATURATED_CYAN (64, 128, 128)" style="background-color: rgb(64, 128, 128); --darkreader-inline-bgcolor:#336666;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_CYAN (191, 255, 255)" style="background-color: rgb(191, 255, 255); --darkreader-inline-bgcolor:#005959;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_CYAN (166, 255, 255)" style="background-color: rgb(166, 255, 255); --darkreader-inline-bgcolor:#006868;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_CYAN (115, 255, 255)" style="background-color: rgb(115, 255, 255); --darkreader-inline-bgcolor:#008787;" data-darkreader-inline-bgcolor=""></td><td title="CYAN (0, 255, 255)" style="background-color: rgb(0, 255, 255); --darkreader-inline-bgcolor:#00cccc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_CYAN (0, 191, 191)" style="background-color: rgb(0, 191, 191); --darkreader-inline-bgcolor:#00bfbf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_CYAN (0, 128, 128)" style="background-color: rgb(0, 128, 128); --darkreader-inline-bgcolor:#008080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_CYAN (0, 64, 64)" style="background-color: rgb(0, 64, 64); --darkreader-inline-bgcolor:#004040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>SKY</td><td title="DESATURATED_SKY (64, 112, 128)" style="background-color: rgb(64, 112, 128); --darkreader-inline-bgcolor:#335966;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_SKY (191, 239, 255)" style="background-color: rgb(191, 239, 255); --darkreader-inline-bgcolor:#004359;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_SKY (166, 233, 255)" style="background-color: rgb(166, 233, 255); --darkreader-inline-bgcolor:#004f68;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_SKY (115, 220, 255)" style="background-color: rgb(115, 220, 255); --darkreader-inline-bgcolor:#006587;" data-darkreader-inline-bgcolor=""></td><td title="SKY (0, 191, 255)" style="background-color: rgb(0, 191, 255); --darkreader-inline-bgcolor:#0099cc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_SKY (0, 143, 191)" style="background-color: rgb(0, 143, 191); --darkreader-inline-bgcolor:#008fbf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_SKY (0, 96, 128)" style="background-color: rgb(0, 96, 128); --darkreader-inline-bgcolor:#006080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_SKY (0, 48, 64)" style="background-color: rgb(0, 48, 64); --darkreader-inline-bgcolor:#003040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>AZURE</td><td title="DESATURATED_AZURE (64, 96, 128)" style="background-color: rgb(64, 96, 128); --darkreader-inline-bgcolor:#334d66;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_AZURE (191, 223, 255)" style="background-color: rgb(191, 223, 255); --darkreader-inline-bgcolor:#2a2d2f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_AZURE (166, 210, 255)" style="background-color: rgb(166, 210, 255); --darkreader-inline-bgcolor:#323537;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_AZURE (115, 185, 255)" style="background-color: rgb(115, 185, 255); --darkreader-inline-bgcolor:#004487;" data-darkreader-inline-bgcolor=""></td><td title="AZURE (0, 127, 255)" style="background-color: rgb(0, 127, 255); --darkreader-inline-bgcolor:#0066cc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_AZURE (0, 95, 191)" style="background-color: rgb(0, 95, 191); --darkreader-inline-bgcolor:#005fbf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_AZURE (0, 64, 128)" style="background-color: rgb(0, 64, 128); --darkreader-inline-bgcolor:#004080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_AZURE (0, 32, 64)" style="background-color: rgb(0, 32, 64); --darkreader-inline-bgcolor:#002040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>BLUE</td><td title="DESATURATED_BLUE (64, 64, 128)" style="background-color: rgb(64, 64, 128); --darkreader-inline-bgcolor:#333366;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_BLUE (191, 191, 255)" style="background-color: rgb(191, 191, 255); --darkreader-inline-bgcolor:#2a2d2f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_BLUE (166, 166, 255)" style="background-color: rgb(166, 166, 255); --darkreader-inline-bgcolor:#323537;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_BLUE (115, 115, 255)" style="background-color: rgb(115, 115, 255); --darkreader-inline-bgcolor:#000087;" data-darkreader-inline-bgcolor=""></td><td title="BLUE (0, 0, 255)" style="background-color: rgb(0, 0, 255); --darkreader-inline-bgcolor:#0000cc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_BLUE (0, 0, 191)" style="background-color: rgb(0, 0, 191); --darkreader-inline-bgcolor:#0000bf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_BLUE (0, 0, 128)" style="background-color: rgb(0, 0, 128); --darkreader-inline-bgcolor:#000080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_BLUE (0, 0, 64)" style="background-color: rgb(0, 0, 64); --darkreader-inline-bgcolor:#000040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>HAN</td><td title="DESATURATED_HAN (80, 64, 128)" style="background-color: rgb(80, 64, 128); --darkreader-inline-bgcolor:#403366;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_HAN (207, 191, 255)" style="background-color: rgb(207, 191, 255); --darkreader-inline-bgcolor:#2a2d2f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_HAN (188, 166, 255)" style="background-color: rgb(188, 166, 255); --darkreader-inline-bgcolor:#323537;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_HAN (149, 115, 255)" style="background-color: rgb(149, 115, 255); --darkreader-inline-bgcolor:#210087;" data-darkreader-inline-bgcolor=""></td><td title="HAN (63, 0, 255)" style="background-color: rgb(63, 0, 255); --darkreader-inline-bgcolor:#3200cc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_HAN (47, 0, 191)" style="background-color: rgb(47, 0, 191); --darkreader-inline-bgcolor:#2f00bf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_HAN (32, 0, 128)" style="background-color: rgb(32, 0, 128); --darkreader-inline-bgcolor:#200080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_HAN (16, 0, 64)" style="background-color: rgb(16, 0, 64); --darkreader-inline-bgcolor:#100040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>VIOLET</td><td title="DESATURATED_VIOLET (96, 64, 128)" style="background-color: rgb(96, 64, 128); --darkreader-inline-bgcolor:#4d3366;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_VIOLET (223, 191, 255)" style="background-color: rgb(223, 191, 255); --darkreader-inline-bgcolor:#2a2d2f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_VIOLET (210, 166, 255)" style="background-color: rgb(210, 166, 255); --darkreader-inline-bgcolor:#323537;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_VIOLET (185, 115, 255)" style="background-color: rgb(185, 115, 255); --darkreader-inline-bgcolor:#440087;" data-darkreader-inline-bgcolor=""></td><td title="VIOLET (127, 0, 255)" style="background-color: rgb(127, 0, 255); --darkreader-inline-bgcolor:#6600cc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_VIOLET (95, 0, 191)" style="background-color: rgb(95, 0, 191); --darkreader-inline-bgcolor:#5f00bf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_VIOLET (64, 0, 128)" style="background-color: rgb(64, 0, 128); --darkreader-inline-bgcolor:#400080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_VIOLET (32, 0, 64)" style="background-color: rgb(32, 0, 64); --darkreader-inline-bgcolor:#200040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>PURPLE</td><td title="DESATURATED_PURPLE (111, 64, 128)" style="background-color: rgb(111, 64, 128); --darkreader-inline-bgcolor:#583366;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_PURPLE (239, 191, 255)" style="background-color: rgb(239, 191, 255); --darkreader-inline-bgcolor:#430059;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_PURPLE (233, 166, 255)" style="background-color: rgb(233, 166, 255); --darkreader-inline-bgcolor:#4f0068;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_PURPLE (220, 115, 255)" style="background-color: rgb(220, 115, 255); --darkreader-inline-bgcolor:#650087;" data-darkreader-inline-bgcolor=""></td><td title="PURPLE (191, 0, 255)" style="background-color: rgb(191, 0, 255); --darkreader-inline-bgcolor:#9900cc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_PURPLE (143, 0, 191)" style="background-color: rgb(143, 0, 191); --darkreader-inline-bgcolor:#8f00bf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_PURPLE (95, 0, 128)" style="background-color: rgb(95, 0, 128); --darkreader-inline-bgcolor:#5f0080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_PURPLE (48, 0, 64)" style="background-color: rgb(48, 0, 64); --darkreader-inline-bgcolor:#300040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>FUCHSIA</td><td title="DESATURATED_FUCHSIA (128, 64, 128)" style="background-color: rgb(128, 64, 128); --darkreader-inline-bgcolor:#663366;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_FUCHSIA (255, 191, 255)" style="background-color: rgb(255, 191, 255); --darkreader-inline-bgcolor:#590059;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_FUCHSIA (255, 166, 255)" style="background-color: rgb(255, 166, 255); --darkreader-inline-bgcolor:#680068;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_FUCHSIA (255, 115, 255)" style="background-color: rgb(255, 115, 255); --darkreader-inline-bgcolor:#870087;" data-darkreader-inline-bgcolor=""></td><td title="FUCHSIA (255, 0, 255)" style="background-color: rgb(255, 0, 255); --darkreader-inline-bgcolor:#cc00cc;" data-darkreader-inline-bgcolor=""></td><td title="DARK_FUCHSIA (191, 0, 191)" style="background-color: rgb(191, 0, 191); --darkreader-inline-bgcolor:#bf00bf;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_FUCHSIA (128, 0, 128)" style="background-color: rgb(128, 0, 128); --darkreader-inline-bgcolor:#800080;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_FUCHSIA (64, 0, 64)" style="background-color: rgb(64, 0, 64); --darkreader-inline-bgcolor:#400040;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>MAGENTA</td><td title="DESATURATED_MAGENTA (128, 64, 111)" style="background-color: rgb(128, 64, 111); --darkreader-inline-bgcolor:#663358;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_MAGENTA (255, 191, 239)" style="background-color: rgb(255, 191, 239); --darkreader-inline-bgcolor:#590043;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_MAGENTA (255, 166, 233)" style="background-color: rgb(255, 166, 233); --darkreader-inline-bgcolor:#68004f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_MAGENTA (255, 115, 220)" style="background-color: rgb(255, 115, 220); --darkreader-inline-bgcolor:#870065;" data-darkreader-inline-bgcolor=""></td><td title="MAGENTA (255, 0, 191)" style="background-color: rgb(255, 0, 191); --darkreader-inline-bgcolor:#cc0099;" data-darkreader-inline-bgcolor=""></td><td title="DARK_MAGENTA (191, 0, 143)" style="background-color: rgb(191, 0, 143); --darkreader-inline-bgcolor:#bf008f;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_MAGENTA (128, 0, 95)" style="background-color: rgb(128, 0, 95); --darkreader-inline-bgcolor:#80005f;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_MAGENTA (64, 0, 48)" style="background-color: rgb(64, 0, 48); --darkreader-inline-bgcolor:#400030;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>PINK</td><td title="DESATURATED_PINK (128, 64, 96)" style="background-color: rgb(128, 64, 96); --darkreader-inline-bgcolor:#66334d;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_PINK (255, 191, 223)" style="background-color: rgb(255, 191, 223); --darkreader-inline-bgcolor:#59002d;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_PINK (255, 166, 210)" style="background-color: rgb(255, 166, 210); --darkreader-inline-bgcolor:#680034;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_PINK (255, 115, 185)" style="background-color: rgb(255, 115, 185); --darkreader-inline-bgcolor:#870044;" data-darkreader-inline-bgcolor=""></td><td title="PINK (255, 0, 127)" style="background-color: rgb(255, 0, 127); --darkreader-inline-bgcolor:#cc0066;" data-darkreader-inline-bgcolor=""></td><td title="DARK_PINK (191, 0, 95)" style="background-color: rgb(191, 0, 95); --darkreader-inline-bgcolor:#bf005f;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_PINK (128, 0, 64)" style="background-color: rgb(128, 0, 64); --darkreader-inline-bgcolor:#800040;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_PINK (64, 0, 32)" style="background-color: rgb(64, 0, 32); --darkreader-inline-bgcolor:#400020;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>CRIMSON</td><td title="DESATURATED_CRIMSON (128, 64, 79)" style="background-color: rgb(128, 64, 79); --darkreader-inline-bgcolor:#66333f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTEST_CRIMSON (255, 191, 207)" style="background-color: rgb(255, 191, 207); --darkreader-inline-bgcolor:#590016;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_CRIMSON (255, 166, 188)" style="background-color: rgb(255, 166, 188); --darkreader-inline-bgcolor:#68001a;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_CRIMSON (255, 115, 149)" style="background-color: rgb(255, 115, 149); --darkreader-inline-bgcolor:#870021;" data-darkreader-inline-bgcolor=""></td><td title="CRIMSON (255, 0, 63)" style="background-color: rgb(255, 0, 63); --darkreader-inline-bgcolor:#cc0032;" data-darkreader-inline-bgcolor=""></td><td title="DARK_CRIMSON (191, 0, 47)" style="background-color: rgb(191, 0, 47); --darkreader-inline-bgcolor:#bf002f;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_CRIMSON (128, 0, 31)" style="background-color: rgb(128, 0, 31); --darkreader-inline-bgcolor:#80001f;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_CRIMSON (64, 0, 16)" style="background-color: rgb(64, 0, 16); --darkreader-inline-bgcolor:#400010;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td></td><th colspan="8">METALLIC COLORS</th></tr>
//! <tr><td>BRASS</td><td title="BRASS (191, 151, 96)" style="background-color: rgb(191, 151, 96); --darkreader-inline-bgcolor:#684e2a;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>COPPER</td><td title="COPPER (196, 136, 124)" style="background-color: rgb(196, 136, 124); --darkreader-inline-bgcolor:#593028;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>GOLD</td><td title="GOLD (229, 191, 0)" style="background-color: rgb(229, 191, 0); --darkreader-inline-bgcolor:#ccaa00;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>SILVER</td><td title="SILVER (203, 203, 203)" style="background-color: rgb(203, 203, 203); --darkreader-inline-bgcolor:#27292b;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td></td><th colspan="8">MISCELLANEOUS COLORS</th></tr>
//! <tr><td>CELADON</td><td title="CELADON (172, 255, 175)" style="background-color: rgb(172, 255, 175); --darkreader-inline-bgcolor:#006504;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>PEACH</td><td title="PEACH (255, 159, 127)" style="background-color: rgb(255, 159, 127); --darkreader-inline-bgcolor:#802000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td></td><th colspan="8">GRAYSCALE &amp; SEPIA</th></tr>
//! <tr><td colspan="2">&nbsp;</td><td>LIGHTEST</td><td>LIGHTER</td><td>LIGHT</td><td>NORMAL</td><td>DARK</td><td>DARKER</td><td>DARKEST</td></tr>
//! <tr><td>GRAY</td><td>&nbsp;</td><td title="LIGHTEST_GRAY (223, 223, 223)" style="background-color: rgb(223, 223, 223); --darkreader-inline-bgcolor:#212325;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_GRAY (191, 191, 191)" style="background-color: rgb(191, 191, 191); --darkreader-inline-bgcolor:#2a2d2f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_GRAY (159, 159, 159)" style="background-color: rgb(159, 159, 159); --darkreader-inline-bgcolor:#343739;" data-darkreader-inline-bgcolor=""></td><td title="GRAY (127, 127, 127)" style="background-color: rgb(127, 127, 127); --darkreader-inline-bgcolor:#3d4043;" data-darkreader-inline-bgcolor=""></td><td title="DARK_GRAY (95, 95, 95)" style="background-color: rgb(95, 95, 95); --darkreader-inline-bgcolor:#3d4043;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_GRAY (63, 63, 63)" style="background-color: rgb(63, 63, 63); --darkreader-inline-bgcolor:#3c4042;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_GRAY (31, 31, 31)" style="background-color: rgb(31, 31, 31); --darkreader-inline-bgcolor:#1d1f21;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>SEPIA</td><td>&nbsp;</td><td title="LIGHTEST_SEPIA (222, 211, 195)" style="background-color: rgb(222, 211, 195); --darkreader-inline-bgcolor:#382e1f;" data-darkreader-inline-bgcolor=""></td><td title="LIGHTER_SEPIA (191, 171, 143)" style="background-color: rgb(191, 171, 143); --darkreader-inline-bgcolor:#4b3e2b;" data-darkreader-inline-bgcolor=""></td><td title="LIGHT_SEPIA (158, 134, 100)" style="background-color: rgb(158, 134, 100); --darkreader-inline-bgcolor:#594b37;" data-darkreader-inline-bgcolor=""></td><td title="SEPIA (127, 101, 63)" style="background-color: rgb(127, 101, 63); --darkreader-inline-bgcolor:#665133;" data-darkreader-inline-bgcolor=""></td><td title="DARK_SEPIA (94, 75, 47)" style="background-color: rgb(94, 75, 47); --darkreader-inline-bgcolor:#5e4b2f;" data-darkreader-inline-bgcolor=""></td><td title="DARKER_SEPIA (63, 50, 31)" style="background-color: rgb(63, 50, 31); --darkreader-inline-bgcolor:#3f321f;" data-darkreader-inline-bgcolor=""></td><td title="DARKEST_SEPIA (31, 24, 15)" style="background-color: rgb(31, 24, 15); --darkreader-inline-bgcolor:#1f180f;" data-darkreader-inline-bgcolor=""></td></tr><tr><td></td><th colspan="8">BLACK AND WHITE</th></tr>
//! <tr><td>BLACK</td><td title="BLACK (0, 0, 0)" style="background-color: rgb(0, 0, 0); --darkreader-inline-bgcolor:#000000;" data-darkreader-inline-bgcolor=""></td></tr>
//! <tr><td>WHITE</td><td title="WHITE (255, 255, 255)" style="background-color: rgb(255, 255, 255); --darkreader-inline-bgcolor:#181a1b;" data-darkreader-inline-bgcolor=""></td></tr>
//! </tbody></table>

use crate::util::FloorRem;
use std::ops::{Add, Mul, Sub};

pub use Color as Colour;

/// A struct representing a 24-bit RGB color with alpha
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
#[cfg_attr(
    feature = "serialization",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub struct Color {
    /// The red component of the color
    pub r: u8,
    /// The green component of the color
    pub g: u8,
    /// The blue component of the color
    pub b: u8,
    /// The opacity of the color
    pub a: u8,
}

impl Color {
    /// Returns a new Color from RGB values.
    ///
    /// # Parameters
    /// * `r` - The color's amount of red.
    /// * `g` - The color's amount of green.
    /// * `b` - The color's amount of blue.
    ///
    /// # Example
    /// ```
    /// # use doryen_extra::color::Color;
    /// let white = Color::new(255, 255, 255);
    /// ```
    pub const fn new(r: u8, g: u8, b: u8) -> Self {
        Self { r, g, b, a: 255 }
    }
    /// Returns a new Color from RGB values with an opacity.
    ///
    /// # Parameters
    /// * `r` - The color's amount of red.
    /// * `g` - The color's amount of green.
    /// * `b` - The color's amount of blue.
    /// * `a` - The color's opacity.
    ///
    /// # Example
    /// ```
    /// # use doryen_extra::color::Color;
    /// let translucent_white = Color::new_with_alpha(255, 255, 255, 127);
    /// ```
    pub const fn new_with_alpha(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self { r, g, b, a }
    }

    /// Returns a new Color from HSV values.
    ///
    /// The saturation and value parameters are automatically clamped to 0 and 1.
    ///
    /// Use `set_hsv()` to fill an existing struct with HSV values.
    ///
    /// # Parameters
    /// * `hue` - The color's hue in degrees.
    /// * `saturation` - The color's saturation, from 0 to 1.
    /// * `value` - The color's value, from 0 to 1.
    ///
    /// # Example
    /// ```
    /// # use doryen_extra::color::Color;
    /// let light_blue = Color::new_hsv(240.0, 0.75, 1.0);
    /// ```
    pub fn new_hsv(hue: f32, saturation: f32, value: f32) -> Self {
        let mut color = Self::new(0, 0, 0);
        color.set_hsv(hue, saturation, value);

        color
    }

    /// Returns a new Color from HSV values with the given opacity.
    ///
    /// The saturation, value and opacity parameters are automatically clamped to 0 and 1.
    ///
    /// Use `set_hsv()` to fill an existing struct with HSV values.
    ///
    /// # Parameters
    /// * `hue` - The color's hue in degrees.
    /// * `saturation` - The color's saturation, from 0 to 1.
    /// * `value` - The color's value, from 0 to 1.
    /// * `opacity` - The color's opacity, from 0 to 1.
    ///
    /// # Example
    /// ```
    /// # use doryen_extra::color::Color;
    /// let translucent_light_blue = Color::new_hsv_with_opacity(240.0, 0.75, 1.0, 0.5);
    /// ```
    pub fn new_hsv_with_opacity(hue: f32, saturation: f32, value: f32, opacity: f32) -> Self {
        let a = (opacity.max(0.0).min(1.0) * 255.0).round() as u8;
        let mut color = Self::new_with_alpha(0, 0, 0, a);
        color.set_hsv(hue, saturation, value);

        color
    }

    /// Sets a colors values from HSV values.
    ///
    /// # Parameters
    /// * `hue` - The color's hue in degrees.
    /// * `saturation` - The color's saturation, from 0 to 1.
    /// * `value` - The color's value, from 0 to 1.
    ///
    /// Values outside the given ranges are clipped to fit within the allowed range.
    #[allow(clippy::many_single_char_names)]
    pub fn set_hsv(&mut self, hue: f32, saturation: f32, value: f32) {
        let saturation = saturation.max(0.0).min(1.0);
        let value = value.max(0.0).min(1.0);

        if saturation == 0.0 {
            /* achromatic (gray) */
            let value = (value * 255.0).round() as u8;
            self.r = value;
            self.g = value;
            self.b = value;
            return;
        }

        let mut hue = hue.floor_modulo(360.0);
        hue /= 60.0; /* sector 0 to 5 */
        let hue_section = hue.floor() as i32;
        let hue_fraction = hue - hue_section as f32;

        let p = ((value * (1.0 - saturation)) * 255.0).round() as u8;
        let q = ((value * (1.0 - saturation * hue_fraction)) * 255.0).round() as u8;
        let t = ((value * (1.0 - saturation * (1.0 - hue_fraction))) * 255.0).round() as u8;
        let v = (value * 255.0).round() as u8;

        let (r, g, b) = match hue_section {
            0 => (v, t, p),
            1 => (q, v, p),
            2 => (p, v, t),
            3 => (p, q, v),
            4 => (t, p, v),
            _ => (v, p, q),
        };
        self.r = r;
        self.g = g;
        self.b = b;
    }

    /// Get a tuple of HSV values from a color.
    pub fn get_hsv(self) -> (f32, f32, f32) {
        let hue = self.get_hue();
        let saturation = self.get_saturation();
        let value = self.get_value();

        (hue, saturation, value)
    }

    /// Change a color's hue.
    ///
    /// # Parameters
    /// * `hue` - The color's hue in degrees. Values outside the
    /// given range loop around to fit within the allowed range.
    ///
    /// # Examples
    /// ```rust
    /// # use doryen_extra::color::Color;
    /// # let mut color = Color::CELADON;
    /// // Sets the hue of the color to 16 degrees.
    /// color.set_hue(16.);
    /// # assert!((color.get_hue() - 16.).abs() < 1.);
    /// ```
    ///
    /// ```rust
    /// # use doryen_extra::color::Color;
    /// # let mut color = Color::CELADON;
    /// // Values outside the range of 0-360 will be clipped:
    /// color.set_hue(420.);
    ///
    /// // The hue is actually set to 60 degrees.
    /// let hue = color.get_hue();
    /// assert!((hue - 60.).abs() < 1.);
    ///
    /// color.set_hue(-90.);
    ///
    /// // The hue is actually set to 270 degrees.
    /// let hue = color.get_hue();
    /// assert!((hue - 270.).abs() < 1.);
    /// ```
    pub fn set_hue(&mut self, hue: f32) {
        let saturation = self.get_saturation();
        let value = self.get_value();

        self.set_hsv(hue, saturation, value);
    }

    /// Return a color's hue in degrees. See [`set_hue`] for examples.
    ///
    /// [`set_hue`]: #method.set_hue
    pub fn get_hue(self) -> f32 {
        let max = self.r.max(self.g).max(self.b);
        let min = self.r.min(self.g).min(self.b);
        let delta = f32::from(max) - f32::from(min);
        if delta == 0.0 {
            return 0.0;
        }

        let mut hue = if self.r == max {
            (f32::from(self.g) - f32::from(self.b)) / delta
        } else if self.g == max {
            2.0 + (f32::from(self.b) - f32::from(self.r)) / delta
        } else {
            4.0 + (f32::from(self.r) - f32::from(self.g)) / delta
        };
        hue *= 60.0;
        hue.floor_modulo(360.0)
    }

    /// Returns a color's saturation in the range \[0, 1\]. See [`set_saturation`] for examples.
    ///
    /// [`set_saturation`]: #method.set_saturation
    pub fn get_saturation(self) -> f32 {
        let max = self.r.max(self.g).max(self.b);
        let min = self.r.min(self.g).min(self.b);
        let delta = f32::from(max) - f32::from(min);
        if max == 0 {
            0.0
        } else {
            delta / f32::from(max)
        }
    }

    /// Change a color's saturation.
    ///
    /// # Parameters
    /// * `saturation` - The color's saturation, from 0 to 1. Values outside the
    /// given range are clipped to fit within the allowed range.
    ///
    /// # Examples
    /// ```rust
    /// # use doryen_extra::color::Color;
    /// # let mut color = Color::CELADON;
    /// // Sets the saturation of the color to 0.75.
    /// color.set_saturation(0.75);
    /// # assert!((color.get_saturation() - 0.75).abs() < 0.001);
    /// ```
    ///
    /// ```rust
    /// # use doryen_extra::color::Color;
    /// # let mut color = Color::CELADON;
    /// // Values outside the range of 0-1 will be clipped:
    /// color.set_saturation(2.);
    ///
    /// // The saturation is actually set to 1.
    /// let saturation = color.get_saturation();
    /// assert!((saturation - 1.).abs() < 0.001);
    ///
    /// color.set_saturation(-2.);
    ///
    /// // The saturation is actually set to 1.
    /// let saturation = color.get_saturation();
    /// assert!((saturation - 0.).abs() < 0.001);
    /// ```
    pub fn set_saturation(&mut self, saturation: f32) {
        let hue = self.get_hue();
        let value = self.get_value();

        self.set_hsv(hue, saturation, value);
    }

    /// Returns a color's value in the range \[0, 1\].
    pub fn get_value(self) -> f32 {
        f32::from(self.r.max(self.g).max(self.b)) / 255.0
    }

    /// Change a color's value.
    ///
    /// # Parameters
    /// * `value` - The color's value, from 0 to 1.
    ///
    /// # Examples
    /// ```rust
    /// # use doryen_extra::color::Color;
    /// # let mut color = Color::CELADON;
    /// // Sets the value of the color to 0.25.
    /// color.set_value(0.25);
    /// # assert!((color.get_value() - 0.25).abs() < 0.001);
    /// ```
    ///
    /// ```rust
    /// # use doryen_extra::color::Color;
    /// # let mut color = Color::CELADON;
    /// // Values outside the range of 0-1 will be clipped:
    /// color.set_value(2.);
    ///
    /// // The saturation is actually set to 1.
    /// let value = color.get_value();
    /// assert!((value - 1.).abs() < 0.001);
    ///
    /// color.set_value(-2.);
    ///
    /// // The saturation is actually set to 0.
    /// let value = color.get_value();
    /// assert!((value - 0.).abs() < 0.001);
    /// ```
    pub fn set_value(&mut self, value: f32) {
        let hue = self.get_hue();
        let saturation = self.get_saturation();

        self.set_hsv(hue, saturation, value);
    }

    /// Shift a color's hue by an amount.
    ///
    /// # Parameters
    /// * `hue_shift` - The distance to shift the hue, in degrees.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use doryen_extra::color::Color;
    /// # let mut color = Color::CELADON;
    /// // Shift the hue by 10 degrees
    /// color.shift_hue(10.);
    /// # assert!(color.get_hue() - (Color::CELADON.get_hue() + 10.).abs() < 1.);
    ///
    /// // Shift the hue back to what it was originally
    /// color.shift_hue(-10.);
    /// # assert!((color.get_hue() - Color::CELADON.get_hue()).abs() < 1.);
    /// ```
    pub fn shift_hue(&mut self, hue_shift: f32) {
        if hue_shift == 0.0 {
            return;
        }
        self.set_hsv(
            self.get_hue() + hue_shift,
            self.get_saturation(),
            self.get_value(),
        );
    }

    /// Scale a color's saturation and value.
    ///
    /// # Parameters
    /// * `saturation_coefficient` - Multiplier for this color's saturation.
    /// * `value_coefficient` - Multiplier for this color's value.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use doryen_extra::color::Color;
    ///
    /// // Let's make a color with a saturation of 0.5 and a value of 0.75
    /// let mut color = Color::new_hsv(10., 0.5, 0.75);
    ///
    /// // Scale them by 1/2 and 1/3, respectively
    /// color.scale_hsv(0.5, 1. / 3.);
    ///
    /// // They should both be 0.25 now
    /// assert!((color.get_saturation() - 0.25).abs() < 0.001);
    /// assert!((color.get_value() - 0.25).abs() < 0.001);
    /// ```
    pub fn scale_hsv(&mut self, saturation_coefficient: f32, value_coefficient: f32) {
        if (saturation_coefficient - 1.0).abs() < 0.001 && (value_coefficient - 1.0).abs() < 0.001 {
            return;
        }
        self.set_hsv(
            self.get_hue(),
            self.get_saturation() * saturation_coefficient,
            self.get_value() * value_coefficient,
        );
    }

    /// Generates an interpolated gradient of colors using RGB interpolation.
    ///
    /// Using RGB interpolation between colors is almost always the wrong choice and tends to
    /// produce really ugly results. You almost certainly don't want to use this; use
    /// `generate_gradient_hsv()` instead.
    ///
    /// # Parameters
    /// * `key_colors` -  The colors to make gradients between.
    /// * `gradient_spans` -  How many interpolated colors to generate between each
    /// pair of key colors.
    ///
    /// # Panics
    /// * If `gradient_spans`' length isn't one less than `key_colors`' length.
    ///
    /// # Example
    /// ```
    /// # use doryen_extra::color::Color;
    /// // Generates no colors at all
    /// let none = Color::generate_gradient_rgb(&[], &[]);
    ///
    /// assert!(none.is_empty());
    /// ```
    ///
    /// ```
    /// # use doryen_extra::color::Color;
    /// // Generates only the given color
    /// let one = Color::generate_gradient_rgb(&[Color::WHITE], &[]);
    ///
    /// assert_eq!(one.len(), 1);
    /// assert_eq!(one[0], Color::WHITE);
    /// ```
    ///
    ///
    /// ```
    /// # use doryen_extra::color::Color;
    /// // Generates every grayscale color between black and white
    /// let grayscale = Color::generate_gradient_rgb(&[Color::BLACK, Color::WHITE], &[254]);
    ///
    /// assert_eq!(grayscale.len(), 256);
    /// # for (i, color) in grayscale.iter().enumerate() {
    /// #     assert_eq!(color.r, i as u8);
    /// #     assert_eq!(color.g, i as u8);
    /// #     assert_eq!(color.b, i as u8);
    /// # }
    /// ```
    pub fn generate_gradient_rgb(key_colors: &[Self], gradient_spans: &[usize]) -> Vec<Self> {
        if key_colors.is_empty() {
            return vec![];
        }

        assert_eq!(
            key_colors.len() - 1,
            gradient_spans.len(),
            "gradient_spans should have one fewer values in it than key_colors"
        );

        let mut result =
            Vec::with_capacity(key_colors.len() + gradient_spans.iter().sum::<usize>());
        for (span, colors) in key_colors.windows(2).enumerate() {
            let start_color = colors[0];
            let end_color = colors[1];
            for s in 0..=gradient_spans[span] {
                let coefficient = s as f32 / (gradient_spans[span] + 1) as f32;
                result.push(start_color.lerp_rgb(end_color, coefficient));
            }
        }
        result.push(*key_colors.last().unwrap());

        result
    }

    /// Generates an interpolated gradient of colors using HSV interpolation.
    ///
    /// # Parameters
    /// * `key_colors` -  The colors to make gradients between.
    /// * `gradient_spans` -  How many interpolated colors to generate between each
    /// pair of key colors.
    ///
    /// # Panics
    /// * If `gradient_spans`' length isn't one less than `key_colors`' length.
    ///
    /// # Examples
    /// ```
    /// # use doryen_extra::color::Color;
    /// // Generates no colors at all
    /// let none = Color::generate_gradient_hsv(&[], &[]);
    ///
    /// assert!(none.is_empty());
    /// ```
    ///
    /// ```
    /// # use doryen_extra::color::Color;
    /// // Generates only the given color
    /// let one = Color::generate_gradient_hsv(&[Color::WHITE], &[]);
    ///
    /// assert_eq!(one.len(), 1);
    /// assert_eq!(one[0], Color::WHITE);
    /// ```
    ///
    /// ```
    /// # use doryen_extra::color::Color;
    /// // Generates every grayscale color between black and white
    /// let grayscale = Color::generate_gradient_hsv(&[Color::BLACK, Color::WHITE], &[254]);
    /// ```
    pub fn generate_gradient_hsv(key_colors: &[Self], gradient_spans: &[usize]) -> Vec<Self> {
        if key_colors.is_empty() {
            return vec![];
        }

        assert_eq!(
            key_colors.len() - 1,
            gradient_spans.len(),
            "gradient_spans should have one fewer values in it than key_colors"
        );

        let mut result =
            Vec::with_capacity(key_colors.len() + gradient_spans.iter().sum::<usize>());
        for (span, colors) in key_colors.windows(2).enumerate() {
            let start_color = colors[0];
            let end_color = colors[1];
            for s in 0..=gradient_spans[span] {
                let coefficient = s as f32 / (gradient_spans[span] + 1) as f32;
                result.push(start_color.lerp_hsv(end_color, coefficient));
            }
        }
        result.push(*key_colors.last().unwrap());

        result
    }

    /// Interpolate two colors together using their RGB representation and return the result.
    ///
    /// You almost certainly don't want to use this; use `lerp_hsv()` instead.
    ///
    /// # Parameters
    /// * `other` - The second color.
    /// * `coefficient` - The coefficient. 0 for entirely the first color, 1 for entirely the second.
    ///
    /// # Panics
    ///
    /// If `coefficient` is outside the range \[0, 1\].
    pub fn lerp_rgb(self, other: Self, coefficient: f32) -> Self {
        assert!(
            coefficient >= 0.0 && coefficient <= 1.0,
            "coefficient is outside the acceptable range [0, 1]"
        );

        Self::new_with_alpha(
            (f32::from(self.r) + (f32::from(other.r) - f32::from(self.r)) * coefficient) as u8,
            (f32::from(self.g) + (f32::from(other.g) - f32::from(self.g)) * coefficient) as u8,
            (f32::from(self.b) + (f32::from(other.b) - f32::from(self.b)) * coefficient) as u8,
            (f32::from(self.a) + (f32::from(other.a) - f32::from(self.a)) * coefficient) as u8,
        )
    }

    /// Interpolate two colors together using their HSV representation and return the result.
    ///
    /// # Parameters
    /// * `other` - The second color.
    /// * `coefficient` - The coefficient. 0 for entirely the first color, 1 for entirely the second.
    ///
    /// # Panics
    ///
    /// If `coefficient` is outside the range \[0, 1\].
    pub fn lerp_hsv(self, other: Self, coefficient: f32) -> Self {
        assert!(
            coefficient >= 0.0 && coefficient <= 1.0,
            "coefficient is outside the acceptable range [0, 1]"
        );
        let (self_hue, self_saturation, self_value) = self.get_hsv();
        let (other_hue, other_saturation, other_value) = other.get_hsv();

        let hue_diff = other_hue - self_hue;
        let hue_delta = hue_diff
            + if hue_diff.abs() > 180.0 {
                if hue_diff < 0.0 {
                    360.0
                } else {
                    -360.0
                }
            } else {
                0.0
            };

        let hue_interpolated = self_hue + coefficient * hue_delta;

        let opacity_interpolated =
            (f32::from(self.a) + (f32::from(other.a) - f32::from(self.a)) * coefficient) / 255.0;

        Self::new_hsv_with_opacity(
            hue_interpolated,
            self_saturation + (other_saturation - self_saturation) * coefficient,
            self_value + (other_value - self_value) * coefficient,
            opacity_interpolated,
        )
    }
}

// Enums-to-color
impl Color {
    /// Takes a `Name` and `Level` value and returns the corresponding color constant.
    #[allow(clippy::too_many_lines)]
    pub fn by_name_and_level(name: Name, level: Level) -> Self {
        match name {
            Name::Red => match level {
                Level::Desaturated => Self::DESATURATED_RED,
                Level::Lightest => Self::LIGHTEST_RED,
                Level::Lighter => Self::LIGHTER_RED,
                Level::Light => Self::LIGHT_RED,
                Level::Normal => Self::RED,
                Level::Dark => Self::DARK_RED,
                Level::Darker => Self::DARKER_RED,
                Level::Darkest => Self::DARKEST_RED,
            },
            Name::Flame => match level {
                Level::Desaturated => Self::DESATURATED_FLAME,
                Level::Lightest => Self::LIGHTEST_FLAME,
                Level::Lighter => Self::LIGHTER_FLAME,
                Level::Light => Self::LIGHT_FLAME,
                Level::Normal => Self::FLAME,
                Level::Dark => Self::DARK_FLAME,
                Level::Darker => Self::DARKER_FLAME,
                Level::Darkest => Self::DARKEST_FLAME,
            },
            Name::Orange => match level {
                Level::Desaturated => Self::DESATURATED_ORANGE,
                Level::Lightest => Self::LIGHTEST_ORANGE,
                Level::Lighter => Self::LIGHTER_ORANGE,
                Level::Light => Self::LIGHT_ORANGE,
                Level::Normal => Self::ORANGE,
                Level::Dark => Self::DARK_ORANGE,
                Level::Darker => Self::DARKER_ORANGE,
                Level::Darkest => Self::DARKEST_ORANGE,
            },
            Name::Amber => match level {
                Level::Desaturated => Self::DESATURATED_AMBER,
                Level::Lightest => Self::LIGHTEST_AMBER,
                Level::Lighter => Self::LIGHTER_AMBER,
                Level::Light => Self::LIGHT_AMBER,
                Level::Normal => Self::AMBER,
                Level::Dark => Self::DARK_AMBER,
                Level::Darker => Self::DARKER_AMBER,
                Level::Darkest => Self::DARKEST_AMBER,
            },
            Name::Yellow => match level {
                Level::Desaturated => Self::DESATURATED_YELLOW,
                Level::Lightest => Self::LIGHTEST_YELLOW,
                Level::Lighter => Self::LIGHTER_YELLOW,
                Level::Light => Self::LIGHT_YELLOW,
                Level::Normal => Self::YELLOW,
                Level::Dark => Self::DARK_YELLOW,
                Level::Darker => Self::DARKER_YELLOW,
                Level::Darkest => Self::DARKEST_YELLOW,
            },
            Name::Lime => match level {
                Level::Desaturated => Self::DESATURATED_LIME,
                Level::Lightest => Self::LIGHTEST_LIME,
                Level::Lighter => Self::LIGHTER_LIME,
                Level::Light => Self::LIGHT_LIME,
                Level::Normal => Self::LIME,
                Level::Dark => Self::DARK_LIME,
                Level::Darker => Self::DARKER_LIME,
                Level::Darkest => Self::DARKEST_LIME,
            },
            Name::Chartreuse => match level {
                Level::Desaturated => Self::DESATURATED_CHARTREUSE,
                Level::Lightest => Self::LIGHTEST_CHARTREUSE,
                Level::Lighter => Self::LIGHTER_CHARTREUSE,
                Level::Light => Self::LIGHT_CHARTREUSE,
                Level::Normal => Self::CHARTREUSE,
                Level::Dark => Self::DARK_CHARTREUSE,
                Level::Darker => Self::DARKER_CHARTREUSE,
                Level::Darkest => Self::DARKEST_CHARTREUSE,
            },
            Name::Green => match level {
                Level::Desaturated => Self::DESATURATED_GREEN,
                Level::Lightest => Self::LIGHTEST_GREEN,
                Level::Lighter => Self::LIGHTER_GREEN,
                Level::Light => Self::LIGHT_GREEN,
                Level::Normal => Self::GREEN,
                Level::Dark => Self::DARK_GREEN,
                Level::Darker => Self::DARKER_GREEN,
                Level::Darkest => Self::DARKEST_GREEN,
            },
            Name::Sea => match level {
                Level::Desaturated => Self::DESATURATED_SEA,
                Level::Lightest => Self::LIGHTEST_SEA,
                Level::Lighter => Self::LIGHTER_SEA,
                Level::Light => Self::LIGHT_SEA,
                Level::Normal => Self::SEA,
                Level::Dark => Self::DARK_SEA,
                Level::Darker => Self::DARKER_SEA,
                Level::Darkest => Self::DARKEST_SEA,
            },
            Name::Turquoise => match level {
                Level::Desaturated => Self::DESATURATED_TURQUOISE,
                Level::Lightest => Self::LIGHTEST_TURQUOISE,
                Level::Lighter => Self::LIGHTER_TURQUOISE,
                Level::Light => Self::LIGHT_TURQUOISE,
                Level::Normal => Self::TURQUOISE,
                Level::Dark => Self::DARK_TURQUOISE,
                Level::Darker => Self::DARKER_TURQUOISE,
                Level::Darkest => Self::DARKEST_TURQUOISE,
            },
            Name::Cyan => match level {
                Level::Desaturated => Self::DESATURATED_CYAN,
                Level::Lightest => Self::LIGHTEST_CYAN,
                Level::Lighter => Self::LIGHTER_CYAN,
                Level::Light => Self::LIGHT_CYAN,
                Level::Normal => Self::CYAN,
                Level::Dark => Self::DARK_CYAN,
                Level::Darker => Self::DARKER_CYAN,
                Level::Darkest => Self::DARKEST_CYAN,
            },
            Name::Sky => match level {
                Level::Desaturated => Self::DESATURATED_SKY,
                Level::Lightest => Self::LIGHTEST_SKY,
                Level::Lighter => Self::LIGHTER_SKY,
                Level::Light => Self::LIGHT_SKY,
                Level::Normal => Self::SKY,
                Level::Dark => Self::DARK_SKY,
                Level::Darker => Self::DARKER_SKY,
                Level::Darkest => Self::DARKEST_SKY,
            },
            Name::Azure => match level {
                Level::Desaturated => Self::DESATURATED_AZURE,
                Level::Lightest => Self::LIGHTEST_AZURE,
                Level::Lighter => Self::LIGHTER_AZURE,
                Level::Light => Self::LIGHT_AZURE,
                Level::Normal => Self::AZURE,
                Level::Dark => Self::DARK_AZURE,
                Level::Darker => Self::DARKER_AZURE,
                Level::Darkest => Self::DARKEST_AZURE,
            },
            Name::Blue => match level {
                Level::Desaturated => Self::DESATURATED_BLUE,
                Level::Lightest => Self::LIGHTEST_BLUE,
                Level::Lighter => Self::LIGHTER_BLUE,
                Level::Light => Self::LIGHT_BLUE,
                Level::Normal => Self::BLUE,
                Level::Dark => Self::DARK_BLUE,
                Level::Darker => Self::DARKER_BLUE,
                Level::Darkest => Self::DARKEST_BLUE,
            },
            Name::Han => match level {
                Level::Desaturated => Self::DESATURATED_HAN,
                Level::Lightest => Self::LIGHTEST_HAN,
                Level::Lighter => Self::LIGHTER_HAN,
                Level::Light => Self::LIGHT_HAN,
                Level::Normal => Self::HAN,
                Level::Dark => Self::DARK_HAN,
                Level::Darker => Self::DARKER_HAN,
                Level::Darkest => Self::DARKEST_HAN,
            },
            Name::Violet => match level {
                Level::Desaturated => Self::DESATURATED_VIOLET,
                Level::Lightest => Self::LIGHTEST_VIOLET,
                Level::Lighter => Self::LIGHTER_VIOLET,
                Level::Light => Self::LIGHT_VIOLET,
                Level::Normal => Self::VIOLET,
                Level::Dark => Self::DARK_VIOLET,
                Level::Darker => Self::DARKER_VIOLET,
                Level::Darkest => Self::DARKEST_VIOLET,
            },
            Name::Purple => match level {
                Level::Desaturated => Self::DESATURATED_PURPLE,
                Level::Lightest => Self::LIGHTEST_PURPLE,
                Level::Lighter => Self::LIGHTER_PURPLE,
                Level::Light => Self::LIGHT_PURPLE,
                Level::Normal => Self::PURPLE,
                Level::Dark => Self::DARK_PURPLE,
                Level::Darker => Self::DARKER_PURPLE,
                Level::Darkest => Self::DARKEST_PURPLE,
            },
            Name::Fuchsia => match level {
                Level::Desaturated => Self::DESATURATED_FUCHSIA,
                Level::Lightest => Self::LIGHTEST_FUCHSIA,
                Level::Lighter => Self::LIGHTER_FUCHSIA,
                Level::Light => Self::LIGHT_FUCHSIA,
                Level::Normal => Self::FUCHSIA,
                Level::Dark => Self::DARK_FUCHSIA,
                Level::Darker => Self::DARKER_FUCHSIA,
                Level::Darkest => Self::DARKEST_FUCHSIA,
            },
            Name::Magenta => match level {
                Level::Desaturated => Self::DESATURATED_MAGENTA,
                Level::Lightest => Self::LIGHTEST_MAGENTA,
                Level::Lighter => Self::LIGHTER_MAGENTA,
                Level::Light => Self::LIGHT_MAGENTA,
                Level::Normal => Self::MAGENTA,
                Level::Dark => Self::DARK_MAGENTA,
                Level::Darker => Self::DARKER_MAGENTA,
                Level::Darkest => Self::DARKEST_MAGENTA,
            },
            Name::Pink => match level {
                Level::Desaturated => Self::DESATURATED_PINK,
                Level::Lightest => Self::LIGHTEST_PINK,
                Level::Lighter => Self::LIGHTER_PINK,
                Level::Light => Self::LIGHT_PINK,
                Level::Normal => Self::PINK,
                Level::Dark => Self::DARK_PINK,
                Level::Darker => Self::DARKER_PINK,
                Level::Darkest => Self::DARKEST_PINK,
            },
            Name::Crimson => match level {
                Level::Desaturated => Self::DESATURATED_CRIMSON,
                Level::Lightest => Self::LIGHTEST_CRIMSON,
                Level::Lighter => Self::LIGHTER_CRIMSON,
                Level::Light => Self::LIGHT_CRIMSON,
                Level::Normal => Self::CRIMSON,
                Level::Dark => Self::DARK_CRIMSON,
                Level::Darker => Self::DARKER_CRIMSON,
                Level::Darkest => Self::DARKEST_CRIMSON,
            },
        }
    }
}

// Constants
#[allow(missing_docs)]
impl Color {
    /* color values */
    pub const BLACK: Self = Self::new(0, 0, 0);
    pub const DARKEST_GRAY: Self = Self::new(31, 31, 31);
    pub const DARKER_GRAY: Self = Self::new(63, 63, 63);
    pub const DARK_GRAY: Self = Self::new(95, 95, 95);
    pub const GRAY: Self = Self::new(127, 127, 127);
    pub const LIGHT_GRAY: Self = Self::new(159, 159, 159);
    pub const LIGHTER_GRAY: Self = Self::new(191, 191, 191);
    pub const LIGHTEST_GRAY: Self = Self::new(223, 223, 223);
    pub const DARKEST_GREY: Self = Self::DARKEST_GRAY;
    pub const DARKER_GREY: Self = Self::DARKER_GRAY;
    pub const DARK_GREY: Self = Self::DARK_GRAY;
    pub const GREY: Self = Self::GRAY;
    pub const LIGHT_GREY: Self = Self::LIGHT_GRAY;
    pub const LIGHTER_GREY: Self = Self::LIGHTER_GRAY;
    pub const LIGHTEST_GREY: Self = Self::LIGHTEST_GRAY;
    pub const WHITE: Self = Self::new(255, 255, 255);

    pub const DARKEST_SEPIA: Self = Self::new(31, 24, 15);
    pub const DARKER_SEPIA: Self = Self::new(63, 50, 31);
    pub const DARK_SEPIA: Self = Self::new(94, 75, 47);
    pub const SEPIA: Self = Self::new(127, 101, 63);
    pub const LIGHT_SEPIA: Self = Self::new(158, 134, 100);
    pub const LIGHTER_SEPIA: Self = Self::new(191, 171, 143);
    pub const LIGHTEST_SEPIA: Self = Self::new(222, 211, 195);

    /* desaturated */
    pub const DESATURATED_RED: Self = Self::new(127, 63, 63);
    pub const DESATURATED_FLAME: Self = Self::new(127, 79, 63);
    pub const DESATURATED_ORANGE: Self = Self::new(127, 95, 63);
    pub const DESATURATED_AMBER: Self = Self::new(127, 111, 63);
    pub const DESATURATED_YELLOW: Self = Self::new(127, 127, 63);
    pub const DESATURATED_LIME: Self = Self::new(111, 127, 63);
    pub const DESATURATED_CHARTREUSE: Self = Self::new(95, 127, 63);
    pub const DESATURATED_GREEN: Self = Self::new(63, 127, 63);
    pub const DESATURATED_SEA: Self = Self::new(63, 127, 95);
    pub const DESATURATED_TURQUOISE: Self = Self::new(63, 127, 111);
    pub const DESATURATED_CYAN: Self = Self::new(63, 127, 127);
    pub const DESATURATED_SKY: Self = Self::new(63, 111, 127);
    pub const DESATURATED_AZURE: Self = Self::new(63, 95, 127);
    pub const DESATURATED_BLUE: Self = Self::new(63, 63, 127);
    pub const DESATURATED_HAN: Self = Self::new(79, 63, 127);
    pub const DESATURATED_VIOLET: Self = Self::new(95, 63, 127);
    pub const DESATURATED_PURPLE: Self = Self::new(111, 63, 127);
    pub const DESATURATED_FUCHSIA: Self = Self::new(127, 63, 127);
    pub const DESATURATED_MAGENTA: Self = Self::new(127, 63, 111);
    pub const DESATURATED_PINK: Self = Self::new(127, 63, 95);
    pub const DESATURATED_CRIMSON: Self = Self::new(127, 63, 79);

    /* lightest */
    pub const LIGHTEST_RED: Self = Self::new(255, 191, 191);
    pub const LIGHTEST_FLAME: Self = Self::new(255, 207, 191);
    pub const LIGHTEST_ORANGE: Self = Self::new(255, 223, 191);
    pub const LIGHTEST_AMBER: Self = Self::new(255, 239, 191);
    pub const LIGHTEST_YELLOW: Self = Self::new(255, 255, 191);
    pub const LIGHTEST_LIME: Self = Self::new(239, 255, 191);
    pub const LIGHTEST_CHARTREUSE: Self = Self::new(223, 255, 191);
    pub const LIGHTEST_GREEN: Self = Self::new(191, 255, 191);
    pub const LIGHTEST_SEA: Self = Self::new(191, 255, 223);
    pub const LIGHTEST_TURQUOISE: Self = Self::new(191, 255, 239);
    pub const LIGHTEST_CYAN: Self = Self::new(191, 255, 255);
    pub const LIGHTEST_SKY: Self = Self::new(191, 239, 255);
    pub const LIGHTEST_AZURE: Self = Self::new(191, 223, 255);
    pub const LIGHTEST_BLUE: Self = Self::new(191, 191, 255);
    pub const LIGHTEST_HAN: Self = Self::new(207, 191, 255);
    pub const LIGHTEST_VIOLET: Self = Self::new(223, 191, 255);
    pub const LIGHTEST_PURPLE: Self = Self::new(239, 191, 255);
    pub const LIGHTEST_FUCHSIA: Self = Self::new(255, 191, 255);
    pub const LIGHTEST_MAGENTA: Self = Self::new(255, 191, 239);
    pub const LIGHTEST_PINK: Self = Self::new(255, 191, 223);
    pub const LIGHTEST_CRIMSON: Self = Self::new(255, 191, 207);

    /* lighter */
    pub const LIGHTER_RED: Self = Self::new(255, 127, 127);
    pub const LIGHTER_FLAME: Self = Self::new(255, 159, 127);
    pub const LIGHTER_ORANGE: Self = Self::new(255, 191, 127);
    pub const LIGHTER_AMBER: Self = Self::new(255, 223, 127);
    pub const LIGHTER_YELLOW: Self = Self::new(255, 255, 127);
    pub const LIGHTER_LIME: Self = Self::new(223, 255, 127);
    pub const LIGHTER_CHARTREUSE: Self = Self::new(191, 255, 127);
    pub const LIGHTER_GREEN: Self = Self::new(127, 255, 127);
    pub const LIGHTER_SEA: Self = Self::new(127, 255, 191);
    pub const LIGHTER_TURQUOISE: Self = Self::new(127, 255, 223);
    pub const LIGHTER_CYAN: Self = Self::new(127, 255, 255);
    pub const LIGHTER_SKY: Self = Self::new(127, 223, 255);
    pub const LIGHTER_AZURE: Self = Self::new(127, 191, 255);
    pub const LIGHTER_BLUE: Self = Self::new(127, 127, 255);
    pub const LIGHTER_HAN: Self = Self::new(159, 127, 255);
    pub const LIGHTER_VIOLET: Self = Self::new(191, 127, 255);
    pub const LIGHTER_PURPLE: Self = Self::new(223, 127, 255);
    pub const LIGHTER_FUCHSIA: Self = Self::new(255, 127, 255);
    pub const LIGHTER_MAGENTA: Self = Self::new(255, 127, 223);
    pub const LIGHTER_PINK: Self = Self::new(255, 127, 191);
    pub const LIGHTER_CRIMSON: Self = Self::new(255, 127, 159);

    /* light */
    pub const LIGHT_RED: Self = Self::new(255, 63, 63);
    pub const LIGHT_FLAME: Self = Self::new(255, 111, 63);
    pub const LIGHT_ORANGE: Self = Self::new(255, 159, 63);
    pub const LIGHT_AMBER: Self = Self::new(255, 207, 63);
    pub const LIGHT_YELLOW: Self = Self::new(255, 255, 63);
    pub const LIGHT_LIME: Self = Self::new(207, 255, 63);
    pub const LIGHT_CHARTREUSE: Self = Self::new(159, 255, 63);
    pub const LIGHT_GREEN: Self = Self::new(63, 255, 63);
    pub const LIGHT_SEA: Self = Self::new(63, 255, 159);
    pub const LIGHT_TURQUOISE: Self = Self::new(63, 255, 207);
    pub const LIGHT_CYAN: Self = Self::new(63, 255, 255);
    pub const LIGHT_SKY: Self = Self::new(63, 207, 255);
    pub const LIGHT_AZURE: Self = Self::new(63, 159, 255);
    pub const LIGHT_BLUE: Self = Self::new(63, 63, 255);
    pub const LIGHT_HAN: Self = Self::new(111, 63, 255);
    pub const LIGHT_VIOLET: Self = Self::new(159, 63, 255);
    pub const LIGHT_PURPLE: Self = Self::new(207, 63, 255);
    pub const LIGHT_FUCHSIA: Self = Self::new(255, 63, 255);
    pub const LIGHT_MAGENTA: Self = Self::new(255, 63, 207);
    pub const LIGHT_PINK: Self = Self::new(255, 63, 159);
    pub const LIGHT_CRIMSON: Self = Self::new(255, 63, 111);

    /* normal */
    pub const RED: Self = Self::new(255, 0, 0);
    pub const FLAME: Self = Self::new(255, 63, 0);
    pub const ORANGE: Self = Self::new(255, 127, 0);
    pub const AMBER: Self = Self::new(255, 191, 0);
    pub const YELLOW: Self = Self::new(255, 255, 0);
    pub const LIME: Self = Self::new(191, 255, 0);
    pub const CHARTREUSE: Self = Self::new(127, 255, 0);
    pub const GREEN: Self = Self::new(0, 255, 0);
    pub const SEA: Self = Self::new(0, 255, 127);
    pub const TURQUOISE: Self = Self::new(0, 255, 191);
    pub const CYAN: Self = Self::new(0, 255, 255);
    pub const SKY: Self = Self::new(0, 191, 255);
    pub const AZURE: Self = Self::new(0, 127, 255);
    pub const BLUE: Self = Self::new(0, 0, 255);
    pub const HAN: Self = Self::new(63, 0, 255);
    pub const VIOLET: Self = Self::new(127, 0, 255);
    pub const PURPLE: Self = Self::new(191, 0, 255);
    pub const FUCHSIA: Self = Self::new(255, 0, 255);
    pub const MAGENTA: Self = Self::new(255, 0, 191);
    pub const PINK: Self = Self::new(255, 0, 127);
    pub const CRIMSON: Self = Self::new(255, 0, 63);

    /* dark */
    pub const DARK_RED: Self = Self::new(191, 0, 0);
    pub const DARK_FLAME: Self = Self::new(191, 47, 0);
    pub const DARK_ORANGE: Self = Self::new(191, 95, 0);
    pub const DARK_AMBER: Self = Self::new(191, 143, 0);
    pub const DARK_YELLOW: Self = Self::new(191, 191, 0);
    pub const DARK_LIME: Self = Self::new(143, 191, 0);
    pub const DARK_CHARTREUSE: Self = Self::new(95, 191, 0);
    pub const DARK_GREEN: Self = Self::new(0, 191, 0);
    pub const DARK_SEA: Self = Self::new(0, 191, 95);
    pub const DARK_TURQUOISE: Self = Self::new(0, 191, 143);
    pub const DARK_CYAN: Self = Self::new(0, 191, 191);
    pub const DARK_SKY: Self = Self::new(0, 143, 191);
    pub const DARK_AZURE: Self = Self::new(0, 95, 191);
    pub const DARK_BLUE: Self = Self::new(0, 0, 191);
    pub const DARK_HAN: Self = Self::new(47, 0, 191);
    pub const DARK_VIOLET: Self = Self::new(95, 0, 191);
    pub const DARK_PURPLE: Self = Self::new(143, 0, 191);
    pub const DARK_FUCHSIA: Self = Self::new(191, 0, 191);
    pub const DARK_MAGENTA: Self = Self::new(191, 0, 143);
    pub const DARK_PINK: Self = Self::new(191, 0, 95);
    pub const DARK_CRIMSON: Self = Self::new(191, 0, 47);

    /* darker */
    pub const DARKER_RED: Self = Self::new(127, 0, 0);
    pub const DARKER_FLAME: Self = Self::new(127, 31, 0);
    pub const DARKER_ORANGE: Self = Self::new(127, 63, 0);
    pub const DARKER_AMBER: Self = Self::new(127, 95, 0);
    pub const DARKER_YELLOW: Self = Self::new(127, 127, 0);
    pub const DARKER_LIME: Self = Self::new(95, 127, 0);
    pub const DARKER_CHARTREUSE: Self = Self::new(63, 127, 0);
    pub const DARKER_GREEN: Self = Self::new(0, 127, 0);
    pub const DARKER_SEA: Self = Self::new(0, 127, 63);
    pub const DARKER_TURQUOISE: Self = Self::new(0, 127, 95);
    pub const DARKER_CYAN: Self = Self::new(0, 127, 127);
    pub const DARKER_SKY: Self = Self::new(0, 95, 127);
    pub const DARKER_AZURE: Self = Self::new(0, 63, 127);
    pub const DARKER_BLUE: Self = Self::new(0, 0, 127);
    pub const DARKER_HAN: Self = Self::new(31, 0, 127);
    pub const DARKER_VIOLET: Self = Self::new(63, 0, 127);
    pub const DARKER_PURPLE: Self = Self::new(95, 0, 127);
    pub const DARKER_FUCHSIA: Self = Self::new(127, 0, 127);
    pub const DARKER_MAGENTA: Self = Self::new(127, 0, 95);
    pub const DARKER_PINK: Self = Self::new(127, 0, 63);
    pub const DARKER_CRIMSON: Self = Self::new(127, 0, 31);

    /* darkest */
    pub const DARKEST_RED: Self = Self::new(63, 0, 0);
    pub const DARKEST_FLAME: Self = Self::new(63, 15, 0);
    pub const DARKEST_ORANGE: Self = Self::new(63, 31, 0);
    pub const DARKEST_AMBER: Self = Self::new(63, 47, 0);
    pub const DARKEST_YELLOW: Self = Self::new(63, 63, 0);
    pub const DARKEST_LIME: Self = Self::new(47, 63, 0);
    pub const DARKEST_CHARTREUSE: Self = Self::new(31, 63, 0);
    pub const DARKEST_GREEN: Self = Self::new(0, 63, 0);
    pub const DARKEST_SEA: Self = Self::new(0, 63, 31);
    pub const DARKEST_TURQUOISE: Self = Self::new(0, 63, 47);
    pub const DARKEST_CYAN: Self = Self::new(0, 63, 63);
    pub const DARKEST_SKY: Self = Self::new(0, 47, 63);
    pub const DARKEST_AZURE: Self = Self::new(0, 31, 63);
    pub const DARKEST_BLUE: Self = Self::new(0, 0, 63);
    pub const DARKEST_HAN: Self = Self::new(15, 0, 63);
    pub const DARKEST_VIOLET: Self = Self::new(31, 0, 63);
    pub const DARKEST_PURPLE: Self = Self::new(47, 0, 63);
    pub const DARKEST_FUCHSIA: Self = Self::new(63, 0, 63);
    pub const DARKEST_MAGENTA: Self = Self::new(63, 0, 47);
    pub const DARKEST_PINK: Self = Self::new(63, 0, 31);
    pub const DARKEST_CRIMSON: Self = Self::new(63, 0, 15);

    /* metallic */
    pub const BRASS: Self = Self::new(191, 151, 96);
    pub const COPPER: Self = Self::new(197, 136, 124);
    pub const GOLD: Self = Self::new(229, 191, 0);
    pub const SILVER: Self = Self::new(203, 203, 203);

    /* miscellaneous */
    pub const CELADON: Self = Self::new(172, 255, 175);
    pub const PEACH: Self = Self::new(255, 159, 127);
}

impl Add for Color {
    type Output = Self;

    /// Add two colors together and return the result.
    fn add(self, rhs: Self) -> Self::Output {
        Self::new_with_alpha(
            self.r.saturating_add(rhs.r),
            self.g.saturating_add(rhs.g),
            self.b.saturating_add(rhs.b),
            self.a.saturating_add(rhs.a),
        )
    }
}

impl Sub for Color {
    type Output = Self;

    /// Subtract the right hand side from the left hand side and return the result.
    fn sub(self, rhs: Self) -> Self::Output {
        Self::new_with_alpha(
            self.r.saturating_sub(rhs.r),
            self.g.saturating_sub(rhs.g),
            self.b.saturating_sub(rhs.b),
            self.a.saturating_sub(rhs.a),
        )
    }
}

impl Mul for Color {
    type Output = Self;

    /// Multiply two colors together and return the result.
    fn mul(self, rhs: Self) -> Self::Output {
        Self::new_with_alpha(
            (f32::from(self.r) * f32::from(rhs.r) / 255.) as u8,
            (f32::from(self.g) * f32::from(rhs.g) / 255.) as u8,
            (f32::from(self.b) * f32::from(rhs.b) / 255.) as u8,
            (f32::from(self.a) * f32::from(rhs.a) / 255.) as u8,
        )
    }
}

impl Mul<f32> for Color {
    type Output = Self;

    /// Multiply a color with a scalar value and return the result.
    fn mul(self, rhs: f32) -> Self::Output {
        Self::new_with_alpha(
            (f32::from(self.r) * rhs).min(255.0).max(0.0) as u8,
            (f32::from(self.g) * rhs).min(255.0).max(0.0) as u8,
            (f32::from(self.b) * rhs).min(255.0).max(0.0) as u8,
            (f32::from(self.a) * rhs).min(255.0).max(0.0) as u8,
        )
    }
}

impl From<Color> for (u8, u8, u8) {
    fn from(c: Color) -> Self {
        (c.r, c.g, c.b)
    }
}

impl From<(u8, u8, u8)> for Color {
    fn from((r, g, b): (u8, u8, u8)) -> Self {
        Self::new(r, g, b)
    }
}

#[cfg(feature = "doryen")]
impl From<Color> for doryen_rs::Color {
    fn from(c: Color) -> Self {
        (c.r, c.g, c.b, c.a)
    }
}

#[cfg(feature = "doryen")]
impl From<doryen_rs::Color> for Color {
    fn from((r, g, b, a): doryen_rs::Color) -> Self {
        Self::new_with_alpha(r, g, b, a)
    }
}

/// Color names
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(
    feature = "serialization",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum Name {
    Red,
    Flame,
    Orange,
    Amber,
    Yellow,
    Lime,
    Chartreuse,
    Green,
    Sea,
    Turquoise,
    Cyan,
    Sky,
    Azure,
    Blue,
    Han,
    Violet,
    Purple,
    Fuchsia,
    Magenta,
    Pink,
    Crimson,
}

/// Color levels
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(
    feature = "serialization",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum Level {
    Desaturated,
    Lightest,
    Lighter,
    Light,
    Normal,
    Dark,
    Darker,
    Darkest,
}

#[cfg(test)]
mod tests {
    use crate::color::Color;

    #[test]
    fn hsv() {
        let red = Color::new_hsv(0., 1., 1.);
        let green = Color::new_hsv(120., 1., 1.);
        let blue = Color::new_hsv(240., 1., 1.);

        assert_eq!(red, Color::new(255, 0, 0));
        assert_eq!(green, Color::new(0, 255, 0));
        assert_eq!(blue, Color::new(0, 0, 255));

        let yellow = Color::new_hsv(60., 1., 1.);
        let cyan = Color::new_hsv(180., 1., 1.);
        let magenta = Color::new_hsv(300., 1., 1.);

        assert_eq!(yellow, Color::new(255, 255, 0));
        assert_eq!(cyan, Color::new(0, 255, 255));
        assert_eq!(magenta, Color::new(255, 0, 255));

        let black = Color::new_hsv(0., 0., 0.);
        let white = Color::new_hsv(0., 0., 1.);
        let gray = Color::new_hsv(0., 0., 0.5);
        let silver = Color::new_hsv(0., 0., 0.75);

        assert_eq!(black, Color::new(0, 0, 0));
        assert_eq!(white, Color::new(255, 255, 255));
        assert_eq!(gray, Color::new(128, 128, 128));
        assert_eq!(silver, Color::new(191, 191, 191));
    }

    #[test]
    fn lerp() {
        let black = Color::BLACK;
        let white = Color::WHITE;

        let left = black.lerp_rgb(white, 0.0);
        let right = black.lerp_rgb(white, 1.0);
        let middle = black.lerp_rgb(white, 0.5);

        assert_eq!(left, black);
        assert_eq!(right, white);
        assert_eq!(middle, Color::GRAY);

        let left = black.lerp_hsv(white, 0.0);
        let right = black.lerp_hsv(white, 1.0);
        let middle = black.lerp_hsv(white, 0.5);

        assert_eq!(left, black);
        assert_eq!(right, white);
        assert_eq!(middle, Color::new(128, 128, 128));

        let orange = Color::ORANGE;
        let cyan = Color::CYAN;

        let middle = orange.lerp_rgb(cyan, 0.5);
        assert_eq!(middle, Color::new(127, 191, 127));

        let middle = orange.lerp_hsv(cyan, 0.5);
        assert_eq!(middle, Color::new(64, 255, 0));

        let middle = Color::LIGHTEST_RED.lerp_rgb(Color::LIGHT_BLUE, 0.5);
        assert_eq!(middle, Color::new(159, 127, 223));

        let middle = Color::LIGHTEST_RED.lerp_hsv(Color::LIGHT_BLUE, 0.5);
        assert_eq!(middle, Color::LIGHTER_FUCHSIA);
    }

    #[test]
    fn operations() {
        let color1 = Color::new(31, 63, 127);
        let color2 = Color::new(1, 2, 3);
        let color3 = Color::new(50, 100, 200);
        assert_eq!(color1 + color2, Color::new(32, 65, 130));
        assert_eq!(color1 - color2, Color::new_with_alpha(30, 61, 124, 0));
        assert_eq!(color1 * color3, Color::new(6, 24, 99));
        assert_eq!(color2 * 2., Color::new(2, 4, 6));
    }

    #[test]
    fn conversions() {
        assert_eq!(Color::from((1, 2, 3)), Color::new(1, 2, 3));
        assert_eq!((1, 2, 3), Color::new(1, 2, 3).into());
        #[cfg(feature = "doryen")]
        {
            assert_eq!(Color::from((1, 2, 3, 4)), Color::new_with_alpha(1, 2, 3, 4));
            assert_eq!((1, 2, 3, 4), Color::new_with_alpha(1, 2, 3, 4).into());
        }
    }

    #[test]
    #[allow(clippy::enum_glob_use)]
    #[allow(clippy::cognitive_complexity)]
    fn by_name_and_level() {
        use crate::color::Level::*;
        use crate::color::Name::*;

        for &n in &[
            Red, Flame, Orange, Amber, Yellow, Lime, Chartreuse, Green, Sea, Turquoise, Cyan, Sky,
            Azure, Blue, Han, Violet, Purple, Fuchsia, Magenta, Pink, Crimson,
        ] {
            for &l in &[
                Desaturated,
                Lightest,
                Lighter,
                Light,
                Normal,
                Dark,
                Darker,
                Darkest,
            ] {
                let color = Color::by_name_and_level(n, l);

                // This is no exact science, clearly, but they all fall within
                // fairly narrow ranges.
                match n {
                    Red => assert!(color.get_hue() < 0.1),
                    Flame => assert!((color.get_hue() - 15.).abs() < 0.8),
                    Orange => assert!((color.get_hue() - 30.).abs() < 0.5),
                    Amber => assert!((color.get_hue() - 45.).abs() < 0.3),
                    Yellow => assert!((color.get_hue() - 60.).abs() < 0.1),
                    Lime => assert!((color.get_hue() - 75.).abs() < 0.3),
                    Chartreuse => assert!((color.get_hue() - 90.).abs() < 0.5),
                    Green => assert!((color.get_hue() - 120.).abs() < 0.1),
                    Sea => assert!((color.get_hue() - 150.).abs() < 0.5),
                    Turquoise => assert!((color.get_hue() - 165.).abs() < 0.3),
                    Cyan => assert!((color.get_hue() - 180.).abs() < 0.1),
                    Sky => assert!((color.get_hue() - 195.).abs() < 0.3),
                    Azure => assert!((color.get_hue() - 210.).abs() < 0.5),
                    Blue => assert!((color.get_hue() - 240.).abs() < 0.1),
                    Han => assert!((color.get_hue() - 255.).abs() < 0.8),
                    Violet => assert!((color.get_hue() - 270.).abs() < 0.5),
                    Purple => assert!((color.get_hue() - 285.).abs() < 0.3),
                    Fuchsia => assert!((color.get_hue() - 300.).abs() < 0.1),
                    Magenta => assert!((color.get_hue() - 315.).abs() < 0.3),
                    Pink => assert!((color.get_hue() - 330.).abs() < 0.5),
                    Crimson => assert!((color.get_hue() - 345.).abs() < 0.8),
                }

                match l {
                    Desaturated => {
                        assert!((color.get_saturation() - 0.5).abs() < 0.1);
                        assert!((color.get_value() - 0.5).abs() < 0.1);
                    }
                    Lightest => {
                        assert!((color.get_saturation() - 0.25).abs() < 0.1);
                        assert!((color.get_value() - 1.0).abs() < 0.1);
                    }
                    Lighter => {
                        assert!((color.get_saturation() - 0.5).abs() < 0.1);
                        assert!((color.get_value() - 1.0).abs() < 0.1);
                    }
                    Light => {
                        assert!((color.get_saturation() - 0.75).abs() < 0.1);
                        assert!((color.get_value() - 1.0).abs() < 0.1);
                    }
                    Normal => {
                        assert!((color.get_saturation() - 1.0).abs() < 0.1);
                        assert!((color.get_value() - 1.0).abs() < 0.1);
                    }
                    Dark => {
                        assert!((color.get_saturation() - 1.0).abs() < 0.1);
                        assert!((color.get_value() - 0.75).abs() < 0.1);
                    }
                    Darker => {
                        assert!((color.get_saturation() - 1.0).abs() < 0.1);
                        assert!((color.get_value() - 0.5).abs() < 0.1);
                    }
                    Darkest => {
                        assert!((color.get_saturation() - 1.0).abs() < 0.1);
                        assert!((color.get_value() - 0.25).abs() < 0.1);
                    }
                }
            }
        }
    }
}