// The MIT License (MIT) // // Copyright (C) 2026 Scott Duensing // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // dvxPal.h -- 8-bit mode palette definition for DVX GUI // // Defines the 256-color palette used in 8-bit (VGA Mode 13h / VESA 8bpp) // video modes. The palette layout follows the same strategy as the X11 // default colormap and Netscape's web-safe colors: // // 0-215: 6x6x6 color cube -- 6 levels per channel (0, 51, 102, 153, // 204, 255), giving 216 uniformly distributed colors. The index // formula (r*36 + g*6 + b) enables O(1) color lookup without // searching. // 216-231: 16-step grey ramp for smooth gradients in window chrome. // 232-239: Dedicated UI chrome colors (highlight, shadow, active title, // etc.) that need exact values not available in the color cube. // 240-255: Reserved for future use (currently black). // // This partition gives good general-purpose color reproduction while // guaranteeing the UI has pixel-perfect colors for its chrome elements. #ifndef DVX_PAL_H #define DVX_PAL_H #include // 256-entry palette for 8-bit mode: // 0-215: 6x6x6 color cube (index = r*36 + g*6 + b) // 216-231: 16-step grey ramp // 232-239: UI chrome colors // 240-255: reserved // Chrome color indices for 8-bit mode #define PAL_CHROME_HIGHLIGHT 232 #define PAL_CHROME_SHADOW 233 #define PAL_CHROME_ACTIVE_BG 234 #define PAL_CHROME_INACTIVE_BG 235 #define PAL_CHROME_DESKTOP 236 #define PAL_CHROME_SELECTION 237 #define PAL_CHROME_TEXT 238 #define PAL_CHROME_WHITE 239 // Generate the default 8-bit palette into a 768-byte buffer (256 * 3, RGB) static inline void dvxGeneratePalette(uint8_t *pal) { int32_t idx = 0; // Entries 0-215: 6x6x6 color cube for (int32_t r = 0; r < 6; r++) { for (int32_t g = 0; g < 6; g++) { for (int32_t b = 0; b < 6; b++) { pal[idx * 3 + 0] = (uint8_t)(r * 51); pal[idx * 3 + 1] = (uint8_t)(g * 51); pal[idx * 3 + 2] = (uint8_t)(b * 51); idx++; } } } // Entries 216-231: 16-step grey ramp for (int32_t i = 0; i < 16; i++) { uint8_t v = (uint8_t)(i * 17); pal[idx * 3 + 0] = v; pal[idx * 3 + 1] = v; pal[idx * 3 + 2] = v; idx++; } // Entries 232-239: UI chrome colors // highlight (white) pal[PAL_CHROME_HIGHLIGHT * 3 + 0] = 255; pal[PAL_CHROME_HIGHLIGHT * 3 + 1] = 255; pal[PAL_CHROME_HIGHLIGHT * 3 + 2] = 255; // shadow (dark grey) pal[PAL_CHROME_SHADOW * 3 + 0] = 80; pal[PAL_CHROME_SHADOW * 3 + 1] = 80; pal[PAL_CHROME_SHADOW * 3 + 2] = 80; // active title bg (navy blue) pal[PAL_CHROME_ACTIVE_BG * 3 + 0] = 0; pal[PAL_CHROME_ACTIVE_BG * 3 + 1] = 0; pal[PAL_CHROME_ACTIVE_BG * 3 + 2] = 128; // inactive title bg (grey) pal[PAL_CHROME_INACTIVE_BG * 3 + 0] = 128; pal[PAL_CHROME_INACTIVE_BG * 3 + 1] = 128; pal[PAL_CHROME_INACTIVE_BG * 3 + 2] = 128; // desktop (steel blue) pal[PAL_CHROME_DESKTOP * 3 + 0] = 51; pal[PAL_CHROME_DESKTOP * 3 + 1] = 102; pal[PAL_CHROME_DESKTOP * 3 + 2] = 153; // selection (navy) pal[PAL_CHROME_SELECTION * 3 + 0] = 0; pal[PAL_CHROME_SELECTION * 3 + 1] = 0; pal[PAL_CHROME_SELECTION * 3 + 2] = 128; // text (black) pal[PAL_CHROME_TEXT * 3 + 0] = 0; pal[PAL_CHROME_TEXT * 3 + 1] = 0; pal[PAL_CHROME_TEXT * 3 + 2] = 0; // bright white pal[PAL_CHROME_WHITE * 3 + 0] = 255; pal[PAL_CHROME_WHITE * 3 + 1] = 255; pal[PAL_CHROME_WHITE * 3 + 2] = 255; // Entries 240-255: reserved (black) for (int32_t i = 240; i < 256; i++) { pal[i * 3 + 0] = 0; pal[i * 3 + 1] = 0; pal[i * 3 + 2] = 0; } } // Find the nearest palette entry for an RGB color using minimum Euclidean // distance in RGB space. The two-phase approach avoids a full 256-entry // linear scan in the common case: // 1. Fast path: snap to the nearest 6x6x6 color cube entry via integer // division (O(1), no branching). // 2. Refinement: linearly scan only the 24 grey ramp and chrome entries // (indices 216-239) to see if any is closer than the cube match. // Entries 240-255 are reserved (black) and skipped to avoid false matches. // This is called by packColor() in 8-bit mode, so it needs to be fast. static inline uint8_t dvxNearestPalEntry(const uint8_t *pal, uint8_t r, uint8_t g, uint8_t b) { // Snap to nearest cube vertex: +25 rounds to nearest 51-step level int32_t ri = (r + 25) / 51; int32_t gi = (g + 25) / 51; int32_t bi = (b + 25) / 51; if (ri > 5) { ri = 5; } if (gi > 5) { gi = 5; } if (bi > 5) { bi = 5; } uint8_t cubeIdx = (uint8_t)(ri * 36 + gi * 6 + bi); int32_t cubeR = ri * 51; int32_t cubeG = gi * 51; int32_t cubeB = bi * 51; int32_t cubeDist = (r - cubeR) * (r - cubeR) + (g - cubeG) * (g - cubeG) + (b - cubeB) * (b - cubeB); // Check grey ramp and chrome colors for a closer match int32_t bestDist = cubeDist; uint8_t bestIdx = cubeIdx; for (int32_t i = 216; i < 240; i++) { int32_t dr = (int32_t)r - (int32_t)pal[i * 3 + 0]; int32_t dg = (int32_t)g - (int32_t)pal[i * 3 + 1]; int32_t db = (int32_t)b - (int32_t)pal[i * 3 + 2]; int32_t dist = dr * dr + dg * dg + db * db; if (dist < bestDist) { bestDist = dist; bestIdx = (uint8_t)i; } } return bestIdx; } #endif // DVX_PAL_H