FreeRDP
Loading...
Searching...
No Matches
TestImage.c
1#include <stdio.h>
2#include <winpr/string.h>
3#include <winpr/assert.h>
4#include <winpr/file.h>
5#include <winpr/path.h>
6#include <winpr/image.h>
7#include <winpr/user.h>
8#include <winpr/endian.h>
9
10static const char test_src_filename[] = TEST_SOURCE_PATH "/rgb";
11static const char test_bin_filename[] = TEST_BINARY_PATH "/rgb";
12
13static BOOL test_image_equal(const wImage* imageA, const wImage* imageB)
14{
15 return winpr_image_equal(imageA, imageB,
16 WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA |
17 WINPR_IMAGE_CMP_FUZZY);
18}
19
20static BOOL test_equal_to(const wImage* bmp, const char* name, UINT32 format)
21{
22 BOOL rc = FALSE;
23 wImage* cmp = winpr_image_new();
24 if (!cmp)
25 goto fail;
26
27 char path[MAX_PATH] = WINPR_C_ARRAY_INIT;
28 (void)_snprintf(path, sizeof(path), "%s.%s", name, winpr_image_format_extension(format));
29 const int cmpSize = winpr_image_read(cmp, path);
30 if (cmpSize <= 0)
31 {
32 (void)fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, path);
33 goto fail;
34 }
35
36 rc = test_image_equal(bmp, cmp);
37 if (!rc)
38 (void)fprintf(stderr, "[%s] winpr_image_eqal failed", __func__);
39
40fail:
41 winpr_image_free(cmp, TRUE);
42 return rc;
43}
44
45static BOOL test_equal(void)
46{
47 BOOL rc = FALSE;
48 wImage* bmp = winpr_image_new();
49
50 if (!bmp)
51 goto fail;
52
53 char path[MAX_PATH] = WINPR_C_ARRAY_INIT;
54 if (_snprintf(path, sizeof(path), "%s.bmp", test_src_filename) < 0)
55 goto fail;
56 if (FAILED(PathCchConvertStyleA(path, sizeof(path), PATH_STYLE_NATIVE)))
57 goto fail;
58
59 const int bmpSize = winpr_image_read(bmp, path);
60 if (bmpSize <= 0)
61 {
62 (void)fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, path);
63 goto fail;
64 }
65
66 for (UINT32 x = 0; x < UINT8_MAX; x++)
67 {
68 if (!winpr_image_format_is_supported(x))
69 continue;
70 if (!test_equal_to(bmp, test_src_filename, x))
71 goto fail;
72 }
73
74 rc = TRUE;
75fail:
76 winpr_image_free(bmp, TRUE);
77
78 return rc;
79}
80
81static BOOL test_read_write_compare(const char* tname, const char* tdst, UINT32 format)
82{
83 BOOL rc = FALSE;
84 wImage* bmp1 = winpr_image_new();
85 wImage* bmp2 = winpr_image_new();
86 wImage* bmp3 = winpr_image_new();
87 if (!bmp1 || !bmp2 || !bmp3)
88 goto fail;
89
90 char spath[MAX_PATH] = WINPR_C_ARRAY_INIT;
91 char dpath[MAX_PATH] = WINPR_C_ARRAY_INIT;
92 char bpath1[MAX_PATH] = WINPR_C_ARRAY_INIT;
93 char bpath2[MAX_PATH] = WINPR_C_ARRAY_INIT;
94 if (_snprintf(spath, sizeof(spath), "%s.%s", tname, winpr_image_format_extension(format)) < 0)
95 goto fail;
96 if (_snprintf(dpath, sizeof(dpath), "%s.%s", tdst, winpr_image_format_extension(format)) < 0)
97 goto fail;
98 if (_snprintf(bpath1, sizeof(bpath1), "%s.src.%s", dpath,
99 winpr_image_format_extension(WINPR_IMAGE_BITMAP)) < 0)
100 goto fail;
101 if (_snprintf(bpath2, sizeof(bpath2), "%s.bin.%s", dpath,
102 winpr_image_format_extension(WINPR_IMAGE_BITMAP)) < 0)
103 goto fail;
104 if (FAILED(PathCchConvertStyleA(spath, sizeof(spath), PATH_STYLE_NATIVE)))
105 goto fail;
106 if (FAILED(PathCchConvertStyleA(dpath, sizeof(dpath), PATH_STYLE_NATIVE)))
107 goto fail;
108 if (FAILED(PathCchConvertStyleA(bpath1, sizeof(bpath1), PATH_STYLE_NATIVE)))
109 goto fail;
110 if (FAILED(PathCchConvertStyleA(bpath2, sizeof(bpath2), PATH_STYLE_NATIVE)))
111 goto fail;
112
113 const int bmpRSize = winpr_image_read(bmp1, spath);
114 if (bmpRSize <= 0)
115 {
116 (void)fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, spath);
117 goto fail;
118 }
119
120 const int bmpWSize = winpr_image_write(bmp1, dpath);
121 if (bmpWSize <= 0)
122 {
123 (void)fprintf(stderr, "[%s] winpr_image_write failed for %s", __func__, dpath);
124 goto fail;
125 }
126
127 const int bmp2RSize = winpr_image_read(bmp2, dpath);
128 if (bmp2RSize <= 0)
129 {
130 (void)fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, dpath);
131 goto fail;
132 }
133
134 const int bmpSrcWSize = winpr_image_write_ex(bmp1, WINPR_IMAGE_BITMAP, bpath1);
135 if (bmpSrcWSize <= 0)
136 {
137 (void)fprintf(stderr, "[%s] winpr_image_write_ex failed for %s", __func__, bpath1);
138 goto fail;
139 }
140
141 /* write a bitmap and read it back.
142 * this tests if we have the proper internal format */
143 const int bmpBinWSize = winpr_image_write_ex(bmp2, WINPR_IMAGE_BITMAP, bpath2);
144 if (bmpBinWSize <= 0)
145 {
146 (void)fprintf(stderr, "[%s] winpr_image_write_ex failed for %s", __func__, bpath2);
147 goto fail;
148 }
149
150 const int bmp3RSize = winpr_image_read(bmp3, bpath2);
151 if (bmp3RSize <= 0)
152 {
153 (void)fprintf(stderr, "[%s] winpr_image_read failed for %s", __func__, bpath2);
154 goto fail;
155 }
156
157 if (!winpr_image_equal(bmp1, bmp2,
158 WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA |
159 WINPR_IMAGE_CMP_FUZZY))
160 {
161 (void)fprintf(stderr, "[%s] winpr_image_eqal failed bmp1 bmp2", __func__);
162 goto fail;
163 }
164
165 rc = winpr_image_equal(bmp3, bmp2,
166 WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA |
167 WINPR_IMAGE_CMP_FUZZY);
168 if (!rc)
169 (void)fprintf(stderr, "[%s] winpr_image_eqal failed bmp3 bmp2", __func__);
170fail:
171 winpr_image_free(bmp1, TRUE);
172 winpr_image_free(bmp2, TRUE);
173 winpr_image_free(bmp3, TRUE);
174 return rc;
175}
176
177static BOOL test_read_write(void)
178{
179 BOOL rc = TRUE;
180 for (UINT32 x = 0; x < UINT8_MAX; x++)
181 {
182 if (!winpr_image_format_is_supported(x))
183 continue;
184 if (!test_read_write_compare(test_src_filename, test_bin_filename, x))
185 rc = FALSE;
186 }
187 return rc;
188}
189
190static BOOL test_load_file(const char* name)
191{
192 BOOL rc = FALSE;
193 wImage* image = winpr_image_new();
194 if (!image || !name)
195 goto fail;
196
197 const int res = winpr_image_read(image, name);
198 rc = (res > 0);
199
200fail:
201 winpr_image_free(image, TRUE);
202 return rc;
203}
204
205static void put_u16(BYTE* p, UINT16 v)
206{
207 p[0] = (BYTE)(v & 0xff);
208 p[1] = (BYTE)((v >> 8) & 0xff);
209}
210
211static void put_u32(BYTE* p, UINT32 v)
212{
213 p[0] = (BYTE)(v & 0xff);
214 p[1] = (BYTE)((v >> 8) & 0xff);
215 p[2] = (BYTE)((v >> 16) & 0xff);
216 p[3] = (BYTE)((v >> 24) & 0xff);
217}
218
219/* A 24bpp BMP whose biSizeImage carries the unaligned image size
220 * (width * bytesPerPixel * height) selects the unaligned read path. The decoder
221 * must not consume more than biSizeImage bytes from the input buffer. The buffer
222 * here is allocated to the exact BMP size, so any over-read is detectable. */
223static BOOL test_unaligned_no_overread(void)
224{
225 BOOL rc = FALSE;
226 const UINT32 width = 1;
227 const UINT32 height = 10;
228 const UINT32 bpp = 3;
229 const UINT32 uscanline = width * bpp; /* 3, not a multiple of 4 */
230 const UINT32 biSizeImage = uscanline * height; /* 30 */
231 const size_t offBits = 54; /* 14 + 40 */
232 const size_t size = offBits + biSizeImage; /* 84 */
233
234 wImage* image = winpr_image_new();
235 BYTE* bmp = (BYTE*)calloc(1, size);
236 if (!image || !bmp)
237 goto fail;
238
239 bmp[0] = 'B';
240 bmp[1] = 'M';
241 put_u32(&bmp[2], (UINT32)size); /* bfSize */
242 put_u32(&bmp[10], (UINT32)offBits); /* bfOffBits */
243 put_u32(&bmp[14], 40); /* biSize */
244 put_u32(&bmp[18], width); /* biWidth */
245 put_u32(&bmp[22], (UINT32)(-(INT32)height)); /* biHeight, top-down */
246 put_u16(&bmp[26], 1); /* biPlanes */
247 put_u16(&bmp[28], 24); /* biBitCount */
248 put_u32(&bmp[30], 0); /* biCompression BI_RGB */
249 put_u32(&bmp[34], biSizeImage); /* biSizeImage */
250 for (UINT32 i = 0; i < biSizeImage; i++)
251 bmp[offBits + i] = (BYTE)i;
252
253 if (winpr_image_read_buffer(image, bmp, size) <= 0)
254 goto fail;
255
256 if ((image->width != width) || (image->height != height))
257 goto fail;
258
259 for (UINT32 row = 0; row < height; row++)
260 {
261 for (UINT32 b = 0; b < uscanline; b++)
262 {
263 if (image->data[1ULL * row * image->scanline + b] != (BYTE)(row * uscanline + b))
264 goto fail;
265 }
266 }
267
268 rc = TRUE;
269fail:
270 free(bmp);
271 winpr_image_free(image, TRUE);
272 return rc;
273}
274
275static BOOL test_load(void)
276{
277 const char* names[] = {
278 "rgb.16a.bmp", "rgb.16a.nocolor.bmp", "rgb.16.bmp", "rgb.16.nocolor.bmp",
279 "rgb.16x.bmp", "rgb.16x.nocolor.bmp", "rgb.24.bmp", "rgb.24.nocolor.bmp",
280 "rgb.32.bmp", "rgb.32.nocolor.bmp", "rgb.32x.bmp", "rgb.32x.nocolor.bmp",
281 "rgb.bmp"
282 };
283
284 for (size_t x = 0; x < ARRAYSIZE(names); x++)
285 {
286 const char* name = names[x];
287 char* fname = GetCombinedPath(TEST_SOURCE_PATH, name);
288 const BOOL res = test_load_file(fname);
289 free(fname);
290 if (!res)
291 return FALSE;
292 }
293
294 return TRUE;
295}
296
297/* Pixel data may follow color masks and additional padding after BITMAPINFOHEADER. */
298static BOOL test_bmp_offbits(UINT32 compression, UINT32 padding, BOOL topDown)
299{
300 BOOL rc = FALSE;
301 const UINT32 width = 2;
302 const UINT32 height = 2;
303 const UINT32 bpp = 32;
304 const UINT32 stride = width * bpp / 8;
305 const UINT32 biSizeImage = stride * height;
306 const size_t headerSize = 54; /* 14 byte file header + 40 byte info header */
307 const size_t maskSize = (compression == BI_BITFIELDS) ? 3 * sizeof(DWORD) : 0;
308 const size_t minOffset = headerSize + maskSize;
309 const size_t offBits = minOffset + padding;
310 const size_t size = offBits + biSizeImage;
311
312 wImage* image = winpr_image_new();
313 BYTE* bmp = (BYTE*)calloc(1, size);
314 if (!image || !bmp)
315 goto fail;
316
317 bmp[0] = 'B';
318 bmp[1] = 'M';
319 put_u32(&bmp[2], (UINT32)size); /* bfSize */
320 put_u32(&bmp[10], (UINT32)offBits); /* bfOffBits */
321 put_u32(&bmp[14], 40); /* biSize */
322 put_u32(&bmp[18], width); /* biWidth */
323 put_u32(&bmp[22], topDown ? (UINT32)(-(INT32)height) : height); /* biHeight */
324 put_u16(&bmp[26], 1); /* biPlanes */
325 put_u16(&bmp[28], bpp); /* biBitCount */
326 put_u32(&bmp[30], compression); /* biCompression */
327 put_u32(&bmp[34], biSizeImage); /* biSizeImage */
328 if (compression == BI_BITFIELDS)
329 {
330 put_u32(&bmp[54], 0x00FF0000); /* red color mask */
331 put_u32(&bmp[58], 0x0000FF00); /* green color mask */
332 put_u32(&bmp[62], 0x000000FF); /* blue color mask */
333 }
334
335 {
336 const BYTE pixels[16] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
337 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
338 memcpy(&bmp[offBits], pixels, sizeof(pixels));
339
340 if (winpr_image_read_buffer(image, bmp, size) <= 0)
341 {
342 (void)fprintf(stderr, "[%s] failed to read BMP at offset %" PRIuz "\n", __func__,
343 offBits);
344 goto fail;
345 }
346
347 if ((image->width != width) || (image->height != height) || (image->bitsPerPixel != bpp) ||
348 (image->bytesPerPixel != bpp / 8) || (image->scanline != stride) || !image->data)
349 {
350 (void)fprintf(stderr, "[%s] unexpected image layout\n", __func__);
351 goto fail;
352 }
353
354 for (size_t row = 0; row < height; row++)
355 {
356 const size_t srcRow = topDown ? row : height - 1 - row;
357 if (memcmp(&image->data[row * image->scanline], &pixels[srcRow * stride], stride) != 0)
358 {
359 (void)fprintf(stderr, "[%s] unexpected pixels in row %" PRIuz "\n", __func__, row);
360 goto fail;
361 }
362 }
363 }
364
365 /* Reject offsets overlapping headers or masks, beyond the buffer, or leaving
366 * too few bytes for the pixel data. */
367 {
368 const UINT32 offsets[] = { 0,
369 (UINT32)(headerSize - 1),
370 (UINT32)(minOffset - 1),
371 (UINT32)(size - 1),
372 (UINT32)size,
373 (UINT32)(size + 1),
374 UINT32_MAX };
375 for (size_t x = 0; x < ARRAYSIZE(offsets); x++)
376 {
377 winpr_image_free(image, TRUE);
378 image = winpr_image_new();
379 if (!image)
380 goto fail;
381 put_u32(&bmp[10], offsets[x]); /* bfOffBits */
382 if (winpr_image_read_buffer(image, bmp, size) > 0)
383 {
384 (void)fprintf(stderr, "[%s] accepted invalid offset %" PRIu32 "\n", __func__,
385 offsets[x]);
386 goto fail;
387 }
388 }
389 }
390
391 rc = TRUE;
392fail:
393 free(bmp);
394 winpr_image_free(image, TRUE);
395 return rc;
396}
397
398/* Bitmaps are written bottom-up with a plain BITMAPINFOHEADER, the layout every reader
399 * understands, e.g. Windows when synthesizing CF_BITMAP from a CF_DIB. */
400static BOOL test_write_bottom_up(void)
401{
402 BOOL rc = FALSE;
403 const UINT32 width = 2;
404 const UINT32 height = 2;
405 const UINT32 stride = width * 4;
406 const size_t offBits = 54;
407 const size_t size = offBits + 1ull * stride * height;
408 const BYTE pixels[16] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
409 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
410
411 wImage* image = winpr_image_new();
412 BYTE* bmp = (BYTE*)calloc(1, size);
413 BYTE* out = nullptr;
414 size_t outSize = 0;
415 if (!image || !bmp)
416 goto fail;
417
418 bmp[0] = 'B';
419 bmp[1] = 'M';
420 put_u32(&bmp[2], (UINT32)size); /* bfSize */
421 put_u32(&bmp[10], (UINT32)offBits); /* bfOffBits */
422 put_u32(&bmp[14], 40); /* biSize */
423 put_u32(&bmp[18], width); /* biWidth */
424 put_u32(&bmp[22], (UINT32)(-(INT32)height)); /* biHeight, top-down */
425 put_u16(&bmp[26], 1); /* biPlanes */
426 put_u16(&bmp[28], 32); /* biBitCount */
427 put_u32(&bmp[30], BI_RGB); /* biCompression */
428 put_u32(&bmp[34], (UINT32)(1ull * stride * height)); /* biSizeImage */
429 memcpy(&bmp[offBits], pixels, sizeof(pixels));
430
431 if (winpr_image_read_buffer(image, bmp, size) <= 0)
432 goto fail;
433
434 out = winpr_image_write_buffer(image, WINPR_IMAGE_BITMAP, &outSize);
435 if (!out || (outSize < offBits + sizeof(pixels)))
436 {
437 (void)fprintf(stderr, "[%s] winpr_image_write_buffer failed\n", __func__);
438 goto fail;
439 }
440
441 {
442 const UINT32 bfOffBits = winpr_Data_Get_UINT32(&out[10]);
443 const UINT32 biSize = winpr_Data_Get_UINT32(&out[14]);
444 const INT32 biHeight = winpr_Data_Get_INT32(&out[22]);
445 const UINT32 biCompression = winpr_Data_Get_UINT32(&out[30]);
446 if ((biSize != 40) || (biHeight != (INT32)height) || (biCompression != BI_RGB) ||
447 (bfOffBits + sizeof(pixels) > outSize))
448 {
449 (void)fprintf(stderr,
450 "[%s] unexpected header biSize=%" PRIu32 " biHeight=%" PRId32
451 " biCompression=%" PRIu32 "\n",
452 __func__, biSize, biHeight, biCompression);
453 goto fail;
454 }
455
456 /* the last row in the file is the top row of the image */
457 if ((memcmp(&out[bfOffBits], &pixels[stride], stride) != 0) ||
458 (memcmp(&out[bfOffBits + stride], &pixels[0], stride) != 0))
459 {
460 (void)fprintf(stderr, "[%s] rows are not stored bottom-up\n", __func__);
461 goto fail;
462 }
463 }
464
465 rc = TRUE;
466fail:
467 free(out);
468 free(bmp);
469 winpr_image_free(image, TRUE);
470 return rc;
471}
472
473/* Rows that are not padded to 4 bytes, as a 24 bpp PNG decodes to, are padded when writing */
474static BOOL test_write_unpadded_rows(void)
475{
476 BOOL rc = FALSE;
477 const UINT32 width = 3;
478 const UINT32 height = 2;
479 const size_t row = 3ull * width; /* 9 bytes */
480 const size_t padded = 12; /* rounded up to 4 bytes */
481 const BYTE pixels[18] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
482 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19 };
483
484 BYTE* out = nullptr;
485 size_t outSize = 0;
486 wImage* image = winpr_image_new();
487 if (!image)
488 goto fail;
489
490 image->data = (BYTE*)malloc(sizeof(pixels));
491 if (!image->data)
492 goto fail;
493 memcpy(image->data, pixels, sizeof(pixels));
494 image->width = width;
495 image->height = height;
496 image->bitsPerPixel = 24;
497 image->bytesPerPixel = 3;
498 image->scanline = (UINT32)row;
499
500 out = winpr_image_write_buffer(image, WINPR_IMAGE_BITMAP, &outSize);
501 if (!out)
502 {
503 (void)fprintf(stderr, "[%s] winpr_image_write_buffer failed\n", __func__);
504 goto fail;
505 }
506
507 {
508 const UINT32 bfOffBits = winpr_Data_Get_UINT32(&out[10]);
509 const BYTE zero[3] = WINPR_C_ARRAY_INIT;
510 if ((outSize != bfOffBits + padded * height) ||
511 (memcmp(&out[bfOffBits], &pixels[row], row) != 0) ||
512 (memcmp(&out[bfOffBits + row], zero, padded - row) != 0) ||
513 (memcmp(&out[bfOffBits + padded], &pixels[0], row) != 0) ||
514 (memcmp(&out[bfOffBits + padded + row], zero, padded - row) != 0))
515 {
516 (void)fprintf(stderr, "[%s] unexpected pixel data\n", __func__);
517 goto fail;
518 }
519 }
520
521 rc = TRUE;
522fail:
523 free(out);
524 winpr_image_free(image, TRUE);
525 return rc;
526}
527
528int TestImage(int argc, char* argv[])
529{
530 int rc = 0;
531
532 WINPR_UNUSED(argc);
533 WINPR_UNUSED(argv);
534
535 if (!test_equal())
536 rc -= 1;
537
538 if (!test_read_write())
539 rc -= 2;
540
541 if (!test_load())
542 rc -= 4;
543
544 if (!test_unaligned_no_overread())
545 rc -= 8;
546
547 if (!test_bmp_offbits(BI_BITFIELDS, 0, FALSE) || !test_bmp_offbits(BI_BITFIELDS, 4, TRUE) ||
548 !test_bmp_offbits(BI_RGB, 0, FALSE) || !test_bmp_offbits(BI_RGB, 12, TRUE))
549 rc -= 16;
550
551 if (!test_write_bottom_up())
552 rc -= 32;
553
554 if (!test_write_unpadded_rows())
555 rc -= 64;
556
557 return rc;
558}