qp_draw_image.c (15838B)
1 // Copyright 2021-2023 Nick Brassel (@tzarc) 2 // SPDX-License-Identifier: GPL-2.0-or-later 3 4 #include "qp_internal.h" 5 #include "qp_draw.h" 6 #include "qp_comms.h" 7 #include "qgf.h" 8 #include "deferred_exec.h" 9 10 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 // QGF image handles 12 13 typedef struct qgf_image_handle_t { 14 painter_image_desc_t base; 15 bool validate_ok; 16 union { 17 qp_stream_t stream; 18 qp_memory_stream_t mem_stream; 19 #ifdef QP_STREAM_HAS_FILE_IO 20 qp_file_stream_t file_stream; 21 #endif // QP_STREAM_HAS_FILE_IO 22 }; 23 } qgf_image_handle_t; 24 25 static qgf_image_handle_t image_descriptors[QUANTUM_PAINTER_NUM_IMAGES] = {0}; 26 27 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 // Helper: load image from stream 29 30 static painter_image_handle_t qp_load_image_internal(bool (*stream_factory)(qgf_image_handle_t *image, void *arg), void *arg) { 31 qp_dprintf("qp_load_image: entry\n"); 32 qgf_image_handle_t *image = NULL; 33 34 // Find a free slot 35 for (int i = 0; i < QUANTUM_PAINTER_NUM_IMAGES; ++i) { 36 if (!image_descriptors[i].validate_ok) { 37 image = &image_descriptors[i]; 38 break; 39 } 40 } 41 42 // Drop out if not found 43 if (!image) { 44 qp_dprintf("qp_load_image: fail (no free slot)\n"); 45 return NULL; 46 } 47 48 if (!stream_factory(image, arg)) { 49 qp_dprintf("qp_load_image: fail (could not create stream)\n"); 50 return NULL; 51 } 52 53 // Now that we know the length, validate the input data 54 if (!qgf_validate_stream(&image->stream)) { 55 qp_dprintf("qp_load_image: fail (failed validation)\n"); 56 return NULL; 57 } 58 59 // Fill out the QP image descriptor 60 qgf_read_graphics_descriptor(&image->stream, &image->base.width, &image->base.height, &image->base.frame_count, NULL); 61 62 // Validation success, we can return the handle 63 image->validate_ok = true; 64 qp_dprintf("qp_load_image: ok\n"); 65 return (painter_image_handle_t)image; 66 } 67 68 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 69 // Quantum Painter External API: qp_load_image_mem 70 71 static inline bool image_mem_stream_factory(qgf_image_handle_t *image, void *arg) { 72 void *buffer = arg; 73 74 // Assume we can read the graphics descriptor 75 image->mem_stream = qp_make_memory_stream((void *)buffer, sizeof(qgf_graphics_descriptor_v1_t)); 76 77 // Update the length of the stream to match, and rewind to the start 78 image->mem_stream.length = qgf_get_total_size(&image->stream); 79 image->mem_stream.position = 0; 80 81 return true; 82 } 83 84 painter_image_handle_t qp_load_image_mem(const void *buffer) { 85 return qp_load_image_internal(image_mem_stream_factory, (void *)buffer); 86 } 87 88 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 89 // Quantum Painter External API: qp_close_image 90 91 bool qp_close_image(painter_image_handle_t image) { 92 qgf_image_handle_t *qgf_image = (qgf_image_handle_t *)image; 93 if (!qgf_image || !qgf_image->validate_ok) { 94 qp_dprintf("qp_close_image: fail (invalid image)\n"); 95 return false; 96 } 97 98 // Free up this image for use elsewhere. 99 qgf_image->validate_ok = false; 100 qp_stream_close(&qgf_image->stream); 101 return true; 102 } 103 104 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 105 // Quantum Painter External API: qp_drawimage 106 107 bool qp_drawimage(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image) { 108 return qp_drawimage_recolor(device, x, y, image, 0, 0, 255, 0, 0, 0); 109 } 110 111 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 112 // Quantum Painter External API: qp_drawimage_recolor 113 114 typedef struct qgf_frame_info_t { 115 painter_compression_t compression_scheme; 116 uint8_t bpp; 117 bool has_palette; 118 bool is_panel_native; 119 bool is_delta; 120 uint16_t left; 121 uint16_t top; 122 uint16_t right; 123 uint16_t bottom; 124 uint16_t delay; 125 } qgf_frame_info_t; 126 127 static bool qp_drawimage_prepare_frame_for_stream_read(painter_device_t device, qgf_image_handle_t *qgf_image, uint16_t frame_number, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, qgf_frame_info_t *info) { 128 painter_driver_t *driver = (painter_driver_t *)device; 129 130 // Drop out if we can't actually place the data we read out anywhere 131 if (!info) { 132 qp_dprintf("Failed to prepare stream for read, output info buffer unavailable\n"); 133 return false; 134 } 135 136 // Seek to the frame 137 qgf_seek_to_frame_descriptor(&qgf_image->stream, frame_number); 138 139 // Read the frame descriptor 140 qgf_frame_v1_t frame_descriptor; 141 if (qp_stream_read(&frame_descriptor, sizeof(qgf_frame_v1_t), 1, &qgf_image->stream) != 1) { 142 qp_dprintf("Failed to read frame_descriptor, expected length was not %d\n", (int)sizeof(qgf_frame_v1_t)); 143 return false; 144 } 145 146 // Parse out the frame info 147 if (!qgf_parse_frame_descriptor(&frame_descriptor, &info->bpp, &info->has_palette, &info->is_panel_native, &info->is_delta, &info->compression_scheme, &info->delay)) { 148 return false; 149 } 150 151 // Ensure we aren't reusing any palette 152 qp_internal_invalidate_palette(); 153 154 if (!qp_internal_bpp_capable(info->bpp)) { 155 qp_dprintf("qp_drawimage_recolor: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE or QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS)\n", (int)info->bpp); 156 qp_comms_stop(device); 157 return false; 158 } 159 160 // Handle palette if needed 161 const uint16_t palette_entries = 1u << info->bpp; 162 bool needs_pixconvert = false; 163 if (info->has_palette) { 164 // Load the palette from the stream 165 if (!qp_internal_load_qgf_palette((qp_stream_t *)&qgf_image->stream, info->bpp)) { 166 return false; 167 } 168 169 needs_pixconvert = true; 170 } else { 171 if (info->bpp <= 8) { 172 // Interpolate from fg/bg 173 needs_pixconvert = qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, palette_entries); 174 } 175 } 176 177 if (needs_pixconvert) { 178 // Convert the palette to native format 179 if (!driver->driver_vtable->palette_convert(device, palette_entries, qp_internal_global_pixel_lookup_table)) { 180 qp_dprintf("qp_drawimage_recolor: fail (could not convert pixels to native)\n"); 181 qp_comms_stop(device); 182 return false; 183 } 184 } 185 186 // Handle delta if needed 187 if (info->is_delta) { 188 qgf_delta_v1_t delta_descriptor; 189 if (qp_stream_read(&delta_descriptor, sizeof(qgf_delta_v1_t), 1, &qgf_image->stream) != 1) { 190 qp_dprintf("Failed to read delta_descriptor, expected length was not %d\n", (int)sizeof(qgf_delta_v1_t)); 191 return false; 192 } 193 194 info->left = delta_descriptor.left; 195 info->top = delta_descriptor.top; 196 info->right = delta_descriptor.right; 197 info->bottom = delta_descriptor.bottom; 198 } 199 200 // Read the data block 201 qgf_data_v1_t data_descriptor; 202 if (qp_stream_read(&data_descriptor, sizeof(qgf_data_v1_t), 1, &qgf_image->stream) != 1) { 203 qp_dprintf("Failed to read data_descriptor, expected length was not %d\n", (int)sizeof(qgf_data_v1_t)); 204 return false; 205 } 206 207 // Stream is now at the point of being able to read pixdata 208 return true; 209 } 210 211 static bool qp_drawimage_recolor_impl(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, int frame_number, qgf_frame_info_t *frame_info, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888) { 212 qp_dprintf("qp_drawimage_recolor: entry\n"); 213 painter_driver_t *driver = (painter_driver_t *)device; 214 if (!driver || !driver->validate_ok) { 215 qp_dprintf("qp_drawimage_recolor: fail (validation_ok == false)\n"); 216 return false; 217 } 218 219 qgf_image_handle_t *qgf_image = (qgf_image_handle_t *)image; 220 if (!qgf_image || !qgf_image->validate_ok) { 221 qp_dprintf("qp_drawimage_recolor: fail (invalid image)\n"); 222 return false; 223 } 224 225 // Read the frame info 226 if (!qp_drawimage_prepare_frame_for_stream_read(device, qgf_image, frame_number, fg_hsv888, bg_hsv888, frame_info)) { 227 qp_dprintf("qp_drawimage_recolor: fail (could not read frame %d)\n", frame_number); 228 return false; 229 } 230 231 if (!qp_comms_start(device)) { 232 qp_dprintf("qp_drawimage_recolor: fail (could not start comms)\n"); 233 return false; 234 } 235 236 uint16_t l, t, r, b; 237 if (frame_info->is_delta) { 238 l = x + frame_info->left; 239 t = y + frame_info->top; 240 r = x + frame_info->right; 241 b = y + frame_info->bottom; 242 } else { 243 l = x; 244 t = y; 245 r = x + image->width - 1; 246 b = y + image->height - 1; 247 } 248 uint32_t pixel_count = ((uint32_t)(r - l + 1)) * (b - t + 1); 249 250 // Configure where we're going to be rendering to 251 if (!driver->driver_vtable->viewport(device, l, t, r, b)) { 252 qp_dprintf("qp_drawimage_recolor: fail (could not set viewport)\n"); 253 qp_comms_stop(device); 254 return false; 255 } 256 257 // Set up the input state 258 qp_internal_byte_input_state_t input_state = {.device = device, .src_stream = &qgf_image->stream}; 259 qp_internal_byte_input_callback input_callback = qp_internal_prepare_input_state(&input_state, frame_info->compression_scheme); 260 if (input_callback == NULL) { 261 qp_dprintf("qp_drawimage_recolor: fail (invalid image compression scheme)\n"); 262 qp_comms_stop(device); 263 return false; 264 } 265 266 // Decode and stream pixels 267 bool ret = qp_internal_appender(device, frame_info->bpp, pixel_count, input_callback, &input_state); 268 269 qp_dprintf("qp_drawimage_recolor: %s\n", ret ? "ok" : "fail"); 270 qp_comms_stop(device); 271 return ret; 272 } 273 274 bool qp_drawimage_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg) { 275 qgf_frame_info_t frame_info = {0}; 276 qp_pixel_t fg_hsv888 = {.hsv888 = {.h = hue_fg, .s = sat_fg, .v = val_fg}}; 277 qp_pixel_t bg_hsv888 = {.hsv888 = {.h = hue_bg, .s = sat_bg, .v = val_bg}}; 278 return qp_drawimage_recolor_impl(device, x, y, image, 0, &frame_info, fg_hsv888, bg_hsv888); 279 } 280 281 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 282 // Quantum Painter External API: qp_animate 283 284 deferred_token qp_animate(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image) { 285 return qp_animate_recolor(device, x, y, image, 0, 0, 255, 0, 0, 0); 286 } 287 288 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 289 // Quantum Painter External API: qp_animate_recolor 290 291 typedef struct animation_state_t { 292 painter_device_t device; 293 uint16_t x; 294 uint16_t y; 295 painter_image_handle_t image; 296 qp_pixel_t fg_hsv888; 297 qp_pixel_t bg_hsv888; 298 uint16_t frame_number; 299 deferred_token defer_token; 300 } animation_state_t; 301 302 static deferred_executor_t animation_executors[QUANTUM_PAINTER_CONCURRENT_ANIMATIONS] = {0}; 303 static animation_state_t animation_states[QUANTUM_PAINTER_CONCURRENT_ANIMATIONS] = {0}; 304 305 static deferred_token qp_render_animation_state(animation_state_t *state, uint16_t *delay_ms) { 306 qgf_frame_info_t frame_info = {0}; 307 qp_dprintf("qp_render_animation_state: entry (frame #%d)\n", (int)state->frame_number); 308 bool ret = qp_drawimage_recolor_impl(state->device, state->x, state->y, state->image, state->frame_number, &frame_info, state->fg_hsv888, state->bg_hsv888); 309 if (ret) { 310 ++state->frame_number; 311 if (state->frame_number >= state->image->frame_count) { 312 state->frame_number = 0; 313 } 314 *delay_ms = frame_info.delay; 315 } 316 qp_dprintf("qp_render_animation_state: %s (delay %dms)\n", ret ? "ok" : "fail", (int)(*delay_ms)); 317 return ret; 318 } 319 320 static uint32_t animation_callback(uint32_t trigger_time, void *cb_arg) { 321 animation_state_t *state = (animation_state_t *)cb_arg; 322 uint16_t delay_ms = 0; 323 bool ret = qp_render_animation_state(state, &delay_ms); 324 if (!ret) { 325 // Setting the device to NULL clears the animation slot 326 state->device = NULL; 327 } 328 // If we're successful, keep animating -- returning 0 cancels the deferred execution 329 return ret ? delay_ms : 0; 330 } 331 332 deferred_token qp_animate_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg) { 333 qp_dprintf("qp_animate_recolor: entry\n"); 334 335 animation_state_t *anim_state = NULL; 336 for (int i = 0; i < QUANTUM_PAINTER_CONCURRENT_ANIMATIONS; ++i) { 337 if (animation_states[i].device == NULL) { 338 anim_state = &animation_states[i]; 339 break; 340 } 341 } 342 343 if (!anim_state) { 344 qp_dprintf("qp_animate_recolor: fail (could not find free animation slot)\n"); 345 return INVALID_DEFERRED_TOKEN; 346 } 347 348 // Prepare the animation state 349 anim_state->device = device; 350 anim_state->x = x; 351 anim_state->y = y; 352 anim_state->image = image; 353 anim_state->fg_hsv888 = (qp_pixel_t){.hsv888 = {.h = hue_fg, .s = sat_fg, .v = val_fg}}; 354 anim_state->bg_hsv888 = (qp_pixel_t){.hsv888 = {.h = hue_bg, .s = sat_bg, .v = val_bg}}; 355 anim_state->frame_number = 0; 356 357 // Draw the first frame 358 uint16_t delay_ms; 359 if (!qp_render_animation_state(anim_state, &delay_ms)) { 360 anim_state->device = NULL; // disregard the allocated animation slot 361 qp_dprintf("qp_animate_recolor: fail (could not render first frame)\n"); 362 return INVALID_DEFERRED_TOKEN; 363 } 364 365 // Set up the timer 366 anim_state->defer_token = defer_exec_advanced(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, delay_ms, animation_callback, anim_state); 367 if (anim_state->defer_token == INVALID_DEFERRED_TOKEN) { 368 anim_state->device = NULL; // disregard the allocated animation slot 369 qp_dprintf("qp_animate_recolor: fail (could not set up animation executor)\n"); 370 return INVALID_DEFERRED_TOKEN; 371 } 372 373 qp_dprintf("qp_animate_recolor: ok (deferred token = %d)\n", (int)anim_state->defer_token); 374 return anim_state->defer_token; 375 } 376 377 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 378 // Quantum Painter External API: qp_stop_animation 379 380 void qp_stop_animation(deferred_token anim_token) { 381 for (int i = 0; i < QUANTUM_PAINTER_CONCURRENT_ANIMATIONS; ++i) { 382 if (animation_states[i].defer_token == anim_token) { 383 cancel_deferred_exec_advanced(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, anim_token); 384 animation_states[i].device = NULL; 385 return; 386 } 387 } 388 } 389 390 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 391 // Quantum Painter Core API: qp_internal_animation_tick 392 393 void qp_internal_animation_tick(void) { 394 static uint32_t last_anim_exec = 0; 395 deferred_exec_advanced_task(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, &last_anim_exec); 396 }