Skip to content

Commit 2cac54a

Browse files
committed
Add 3-panel BMP diff generator and use in tests
Implement emulator_save_diff_bmp to produce a 3-panel side-by-side BMP (reference | output | diff) where differing pixels are shown in magenta and matching pixels are dimmed. Add declaration to the header and update screenshot tests to save diff images on regression and include diff paths in failure messages for easier debugging. Files changed: test/test_screenshots/emulator_display.cpp, .h, and test/test_screenshots/test_screenshots.cpp.
1 parent 5c25cbe commit 2cac54a

3 files changed

Lines changed: 151 additions & 6 deletions

File tree

test/test_screenshots/emulator_display.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,116 @@ int emulator_compare_bmp(const char* file_a, const char* file_b) {
192192
return diff_count;
193193
}
194194

195+
// ---------------------------------------------------------------------------
196+
// 3-panel diff visualizer: [reference | output | diff-highlighted]
197+
// Matching pixels in the diff panel are dimmed to 30% brightness so regressions
198+
// jump out immediately. Differing pixels are shown as magenta (255, 0, 255).
199+
// The combined image is (SCREEN_WIDTH * 3 + 4) wide with 2-px white separators.
200+
// ---------------------------------------------------------------------------
201+
bool emulator_save_diff_bmp(const char* ref_path, const char* out_path,
202+
const char* diff_path) {
203+
const int W = SCREEN_WIDTH;
204+
const int H = SCREEN_HEIGHT;
205+
const int PANEL_BYTES = W * H * 3;
206+
207+
// Heap-allocate pixel buffers (bottom-up BMP rows, BGR order)
208+
uint8_t* ref_px = static_cast<uint8_t*>(malloc(PANEL_BYTES));
209+
uint8_t* out_px = static_cast<uint8_t*>(malloc(PANEL_BYTES));
210+
if (!ref_px || !out_px) { free(ref_px); free(out_px); return false; }
211+
212+
auto read_pixels = [&](const char* path, uint8_t* dst) -> bool {
213+
FILE* f = fopen(path, "rb");
214+
if (!f) return false;
215+
fseek(f, 54, SEEK_SET); // skip BMP header
216+
bool ok = (fread(dst, 1, PANEL_BYTES, f) == (size_t)PANEL_BYTES);
217+
fclose(f);
218+
return ok;
219+
};
220+
221+
if (!read_pixels(ref_path, ref_px) || !read_pixels(out_path, out_px)) {
222+
free(ref_px); free(out_px); return false;
223+
}
224+
225+
// Combined image layout: ref | 2px sep | output | 2px sep | diff
226+
const int SEP = 2;
227+
const int TOTAL_W = W * 3 + SEP * 2;
228+
int row_bytes = TOTAL_W * 3;
229+
int padding = (4 - (row_bytes % 4)) % 4;
230+
int padded = row_bytes + padding;
231+
int pix_size = padded * H;
232+
int file_size = 54 + pix_size;
233+
234+
FILE* f = fopen(diff_path, "wb");
235+
if (!f) { free(ref_px); free(out_px); return false; }
236+
237+
// BMP file header
238+
uint8_t fhdr[14] = {
239+
'B', 'M',
240+
(uint8_t)file_size, (uint8_t)(file_size >> 8),
241+
(uint8_t)(file_size >> 16),(uint8_t)(file_size >> 24),
242+
0, 0, 0, 0, 54, 0, 0, 0
243+
};
244+
fwrite(fhdr, 1, 14, f);
245+
246+
// BMP info header
247+
uint8_t ihdr[40] = {};
248+
ihdr[0] = 40;
249+
ihdr[4] = (uint8_t)TOTAL_W; ihdr[5] = (uint8_t)(TOTAL_W >> 8);
250+
ihdr[8] = (uint8_t)H; ihdr[9] = (uint8_t)(H >> 8);
251+
ihdr[12] = 1; // color planes
252+
ihdr[14] = 24; // bits per pixel
253+
fwrite(ihdr, 1, 40, f);
254+
255+
// Rows are written bottom-up (matches how emulator_save_bmp stores them)
256+
uint8_t pad_bytes[3] = {0, 0, 0};
257+
for (int row = 0; row < H; row++) {
258+
// Source row index inside the bottom-up pixel arrays
259+
int src_row = row; // same order: both arrays are bottom-up
260+
261+
for (int panel = 0; panel < 3; panel++) {
262+
// Write 2-px white separator before panels 1 and 2
263+
if (panel > 0) {
264+
for (int s = 0; s < SEP; s++) {
265+
uint8_t white[3] = {255, 255, 255};
266+
fwrite(white, 1, 3, f);
267+
}
268+
}
269+
270+
for (int col = 0; col < W; col++) {
271+
int idx = (src_row * W + col) * 3;
272+
uint8_t r_b = ref_px[idx], r_g = ref_px[idx+1], r_r = ref_px[idx+2];
273+
uint8_t o_b = out_px[idx], o_g = out_px[idx+1], o_r = out_px[idx+2];
274+
bool differs = (r_b != o_b || r_g != o_g || r_r != o_r);
275+
276+
uint8_t px[3];
277+
if (panel == 0) {
278+
// Left: reference as-is
279+
px[0] = r_b; px[1] = r_g; px[2] = r_r;
280+
} else if (panel == 1) {
281+
// Middle: output as-is
282+
px[0] = o_b; px[1] = o_g; px[2] = o_r;
283+
} else {
284+
// Right: diff — magenta where different, dimmed output where same
285+
if (differs) {
286+
px[0] = 255; px[1] = 0; px[2] = 255; // magenta (BGR)
287+
} else {
288+
px[0] = (uint8_t)(o_b * 30 / 100);
289+
px[1] = (uint8_t)(o_g * 30 / 100);
290+
px[2] = (uint8_t)(o_r * 30 / 100);
291+
}
292+
}
293+
fwrite(px, 1, 3, f);
294+
}
295+
}
296+
if (padding > 0) fwrite(pad_bytes, 1, padding, f);
297+
}
298+
299+
fclose(f);
300+
free(ref_px);
301+
free(out_px);
302+
return true;
303+
}
304+
195305
void emulator_teardown() {
196306
// Delete the screen (which deletes all child widgets)
197307
// Don't call lv_deinit - keep LVGL alive across tests

test/test_screenshots/emulator_display.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ void emulator_render_frame();
2222
// Returns true on success
2323
bool emulator_save_bmp(const char* filename);
2424

25-
// Compare two BMP files pixel-by-pixel
26-
// Returns the number of differing pixels (0 = identical)
25+
// Compare two BMP files pixel-by-pixel.
26+
// Returns the number of differing pixels (0 = identical).
2727
int emulator_compare_bmp(const char* file_a, const char* file_b);
2828

29+
// Save a 3-panel side-by-side diff BMP: [reference | output | diff-highlighted].
30+
// Differing pixels are shown as magenta in the diff panel; matching pixels are dimmed.
31+
// Returns true on success.
32+
bool emulator_save_diff_bmp(const char* ref_path, const char* out_path,
33+
const char* diff_path);
34+
2935
// Clean up LVGL state between tests
3036
void emulator_teardown();
3137

test/test_screenshots/test_screenshots.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,17 @@ class ScreenshotTest : public ::testing::Test {
132132
snprintf(ref_path, sizeof(ref_path), "%s/%s.bmp", REFERENCE_DIR, name);
133133
if (file_exists(ref_path)) {
134134
int diff = emulator_compare_bmp(ref_path, out_path);
135-
EXPECT_EQ(0, diff) << "Screenshot regression: " << name
136-
<< " has " << diff << " differing pixels";
135+
if (diff > 0) {
136+
char diff_path[256];
137+
snprintf(diff_path, sizeof(diff_path), "%s/%s_diff.bmp", OUTPUT_DIR, name);
138+
emulator_save_diff_bmp(ref_path, out_path, diff_path);
139+
EXPECT_EQ(0, diff)
140+
<< "Screenshot regression: " << name << " has " << diff << " differing pixels\n"
141+
<< " Reference: " << ref_path << "\n"
142+
<< " Output: " << out_path << "\n"
143+
<< " Diff: " << diff_path << " (magenta = changed pixels)\n"
144+
<< " View all: open " << OUTPUT_DIR;
145+
}
137146
} else {
138147
// No reference yet - copy output as new reference
139148
printf(" [INFO] No reference for '%s' - generating initial reference\n", name);
@@ -316,7 +325,17 @@ TEST_F(ScreenshotTest, SplashScreen_Light) {
316325

317326
if (file_exists(ref_path)) {
318327
int diff = emulator_compare_bmp(ref_path, out_path);
319-
EXPECT_EQ(0, diff) << "Screenshot regression: splash_light has " << diff << " differing pixels";
328+
if (diff > 0) {
329+
char diff_path[256];
330+
snprintf(diff_path, sizeof(diff_path), "%s/splash_light_diff.bmp", OUTPUT_DIR);
331+
emulator_save_diff_bmp(ref_path, out_path, diff_path);
332+
EXPECT_EQ(0, diff)
333+
<< "Screenshot regression: splash_light has " << diff << " differing pixels\n"
334+
<< " Reference: " << ref_path << "\n"
335+
<< " Output: " << out_path << "\n"
336+
<< " Diff: " << diff_path << " (magenta = changed pixels)\n"
337+
<< " View all: open " << OUTPUT_DIR;
338+
}
320339
} else {
321340
printf(" [INFO] No reference for 'splash_light' - generating initial reference\n");
322341
FILE* src = fopen(out_path, "rb");
@@ -371,7 +390,17 @@ TEST_F(ScreenshotTest, SplashScreen_Dark) {
371390

372391
if (file_exists(ref_path)) {
373392
int diff = emulator_compare_bmp(ref_path, out_path);
374-
EXPECT_EQ(0, diff) << "Screenshot regression: splash_dark has " << diff << " differing pixels";
393+
if (diff > 0) {
394+
char diff_path[256];
395+
snprintf(diff_path, sizeof(diff_path), "%s/splash_dark_diff.bmp", OUTPUT_DIR);
396+
emulator_save_diff_bmp(ref_path, out_path, diff_path);
397+
EXPECT_EQ(0, diff)
398+
<< "Screenshot regression: splash_dark has " << diff << " differing pixels\n"
399+
<< " Reference: " << ref_path << "\n"
400+
<< " Output: " << out_path << "\n"
401+
<< " Diff: " << diff_path << " (magenta = changed pixels)\n"
402+
<< " View all: open " << OUTPUT_DIR;
403+
}
375404
} else {
376405
printf(" [INFO] No reference for 'splash_dark' - generating initial reference\n");
377406
FILE* src = fopen(out_path, "rb");

0 commit comments

Comments
 (0)