Keep album full-screen after GIF and reduce motion-region flicker.
Unify STRETCH layout for all photos, decode GIF 1:1 with disposal restore, and ship a 160x120 sample GIF. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -271,60 +271,76 @@ static int32_t gif_seek(GIFFILE *pFile, int32_t iPosition) {
|
||||
return pFile->iPos;
|
||||
}
|
||||
|
||||
// GIF disposal tracking (apply previous frame's disposal at start of next frame).
|
||||
static uint8_t g_gif_pend_disposal = 0;
|
||||
static int16_t g_gif_pend_x = 0;
|
||||
static int16_t g_gif_pend_y = 0;
|
||||
static int16_t g_gif_pend_w = 0;
|
||||
static int16_t g_gif_pend_h = 0;
|
||||
static uint16_t g_gif_bg565 = 0x0000;
|
||||
|
||||
static void gif_fill_rect(uint16_t *dst, int x0, int y0, int w, int h,
|
||||
uint16_t color) {
|
||||
if (!dst || w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
for (int y = y0; y < y0 + h; y++) {
|
||||
if (y < 0 || y >= (int)CANVAS_H) {
|
||||
continue;
|
||||
}
|
||||
for (int x = x0; x < x0 + w; x++) {
|
||||
if (x < 0 || x >= (int)CANVAS_W) {
|
||||
continue;
|
||||
}
|
||||
dst[(size_t)y * CANVAS_W + (size_t)x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1:1 blit into canvas (centered / clipped). No per-frame downscale — that
|
||||
// caused moving-region flicker with disposal + transparency.
|
||||
static void gif_draw(GIFDRAW *pDraw) {
|
||||
if (!pDraw || !g_draw_dst || g_src_w <= 0 || g_dst_w <= 0) {
|
||||
if (!pDraw || !g_draw_dst || g_src_w <= 0) {
|
||||
return;
|
||||
}
|
||||
if (g_gif_stop) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int src_y = pDraw->iY + pDraw->y;
|
||||
uint16_t *dst = g_draw_dst;
|
||||
|
||||
// Fast path: 1:1 into canvas region
|
||||
if (g_src_w == g_dst_w && g_src_h == g_dst_h) {
|
||||
const int y = g_off_y + src_y;
|
||||
if (y < 0 || y >= (int)CANVAS_H) {
|
||||
return;
|
||||
if (pDraw->y == 0) {
|
||||
// Restore previous frame rect to background before drawing this frame.
|
||||
if (g_gif_pend_disposal == 2) {
|
||||
gif_fill_rect(dst, g_gif_pend_x, g_gif_pend_y, g_gif_pend_w, g_gif_pend_h,
|
||||
g_gif_bg565);
|
||||
}
|
||||
uint16_t *row = dst + (size_t)y * CANVAS_W + (size_t)g_off_x + pDraw->iX;
|
||||
for (int x = 0; x < pDraw->iWidth; x++) {
|
||||
const uint8_t idx = pDraw->pPixels[x];
|
||||
if (pDraw->ucHasTransparency && idx == pDraw->ucTransparent) {
|
||||
continue;
|
||||
}
|
||||
row[x] = pDraw->pPalette[idx];
|
||||
g_gif_pend_disposal = pDraw->ucDisposalMethod;
|
||||
g_gif_pend_x = (int16_t)(g_off_x + pDraw->iX);
|
||||
g_gif_pend_y = (int16_t)(g_off_y + pDraw->iY);
|
||||
g_gif_pend_w = (int16_t)pDraw->iWidth;
|
||||
g_gif_pend_h = (int16_t)pDraw->iHeight;
|
||||
if (pDraw->pPalette) {
|
||||
g_gif_bg565 = pDraw->pPalette[pDraw->ucBackground];
|
||||
}
|
||||
}
|
||||
|
||||
const int y = g_off_y + pDraw->iY + pDraw->y;
|
||||
if (y < 0 || y >= (int)CANVAS_H) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scaled: only dest rows that sample this source line
|
||||
int dy = 0;
|
||||
while (dy < g_dst_h && (dy * g_src_h) / g_dst_h < src_y) {
|
||||
dy++;
|
||||
}
|
||||
while (dy < g_dst_h && (dy * g_src_h) / g_dst_h == src_y) {
|
||||
const int y = g_off_y + dy;
|
||||
if (y >= 0 && y < (int)CANVAS_H) {
|
||||
for (int dx = 0; dx < g_dst_w; dx++) {
|
||||
const int sx = (dx * g_src_w) / g_dst_w;
|
||||
const int lx = sx - pDraw->iX;
|
||||
if (lx < 0 || lx >= pDraw->iWidth) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t idx = pDraw->pPixels[lx];
|
||||
if (pDraw->ucHasTransparency && idx == pDraw->ucTransparent) {
|
||||
continue;
|
||||
}
|
||||
const int x = g_off_x + dx;
|
||||
if (x < 0 || x >= (int)CANVAS_W) {
|
||||
continue;
|
||||
}
|
||||
dst[(size_t)y * CANVAS_W + (size_t)x] = pDraw->pPalette[idx];
|
||||
}
|
||||
const int x0 = g_off_x + pDraw->iX;
|
||||
for (int i = 0; i < pDraw->iWidth; i++) {
|
||||
const int x = x0 + i;
|
||||
if (x < 0 || x >= (int)CANVAS_W) {
|
||||
continue;
|
||||
}
|
||||
dy++;
|
||||
const uint8_t idx = pDraw->pPixels[i];
|
||||
if (pDraw->ucHasTransparency && idx == pDraw->ucTransparent) {
|
||||
continue;
|
||||
}
|
||||
dst[(size_t)y * CANVAS_W + (size_t)x] = pDraw->pPalette[idx];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,12 +397,16 @@ static void gif_task(void * /*arg*/) {
|
||||
continue;
|
||||
}
|
||||
|
||||
g_dst_w = CANVAS_W;
|
||||
g_dst_h = CANVAS_H;
|
||||
// 1:1 into canvas (center; clip if GIF larger than 160x120).
|
||||
g_off_x = ((int)CANVAS_W - g_src_w) / 2;
|
||||
g_off_y = ((int)CANVAS_H - g_src_h) / 2;
|
||||
g_dst_w = g_src_w;
|
||||
g_dst_h = g_src_h;
|
||||
g_img_w = CANVAS_W;
|
||||
g_img_h = CANVAS_H;
|
||||
g_off_x = 0;
|
||||
g_off_y = 0;
|
||||
g_gif_pend_disposal = 0;
|
||||
g_gif_pend_w = 0;
|
||||
g_gif_pend_h = 0;
|
||||
|
||||
// Double-buffer: show A, decode into B (B starts as copy of A).
|
||||
g_show = g_pixels;
|
||||
|
||||
25
src/ui.cpp
25
src/ui.cpp
@@ -85,21 +85,12 @@ static void show_current() {
|
||||
const PhotoEntry *e = photo_list_get((uint16_t)g_index);
|
||||
const bool ok = e && photo_view_show(e);
|
||||
if (img_photo) {
|
||||
const bool gif = ok && photo_view_is_gif();
|
||||
// GIF: integer 2x scale (cheap, less tear). Still: full-screen stretch.
|
||||
if (gif) {
|
||||
lv_image_set_inner_align(img_photo, LV_IMAGE_ALIGN_DEFAULT);
|
||||
lv_image_set_scale(img_photo, 512); // 256=1x
|
||||
lv_image_set_pivot(img_photo, photo_view_canvas_w() / 2,
|
||||
photo_view_canvas_h() / 2);
|
||||
lv_obj_set_size(img_photo, photo_view_canvas_w(), photo_view_canvas_h());
|
||||
lv_obj_align(img_photo, LV_ALIGN_CENTER, 0, 0);
|
||||
} else {
|
||||
lv_image_set_scale(img_photo, 256);
|
||||
lv_image_set_inner_align(img_photo, LV_IMAGE_ALIGN_STRETCH);
|
||||
lv_obj_set_size(img_photo, UI_HOR_RES, UI_VER_RES);
|
||||
lv_obj_set_pos(img_photo, 0, 0);
|
||||
}
|
||||
// Always full-screen stretch so JPG/GIF share the same size (no shrink after GIF).
|
||||
lv_image_set_scale(img_photo, LV_SCALE_NONE);
|
||||
lv_image_set_pivot(img_photo, 0, 0);
|
||||
lv_image_set_inner_align(img_photo, LV_IMAGE_ALIGN_STRETCH);
|
||||
lv_obj_set_size(img_photo, UI_HOR_RES, UI_VER_RES);
|
||||
lv_obj_align(img_photo, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||
|
||||
const lv_image_dsc_t *dsc = photo_view_dsc();
|
||||
if (ok && dsc) {
|
||||
@@ -201,9 +192,9 @@ void ui_create() {
|
||||
lv_obj_add_event_cb(tap, on_tap_zone, LV_EVENT_CLICKED, nullptr);
|
||||
|
||||
img_photo = lv_image_create(scr);
|
||||
// Full-screen widget + stretch avoids scale-transform gaps/stripes behind the photo.
|
||||
lv_obj_set_size(img_photo, UI_HOR_RES, UI_VER_RES);
|
||||
lv_obj_set_pos(img_photo, 0, 0);
|
||||
lv_obj_align(img_photo, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||
lv_image_set_scale(img_photo, LV_SCALE_NONE);
|
||||
lv_image_set_inner_align(img_photo, LV_IMAGE_ALIGN_STRETCH);
|
||||
lv_obj_set_style_bg_color(img_photo, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_bg_opa(img_photo, LV_OPA_COVER, 0);
|
||||
|
||||
Reference in New Issue
Block a user