图库: 阅读器页槽改 RGB565 常驻,页图内存减半(M32)
- 页槽从 RGBA 改成「已落框的 RGB565 位图」(2B/px):真机 480×853 页 1599KB → 800KB, 两页常驻 3.2MB → 1.6MB。解码仍在 worker 里产出 RGBA,当场转 565 并释放 RGBA。 - SDK 新增 565 原语(meow_ui_draw.h / meow_ui_gfx.c):meow_gfx_blit_rgb565 命中页每行 一次 memcpy(单次拷贝、无每帧重采样),缩放兜底走 565 最近邻。量化与 back_buf 用同一个 rgb565(),565 屏上落下的像素与旧 RGBA 路径逐像素一致(不引入色偏/新增色带)。 - 预算按 565 重算:单页像素上限沿用旧 3MB@RGBA 的同几何口径(=1.5MB@565), MR_RESIDENT_CAP = 3 槽 + 1 张在途 = 3.2MB;reader_estimate_peak 新增 convert_bytes (落槽暂存 2B/px)与 peak_bytes 一起参与门控,避免实测超预算。 - 结果队列(已解码、未落槽)字节计入总预算(pending_bytes),不再「偷吃」最多 2 页。 - CBZ meta() 改读条目 4KB 前缀(mz_read_prefix;deflate 解到 4KB 即停,输入缓冲走栈), 500 页排版不再触发 500 次全量解压。 - 测试:B3/B8 的 565 常驻与在途预算断言、CBZ 500 页 stored/deflate 前后对比、 P12 565 与 RGBA 路径的逐像素颜色对比(量化误差/RMSE/灰阶梯)。
This commit is contained in:
1 parent
bbc73635a6
commit
7036020005
11 files changed
+910
-111
No files matched your search
@@ -15,7 +15,11 @@
|
||||
* P5 「先点适高,再切条漫」:scaled 不增长(评审点名的第二入口);
|
||||
* P6 双击放大再双击缩回:缩回后按新几何重解 → scaled 不增长(P2-1);
|
||||
* P7 静止若干帧不产生新解码(无重解抖动/活锁,P2-2);
|
||||
* P8 驻留页图有字节上界(<= 9MB)。
|
||||
* P8 驻留页图有字节上界(<= 9MB);
|
||||
* P12(M32)颜色正确性:页槽改 RGB565 后,
|
||||
* a) 565 blit 与 RGBA blit 在 565 屏上**逐像素一致**(同一套量化 → 不引入色偏);
|
||||
* b) 565 量化误差在理论上界内(R/B ≤7、G ≤3),并给出 RMSE 与代表性采样;
|
||||
* c) 8bit 灰阶梯/肤色渐变单调、无新增色带(与 RGBA 路径的阶梯数完全相同)。
|
||||
*
|
||||
* 编译:test/run_tests.sh(与 test_open_book_host 同口径;只发布 meow_ui.a 时 SKIP)。
|
||||
*/
|
||||
@@ -25,6 +29,7 @@
|
||||
#include "logic/reader/nav.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -83,6 +88,50 @@ static int write_bmp24(const char *path, int w, int h, unsigned seed)
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
/* 颜色测试夹具:mode 0 = 8bit 灰阶梯(x 方向 0→255);mode 1 = 肤色渐变(R/G/B 同向缓降)。 */
|
||||
static int write_bmp24_grad(const char *path, int w, int h, int mode)
|
||||
{
|
||||
int stride = ((w * 3 + 3) / 4) * 4;
|
||||
int imgsz = stride * h;
|
||||
unsigned char fh[14] = { 0 }, ih[40] = { 0 };
|
||||
fh[0] = 'B'; fh[1] = 'M';
|
||||
put32(fh + 2, (unsigned)(54 + imgsz));
|
||||
put32(fh + 10, 54);
|
||||
put32(ih + 0, 40);
|
||||
put32(ih + 4, (unsigned)w);
|
||||
put32(ih + 8, (unsigned)h);
|
||||
put16(ih + 12, 1);
|
||||
put16(ih + 14, 24);
|
||||
put32(ih + 20, (unsigned)imgsz);
|
||||
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
int ok = fwrite(fh, 1, sizeof fh, f) == sizeof fh &&
|
||||
fwrite(ih, 1, sizeof ih, f) == sizeof ih;
|
||||
unsigned char *row = (unsigned char *)calloc(1, (size_t)stride);
|
||||
if (!row) ok = 0;
|
||||
for (int y = 0; ok && y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
int r, g, b;
|
||||
if (mode == 0) {
|
||||
int v = w > 1 ? x * 255 / (w - 1) : 0;
|
||||
r = g = b = v;
|
||||
} else {
|
||||
r = 235 - (x * 65) / (w > 1 ? w - 1 : 1);
|
||||
g = 175 - (x * 60) / (w > 1 ? w - 1 : 1);
|
||||
b = 130 - (x * 45) / (w > 1 ? w - 1 : 1);
|
||||
}
|
||||
row[x * 3 + 0] = (unsigned char)b; /* BMP 是 BGR */
|
||||
row[x * 3 + 1] = (unsigned char)g;
|
||||
row[x * 3 + 2] = (unsigned char)r;
|
||||
}
|
||||
if (fwrite(row, 1, (size_t)stride, f) != (size_t)stride) ok = 0;
|
||||
}
|
||||
free(row);
|
||||
if (fclose(f) != 0) ok = 0;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int mkdir_p(const char *path)
|
||||
{
|
||||
char tmp[MR_PATH_MAX];
|
||||
@@ -156,6 +205,125 @@ static void mode_toggle(void)
|
||||
|
||||
static void draw_once(void) { meow_ui_paint_full(); }
|
||||
|
||||
/* ---- P12(M32):RGB565 页槽颜色正确性 ----
|
||||
* 同一张图用两条路径各画一次,逐像素比较 back_buf(565 屏):
|
||||
* a) RGBA 路径 meow_ui_img → put_px → rgb565()(今天的上屏方式)
|
||||
* b) 565 路径 meow_gfx_blit_rgb565(页槽新路径)
|
||||
* 两条路径本该逐像素相同(同一个量化函数);再量 565 相对 8bit 源的量化误差,
|
||||
* 确认不出界、灰阶梯单调(不新增色带)。 */
|
||||
static void capture565(int x, int y, int w, int h, uint16_t *out)
|
||||
{
|
||||
int pitch = meow_ui_back_buf_pitch();
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
for (int yy = 0; yy < h; yy++)
|
||||
for (int xx = 0; xx < w; xx++)
|
||||
out[(size_t)yy * w + xx] =
|
||||
*(const uint16_t *)(bb + (size_t)(y + yy) * pitch + (size_t)(x + xx) * 2);
|
||||
}
|
||||
|
||||
static int ex5i(int v) { return (v << 3) | (v >> 2); }
|
||||
static int ex6i(int v) { return (v << 2) | (v >> 4); }
|
||||
|
||||
static void color_check(void)
|
||||
{
|
||||
const char *dir = getenv("MEOW_DATA_DIR");
|
||||
if (!dir || !dir[0]) dir = ".";
|
||||
if (meow_ui_back_buf_bpp() != 16) {
|
||||
printf(" SKIP color check: host back_buf is %d bpp (only 565 screens are comparable)\n",
|
||||
meow_ui_back_buf_bpp());
|
||||
return;
|
||||
}
|
||||
struct { const char *name; int w, h, mode; const char *label; } fx[2] = {
|
||||
{ "color_gray.bmp", 256, 64, 0, "8bit 灰阶梯 256→0..255" },
|
||||
{ "color_skin.bmp", 480, 360, 1, "肤色渐变 235,175,130 → 170,115,85" },
|
||||
};
|
||||
for (size_t k = 0; k < sizeof fx / sizeof fx[0]; k++) {
|
||||
char path[MR_PATH_MAX];
|
||||
snprintf(path, sizeof path, "%s/%s", dir, fx[k].name);
|
||||
if (write_bmp24_grad(path, fx[k].w, fx[k].h, fx[k].mode) != 0) {
|
||||
CHECK(0, "颜色夹具写入 %s", fx[k].name);
|
||||
continue;
|
||||
}
|
||||
meow_img *im = meow_img_load_file(path);
|
||||
if (!im) { CHECK(0, "颜色夹具解码 %s", fx[k].name); continue; }
|
||||
int w = meow_img_w(im), h = meow_img_h(im);
|
||||
size_t n = (size_t)w * h;
|
||||
uint16_t *px = (uint16_t *)malloc(n * 2);
|
||||
uint16_t *ref = (uint16_t *)malloc(n * 2);
|
||||
uint16_t *got = (uint16_t *)malloc(n * 2);
|
||||
if (!px || !ref || !got) {
|
||||
CHECK(0, "颜色夹具缓冲分配");
|
||||
free(px); free(ref); free(got); meow_img_free(im); continue;
|
||||
}
|
||||
meow_gfx_rgba_to_rgb565(meow_img_pixels(im), px, (int)n);
|
||||
|
||||
meow_ui_clear(0x000000);
|
||||
meow_ui_img(0, 0, im); /* 旧路径:RGBA blit */
|
||||
capture565(0, 0, w, h, ref);
|
||||
meow_ui_clear(0x000000);
|
||||
meow_gfx_blit_rgb565(0, 0, w, h, px); /* 新路径:565 blit(页槽) */
|
||||
capture565(0, 0, w, h, got);
|
||||
|
||||
size_t diff = 0;
|
||||
for (size_t i = 0; i < n; i++) if (ref[i] != got[i]) diff++;
|
||||
|
||||
/* 量化误差:565 展开回 8bit 与源像素比。 */
|
||||
const unsigned char *rgba = meow_img_pixels(im);
|
||||
long long se = 0;
|
||||
int maxrb = 0, maxg = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
int r5 = (got[i] >> 11) & 0x1F, g6 = (got[i] >> 5) & 0x3F, b5 = got[i] & 0x1F;
|
||||
int er = ex5i(r5) - rgba[i * 4 + 0];
|
||||
int eg = ex6i(g6) - rgba[i * 4 + 1];
|
||||
int eb = ex5i(b5) - rgba[i * 4 + 2];
|
||||
if (abs(er) > maxrb) maxrb = abs(er);
|
||||
if (abs(eb) > maxrb) maxrb = abs(eb);
|
||||
if (abs(eg) > maxg) maxg = abs(eg);
|
||||
se += (long long)er * er + (long long)eg * eg + (long long)eb * eb;
|
||||
}
|
||||
double rmse = sqrt((double)se / (double)(n * 3));
|
||||
|
||||
/* 灰阶梯:输出必须单调不减,且阶梯数与 RGBA 路径完全一致(没有新增色带)。 */
|
||||
int mono = 1, lv = 1, lv_ref = 1;
|
||||
for (int x = 1; x < w; x++) {
|
||||
uint16_t a = got[(size_t)(h / 2) * w + x], b = got[(size_t)(h / 2) * w + x - 1];
|
||||
uint16_t ar = ref[(size_t)(h / 2) * w + x], br = ref[(size_t)(h / 2) * w + x - 1];
|
||||
if (a < b) mono = 0;
|
||||
if (a != b) lv++;
|
||||
if (ar != br) lv_ref++;
|
||||
}
|
||||
|
||||
printf(" [P12] %-42s 差异像素=%zu/%zu 误差 max R/B=%d G=%d RMSE=%.2f 阶梯=%d\n",
|
||||
fx[k].label, diff, n, maxrb, maxg, rmse, lv);
|
||||
if (fx[k].mode == 0) {
|
||||
for (int x = 0; x < w; x += w / 8) {
|
||||
int i = (h / 2) * w + x;
|
||||
printf(" x=%3d 源灰度=%3d → RGBA路径=0x%04X 565路径=0x%04X\n",
|
||||
x, rgba[(size_t)i * 4], ref[i], got[i]);
|
||||
}
|
||||
} else {
|
||||
for (int x = 0; x < w; x += w / 4) {
|
||||
int i = (h / 2) * w + x;
|
||||
printf(" x=%3d 源 RGB=(%3d,%3d,%3d) → RGBA路径=0x%04X 565路径=0x%04X\n",
|
||||
x, rgba[(size_t)i * 4], rgba[(size_t)i * 4 + 1], rgba[(size_t)i * 4 + 2],
|
||||
ref[i], got[i]);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(diff == 0, "%s:565 blit 与 RGBA blit 逐像素一致(差异 %zu)", fx[k].label, diff);
|
||||
CHECK(maxrb <= 7 && maxg <= 3,
|
||||
"%s:量化误差在 565 理论界内(R/B %d ≤7,G %d ≤3)", fx[k].label, maxrb, maxg);
|
||||
CHECK(rmse <= 3.5, "%s:RMSE %.2f ≤3.5", fx[k].label, rmse);
|
||||
if (fx[k].mode == 0) {
|
||||
CHECK(mono, "%s:灰阶梯输出单调(无色带跳变/反转)", fx[k].label);
|
||||
CHECK(lv >= 30 && lv == lv_ref,
|
||||
"%s:阶梯数 %d ≥30 且与 RGBA 路径一致(%d)", fx[k].label, lv, lv_ref);
|
||||
}
|
||||
free(px); free(ref); free(got);
|
||||
meow_img_free(im);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const char *base = getenv("TMPDIR");
|
||||
@@ -328,6 +496,9 @@ int main(void)
|
||||
mr_reader_ui_decode_count());
|
||||
CHECK(kb <= 9 * 1024, "resident page images bounded (got %ldKB)", kb);
|
||||
|
||||
/* ---- P12(M32):565 页槽的颜色正确性(需要 565 屏的 back_buf)---- */
|
||||
color_check();
|
||||
|
||||
/* ---- P9 800x1280(评审表格另一档):翻页 + 条漫都必须零重采样 ---- */
|
||||
meow_view_page_pop();
|
||||
mr_reader_open_dir(book2);
|
||||
|
||||
Reference in new issue
Block a user