Allow gapless frames

This commit is contained in:
Kovid Goyal 2021-02-01 16:22:36 +05:30
parent 9cf5348c36
commit bb5c5a8e4f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 29 additions and 12 deletions

View File

@ -551,15 +551,16 @@ Key Value Default Description
``c`` Positive integer ``0`` The 1-based frame number of the frame whose image data serves as the base data
when creating a new frame, by default the base data is black, fully transparent pixels
``r`` Positive integer ``0`` The 1-based frame number of the frame that is being edited. By default, a new frame is created
``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the previous one. Values less than
one are ignored, new frames are given a default gap of ``40ms`` if not specified.
``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the previous one. A value of
zero is ignored. Negative values create a *gapless* frame. If not specified,
frames have a default gap of ``40ms``. The root frame defaults to zero gap.
**Keys for animation control**
-----------------------------------------------------------
``s`` Positive integer ``0`` ``1`` - start animation, ``>1`` - stop animation
``r`` Positive integer ``0`` The 1-based frame number of the frame that is being affected
``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the previous one. Values less than
one are ignored
``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the previous one. A value of
zero is ignored. Negative values create a *gapless* frame.
``c`` Positive integer ``0`` The 1-based frame number of the frame that should be made the current frame

View File

@ -766,7 +766,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
rd->texture_id = img->texture_id;
img->is_drawn = true;
}
if (img->is_drawn && !was_drawn && img->animation_enabled && img->extra_framecnt) {
if (img->is_drawn && !was_drawn && img->animation_enabled && img->extra_framecnt && img->animation_duration) {
self->has_images_needing_animation = true;
global_state.has_active_animated_images = true;
}
@ -821,6 +821,14 @@ update_current_frame(GraphicsManager *self, Image *img, void *data) {
img->current_frame_shown_at = monotonic();
}
static inline void
change_gap(Image *img, Frame *f, int32_t gap) {
uint32_t prev_gap = f->gap;
f->gap = MAX(0, gap);
img->animation_duration = prev_gap < img->animation_duration ? img->animation_duration - prev_gap : 0;
img->animation_duration += f->gap;
}
static Image*
handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, Image *img, const uint8_t *payload, bool *is_dirty) {
uint32_t frame_number = g->_frame_number, fmt = g->format ? g->format : RGBA;
@ -925,15 +933,15 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I
ABRT("ENOSPC", "Failed to cache data for image frame");
}
if (is_new_frame) {
if (!img->extra_framecnt) img->root_frame.gap = g->_gap > 0 ? g->_gap : DEFAULT_GAP;
Frame *frames = realloc(img->extra_frames, sizeof(img->extra_frames[0]) * img->extra_framecnt + 1);
if (!frames) ABRT("ENOMEM", "Out of memory");
img->extra_frames = frames;
img->extra_framecnt++;
img->extra_frames[frame_number - 2].gap = DEFAULT_GAP;
img->extra_frames[frame_number - 2].id = key.frame_id;
img->animation_duration += DEFAULT_GAP;
}
if (g->_gap > 0) img->extra_frames[frame_number - 2].gap = g->_gap;
if (g->_gap > 0) change_gap(img, img->extra_frames + frame_number - 2, g->_gap);
}
return img;
}
@ -957,17 +965,21 @@ handle_delete_frame_command(GraphicsManager *self, const GraphicsCommand *g, boo
*is_dirty = true;
ImageAndFrame key = {.image_id=img->internal_id};
bool remove_root = frame_number == 1;
uint32_t removed_gap = 0;
if (remove_root) {
key.frame_id = img->root_frame.id;
remove_from_cache(self, key);
if (PyErr_Occurred()) PyErr_Print();
removed_gap = img->root_frame.gap;
img->root_frame = img->extra_frames[0];
}
unsigned removed_idx = remove_root ? 0 : frame_number - 2;
if (!remove_root) {
key.frame_id = img->extra_frames[removed_idx].id;
removed_gap = img->extra_frames[removed_idx].gap;
remove_from_cache(self, key);
}
img->animation_duration = removed_gap < img->animation_duration ? img->animation_duration - removed_gap : 0;
if (PyErr_Occurred()) PyErr_Print();
if (removed_idx < img->extra_framecnt - 1) memmove(img->extra_frames + removed_idx, img->extra_frames + removed_idx + 1, sizeof(img->extra_frames[0]) * img->extra_framecnt - 1 - removed_idx);
img->extra_framecnt--;
@ -987,7 +999,7 @@ handle_animation_control_command(GraphicsManager *self, bool *is_dirty, const Gr
uint32_t frame_idx = g->_frame_number - 1;
if (frame_idx <= img->extra_framecnt) {
Frame *f = frame_idx ? img->extra_frames + frame_idx - 1 : &img->root_frame;
if (g->_gap > 0) f->gap = g->_gap;
if (g->_gap) change_gap(img, f, g->_gap);
}
}
if (g->_other_frame_number) {
@ -1018,13 +1030,15 @@ scan_active_animations(GraphicsManager *self, const monotonic_t now, monotonic_t
self->context_made_current_for_this_command = true;
for (size_t i = self->image_count; i-- > 0;) {
Image *img = self->images + i;
if (img->animation_enabled && img->extra_framecnt && img->is_drawn) {
if (img->animation_enabled && img->extra_framecnt && img->is_drawn && img->animation_duration) {
self->has_images_needing_animation = true;
Frame *next_frame = img->current_frame_index + 1 < img->extra_framecnt ? img->extra_frames + img->current_frame_index + 1 : &img->root_frame;
monotonic_t next_frame_at = img->current_frame_shown_at + ms_to_monotonic_t(next_frame->gap);
if (now >= next_frame_at) {
dirtied = true;
do {
img->current_frame_index = (img->current_frame_index + 1) % (img->extra_framecnt + 1);
} while(!current_frame(img)->gap);
update_current_frame(self, img, NULL);
next_frame = img->current_frame_index + 1 < img->extra_framecnt ? img->extra_frames + img->current_frame_index + 1 : &img->root_frame;
}
@ -1341,10 +1355,11 @@ image_as_dict(GraphicsManager *self, Image *img) {
if (PyErr_Occurred()) { Py_CLEAR(frames); return NULL; }
}
key.frame_id = img->root_frame.id;
return Py_BuildValue("{sI sI sI sI sK sI sI sO sO sO sI sI sI sN sN}",
return Py_BuildValue("{sI sI sI sI sK sI sI sO sO sO sI sI sI sI sN sN}",
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt), U(client_number),
B(data_loaded), B(is_4byte_aligned), B(animation_enabled),
U(current_frame_index), "root_frame_gap", img->root_frame.gap, U(current_frame_index),
U(animation_duration),
"data", read_from_cache_python(self, key), "extra_frames", frames
);
#undef B

View File

@ -57,6 +57,7 @@ typedef struct {
ImageRef *refs;
Frame *extra_frames, root_frame;
uint32_t current_frame_index, frame_id_counter;
uint64_t animation_duration;
size_t refcnt, refcap, extra_framecnt;
monotonic_t atime;
size_t used_storage;

View File

@ -638,7 +638,7 @@ class TestGraphics(BaseTest):
))
# test changing gaps
img = g.image_for_client_id(1)
self.assertEqual(img['root_frame_gap'], 77)
self.assertEqual(img['root_frame_gap'], 0)
self.assertIsNone(li(a='a', i=1, r=1, z=13))
img = g.image_for_client_id(1)
self.assertEqual(img['root_frame_gap'], 13)