1212#include <stdint.h>
1313#include <core/dangerous_defines.h>
1414
15- #define ACTIVE_SHIFT 2
15+ #define ACTIVE_SHIFT 2
16+ #define BUBBLE_ANIMATION_RESUME_PREVIEW_MAX_BYTES (4U * 1024U)
17+ #define COMPRESS_ICON_HEADER_SIZE 4U
1618
1719typedef struct {
1820 const BubbleAnimation * current ;
@@ -24,6 +26,7 @@ typedef struct {
2426 uint8_t active_shift ;
2527 uint32_t active_ended_at ;
2628 Icon * freeze_frame ;
29+ uint8_t freeze_frame_index ;
2730} BubbleAnimationViewModel ;
2831
2932struct BubbleAnimationView {
@@ -62,7 +65,13 @@ static void bubble_animation_draw_callback(Canvas* canvas, void* model_) {
6265
6366 if (model -> freeze_frame ) {
6467 uint8_t y_offset = canvas_height (canvas ) - icon_get_height (model -> freeze_frame );
65- canvas_draw_icon (canvas , 0 , y_offset , model -> freeze_frame );
68+ canvas_draw_bitmap (
69+ canvas ,
70+ 0 ,
71+ y_offset ,
72+ icon_get_width (model -> freeze_frame ),
73+ icon_get_height (model -> freeze_frame ),
74+ icon_get_frame_data (model -> freeze_frame , model -> freeze_frame_index ));
6675 return ;
6776 }
6877
@@ -235,11 +244,20 @@ static void bubble_animation_timer_callback(void* context) {
235244
236245 BubbleAnimationViewModel * model = view_get_model (view -> view );
237246
247+ if (model -> freeze_frame ) {
248+ const uint8_t frame_count = icon_get_frame_count (model -> freeze_frame );
249+ if (frame_count > 1U ) {
250+ model -> freeze_frame_index = (model -> freeze_frame_index + 1U ) % frame_count ;
251+ }
252+ view_commit_model (view -> view , true);
253+ return ;
254+ }
255+
238256 if (model -> active_shift > 0 ) {
239257 activate = (-- model -> active_shift == 0 );
240258 }
241259
242- if (!model -> freeze_frame && ! activate ) {
260+ if (!activate ) {
243261 bubble_animation_next_frame (model );
244262 }
245263
@@ -250,28 +268,57 @@ static void bubble_animation_timer_callback(void* context) {
250268 }
251269}
252270
253- /* always freeze first passive frame, because
254- * animation is always activated at unfreezing and played
255- * passive frame first, and 2 frames after - active
256- */
257- static Icon * bubble_animation_clone_first_frame (const Icon * icon_orig ) {
258- furi_assert (icon_orig );
271+ static size_t bubble_animation_frame_data_size (const uint8_t * frame , size_t bitmap_size ) {
272+ furi_assert (frame );
273+
274+ if (frame [0 ] == 0U ) {
275+ return bitmap_size + 1U ;
276+ }
277+
278+ uint16_t compressed_size = 0U ;
279+ memcpy (& compressed_size , & frame [2 ], sizeof (compressed_size ));
280+ return COMPRESS_ICON_HEADER_SIZE + compressed_size ;
281+ }
282+
283+ static Icon * bubble_animation_clone_preview (const BubbleAnimation * animation ) {
284+ furi_assert (animation );
285+ const Icon * icon_orig = & animation -> icon_animation ;
259286 furi_assert (icon_orig -> frames );
260- furi_assert (icon_orig -> frames [0 ]);
287+ furi_assert (animation -> frame_order );
288+ const uint8_t preview_frames = animation -> passive_frames ? animation -> passive_frames :
289+ animation -> active_frames ;
290+ furi_assert (preview_frames > 0U );
291+
292+ const size_t bitmap_size = ROUND_UP_TO (icon_orig -> width , 8 ) * icon_orig -> height ;
293+ size_t preview_size = 0U ;
294+ uint8_t frame_count = 0U ;
295+ for (uint8_t i = 0U ; i < preview_frames ; ++ i ) {
296+ const uint8_t source_index = animation -> frame_order [i ];
297+ furi_assert (source_index < icon_orig -> frame_count );
298+ const size_t frame_size =
299+ bubble_animation_frame_data_size (icon_orig -> frames [source_index ], bitmap_size );
300+ if (frame_count &&
301+ ((preview_size + frame_size ) > BUBBLE_ANIMATION_RESUME_PREVIEW_MAX_BYTES )) {
302+ break ;
303+ }
304+ preview_size += frame_size ;
305+ ++ frame_count ;
306+ }
307+ furi_assert (frame_count > 0U );
261308
262309 Icon * icon_clone = malloc (sizeof (Icon ));
263310 memcpy (icon_clone , icon_orig , sizeof (Icon ));
264311
265- icon_clone -> frames = malloc (sizeof (uint8_t * ));
266- /* icon bitmap can be either compressed or not. It is compressed if
267- * compressed size is less than original, so max size for bitmap is
268- * uncompressed (width * height) + 1 byte (in uncompressed case)
269- * for compressed header
270- */
271- size_t max_bitmap_size = ROUND_UP_TO ( icon_orig -> width , 8 ) * icon_orig -> height + 1 ;
272- FURI_CONST_ASSIGN_PTR (icon_clone -> frames [0 ], malloc ( max_bitmap_size ) );
273- memcpy (( void * ) icon_clone -> frames [ 0 ], icon_orig -> frames [ 0 ], max_bitmap_size );
274- FURI_CONST_ASSIGN (icon_clone -> frame_count , 1 );
312+ icon_clone -> frames = malloc (sizeof (uint8_t * ) * frame_count );
313+ for ( uint8_t i = 0U ; i < frame_count ; ++ i ) {
314+ const uint8_t source_index = animation -> frame_order [ i ];
315+ const uint8_t * source_frame = icon_orig -> frames [ source_index ];
316+ const size_t frame_size = bubble_animation_frame_data_size ( source_frame , bitmap_size );
317+ uint8_t * frame = malloc ( frame_size );
318+ memcpy ( frame , source_frame , frame_size ) ;
319+ FURI_CONST_ASSIGN_PTR (icon_clone -> frames [i ], frame );
320+ }
321+ FURI_CONST_ASSIGN (icon_clone -> frame_count , frame_count );
275322
276323 return icon_clone ;
277324}
@@ -280,7 +327,9 @@ static void bubble_animation_release_frame(Icon** icon) {
280327 furi_assert (icon );
281328 furi_assert (* icon );
282329
283- free ((void * )(* icon )-> frames [0 ]);
330+ for (uint8_t i = 0U ; i < (* icon )-> frame_count ; ++ i ) {
331+ free ((void * )(* icon )-> frames [i ]);
332+ }
284333 free ((void * )(* icon )-> frames );
285334 free (* icon );
286335 * icon = NULL ;
@@ -372,9 +421,12 @@ void bubble_animation_view_set_animation(
372421 model -> current_bubble = bubble_animation_pick_bubble (model , false);
373422 model -> current_frame = 0 ;
374423 model -> active_cycle = 0 ;
424+ const bool frozen = model -> freeze_frame != NULL ;
375425 view_commit_model (view -> view , true);
376426
377- furi_timer_start (view -> timer , 1000 / new_animation -> icon_animation .frame_rate );
427+ if (!frozen ) {
428+ furi_timer_start (view -> timer , 1000 / new_animation -> icon_animation .frame_rate );
429+ }
378430}
379431
380432void bubble_animation_freeze (BubbleAnimationView * view ) {
@@ -383,20 +435,44 @@ void bubble_animation_freeze(BubbleAnimationView* view) {
383435 BubbleAnimationViewModel * model = view_get_model (view -> view );
384436 furi_assert (model -> current );
385437 furi_assert (!model -> freeze_frame );
386- model -> freeze_frame = bubble_animation_clone_first_frame (& model -> current -> icon_animation );
438+ model -> freeze_frame = bubble_animation_clone_preview (model -> current );
439+ model -> freeze_frame_index = 0U ;
387440 model -> current = NULL ;
388441 view_commit_model (view -> view , false);
389442 furi_timer_stop (view -> timer );
390443}
391444
445+ void bubble_animation_start_resume_preview (BubbleAnimationView * view ) {
446+ furi_assert (view );
447+
448+ BubbleAnimationViewModel * model = view_get_model (view -> view );
449+ furi_assert (model -> freeze_frame );
450+ model -> freeze_frame_index = 0U ;
451+ const uint8_t frame_count = icon_get_frame_count (model -> freeze_frame );
452+ const uint8_t frame_rate = model -> freeze_frame -> frame_rate ;
453+ view_commit_model (view -> view , true);
454+
455+ if ((frame_count > 1U ) && frame_rate ) {
456+ furi_timer_start (view -> timer , 1000U / frame_rate );
457+ }
458+ }
459+
392460void bubble_animation_unfreeze (BubbleAnimationView * view ) {
393461 furi_assert (view );
394462 uint8_t frame_rate ;
395463
464+ furi_timer_stop (view -> timer );
465+
396466 BubbleAnimationViewModel * model = view_get_model (view -> view );
397467 furi_assert (model -> freeze_frame );
398- bubble_animation_release_frame (& model -> freeze_frame );
399468 furi_assert (model -> current );
469+ const uint8_t playback_frames = model -> current -> passive_frames ?
470+ model -> current -> passive_frames :
471+ model -> current -> active_frames ;
472+ if (playback_frames ) {
473+ model -> current_frame = model -> freeze_frame_index % playback_frames ;
474+ }
475+ bubble_animation_release_frame (& model -> freeze_frame );
400476 frame_rate = model -> current -> icon_animation .frame_rate ;
401477 view_commit_model (view -> view , true);
402478
0 commit comments