2023-03-20 09:13:53 +06:00
// Copyright 2021-2023 Nick Brassel (@tzarc)
2022-04-13 14:00:18 +06:00
// SPDX-License-Identifier: GPL-2.0-or-later
# include "qp_internal.h"
# include "qp_draw.h"
# include "qp_comms.h"
# include "qgf.h"
# include "deferred_exec.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// QGF image handles
typedef struct qgf_image_handle_t {
painter_image_desc_t base ;
bool validate_ok ;
union {
qp_stream_t stream ;
qp_memory_stream_t mem_stream ;
# ifdef QP_STREAM_HAS_FILE_IO
qp_file_stream_t file_stream ;
# endif // QP_STREAM_HAS_FILE_IO
} ;
} qgf_image_handle_t ;
static qgf_image_handle_t image_descriptors [ QUANTUM_PAINTER_NUM_IMAGES ] = { 0 } ;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-09-19 03:30:08 +06:00
// Helper: load image from stream
2022-04-13 14:00:18 +06:00
2022-09-19 03:30:08 +06:00
static painter_image_handle_t qp_load_image_internal ( bool ( * stream_factory ) ( qgf_image_handle_t * image , void * arg ) , void * arg ) {
qp_dprintf ( " qp_load_image: entry \n " ) ;
2022-04-13 14:00:18 +06:00
qgf_image_handle_t * image = NULL ;
// Find a free slot
for ( int i = 0 ; i < QUANTUM_PAINTER_NUM_IMAGES ; + + i ) {
if ( ! image_descriptors [ i ] . validate_ok ) {
image = & image_descriptors [ i ] ;
break ;
}
}
// Drop out if not found
if ( ! image ) {
2022-09-19 03:30:08 +06:00
qp_dprintf ( " qp_load_image: fail (no free slot) \n " ) ;
2022-04-13 14:00:18 +06:00
return NULL ;
}
2022-09-19 03:30:08 +06:00
if ( ! stream_factory ( image , arg ) ) {
qp_dprintf ( " qp_load_image: fail (could not create stream) \n " ) ;
return NULL ;
}
2022-04-13 14:00:18 +06:00
// Now that we know the length, validate the input data
if ( ! qgf_validate_stream ( & image - > stream ) ) {
2022-09-19 03:30:08 +06:00
qp_dprintf ( " qp_load_image: fail (failed validation) \n " ) ;
2022-04-13 14:00:18 +06:00
return NULL ;
}
// Fill out the QP image descriptor
qgf_read_graphics_descriptor ( & image - > stream , & image - > base . width , & image - > base . height , & image - > base . frame_count , NULL ) ;
// Validation success, we can return the handle
image - > validate_ok = true ;
2022-09-19 03:30:08 +06:00
qp_dprintf ( " qp_load_image: ok \n " ) ;
2022-04-13 14:00:18 +06:00
return ( painter_image_handle_t ) image ;
}
2022-09-19 03:30:08 +06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_load_image_mem
static inline bool image_mem_stream_factory ( qgf_image_handle_t * image , void * arg ) {
void * buffer = arg ;
// Assume we can read the graphics descriptor
image - > mem_stream = qp_make_memory_stream ( ( void * ) buffer , sizeof ( qgf_graphics_descriptor_v1_t ) ) ;
// Update the length of the stream to match, and rewind to the start
image - > mem_stream . length = qgf_get_total_size ( & image - > stream ) ;
image - > mem_stream . position = 0 ;
return true ;
}
painter_image_handle_t qp_load_image_mem ( const void * buffer ) {
return qp_load_image_internal ( image_mem_stream_factory , ( void * ) buffer ) ;
}
2022-04-13 14:00:18 +06:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_close_image
bool qp_close_image ( painter_image_handle_t image ) {
qgf_image_handle_t * qgf_image = ( qgf_image_handle_t * ) image ;
if ( ! qgf_image - > validate_ok ) {
qp_dprintf ( " qp_close_image: fail (invalid image) \n " ) ;
return false ;
}
// Free up this image for use elsewhere.
qgf_image - > validate_ok = false ;
2022-09-19 03:30:08 +06:00
qp_stream_close ( & qgf_image - > stream ) ;
2022-04-13 14:00:18 +06:00
return true ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_drawimage
bool qp_drawimage ( painter_device_t device , uint16_t x , uint16_t y , painter_image_handle_t image ) {
return qp_drawimage_recolor ( device , x , y , image , 0 , 0 , 255 , 0 , 0 , 0 ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_drawimage_recolor
typedef struct qgf_frame_info_t {
painter_compression_t compression_scheme ;
uint8_t bpp ;
bool has_palette ;
bool is_delta ;
uint16_t left ;
uint16_t top ;
uint16_t right ;
uint16_t bottom ;
uint16_t delay ;
} qgf_frame_info_t ;
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 ) {
2023-03-25 23:56:04 +06:00
painter_driver_t * driver = ( painter_driver_t * ) device ;
2022-04-13 14:00:18 +06:00
// Drop out if we can't actually place the data we read out anywhere
if ( ! info ) {
qp_dprintf ( " Failed to prepare stream for read, output info buffer unavailable \n " ) ;
return false ;
}
// Seek to the frame
qgf_seek_to_frame_descriptor ( & qgf_image - > stream , frame_number ) ;
// Read the frame descriptor
qgf_frame_v1_t frame_descriptor ;
if ( qp_stream_read ( & frame_descriptor , sizeof ( qgf_frame_v1_t ) , 1 , & qgf_image - > stream ) ! = 1 ) {
qp_dprintf ( " Failed to read frame_descriptor, expected length was not %d \n " , ( int ) sizeof ( qgf_frame_v1_t ) ) ;
return false ;
}
// Parse out the frame info
if ( ! qgf_parse_frame_descriptor ( & frame_descriptor , & info - > bpp , & info - > has_palette , & info - > is_delta , & info - > compression_scheme , & info - > delay ) ) {
return false ;
}
// Ensure we aren't reusing any palette
qp_internal_invalidate_palette ( ) ;
2022-04-17 04:30:51 +06:00
if ( ! qp_internal_bpp_capable ( info - > bpp ) ) {
2023-01-14 16:24:54 +06:00
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 ) ;
2022-04-17 04:30:51 +06:00
qp_comms_stop ( device ) ;
return false ;
}
2022-04-13 14:00:18 +06:00
// Handle palette if needed
const uint16_t palette_entries = 1u < < info - > bpp ;
bool needs_pixconvert = false ;
if ( info - > has_palette ) {
// Load the palette from the stream
if ( ! qp_internal_load_qgf_palette ( ( qp_stream_t * ) & qgf_image - > stream , info - > bpp ) ) {
return false ;
}
needs_pixconvert = true ;
} else {
2023-01-14 16:24:54 +06:00
if ( info - > bpp < = 8 ) {
// Interpolate from fg/bg
needs_pixconvert = qp_internal_interpolate_palette ( fg_hsv888 , bg_hsv888 , palette_entries ) ;
}
2022-04-13 14:00:18 +06:00
}
if ( needs_pixconvert ) {
// Convert the palette to native format
if ( ! driver - > driver_vtable - > palette_convert ( device , palette_entries , qp_internal_global_pixel_lookup_table ) ) {
qp_dprintf ( " qp_drawimage_recolor: fail (could not convert pixels to native) \n " ) ;
qp_comms_stop ( device ) ;
return false ;
}
}
// Handle delta if needed
if ( info - > is_delta ) {
qgf_delta_v1_t delta_descriptor ;
if ( qp_stream_read ( & delta_descriptor , sizeof ( qgf_delta_v1_t ) , 1 , & qgf_image - > stream ) ! = 1 ) {
qp_dprintf ( " Failed to read delta_descriptor, expected length was not %d \n " , ( int ) sizeof ( qgf_delta_v1_t ) ) ;
return false ;
}
info - > left = delta_descriptor . left ;
info - > top = delta_descriptor . top ;
info - > right = delta_descriptor . right ;
info - > bottom = delta_descriptor . bottom ;
}
// Read the data block
qgf_data_v1_t data_descriptor ;
if ( qp_stream_read ( & data_descriptor , sizeof ( qgf_data_v1_t ) , 1 , & qgf_image - > stream ) ! = 1 ) {
qp_dprintf ( " Failed to read data_descriptor, expected length was not %d \n " , ( int ) sizeof ( qgf_data_v1_t ) ) ;
return false ;
}
// Stream is now at the point of being able to read pixdata
return true ;
}
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 ) {
qp_dprintf ( " qp_drawimage_recolor: entry \n " ) ;
2023-03-25 23:56:04 +06:00
painter_driver_t * driver = ( painter_driver_t * ) device ;
2022-04-13 14:00:18 +06:00
if ( ! driver - > validate_ok ) {
qp_dprintf ( " qp_drawimage_recolor: fail (validation_ok == false) \n " ) ;
return false ;
}
qgf_image_handle_t * qgf_image = ( qgf_image_handle_t * ) image ;
if ( ! qgf_image - > validate_ok ) {
qp_dprintf ( " qp_drawimage_recolor: fail (invalid image) \n " ) ;
return false ;
}
// Read the frame info
if ( ! qp_drawimage_prepare_frame_for_stream_read ( device , qgf_image , frame_number , fg_hsv888 , bg_hsv888 , frame_info ) ) {
qp_dprintf ( " qp_drawimage_recolor: fail (could not read frame %d) \n " , frame_number ) ;
return false ;
}
if ( ! qp_comms_start ( device ) ) {
qp_dprintf ( " qp_drawimage_recolor: fail (could not start comms) \n " ) ;
return false ;
}
uint16_t l , t , r , b ;
if ( frame_info - > is_delta ) {
l = x + frame_info - > left ;
t = y + frame_info - > top ;
r = x + frame_info - > right - 1 ;
b = y + frame_info - > bottom - 1 ;
} else {
l = x ;
t = y ;
r = x + image - > width - 1 ;
b = y + image - > height - 1 ;
}
uint32_t pixel_count = ( ( uint32_t ) ( r - l + 1 ) ) * ( b - t + 1 ) ;
// Configure where we're going to be rendering to
if ( ! driver - > driver_vtable - > viewport ( device , l , t , r , b ) ) {
qp_dprintf ( " qp_drawimage_recolor: fail (could not set viewport) \n " ) ;
qp_comms_stop ( device ) ;
return false ;
}
// Set up the input state
2023-03-25 23:56:04 +06:00
qp_internal_byte_input_state_t input_state = { . device = device , . src_stream = & qgf_image - > stream } ;
qp_internal_byte_input_callback input_callback = qp_internal_prepare_input_state ( & input_state , frame_info - > compression_scheme ) ;
2022-04-13 14:00:18 +06:00
if ( input_callback = = NULL ) {
qp_dprintf ( " qp_drawimage_recolor: fail (invalid image compression scheme) \n " ) ;
qp_comms_stop ( device ) ;
return false ;
}
2023-01-14 16:24:54 +06:00
bool ret = false ;
if ( frame_info - > bpp < = 8 ) {
// Set up the output state
2023-03-25 23:56:04 +06:00
qp_internal_pixel_output_state_t output_state = { . device = device , . pixel_write_pos = 0 , . max_pixels = qp_internal_num_pixels_in_buffer ( device ) } ;
2022-04-13 14:00:18 +06:00
2023-01-14 16:24:54 +06:00
// Decode the pixel data and stream to the display
ret = qp_internal_decode_palette ( device , pixel_count , frame_info - > bpp , input_callback , & input_state , qp_internal_global_pixel_lookup_table , qp_internal_pixel_appender , & output_state ) ;
// Any leftovers need transmission as well.
if ( ret & & output_state . pixel_write_pos > 0 ) {
ret & = driver - > driver_vtable - > pixdata ( device , qp_internal_global_pixdata_buffer , output_state . pixel_write_pos ) ;
}
2023-04-03 08:42:46 +06:00
} else if ( frame_info - > bpp ! = driver - > native_bits_per_pixel ) {
// Prevent stuff like drawing 24bpp images on 16bpp displays
qp_dprintf ( " Image's bpp doesn't match the target display's native_bits_per_pixel \n " ) ;
return false ;
2023-01-14 16:24:54 +06:00
} else {
// Set up the output state
2023-03-25 23:56:04 +06:00
qp_internal_byte_output_state_t output_state = { . device = device , . byte_write_pos = 0 , . max_bytes = qp_internal_num_pixels_in_buffer ( device ) * driver - > native_bits_per_pixel / 8 } ;
2023-01-14 16:24:54 +06:00
// Stream the raw pixel data to the display
uint32_t byte_count = pixel_count * frame_info - > bpp / 8 ;
ret = qp_internal_send_bytes ( device , byte_count , input_callback , & input_state , qp_internal_byte_appender , & output_state ) ;
// Any leftovers need transmission as well.
if ( ret & & output_state . byte_write_pos > 0 ) {
ret & = driver - > driver_vtable - > pixdata ( device , qp_internal_global_pixdata_buffer , output_state . byte_write_pos * 8 / driver - > native_bits_per_pixel ) ;
}
2022-04-13 14:00:18 +06:00
}
qp_dprintf ( " qp_drawimage_recolor: %s \n " , ret ? " ok " : " fail " ) ;
qp_comms_stop ( device ) ;
return ret ;
}
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 ) {
qgf_frame_info_t frame_info = { 0 } ;
qp_pixel_t fg_hsv888 = { . hsv888 = { . h = hue_fg , . s = sat_fg , . v = val_fg } } ;
qp_pixel_t bg_hsv888 = { . hsv888 = { . h = hue_bg , . s = sat_bg , . v = val_bg } } ;
return qp_drawimage_recolor_impl ( device , x , y , image , 0 , & frame_info , fg_hsv888 , bg_hsv888 ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_animate
deferred_token qp_animate ( painter_device_t device , uint16_t x , uint16_t y , painter_image_handle_t image ) {
return qp_animate_recolor ( device , x , y , image , 0 , 0 , 255 , 0 , 0 , 0 ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_animate_recolor
typedef struct animation_state_t {
painter_device_t device ;
uint16_t x ;
uint16_t y ;
painter_image_handle_t image ;
qp_pixel_t fg_hsv888 ;
qp_pixel_t bg_hsv888 ;
uint16_t frame_number ;
deferred_token defer_token ;
} animation_state_t ;
static deferred_executor_t animation_executors [ QUANTUM_PAINTER_CONCURRENT_ANIMATIONS ] = { 0 } ;
static animation_state_t animation_states [ QUANTUM_PAINTER_CONCURRENT_ANIMATIONS ] = { 0 } ;
static deferred_token qp_render_animation_state ( animation_state_t * state , uint16_t * delay_ms ) {
qgf_frame_info_t frame_info = { 0 } ;
qp_dprintf ( " qp_render_animation_state: entry (frame #%d) \n " , ( int ) state - > frame_number ) ;
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 ) ;
if ( ret ) {
+ + state - > frame_number ;
if ( state - > frame_number > = state - > image - > frame_count ) {
state - > frame_number = 0 ;
}
* delay_ms = frame_info . delay ;
}
qp_dprintf ( " qp_render_animation_state: %s (delay %dms) \n " , ret ? " ok " : " fail " , ( int ) ( * delay_ms ) ) ;
return ret ;
}
static uint32_t animation_callback ( uint32_t trigger_time , void * cb_arg ) {
animation_state_t * state = ( animation_state_t * ) cb_arg ;
uint16_t delay_ms ;
bool ret = qp_render_animation_state ( state , & delay_ms ) ;
if ( ! ret ) {
// Setting the device to NULL clears the animation slot
state - > device = NULL ;
}
// If we're successful, keep animating -- returning 0 cancels the deferred execution
return ret ? delay_ms : 0 ;
}
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 ) {
qp_dprintf ( " qp_animate_recolor: entry \n " ) ;
animation_state_t * anim_state = NULL ;
for ( int i = 0 ; i < QUANTUM_PAINTER_CONCURRENT_ANIMATIONS ; + + i ) {
if ( animation_states [ i ] . device = = NULL ) {
anim_state = & animation_states [ i ] ;
break ;
}
}
if ( ! anim_state ) {
qp_dprintf ( " qp_animate_recolor: fail (could not find free animation slot) \n " ) ;
return INVALID_DEFERRED_TOKEN ;
}
// Prepare the animation state
anim_state - > device = device ;
anim_state - > x = x ;
anim_state - > y = y ;
anim_state - > image = image ;
anim_state - > fg_hsv888 = ( qp_pixel_t ) { . hsv888 = { . h = hue_fg , . s = sat_fg , . v = val_fg } } ;
anim_state - > bg_hsv888 = ( qp_pixel_t ) { . hsv888 = { . h = hue_bg , . s = sat_bg , . v = val_bg } } ;
anim_state - > frame_number = 0 ;
// Draw the first frame
uint16_t delay_ms ;
if ( ! qp_render_animation_state ( anim_state , & delay_ms ) ) {
anim_state - > device = NULL ; // disregard the allocated animation slot
qp_dprintf ( " qp_animate_recolor: fail (could not render first frame) \n " ) ;
return INVALID_DEFERRED_TOKEN ;
}
// Set up the timer
anim_state - > defer_token = defer_exec_advanced ( animation_executors , QUANTUM_PAINTER_CONCURRENT_ANIMATIONS , delay_ms , animation_callback , anim_state ) ;
if ( anim_state - > defer_token = = INVALID_DEFERRED_TOKEN ) {
anim_state - > device = NULL ; // disregard the allocated animation slot
qp_dprintf ( " qp_animate_recolor: fail (could not set up animation executor) \n " ) ;
return INVALID_DEFERRED_TOKEN ;
}
qp_dprintf ( " qp_animate_recolor: ok (deferred token = %d) \n " , ( int ) anim_state - > defer_token ) ;
return anim_state - > defer_token ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_stop_animation
void qp_stop_animation ( deferred_token anim_token ) {
for ( int i = 0 ; i < QUANTUM_PAINTER_CONCURRENT_ANIMATIONS ; + + i ) {
if ( animation_states [ i ] . defer_token = = anim_token ) {
cancel_deferred_exec_advanced ( animation_executors , QUANTUM_PAINTER_CONCURRENT_ANIMATIONS , anim_token ) ;
animation_states [ i ] . device = NULL ;
return ;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter Core API: qp_internal_animation_tick
void qp_internal_animation_tick ( void ) {
static uint32_t last_anim_exec = 0 ;
deferred_exec_advanced_task ( animation_executors , QUANTUM_PAINTER_CONCURRENT_ANIMATIONS , & last_anim_exec ) ;
}