logo

qmk_firmware

custom branch of QMK firmware git clone https://anongit.hacktivis.me/git/qmk_firmware.git

qp_draw_image.c (15838B)


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