qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

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 }