qp_draw_core.c (13189B)
1 // Copyright 2021-2022 Nick Brassel (@tzarc) 2 // Copyright 2021 Paul Cotter (@gr1mr3aver) 3 // SPDX-License-Identifier: GPL-2.0-or-later 4 5 #include "compiler_support.h" 6 #include "qp_internal.h" 7 #include "qp_comms.h" 8 #include "qp_draw.h" 9 #include "qgf.h" 10 11 STATIC_ASSERT((QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE > 0) && (QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE % 16) == 0, "QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE needs to be a non-zero multiple of 16"); 12 13 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 14 // Global variables 15 // 16 // NOTE: The variables in this section are intentionally outside a stack frame. They are able to be defined with larger 17 // sizes than the normal stack frames would allow, and as such need to be external. 18 // 19 // **** DO NOT refactor this and decide to place the variables inside the function calling them -- you will **** 20 // **** very likely get artifacts rendered to the screen as a result. **** 21 // 22 23 // Buffer used for transmitting native pixel data to the downstream device. 24 __attribute__((__aligned__(4))) uint8_t qp_internal_global_pixdata_buffer[QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE]; 25 26 // Static buffer to contain a generated color palette 27 static bool generated_palette = false; 28 static int16_t generated_steps = -1; 29 __attribute__((__aligned__(4))) static qp_pixel_t interpolated_fg_hsv888; 30 __attribute__((__aligned__(4))) static qp_pixel_t interpolated_bg_hsv888; 31 #if QUANTUM_PAINTER_SUPPORTS_256_PALETTE 32 __attribute__((__aligned__(4))) qp_pixel_t qp_internal_global_pixel_lookup_table[256]; 33 #else 34 __attribute__((__aligned__(4))) qp_pixel_t qp_internal_global_pixel_lookup_table[16]; 35 #endif 36 37 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 38 // Helpers 39 40 uint32_t qp_internal_num_pixels_in_buffer(painter_device_t device) { 41 painter_driver_t *driver = (painter_driver_t *)device; 42 return ((QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE * 8) / driver->native_bits_per_pixel); 43 } 44 45 // qp_setpixel internal implementation, but accepts a buffer with pre-converted native pixel. Only the first pixel is used. 46 bool qp_internal_setpixel_impl(painter_device_t device, uint16_t x, uint16_t y) { 47 painter_driver_t *driver = (painter_driver_t *)device; 48 return driver->driver_vtable->viewport(device, x, y, x, y) && driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, 1); 49 } 50 51 // Fills the global native pixel buffer with equivalent pixels matching the supplied HSV 52 void qp_internal_fill_pixdata(painter_device_t device, uint32_t num_pixels, uint8_t hue, uint8_t sat, uint8_t val) { 53 painter_driver_t *driver = (painter_driver_t *)device; 54 uint32_t pixels_in_pixdata = qp_internal_num_pixels_in_buffer(device); 55 num_pixels = MIN(pixels_in_pixdata, num_pixels); 56 57 // Convert the color to native pixel format 58 qp_pixel_t color = {.hsv888 = {.h = hue, .s = sat, .v = val}}; 59 driver->driver_vtable->palette_convert(device, 1, &color); 60 61 // Append the required number of pixels 62 uint8_t palette_idx = 0; 63 for (uint32_t i = 0; i < num_pixels; ++i) { 64 driver->driver_vtable->append_pixels(device, qp_internal_global_pixdata_buffer, &color, i, 1, &palette_idx); 65 } 66 } 67 68 // Resets the global palette so that it can be regenerated. Only needed if the colors are identical, but a different display is used with a different internal pixel format. 69 void qp_internal_invalidate_palette(void) { 70 generated_palette = false; 71 generated_steps = -1; 72 } 73 74 // Interpolates between two colors to generate a palette 75 bool qp_internal_interpolate_palette(qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, int16_t steps) { 76 // Check if we need to generate a new palette -- if the input parameters match then assume the palette can stay unchanged. 77 // This may present a problem if using the same parameters but a different screen converts pixels -- use qp_internal_invalidate_palette() to reset. 78 if (generated_palette == true && generated_steps == steps && memcmp(&interpolated_fg_hsv888, &fg_hsv888, sizeof(fg_hsv888)) == 0 && memcmp(&interpolated_bg_hsv888, &bg_hsv888, sizeof(bg_hsv888)) == 0) { 79 // We already have the correct palette, no point regenerating it. 80 return false; 81 } 82 83 // Save the parameters so we know whether we can skip generation 84 generated_palette = true; 85 generated_steps = steps; 86 interpolated_fg_hsv888 = fg_hsv888; 87 interpolated_bg_hsv888 = bg_hsv888; 88 89 int16_t hue_fg = fg_hsv888.hsv888.h; 90 int16_t hue_bg = bg_hsv888.hsv888.h; 91 92 // Make sure we take the "shortest" route from one hue to the other 93 if ((hue_fg - hue_bg) >= 128) { 94 hue_bg += 256; 95 } else if ((hue_fg - hue_bg) <= -128) { 96 hue_bg -= 256; 97 } 98 99 // Interpolate each of the lookup table entries 100 for (int16_t i = 0; i < steps; ++i) { 101 qp_internal_global_pixel_lookup_table[i].hsv888.h = (uint8_t)((hue_fg - hue_bg) * i / (steps - 1) + hue_bg); 102 qp_internal_global_pixel_lookup_table[i].hsv888.s = (uint8_t)((fg_hsv888.hsv888.s - bg_hsv888.hsv888.s) * i / (steps - 1) + bg_hsv888.hsv888.s); 103 qp_internal_global_pixel_lookup_table[i].hsv888.v = (uint8_t)((fg_hsv888.hsv888.v - bg_hsv888.hsv888.v) * i / (steps - 1) + bg_hsv888.hsv888.v); 104 105 qp_dprintf("qp_internal_interpolate_palette: %3d of %d -- H: %3d, S: %3d, V: %3d\n", (int)(i + 1), (int)steps, (int)qp_internal_global_pixel_lookup_table[i].hsv888.h, (int)qp_internal_global_pixel_lookup_table[i].hsv888.s, (int)qp_internal_global_pixel_lookup_table[i].hsv888.v); 106 } 107 108 return true; 109 } 110 111 // Helper shared between image and font rendering -- sets up the global palette to match the palette block specified in the asset. Expects the stream to be positioned at the start of the block header. 112 bool qp_internal_load_qgf_palette(qp_stream_t *stream, uint8_t bpp) { 113 qgf_palette_v1_t palette_descriptor; 114 if (qp_stream_read(&palette_descriptor, sizeof(qgf_palette_v1_t), 1, stream) != 1) { 115 qp_dprintf("Failed to read palette_descriptor, expected length was not %d\n", (int)sizeof(qgf_palette_v1_t)); 116 return false; 117 } 118 119 // BPP determines the number of palette entries, each entry is a HSV888 triplet. 120 const uint16_t palette_entries = 1u << bpp; 121 122 // Ensure we aren't reusing any palette 123 qp_internal_invalidate_palette(); 124 125 // Read the palette entries 126 for (uint16_t i = 0; i < palette_entries; ++i) { 127 // Read the palette entry 128 qgf_palette_entry_v1_t entry; 129 if (qp_stream_read(&entry, sizeof(qgf_palette_entry_v1_t), 1, stream) != 1) { 130 return false; 131 } 132 133 // Update the lookup table 134 qp_internal_global_pixel_lookup_table[i].hsv888.h = entry.h; 135 qp_internal_global_pixel_lookup_table[i].hsv888.s = entry.s; 136 qp_internal_global_pixel_lookup_table[i].hsv888.v = entry.v; 137 138 qp_dprintf("qp_internal_load_qgf_palette: %3d of %d -- H: %3d, S: %3d, V: %3d\n", (int)(i + 1), (int)palette_entries, (int)qp_internal_global_pixel_lookup_table[i].hsv888.h, (int)qp_internal_global_pixel_lookup_table[i].hsv888.s, (int)qp_internal_global_pixel_lookup_table[i].hsv888.v); 139 } 140 141 return true; 142 } 143 144 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 145 // Quantum Painter External API: qp_setpixel 146 147 bool qp_setpixel(painter_device_t device, uint16_t x, uint16_t y, uint8_t hue, uint8_t sat, uint8_t val) { 148 painter_driver_t *driver = (painter_driver_t *)device; 149 if (!driver || !driver->validate_ok) { 150 qp_dprintf("qp_setpixel: fail (validation_ok == false)\n"); 151 return false; 152 } 153 154 if (!qp_comms_start(device)) { 155 qp_dprintf("Failed to start comms in qp_setpixel\n"); 156 return false; 157 } 158 159 qp_internal_fill_pixdata(device, 1, hue, sat, val); 160 bool ret = qp_internal_setpixel_impl(device, x, y); 161 qp_comms_stop(device); 162 qp_dprintf("qp_setpixel: %s\n", ret ? "ok" : "fail"); 163 return ret; 164 } 165 166 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 167 // Quantum Painter External API: qp_line 168 169 bool qp_line(painter_device_t device, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t hue, uint8_t sat, uint8_t val) { 170 if (x0 == x1 || y0 == y1) { 171 qp_dprintf("qp_line(%d, %d, %d, %d): entry (deferring to qp_rect)\n", (int)x0, (int)y0, (int)x1, (int)y1); 172 bool ret = qp_rect(device, x0, y0, x1, y1, hue, sat, val, true); 173 qp_dprintf("qp_line(%d, %d, %d, %d): %s (deferred to qp_rect)\n", (int)x0, (int)y0, (int)x1, (int)y1, ret ? "ok" : "fail"); 174 return ret; 175 } 176 177 qp_dprintf("qp_line(%d, %d, %d, %d): entry\n", (int)x0, (int)y0, (int)x1, (int)y1); 178 painter_driver_t *driver = (painter_driver_t *)device; 179 if (!driver || !driver->validate_ok) { 180 qp_dprintf("qp_line: fail (validation_ok == false)\n"); 181 return false; 182 } 183 184 if (!qp_comms_start(device)) { 185 qp_dprintf("Failed to start comms in qp_line\n"); 186 return false; 187 } 188 189 qp_internal_fill_pixdata(device, 1, hue, sat, val); 190 191 // draw angled line using Bresenham's algo 192 int16_t x = ((int16_t)x0); 193 int16_t y = ((int16_t)y0); 194 int16_t slopex = ((int16_t)x0) < ((int16_t)x1) ? 1 : -1; 195 int16_t slopey = ((int16_t)y0) < ((int16_t)y1) ? 1 : -1; 196 int16_t dx = abs(((int16_t)x1) - ((int16_t)x0)); 197 int16_t dy = -abs(((int16_t)y1) - ((int16_t)y0)); 198 199 int16_t e = dx + dy; 200 int16_t e2 = 2 * e; 201 202 bool ret = true; 203 while (x != x1 || y != y1) { 204 if (!qp_internal_setpixel_impl(device, x, y)) { 205 ret = false; 206 break; 207 } 208 e2 = 2 * e; 209 if (e2 >= dy) { 210 e += dy; 211 x += slopex; 212 } 213 if (e2 <= dx) { 214 e += dx; 215 y += slopey; 216 } 217 } 218 // draw the last pixel 219 if (!qp_internal_setpixel_impl(device, x, y)) { 220 ret = false; 221 } 222 223 qp_comms_stop(device); 224 qp_dprintf("qp_line(%d, %d, %d, %d): %s\n", (int)x0, (int)y0, (int)x1, (int)y1, ret ? "ok" : "fail"); 225 return ret; 226 } 227 228 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 229 // Quantum Painter External API: qp_rect 230 231 bool qp_internal_fillrect_helper_impl(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { 232 uint32_t pixels_in_pixdata = qp_internal_num_pixels_in_buffer(device); 233 painter_driver_t *driver = (painter_driver_t *)device; 234 235 uint16_t l = MIN(left, right); 236 uint16_t r = MAX(left, right); 237 uint16_t t = MIN(top, bottom); 238 uint16_t b = MAX(top, bottom); 239 uint16_t w = r - l + 1; 240 uint16_t h = b - t + 1; 241 242 uint32_t remaining = w * h; 243 driver->driver_vtable->viewport(device, l, t, r, b); 244 while (remaining > 0) { 245 uint32_t transmit = MIN(remaining, pixels_in_pixdata); 246 if (!driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, transmit)) { 247 return false; 248 } 249 remaining -= transmit; 250 } 251 return true; 252 } 253 254 bool qp_rect(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom, uint8_t hue, uint8_t sat, uint8_t val, bool filled) { 255 qp_dprintf("qp_rect(%d, %d, %d, %d): entry\n", (int)left, (int)top, (int)right, (int)bottom); 256 painter_driver_t *driver = (painter_driver_t *)device; 257 if (!driver || !driver->validate_ok) { 258 qp_dprintf("qp_rect: fail (validation_ok == false)\n"); 259 return false; 260 } 261 262 // Cater for cases where people have submitted the coordinates backwards 263 uint16_t l = MIN(left, right); 264 uint16_t r = MAX(left, right); 265 uint16_t t = MIN(top, bottom); 266 uint16_t b = MAX(top, bottom); 267 uint16_t w = r - l + 1; 268 uint16_t h = b - t + 1; 269 270 bool ret = true; 271 if (!qp_comms_start(device)) { 272 qp_dprintf("Failed to start comms in qp_rect\n"); 273 return false; 274 } 275 276 if (filled) { 277 // Fill up the pixdata buffer with the required number of native pixels 278 qp_internal_fill_pixdata(device, w * h, hue, sat, val); 279 280 // Perform the draw 281 ret = qp_internal_fillrect_helper_impl(device, l, t, r, b); 282 } else { 283 // Fill up the pixdata buffer with the required number of native pixels 284 qp_internal_fill_pixdata(device, MAX(w, h), hue, sat, val); 285 286 // Draw 4x filled single-width rects to create an outline 287 if (!qp_internal_fillrect_helper_impl(device, l, t, r, t) || !qp_internal_fillrect_helper_impl(device, l, b, r, b) || !qp_internal_fillrect_helper_impl(device, l, t + 1, l, b - 1) || !qp_internal_fillrect_helper_impl(device, r, t + 1, r, b - 1)) { 288 ret = false; 289 } 290 } 291 292 qp_comms_stop(device); 293 qp_dprintf("qp_rect(%d, %d, %d, %d): %s\n", (int)l, (int)t, (int)r, (int)b, ret ? "ok" : "fail"); 294 return ret; 295 }