FreeRDP
Loading...
Searching...
No Matches
image.c
1
22#include <setjmp.h>
23#include <stdlib.h>
24
25#include <winpr/config.h>
26
27#include <winpr/wtypes.h>
28#include <winpr/crt.h>
29#include <winpr/file.h>
30#include <winpr/cast.h>
31
32#include <winpr/image.h>
33
34#if defined(WINPR_UTILS_IMAGE_PNG)
35#include <png.h>
36#endif
37
38#if defined(WINPR_UTILS_IMAGE_JPEG)
39#define INT32 INT32_WINPR
40#include <jpeglib.h>
41#undef INT32
42#endif
43
44#if defined(WINPR_UTILS_IMAGE_JPEG)
45typedef struct
46{
47 struct jpeg_error_mgr pub;
48 jmp_buf jump;
49} WINPR_JPEG_ERROR_MGR;
50
51/* libjpeg's default error handler calls exit(); longjmp back to the caller
52 * instead (the setjmp target is registered right after
53 * jpeg_create_compress()/jpeg_create_decompress(), see the libjpeg example). */
54WINPR_NORETURN(static void winpr_jpeg_error_exit(j_common_ptr cinfo));
55
56void winpr_jpeg_error_exit(j_common_ptr cinfo)
57{
58 WINPR_JPEG_ERROR_MGR* err = (WINPR_JPEG_ERROR_MGR*)cinfo->err;
59 longjmp(err->jump, 1);
60}
61#endif
62
63#if defined(WINPR_UTILS_IMAGE_WEBP)
64#include <webp/encode.h>
65#include <webp/decode.h>
66#endif
67
68#if defined(WITH_LODEPNG)
69#include <lodepng.h>
70#endif
71#include <winpr/stream.h>
72
73#include "image.h"
74#include "../log.h"
75#define TAG WINPR_TAG("utils.image")
76
77#if defined(WINPR_UTILS_IMAGE_JPEG)
78#ifndef MAX
79#define MAX(a, b) ((a) > (b)) ? (a) : (b)
80#endif
81#endif
82
83WINPR_ATTR_NODISCARD
84static SSIZE_T winpr_convert_from_jpeg(const BYTE* comp_data, size_t comp_data_bytes, UINT32* width,
85 UINT32* height, UINT32* bpp, BYTE** ppdecomp_data,
86 UINT32* scanline);
87
88WINPR_ATTR_NODISCARD
89static SSIZE_T winpr_convert_from_png(const BYTE* comp_data, size_t comp_data_bytes, UINT32* width,
90 UINT32* height, UINT32* bpp, BYTE** ppdecomp_data,
91 UINT32* scanline);
92
93WINPR_ATTR_NODISCARD
94static SSIZE_T winpr_convert_from_webp(const BYTE* comp_data, size_t comp_data_bytes, UINT32* width,
95 UINT32* height, UINT32* bpp, BYTE** ppdecomp_data,
96 UINT32* scanline);
97
98BOOL writeBitmapFileHeader(wStream* s, const WINPR_BITMAP_FILE_HEADER* bf)
99{
100 if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_FILE_HEADER)))
101 return FALSE;
102
103 Stream_Write_UINT8(s, bf->bfType[0]);
104 Stream_Write_UINT8(s, bf->bfType[1]);
105 Stream_Write_UINT32(s, bf->bfSize);
106 Stream_Write_UINT16(s, bf->bfReserved1);
107 Stream_Write_UINT16(s, bf->bfReserved2);
108 Stream_Write_UINT32(s, bf->bfOffBits);
109 return TRUE;
110}
111
112BOOL readBitmapFileHeader(wStream* s, WINPR_BITMAP_FILE_HEADER* bf)
113{
114 static wLog* log = nullptr;
115 if (!log)
116 log = WLog_Get(TAG);
117
118 if (!s || !bf ||
119 (!Stream_CheckAndLogRequiredLengthWLog(log, s, sizeof(WINPR_BITMAP_FILE_HEADER))))
120 return FALSE;
121
122 Stream_Read_UINT8(s, bf->bfType[0]);
123 Stream_Read_UINT8(s, bf->bfType[1]);
124 Stream_Read_UINT32(s, bf->bfSize);
125 Stream_Read_UINT16(s, bf->bfReserved1);
126 Stream_Read_UINT16(s, bf->bfReserved2);
127 Stream_Read_UINT32(s, bf->bfOffBits);
128
129 if (bf->bfSize < sizeof(WINPR_BITMAP_FILE_HEADER))
130 {
131 WLog_Print(log, WLOG_ERROR, "Invalid bitmap::bfSize=%" PRIu32 ", require at least %" PRIuz,
132 bf->bfSize, sizeof(WINPR_BITMAP_FILE_HEADER));
133 return FALSE;
134 }
135
136 if ((bf->bfType[0] != 'B') || (bf->bfType[1] != 'M'))
137 {
138 WLog_Print(log, WLOG_ERROR, "Invalid bitmap header [%c%c], expected [BM]", bf->bfType[0],
139 bf->bfType[1]);
140 return FALSE;
141 }
142 return Stream_CheckAndLogRequiredLengthWLog(log, s,
143 bf->bfSize - sizeof(WINPR_BITMAP_FILE_HEADER));
144}
145
146BOOL writeBitmapInfoHeader(wStream* s, const WINPR_BITMAP_INFO_HEADER* bi)
147{
148 if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_INFO_HEADER)))
149 return FALSE;
150
151 Stream_Write_UINT32(s, bi->biSize);
152 Stream_Write_INT32(s, bi->biWidth);
153 Stream_Write_INT32(s, bi->biHeight);
154 Stream_Write_UINT16(s, bi->biPlanes);
155 Stream_Write_UINT16(s, bi->biBitCount);
156 Stream_Write_UINT32(s, bi->biCompression);
157 Stream_Write_UINT32(s, bi->biSizeImage);
158 Stream_Write_INT32(s, bi->biXPelsPerMeter);
159 Stream_Write_INT32(s, bi->biYPelsPerMeter);
160 Stream_Write_UINT32(s, bi->biClrUsed);
161 Stream_Write_UINT32(s, bi->biClrImportant);
162 return TRUE;
163}
164
165BOOL readBitmapInfoHeader(wStream* s, WINPR_BITMAP_INFO_HEADER* bi, size_t* poffset)
166{
167 if (!s || !bi || (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(WINPR_BITMAP_INFO_HEADER))))
168 return FALSE;
169
170 const size_t start = Stream_GetPosition(s);
171 Stream_Read_UINT32(s, bi->biSize);
172 Stream_Read_INT32(s, bi->biWidth);
173 Stream_Read_INT32(s, bi->biHeight);
174 Stream_Read_UINT16(s, bi->biPlanes);
175 Stream_Read_UINT16(s, bi->biBitCount);
176 Stream_Read_UINT32(s, bi->biCompression);
177 Stream_Read_UINT32(s, bi->biSizeImage);
178 Stream_Read_INT32(s, bi->biXPelsPerMeter);
179 Stream_Read_INT32(s, bi->biYPelsPerMeter);
180 Stream_Read_UINT32(s, bi->biClrUsed);
181 Stream_Read_UINT32(s, bi->biClrImportant);
182
183 switch (bi->biBitCount)
184 {
185 case 1:
186 case 4:
187 case 8:
188 case 16:
189 case 24:
190 case 32:
191 break;
192 default:
193 WLog_WARN(TAG, "invalid biBitCount=%" PRIu32, bi->biBitCount);
194 return FALSE;
195 }
196
197 if (bi->biWidth < 0)
198 {
199 WLog_WARN(TAG, "negative biWidth=%" PRId32, bi->biWidth);
200 return FALSE;
201 }
202
203 /* https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader */
204 size_t offset = 0;
205 switch (bi->biCompression)
206 {
207 case BI_RGB:
208 if (bi->biBitCount <= 8)
209 {
210 DWORD used = bi->biClrUsed;
211 if (used == 0)
212 used = 1u << bi->biBitCount;
213 offset += sizeof(RGBQUAD) * used;
214 }
215 if (bi->biSizeImage == 0)
216 {
217 const UINT64 rawustride =
218 WINPR_ASSERTING_INT_CAST(UINT64, bi->biWidth) * bi->biBitCount;
219 const UINT64 ustride = ((rawustride + 31) & ~31u) >> 3;
220 if (ustride > UINT32_MAX)
221 {
222 WLog_ERR(TAG, "bi->biWidth * bi->biBitCount > UINT32_MAX");
223 return FALSE;
224 }
225
226 const UINT32 stride = WINPR_ASSERTING_INT_CAST(uint32_t, ustride);
227 const UINT64 usize = WINPR_ASSERTING_INT_CAST(UINT64, llabs(bi->biHeight)) * stride;
228 if (usize > UINT32_MAX)
229 {
230 WLog_ERR(TAG, "abs(bi->biHeight) * stride > UINT32_MAX");
231 return FALSE;
232 }
233 bi->biSizeImage = WINPR_ASSERTING_INT_CAST(uint32_t, usize);
234 }
235 break;
236 case BI_BITFIELDS:
237 /* BITMAPV4HEADER and BITMAPV5HEADER include the color masks in biSize. */
238 if (bi->biSize == sizeof(WINPR_BITMAP_INFO_HEADER))
239 offset += sizeof(DWORD) * 3; // 3 DWORD color masks
240 break;
241 default:
242 WLog_ERR(TAG, "unsupported biCompression %" PRIu32, bi->biCompression);
243 return FALSE;
244 }
245
246 if (bi->biSizeImage == 0)
247 {
248 WLog_ERR(TAG, "invalid biSizeImage %" PRIu32, bi->biSizeImage);
249 return FALSE;
250 }
251
252 const size_t pos = Stream_GetPosition(s) - start;
253 if (bi->biSize < pos)
254 {
255 WLog_ERR(TAG, "invalid biSize %" PRIu32 " < (actual) offset %" PRIuz, bi->biSize, pos);
256 return FALSE;
257 }
258
259 *poffset = offset;
260 return Stream_SafeSeek(s, bi->biSize - pos);
261}
262
263WINPR_ATTR_MALLOC(Stream_Free, 1)
264static wStream* winpr_bitmap_construct_header_stream(size_t width, size_t height, size_t bpp,
265 BOOL bottomUp)
266{
267 WINPR_BITMAP_FILE_HEADER bf = WINPR_C_ARRAY_INIT;
268 WINPR_BITMAP_INFO_HEADER bi = WINPR_C_ARRAY_INIT;
269
270 size_t stride = (width * bpp + 7) / 8;
271 if ((stride % 4) != 0)
272 stride += 4 - (stride % 4);
273
274 size_t imgSize = stride * height;
275 if ((width > INT32_MAX) || (height > INT32_MAX) || (bpp > UINT16_MAX) || (imgSize > UINT32_MAX))
276 return nullptr;
277
278 wStream* s = Stream_New(nullptr, WINPR_IMAGE_BMP_HEADER_LEN);
279 if (!s)
280 return nullptr;
281
282 bf.bfType[0] = 'B';
283 bf.bfType[1] = 'M';
284 bf.bfReserved1 = 0;
285 bf.bfReserved2 = 0;
286 bi.biSize = (UINT32)sizeof(WINPR_BITMAP_INFO_HEADER);
287 bf.bfOffBits = (UINT32)sizeof(WINPR_BITMAP_FILE_HEADER) + bi.biSize;
288 bi.biSizeImage = (UINT32)imgSize;
289 bf.bfSize = bf.bfOffBits + bi.biSizeImage;
290 bi.biWidth = (INT32)width;
291 bi.biHeight = bottomUp ? (INT32)height : -1 * (INT32)height;
292 bi.biPlanes = 1;
293 bi.biBitCount = (UINT16)bpp;
294 bi.biCompression = BI_RGB;
295 bi.biXPelsPerMeter = (INT32)width;
296 bi.biYPelsPerMeter = (INT32)height;
297 bi.biClrUsed = 0;
298 bi.biClrImportant = 0;
299
300 size_t offset = 0;
301 switch (bi.biCompression)
302 {
303 case BI_RGB:
304 if (bi.biBitCount <= 8)
305 {
306 DWORD used = bi.biClrUsed;
307 if (used == 0)
308 used = 1u << bi.biBitCount;
309 offset += sizeof(RGBQUAD) * used;
310 }
311 if (bi.biSizeImage == 0)
312 {
313 const UINT64 rawustride =
314 WINPR_ASSERTING_INT_CAST(UINT64, bi.biWidth) * bi.biBitCount;
315 const UINT64 ustride = ((rawustride + 31) & ~31u) >> 3;
316 if (ustride > UINT32_MAX)
317 {
318 WLog_ERR(TAG, "bi->biWidth * bi->biBitCount > UINT32_MAX");
319 goto fail;
320 }
321
322 const UINT32 xstride = WINPR_ASSERTING_INT_CAST(uint32_t, ustride);
323 const UINT64 usize = WINPR_ASSERTING_INT_CAST(UINT64, llabs(bi.biHeight)) * xstride;
324 if (usize > UINT32_MAX)
325 {
326 WLog_ERR(TAG, "abs(bi->biHeight) * stride > UINT32_MAX");
327 goto fail;
328 }
329 bi.biSizeImage = WINPR_ASSERTING_INT_CAST(uint32_t, usize);
330 }
331 break;
332 case BI_BITFIELDS:
333 offset += sizeof(DWORD) * 3; // 3 DWORD color masks
334 break;
335 default:
336 return nullptr;
337 }
338
339 if (offset > UINT32_MAX - bf.bfOffBits)
340 goto fail;
341 bf.bfOffBits += WINPR_ASSERTING_INT_CAST(UINT32, offset);
342
343 if (!writeBitmapFileHeader(s, &bf))
344 goto fail;
345
346 if (!writeBitmapInfoHeader(s, &bi))
347 goto fail;
348
349 if (!Stream_EnsureRemainingCapacity(s, offset))
350 goto fail;
351
352 Stream_Zero(s, offset);
353 const size_t pos = Stream_GetPosition(s);
354 if (pos != bf.bfOffBits)
355 goto fail;
356 return s;
357fail:
358 Stream_Free(s, TRUE);
359 return nullptr;
360}
361
362BYTE* winpr_bitmap_construct_header(size_t width, size_t height, size_t bpp)
363{
364 wStream* s = winpr_bitmap_construct_header_stream(width, height, bpp, FALSE);
365 if (!s)
366 return nullptr;
367 void* data = Stream_Buffer(s);
368 Stream_Free(s, FALSE);
369 return data;
370}
371
376WINPR_ATTR_MALLOC(free, 1)
377static void* winpr_bitmap_write_buffer(const BYTE* data, size_t size, UINT32 width, UINT32 height,
378 UINT32 stride, UINT32 bpp, UINT32* pSize)
379{
380 WINPR_ASSERT(data || (size == 0));
381 WINPR_ASSERT(pSize);
382
383 void* result = nullptr;
384 if ((height == 0) || (width == 0) || (bpp == 0) || (width > SIZE_MAX / height) ||
385 (width > SIZE_MAX / bpp))
386 return nullptr;
387
388 size_t bpp_stride = 1ull * width * bpp;
389 if (bpp < 8)
390 {
391 if ((bpp_stride % 8) != 0)
392 bpp_stride += 8 - (bpp_stride % 8);
393 }
394 bpp_stride /= 8ull;
395
396 if ((bpp_stride % 4) != 0)
397 bpp_stride += 4 - (bpp_stride % 4);
398
399 if ((bpp_stride < 4) || (bpp_stride > UINT32_MAX))
400 return nullptr;
401
402 /* the pixels of a row, without the padding to 4 bytes BMP requires */
403 if (width > (SIZE_MAX - 7ull) / bpp)
404 return nullptr;
405 const size_t row = (1ull * width * bpp + 7ull) / 8ull;
406
407 if (stride == 0)
408 stride = (UINT32)bpp_stride;
409
410 if (stride > SIZE_MAX / height)
411 return nullptr;
412
413 if (stride > size / height)
414 return nullptr;
415
416 if (row > stride)
417 return nullptr;
418
419 /* Store the rows bottom-up: some readers, e.g. Windows when synthesizing CF_BITMAP from a
420 * CF_DIB, do not handle top-down bitmaps. */
421 wStream* s = winpr_bitmap_construct_header_stream(width, height, bpp, TRUE);
422 if (!s)
423 goto fail;
424
425 /* The source rows may be padded differently, e.g. not at all for a 24 bpp PNG: write each
426 * row with the padding the header announces. */
427 for (size_t y = height; y > 0; y--)
428 {
429 if (!Stream_EnsureRemainingCapacity(s, bpp_stride))
430 goto fail;
431
432 const size_t offset = (y - 1) * stride;
433 Stream_Write(s, &data[offset], row);
434 Stream_Zero(s, bpp_stride - row);
435 }
436
437 result = Stream_Buffer(s);
438 {
439 const size_t pos = Stream_GetPosition(s);
440 if (pos > UINT32_MAX)
441 goto fail;
442 *pSize = (UINT32)pos;
443 }
444fail:
445 Stream_Free(s, result == nullptr);
446 return result;
447}
448
449int winpr_bitmap_write(const char* filename, const BYTE* data, size_t width, size_t height,
450 size_t bpp)
451{
452 return winpr_bitmap_write_ex(filename, data, 0, width, height, bpp);
453}
454
455int winpr_bitmap_write_ex(const char* filename, const BYTE* data, size_t stride, size_t width,
456 size_t height, size_t bpp)
457{
458 FILE* fp = nullptr;
459 int ret = -1;
460 void* bmpdata = nullptr;
461 const size_t bpp_stride = ((((width * bpp) + 31) & (size_t)~31) >> 3);
462
463 if ((stride > UINT32_MAX) || (width > UINT32_MAX) || (height > UINT32_MAX) ||
464 (bpp > UINT32_MAX))
465 goto fail;
466
467 if (stride == 0)
468 stride = bpp_stride;
469
470 {
471 UINT32 bmpsize = 0;
472 {
473 const size_t size = stride * 1ull * height;
474 bmpdata = winpr_bitmap_write_buffer(data, size, (UINT32)width, (UINT32)height,
475 (UINT32)stride, (UINT32)bpp, &bmpsize);
476 }
477 if (!bmpdata)
478 goto fail;
479
480 fp = winpr_fopen(filename, "w+b");
481 if (!fp)
482 {
483 WLog_ERR(TAG, "failed to open file %s", filename);
484 goto fail;
485 }
486
487 if (fwrite(bmpdata, bmpsize, 1, fp) != 1)
488 goto fail;
489 }
490
491 ret = 0;
492fail:
493 if (fp)
494 (void)fclose(fp);
495 free(bmpdata);
496 return ret;
497}
498
499WINPR_ATTR_NODISCARD
500static int write_and_free(const char* filename, void* data, size_t size)
501{
502 int status = -1;
503 if (!data)
504 goto fail;
505
506 {
507 FILE* fp = winpr_fopen(filename, "w+b");
508 if (!fp)
509 goto fail;
510
511 {
512 const size_t w = fwrite(data, 1, size, fp);
513 (void)fclose(fp);
514 status = (w == size) ? 1 : -1;
515 }
516 }
517fail:
518 free(data);
519 return status;
520}
521
522int winpr_image_write(wImage* image, const char* filename)
523{
524 WINPR_ASSERT(image);
525 return winpr_image_write_ex(image, WINPR_ASSERTING_INT_CAST(uint32_t, image->type), filename);
526}
527
528int winpr_image_write_ex(wImage* image, UINT32 format, const char* filename)
529{
530 WINPR_ASSERT(image);
531
532 size_t size = 0;
533 void* data = winpr_image_write_buffer(image, format, &size);
534 if (!data)
535 return -1;
536 return write_and_free(filename, data, size);
537}
538
539WINPR_ATTR_NODISCARD
540static int winpr_image_bitmap_read_buffer(wImage* image, const BYTE* buffer, size_t size)
541{
542 int rc = -1;
543 BOOL vFlip = 0;
544 WINPR_BITMAP_FILE_HEADER bf = WINPR_C_ARRAY_INIT;
545 WINPR_BITMAP_INFO_HEADER bi = WINPR_C_ARRAY_INIT;
546 wStream sbuffer = WINPR_C_ARRAY_INIT;
547 wStream* s = Stream_StaticConstInit(&sbuffer, buffer, size);
548
549 if (!s)
550 return -1;
551
552 size_t bmpoffset = 0;
553 if (!readBitmapFileHeader(s, &bf) || !readBitmapInfoHeader(s, &bi, &bmpoffset))
554 goto fail;
555
556 if ((bf.bfType[0] != 'B') || (bf.bfType[1] != 'M'))
557 {
558 WLog_WARN(TAG, "Invalid bitmap header %c%c", bf.bfType[0], bf.bfType[1]);
559 goto fail;
560 }
561
562 image->type = WINPR_IMAGE_BITMAP;
563
564 {
565 /* bfOffBits may include color masks, a color table or padding after the
566 * info header. It must leave room for the required color data. */
567 const size_t pos = Stream_GetPosition(s);
568 const size_t expect = bf.bfOffBits;
569 if ((pos > expect) || (bmpoffset > expect - pos))
570 {
571 WLog_WARN(TAG, "pos=%" PRIuz ", expected %" PRIuz ", offset=%" PRIuz, pos, expect,
572 bmpoffset);
573 goto fail;
574 }
575
576 if (!Stream_SafeSeek(s, expect - pos))
577 goto fail;
578 }
579
580 if (!Stream_CheckAndLogRequiredLength(TAG, s, bi.biSizeImage))
581 goto fail;
582
583 if (bi.biWidth <= 0)
584 {
585 WLog_WARN(TAG, "bi.biWidth=%" PRId32, bi.biWidth);
586 goto fail;
587 }
588
589 image->width = (UINT32)bi.biWidth;
590
591 if (bi.biHeight < 0)
592 {
593 vFlip = FALSE;
594 image->height = (UINT32)(-1ll * bi.biHeight);
595 }
596 else
597 {
598 vFlip = TRUE;
599 image->height = (UINT32)bi.biHeight;
600 }
601
602 if (image->width <= 0)
603 {
604 WLog_WARN(TAG, "image->width=%" PRIu32, image->width);
605 goto fail;
606 }
607
608 if (image->height <= 0)
609 {
610 WLog_WARN(TAG, "image->height=%" PRIu32, image->height);
611 goto fail;
612 }
613
614 image->bitsPerPixel = bi.biBitCount;
615 {
616 const size_t bpp = (bi.biBitCount + 7UL) / 8UL;
617 image->bytesPerPixel = WINPR_ASSERTING_INT_CAST(uint32_t, bpp);
618 if (image->bytesPerPixel == 0)
619 goto fail;
620
621 if (image->width > (UINT32_MAX / image->bytesPerPixel) - 4ull)
622 goto fail;
623
624 image->scanline = image->width * image->bytesPerPixel;
625 if ((image->scanline % 4) != 0)
626 image->scanline += 4 - image->scanline % 4;
627
628 if (image->height > UINT32_MAX / image->scanline)
629 goto fail;
630
631 {
632 const size_t bmpsize = 1ULL * image->scanline * image->height;
633 if (bmpsize != bi.biSizeImage)
634 WLog_WARN(TAG, "bmpsize=%" PRIuz " != bi.biSizeImage=%" PRIu32, bmpsize,
635 bi.biSizeImage);
636
637 {
638 size_t scanline = image->scanline;
639 if (bi.biSizeImage < bmpsize)
640 {
641 /* Workaround for unaligned bitmaps */
642 const size_t uscanline = image->width * bpp;
643 const size_t unaligned = image->height * uscanline;
644 if (bi.biSizeImage != unaligned)
645 goto fail;
646 scanline = uscanline;
647 }
648
649 image->data = nullptr;
650 {
651 const size_t asize = 1ULL * image->scanline * image->height;
652 if (asize > 0)
653 image->data = (BYTE*)malloc(asize);
654 }
655
656 if (!image->data)
657 goto fail;
658
659 if (!vFlip)
660 {
661 BYTE* pDstData = image->data;
662
663 for (size_t index = 0; index < image->height; index++)
664 {
665 Stream_Read(s, pDstData, scanline);
666 pDstData += image->scanline;
667 }
668 }
669 else
670 {
671 BYTE* pDstData = &(image->data[(image->height - 1ull) * image->scanline]);
672
673 for (size_t index = 0; index < image->height; index++)
674 {
675 Stream_Read(s, pDstData, scanline);
676 pDstData -= image->scanline;
677 }
678 }
679 }
680 }
681 }
682
683 rc = 1;
684fail:
685
686 if (rc < 0)
687 {
688 free(image->data);
689 image->data = nullptr;
690 }
691
692 return rc;
693}
694
695int winpr_image_read(wImage* image, const char* filename)
696{
697 int status = -1;
698
699 FILE* fp = winpr_fopen(filename, "rb");
700 if (!fp)
701 {
702 WLog_ERR(TAG, "failed to open file %s", filename);
703 return -1;
704 }
705
706 (void)fseek(fp, 0, SEEK_END);
707 INT64 pos = _ftelli64(fp);
708 (void)fseek(fp, 0, SEEK_SET);
709
710 if (pos > 0)
711 {
712 BYTE* buffer = malloc((size_t)pos);
713 if (buffer)
714 {
715 size_t r = fread(buffer, 1, (size_t)pos, fp);
716 if (r == (size_t)pos)
717 {
718 status = winpr_image_read_buffer(image, buffer, (size_t)pos);
719 }
720 }
721 free(buffer);
722 }
723 (void)fclose(fp);
724 return status;
725}
726
727int winpr_image_read_buffer(wImage* image, const BYTE* buffer, size_t size)
728{
729 BYTE sig[12] = WINPR_C_ARRAY_INIT;
730 int status = -1;
731
732 if (size < sizeof(sig))
733 return -1;
734
735 CopyMemory(sig, buffer, sizeof(sig));
736
737 if ((sig[0] == 'B') && (sig[1] == 'M'))
738 {
739 image->type = WINPR_IMAGE_BITMAP;
740 status = winpr_image_bitmap_read_buffer(image, buffer, size);
741 }
742 else if ((sig[0] == 'R') && (sig[1] == 'I') && (sig[2] == 'F') && (sig[3] == 'F') &&
743 (sig[8] == 'W') && (sig[9] == 'E') && (sig[10] == 'B') && (sig[11] == 'P'))
744 {
745 image->type = WINPR_IMAGE_WEBP;
746 const SSIZE_T rc =
747 winpr_convert_from_webp(buffer, size, &image->width, &image->height,
748 &image->bitsPerPixel, &image->data, &image->scanline);
749 if (rc >= 0)
750 {
751 image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
752 status = 1;
753 }
754 }
755 else if ((sig[0] == 0xFF) && (sig[1] == 0xD8) && (sig[2] == 0xFF) && (sig[3] == 0xE0) &&
756 (sig[6] == 0x4A) && (sig[7] == 0x46) && (sig[8] == 0x49) && (sig[9] == 0x46) &&
757 (sig[10] == 0x00))
758 {
759 image->type = WINPR_IMAGE_JPEG;
760 const SSIZE_T rc =
761 winpr_convert_from_jpeg(buffer, size, &image->width, &image->height,
762 &image->bitsPerPixel, &image->data, &image->scanline);
763 if (rc >= 0)
764 {
765 image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
766 status = 1;
767 }
768 }
769 else if ((sig[0] == 0x89) && (sig[1] == 'P') && (sig[2] == 'N') && (sig[3] == 'G') &&
770 (sig[4] == '\r') && (sig[5] == '\n') && (sig[6] == 0x1A) && (sig[7] == '\n'))
771 {
772 image->type = WINPR_IMAGE_PNG;
773 const SSIZE_T rc =
774 winpr_convert_from_png(buffer, size, &image->width, &image->height,
775 &image->bitsPerPixel, &image->data, &image->scanline);
776 if (rc >= 0)
777 {
778 image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
779 status = 1;
780 }
781 }
782
783 return status;
784}
785
786wImage* winpr_image_new(void)
787{
788 wImage* image = (wImage*)calloc(1, sizeof(wImage));
789
790 if (!image)
791 return nullptr;
792
793 return image;
794}
795
796void winpr_image_free(wImage* image, BOOL bFreeBuffer)
797{
798 if (!image)
799 return;
800
801 if (bFreeBuffer)
802 free(image->data);
803
804 free(image);
805}
806
807WINPR_ATTR_MALLOC(free, 1)
808static void* winpr_convert_to_jpeg(WINPR_ATTR_UNUSED const void* data,
809 WINPR_ATTR_UNUSED size_t size, UINT32 width, UINT32 height,
810 UINT32 stride, UINT32 bpp, UINT32* pSize)
811{
812 WINPR_ASSERT(data || (size == 0));
813 WINPR_ASSERT(pSize);
814
815 *pSize = 0;
816 if ((width == 0) || (height == 0) || (stride > SIZE_MAX / height) || (bpp < 1) || (bpp > 32))
817 return nullptr;
818
819#if !defined(WINPR_UTILS_IMAGE_JPEG)
820 WLog_WARN(TAG, "JPEG not supported in this build");
821 return nullptr;
822#else
823 BYTE* outbuffer = nullptr;
824 unsigned long outsize = 0;
825 struct jpeg_compress_struct cinfo = WINPR_C_ARRAY_INIT;
826
827 const size_t expect1 = 1ull * stride * height;
828 const size_t bytes = (bpp + 7) / 8;
829 const size_t expect2 = 1ull * width * height * bytes;
830 if (expect1 < expect2)
831 return nullptr;
832 if (expect1 > size)
833 return nullptr;
834
835 /* Set up the error handler. */
836 WINPR_JPEG_ERROR_MGR jerr = WINPR_C_ARRAY_INIT;
837 cinfo.err = jpeg_std_error(&jerr.pub);
838 jerr.pub.error_exit = winpr_jpeg_error_exit;
839 jpeg_create_compress(&cinfo);
840
841 /* Set up error handling (must be after jpeg_create_compress). */
842 if (setjmp(jerr.jump) != 0)
843 goto fail;
844
845 jpeg_mem_dest(&cinfo, &outbuffer, &outsize);
846
847 cinfo.image_width = width;
848 cinfo.image_height = height;
849 WINPR_ASSERT(bpp <= INT32_MAX / 8);
850 cinfo.input_components = (int)(bpp + 7) / 8;
851 cinfo.in_color_space = (bpp > 24) ? JCS_EXT_BGRA : JCS_EXT_BGR;
852 cinfo.data_precision = 8;
853
854 jpeg_set_defaults(&cinfo);
855 jpeg_set_quality(&cinfo, 100, TRUE);
856 /* Use 4:4:4 subsampling (default is 4:2:0) */
857 cinfo.comp_info[0].h_samp_factor = cinfo.comp_info[0].v_samp_factor = 1;
858
859 jpeg_start_compress(&cinfo, TRUE);
860
861 const JSAMPLE* cdata = data;
862 for (size_t x = 0; x < height; x++)
863 {
864 WINPR_ASSERT(x * stride <= UINT32_MAX);
865 const JDIMENSION offset = (JDIMENSION)x * stride;
866
867 /* libjpeg is not const correct, we must cast here to avoid issues
868 * with newer C compilers type check errors */
869 JSAMPLE* coffset = WINPR_CAST_CONST_PTR_AWAY(&cdata[offset], JSAMPLE*);
870 if (jpeg_write_scanlines(&cinfo, &coffset, 1) != 1)
871 goto fail;
872 }
873
874 WINPR_ASSERT(outsize <= UINT32_MAX);
875 *pSize = (UINT32)outsize;
876 jpeg_finish_compress(&cinfo);
877 jpeg_destroy_compress(&cinfo);
878
879 return outbuffer;
880
881fail:
882 /* Do not call libjpeg again after an error (a longjmp may have left the
883 * compressor in an undefined state); just clean up. */
884 jpeg_destroy_compress(&cinfo);
885 free(outbuffer);
886
887 return nullptr;
888#endif
889}
890
891// NOLINTBEGIN(readability-non-const-parameter)
892SSIZE_T winpr_convert_from_jpeg(WINPR_ATTR_UNUSED const BYTE* comp_data,
893 WINPR_ATTR_UNUSED size_t comp_data_bytes,
894 WINPR_ATTR_UNUSED UINT32* width, WINPR_ATTR_UNUSED UINT32* height,
895 WINPR_ATTR_UNUSED UINT32* bpp,
896 WINPR_ATTR_UNUSED BYTE** ppdecomp_data, UINT32* scanline)
897// NOLINTEND(readability-non-const-parameter)
898{
899 WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
900 WINPR_ASSERT(width);
901 WINPR_ASSERT(height);
902 WINPR_ASSERT(bpp);
903 WINPR_ASSERT(ppdecomp_data);
904 WINPR_ASSERT(scanline);
905
906 *width = 0;
907 *height = 0;
908 *bpp = 0;
909 *ppdecomp_data = nullptr;
910 *scanline = 0;
911
912#if !defined(WINPR_UTILS_IMAGE_JPEG)
913 WLog_WARN(TAG, "JPEG not supported in this build");
914 return -1;
915#else
916 struct jpeg_decompress_struct cinfo = WINPR_C_ARRAY_INIT;
917 WINPR_JPEG_ERROR_MGR jerr = WINPR_C_ARRAY_INIT;
918 SSIZE_T size = -1;
919 BYTE* decomp_data = nullptr;
920
921 cinfo.err = jpeg_std_error(&jerr.pub);
922 jerr.pub.error_exit = winpr_jpeg_error_exit;
923 jpeg_create_decompress(&cinfo);
924
925 /* Set up error handling (must be after jpeg_create_decompress). */
926 if (setjmp(jerr.jump) != 0)
927 goto fail;
928
929 jpeg_mem_src(&cinfo, comp_data, comp_data_bytes);
930
931 if (jpeg_read_header(&cinfo, 1) != JPEG_HEADER_OK)
932 goto fail;
933
934 cinfo.out_color_space = cinfo.num_components > 3 ? JCS_EXT_RGBA : JCS_EXT_BGR;
935 const int components = MAX(3, cinfo.num_components);
936
937 *width = WINPR_ASSERTING_INT_CAST(uint32_t, cinfo.image_width);
938 *height = WINPR_ASSERTING_INT_CAST(uint32_t, cinfo.image_height);
939 *bpp = WINPR_ASSERTING_INT_CAST(uint32_t, cinfo.num_components * 8);
940
941 if (!jpeg_start_decompress(&cinfo))
942 goto fail;
943
944 size_t stride = 1ULL * cinfo.image_width * WINPR_ASSERTING_INT_CAST(size_t, components);
945
946 if ((stride == 0) || (cinfo.image_height == 0) || (stride > UINT32_MAX))
947 goto fail;
948 *scanline = (UINT32)stride;
949
950 decomp_data = calloc(stride, cinfo.image_height);
951 if (decomp_data)
952 {
953 while (cinfo.output_scanline < cinfo.image_height)
954 {
955 JSAMPROW row = &decomp_data[cinfo.output_scanline * stride];
956 if (jpeg_read_scanlines(&cinfo, &row, 1) != 1)
957 goto fail;
958 }
959 const size_t ssize = stride * cinfo.image_height;
960 WINPR_ASSERT(ssize < SSIZE_MAX);
961 size = (SSIZE_T)ssize;
962 }
963 jpeg_finish_decompress(&cinfo);
964
965fail:
966 jpeg_destroy_decompress(&cinfo);
967 *ppdecomp_data = decomp_data;
968 return size;
969#endif
970}
971
972WINPR_ATTR_MALLOC(free, 1)
973static void* winpr_convert_to_webp(WINPR_ATTR_UNUSED const void* data,
974 WINPR_ATTR_UNUSED size_t size, WINPR_ATTR_UNUSED UINT32 width,
975 WINPR_ATTR_UNUSED UINT32 height, WINPR_ATTR_UNUSED UINT32 stride,
976 WINPR_ATTR_UNUSED UINT32 bpp, UINT32* pSize)
977{
978 WINPR_ASSERT(data || (size == 0));
979 WINPR_ASSERT(pSize);
980
981 *pSize = 0;
982
983#if !defined(WINPR_UTILS_IMAGE_WEBP)
984 WLog_WARN(TAG, "WEBP not supported in this build");
985 return nullptr;
986#else
987 size_t dstSize = 0;
988 uint8_t* pDstData = nullptr;
989 WINPR_ASSERT(width <= INT32_MAX);
990 WINPR_ASSERT(height <= INT32_MAX);
991 WINPR_ASSERT(stride <= INT32_MAX);
992 switch (bpp)
993 {
994 case 32:
995 dstSize = WebPEncodeLosslessBGRA(data, (int)width, (int)height, (int)stride, &pDstData);
996 break;
997 case 24:
998 dstSize = WebPEncodeLosslessBGR(data, (int)width, (int)height, (int)stride, &pDstData);
999 break;
1000 default:
1001 return nullptr;
1002 }
1003
1004 void* rc = nullptr;
1005 if (dstSize > 0)
1006 rc = malloc(dstSize);
1007 if (rc)
1008 {
1009 memcpy(rc, pDstData, dstSize);
1010
1011 WINPR_ASSERT(dstSize <= UINT32_MAX);
1012 *pSize = (UINT32)dstSize;
1013 }
1014
1015 WebPFree(pDstData);
1016 return rc;
1017#endif
1018}
1019
1020SSIZE_T winpr_convert_from_webp(WINPR_ATTR_UNUSED const BYTE* comp_data,
1021 WINPR_ATTR_UNUSED size_t comp_data_bytes, UINT32* width,
1022 UINT32* height, UINT32* bpp, BYTE** ppdecomp_data, UINT32* scanline)
1023{
1024 WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
1025 WINPR_ASSERT(width);
1026 WINPR_ASSERT(height);
1027 WINPR_ASSERT(bpp);
1028 WINPR_ASSERT(ppdecomp_data);
1029 WINPR_ASSERT(scanline);
1030
1031 *width = 0;
1032 *height = 0;
1033 *bpp = 0;
1034 *ppdecomp_data = nullptr;
1035 *scanline = 0;
1036
1037#if !defined(WINPR_UTILS_IMAGE_WEBP)
1038 WLog_WARN(TAG, "WEBP not supported in this build");
1039 return -1;
1040#else
1041
1042 int w = 0;
1043 int h = 0;
1044 uint8_t* dst = WebPDecodeBGRA(comp_data, comp_data_bytes, &w, &h);
1045 if (!dst || (w < 0) || (h < 0))
1046 {
1047 free(dst);
1048 return -1;
1049 }
1050
1051 if ((w <= 0) || ((UINT32)w > UINT32_MAX / 4ull) || (h <= 0) ||
1052 ((UINT32)h > UINT32_MAX / 4ul / (UINT32)w))
1053 {
1054 free(dst);
1055 return -1;
1056 }
1057 *width = WINPR_ASSERTING_INT_CAST(uint32_t, w);
1058 *height = WINPR_ASSERTING_INT_CAST(uint32_t, h);
1059 *bpp = 32;
1060 *scanline = (*width) * 4ul;
1061 *ppdecomp_data = dst;
1062 return 1ll * (*scanline) * h;
1063#endif
1064}
1065
1066#if defined(WINPR_UTILS_IMAGE_PNG)
1067struct png_mem_encode
1068{
1069 char* buffer;
1070 size_t size;
1071};
1072
1073static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
1074{
1075 /* with libpng15 next line causes pointer deference error; use libpng12 */
1076 struct png_mem_encode* p =
1077 (struct png_mem_encode*)png_get_io_ptr(png_ptr); /* was png_ptr->io_ptr */
1078 size_t nsize = p->size + length;
1079
1080 /* A wrapped size or a failed (re)allocation must abort the write:
1081 * growing on would memcpy past the end of the undersized buffer
1082 * (CVE-2026-109256). png_error longjmps to save_png_to_buffer,
1083 * which frees the old buffer. */
1084 if (nsize < p->size)
1085 png_error(png_ptr, "Write Error");
1086
1087 /* allocate or grow buffer */
1088 if (p->buffer)
1089 {
1090 char* tmp = realloc(p->buffer, nsize);
1091 if (!tmp)
1092 png_error(png_ptr, "Write Error");
1093 p->buffer = tmp;
1094 }
1095 else
1096 p->buffer = malloc(nsize);
1097
1098 if (!p->buffer)
1099 png_error(png_ptr, "Write Error");
1100
1101 /* copy new bytes to end of buffer */
1102 memcpy(p->buffer + p->size, data, length);
1103 p->size += length;
1104}
1105
1106/* This is optional but included to show how png_set_write_fn() is called */
1107static void png_flush(WINPR_ATTR_UNUSED png_structp png_ptr)
1108{
1109}
1110
1111WINPR_ATTR_NODISCARD
1112static SSIZE_T save_png_to_buffer(UINT32 bpp, UINT32 width, uint32_t stride, UINT32 height,
1113 const uint8_t* data, size_t size, void** pDstData)
1114{
1115 SSIZE_T rc = -1;
1116 png_structp png_ptr = nullptr;
1117 png_infop info_ptr = nullptr;
1118 png_byte** row_pointers = nullptr;
1119 struct png_mem_encode state = WINPR_C_ARRAY_INIT;
1120
1121 *pDstData = nullptr;
1122
1123 if (!data || (size == 0))
1124 return 0;
1125
1126 if ((width == 0) || (height == 0) || (bpp == 0))
1127 return -1;
1128
1129 WINPR_ASSERT(pDstData);
1130
1131 if (size / height < stride)
1132 goto fail;
1133
1134 /* Initialize the write struct. */
1135 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
1136 if (png_ptr == nullptr)
1137 goto fail;
1138
1139 /* Initialize the info struct. */
1140 info_ptr = png_create_info_struct(png_ptr);
1141 if (info_ptr == nullptr)
1142 goto fail;
1143
1144 /* Set up error handling. */
1145 if (setjmp(png_jmpbuf(png_ptr)))
1146 goto fail;
1147
1148 /* Set image attributes. */
1149 int colorType = PNG_COLOR_TYPE_PALETTE;
1150 if (bpp > 8)
1151 colorType = PNG_COLOR_TYPE_RGB;
1152 if (bpp > 16)
1153 colorType = PNG_COLOR_TYPE_RGB;
1154 if (bpp > 24)
1155 colorType = PNG_COLOR_TYPE_RGBA;
1156
1157 const UINT32 pngChannels = (bpp > 24) ? 4 : ((bpp > 8) ? 3 : 1);
1158 const UINT32 srcBytesPerPixel = (bpp + 7) / 8;
1159 /* The row copy below emits exactly srcBytesPerPixel bytes per pixel; reject
1160 * inputs where that does not match the PNG channel count (e.g. 16bpp
1161 * sources), otherwise libpng reads past the (narrower) row buffer. */
1162 if ((srcBytesPerPixel != pngChannels) || (width > stride / srcBytesPerPixel))
1163 {
1164 WLog_ERR(TAG,
1165 "unsupported conversion to PNG: bpp=%" PRIu32 ", stride=%" PRIu32
1166 ", min expected stride=%llu",
1167 bpp, stride, 1ull * width * srcBytesPerPixel);
1168 goto fail;
1169 }
1170
1171 png_set_IHDR(png_ptr, info_ptr, width, height, 8, colorType, PNG_INTERLACE_NONE,
1172 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1173
1174 /* Initialize rows of PNG. */
1175#if SIZE_MAX <= UINT32_MAX
1176 if (height > SIZE_MAX / sizeof(png_byte*))
1177 goto fail;
1178#endif
1179
1180 row_pointers = (png_byte**)png_malloc(png_ptr, height * sizeof(png_byte*));
1181 for (size_t y = 0; y < height; ++y)
1182 {
1183 const uint8_t* line = &data[y * stride];
1184
1185 if (width > SIZE_MAX / sizeof(uint8_t) / pngChannels)
1186 goto fail;
1187
1188 uint8_t* row = png_malloc(png_ptr, sizeof(uint8_t) * width * pngChannels);
1189 row_pointers[y] = (png_byte*)row;
1190 for (size_t x = 0; x < width; ++x)
1191 {
1192
1193 *row++ = *line++;
1194 if (bpp > 8)
1195 *row++ = *line++;
1196 if (bpp > 16)
1197 *row++ = *line++;
1198 if (bpp > 24)
1199 *row++ = *line++;
1200 }
1201 }
1202
1203 /* Actually write the image data. */
1204 png_set_write_fn(png_ptr, &state, png_write_data, png_flush);
1205 png_set_rows(png_ptr, info_ptr, row_pointers);
1206 png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, nullptr);
1207
1208 /* Cleanup. */
1209 for (size_t y = 0; y < height; y++)
1210 png_free(png_ptr, row_pointers[y]);
1211 png_free(png_ptr, (void*)row_pointers);
1212
1213 /* Finish writing. */
1214 if (state.size > SSIZE_MAX)
1215 goto fail;
1216 rc = (SSIZE_T)state.size;
1217 *pDstData = state.buffer;
1218fail:
1219 png_destroy_write_struct(&png_ptr, &info_ptr);
1220 if (rc < 0)
1221 free(state.buffer);
1222 return rc;
1223}
1224
1225typedef struct
1226{
1227 png_bytep buffer;
1228 png_uint_32 bufsize;
1229 png_uint_32 current_pos;
1230} MEMORY_READER_STATE;
1231
1232static void read_data_memory(png_structp png_ptr, png_bytep data, size_t length)
1233{
1234 MEMORY_READER_STATE* f = png_get_io_ptr(png_ptr);
1235 if (length > (f->bufsize - f->current_pos))
1236 png_error(png_ptr, "read error in read_data_memory (loadpng)");
1237 else
1238 {
1239 memcpy(data, f->buffer + f->current_pos, length);
1240 f->current_pos += length;
1241 }
1242}
1243
1244WINPR_ATTR_MALLOC(free, 1)
1245static void* winpr_read_png_from_buffer(const void* data, size_t SrcSize, size_t* pSize,
1246 UINT32* pWidth, UINT32* pHeight, UINT32* pBpp,
1247 UINT32* pScanline)
1248{
1249 void* rc = nullptr;
1250 png_uint_32 width = 0;
1251 png_uint_32 height = 0;
1252 int bit_depth = 0;
1253 int color_type = 0;
1254 int interlace_type = 0;
1255 int transforms = PNG_TRANSFORM_IDENTITY;
1256 MEMORY_READER_STATE memory_reader_state = WINPR_C_ARRAY_INIT;
1257 png_bytepp row_pointers = nullptr;
1258 png_infop info_ptr = nullptr;
1259
1260 *pSize = 0;
1261 *pWidth = 0;
1262 *pHeight = 0;
1263 *pBpp = 0;
1264 *pScanline = 0;
1265
1266 if (SrcSize > UINT32_MAX)
1267 return nullptr;
1268
1269 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
1270 if (!png_ptr)
1271 goto fail;
1272 info_ptr = png_create_info_struct(png_ptr);
1273 if (!info_ptr)
1274 goto fail;
1275
1276 /* Set up error handling. */
1277 if (setjmp(png_jmpbuf(png_ptr)))
1278 goto fail;
1279
1280 memory_reader_state.buffer = WINPR_CAST_CONST_PTR_AWAY(data, png_bytep);
1281 memory_reader_state.bufsize = (UINT32)SrcSize;
1282 memory_reader_state.current_pos = 0;
1283
1284 png_set_read_fn(png_ptr, &memory_reader_state, read_data_memory);
1285
1286 transforms |= PNG_TRANSFORM_BGR;
1287 png_read_png(png_ptr, info_ptr, transforms, nullptr);
1288
1289 if (png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type,
1290 nullptr, nullptr) != 1)
1291 goto fail;
1292
1293 WINPR_ASSERT(bit_depth >= 0);
1294 const png_byte channelcount = png_get_channels(png_ptr, info_ptr);
1295 const size_t bpp = channelcount * (size_t)bit_depth;
1296
1297 row_pointers = png_get_rows(png_ptr, info_ptr);
1298 if (row_pointers)
1299 {
1300 if ((width == 0) || (width > SIZE_MAX / bpp))
1301 goto fail;
1302
1303 const size_t png_stride = png_get_rowbytes(png_ptr, info_ptr);
1304 if ((png_stride == 0) || (height == 0) || (png_stride > SIZE_MAX / height))
1305 goto fail;
1306 const size_t size = 1ULL * png_stride * height;
1307
1308 rc = calloc(png_stride, height);
1309 if (rc)
1310 {
1311 char* cur = rc;
1312 for (png_uint_32 i = 0; i < height; i++)
1313 {
1314 memcpy(cur, row_pointers[i], png_stride);
1315 cur += png_stride;
1316 }
1317 *pSize = size;
1318 *pWidth = width;
1319 *pHeight = height;
1320 WINPR_ASSERT(bpp <= UINT32_MAX);
1321 *pBpp = (UINT32)bpp;
1322 *pScanline = (UINT32)png_stride;
1323 }
1324 }
1325fail:
1326
1327 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
1328 return rc;
1329}
1330#endif
1331
1332WINPR_ATTR_MALLOC(free, 1)
1333static void* winpr_convert_to_png(WINPR_ATTR_UNUSED const void* data, WINPR_ATTR_UNUSED size_t size,
1334 UINT32 width, UINT32 height, UINT32 stride, UINT32 bpp,
1335 UINT32* pSize)
1336{
1337 WINPR_ASSERT(data || (size == 0));
1338 WINPR_ASSERT(pSize);
1339
1340 *pSize = 0;
1341
1342 if ((width == 0) || (height == 0) || (stride == 0) || (bpp == 0))
1343 return nullptr;
1344 if (height > UINT32_MAX / stride)
1345 return nullptr;
1346
1347#if defined(WINPR_UTILS_IMAGE_PNG)
1348 void* dst = nullptr;
1349 SSIZE_T rc = save_png_to_buffer(bpp, width, stride, height, data, size, &dst);
1350 if (rc <= 0)
1351 return nullptr;
1352 *pSize = (UINT32)rc;
1353 return dst;
1354#elif defined(WITH_LODEPNG)
1355 {
1356 BYTE* dst = nullptr;
1357 size_t dstsize = 0;
1358 unsigned rc = 1;
1359
1360 switch (bpp)
1361 {
1362 case 32:
1363 rc = lodepng_encode32(&dst, &dstsize, data, width, height);
1364 break;
1365 case 24:
1366 rc = lodepng_encode24(&dst, &dstsize, data, width, height);
1367 break;
1368 default:
1369 break;
1370 }
1371 if (rc)
1372 return nullptr;
1373 *pSize = (UINT32)dstsize;
1374 return dst;
1375 }
1376#else
1377 WLog_WARN(TAG, "PNG not supported in this build");
1378 return nullptr;
1379#endif
1380}
1381
1382SSIZE_T winpr_convert_from_png(WINPR_ATTR_UNUSED const BYTE* comp_data,
1383 WINPR_ATTR_UNUSED size_t comp_data_bytes, UINT32* width,
1384 UINT32* height, UINT32* bpp, BYTE** ppdecomp_data, UINT32* scanline)
1385{
1386 WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
1387 WINPR_ASSERT(width);
1388 WINPR_ASSERT(height);
1389 WINPR_ASSERT(bpp);
1390 WINPR_ASSERT(ppdecomp_data);
1391 WINPR_ASSERT(scanline);
1392
1393 *width = 0;
1394 *height = 0;
1395 *bpp = 0;
1396 *ppdecomp_data = nullptr;
1397 *scanline = 0;
1398
1399#if defined(WINPR_UTILS_IMAGE_PNG)
1400 size_t len = 0;
1401 *ppdecomp_data =
1402 winpr_read_png_from_buffer(comp_data, comp_data_bytes, &len, width, height, bpp, scanline);
1403 if (!*ppdecomp_data)
1404 return -1;
1405 return (SSIZE_T)len;
1406#elif defined(WITH_LODEPNG)
1407 *bpp = 32;
1408 if (*width > UINT32_MAX / 4)
1409 return -1;
1410
1411 SSIZE_T rc =
1412 lodepng_decode32((unsigned char**)ppdecomp_data, width, height, comp_data, comp_data_bytes);
1413 *scanline = *width * 4;
1414 return rc;
1415#else
1416 WLog_WARN(TAG, "PNG not supported in this build");
1417 return -1;
1418#endif
1419}
1420
1421BOOL winpr_image_format_is_supported(UINT32 format)
1422{
1423 switch (format)
1424 {
1425 case WINPR_IMAGE_BITMAP:
1426#if defined(WINPR_UTILS_IMAGE_PNG) || defined(WITH_LODEPNG)
1427 case WINPR_IMAGE_PNG:
1428#endif
1429#if defined(WINPR_UTILS_IMAGE_JPEG)
1430 case WINPR_IMAGE_JPEG:
1431#endif
1432#if defined(WINPR_UTILS_IMAGE_WEBP)
1433 case WINPR_IMAGE_WEBP:
1434#endif
1435 return TRUE;
1436 default:
1437 return FALSE;
1438 }
1439}
1440
1441WINPR_ATTR_MALLOC(free, 1)
1442static BYTE* convert(const wImage* image, size_t* pstride, WINPR_ATTR_UNUSED UINT32 flags)
1443{
1444 WINPR_ASSERT(image);
1445 WINPR_ASSERT(pstride);
1446
1447 *pstride = 0;
1448 if (image->bitsPerPixel < 24)
1449 return nullptr;
1450
1451 const size_t stride = image->width * 4ull;
1452 BYTE* data = calloc(stride, image->height);
1453 if (data)
1454 {
1455 for (size_t y = 0; y < image->height; y++)
1456 {
1457 const BYTE* srcLine = &image->data[image->scanline * y];
1458 BYTE* dstLine = &data[stride * y];
1459 if (image->bitsPerPixel == 32)
1460 memcpy(dstLine, srcLine, stride);
1461 else
1462 {
1463 for (size_t x = 0; x < image->width; x++)
1464 {
1465 const BYTE* src = &srcLine[image->bytesPerPixel * x];
1466 BYTE* dst = &dstLine[4ull * x];
1467 BYTE b = *src++;
1468 BYTE g = *src++;
1469 BYTE r = *src++;
1470
1471 *dst++ = b;
1472 *dst++ = g;
1473 *dst++ = r;
1474 *dst++ = 0xff;
1475 }
1476 }
1477 }
1478 *pstride = stride;
1479 }
1480 return data;
1481}
1482
1483WINPR_ATTR_NODISCARD
1484static BOOL compare_byte_relaxed(BYTE a, BYTE b, UINT32 flags)
1485{
1486 if (a != b)
1487 {
1488 if ((flags & WINPR_IMAGE_CMP_FUZZY) != 0)
1489 {
1490 const int diff = abs((int)a) - abs((int)b);
1491 /* filter out quantization errors */
1492 if (diff > 6)
1493 return FALSE;
1494 }
1495 else
1496 {
1497 return FALSE;
1498 }
1499 }
1500 return TRUE;
1501}
1502
1503WINPR_ATTR_NODISCARD
1504static BOOL compare_pixel(const BYTE* pa, const BYTE* pb, UINT32 flags)
1505{
1506 WINPR_ASSERT(pa);
1507 WINPR_ASSERT(pb);
1508
1509 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1510 return FALSE;
1511 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1512 return FALSE;
1513 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1514 return FALSE;
1515 if ((flags & WINPR_IMAGE_CMP_IGNORE_ALPHA) == 0)
1516 {
1517 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1518 return FALSE;
1519 }
1520 return TRUE;
1521}
1522
1523BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB, UINT32 flags)
1524{
1525 if (imageA == imageB)
1526 return TRUE;
1527 if (!imageA || !imageB)
1528 return FALSE;
1529
1530 if (imageA->height != imageB->height)
1531 return FALSE;
1532 if (imageA->width != imageB->width)
1533 return FALSE;
1534
1535 if ((flags & WINPR_IMAGE_CMP_IGNORE_DEPTH) == 0)
1536 {
1537 if (imageA->bitsPerPixel != imageB->bitsPerPixel)
1538 return FALSE;
1539 if (imageA->bytesPerPixel != imageB->bytesPerPixel)
1540 return FALSE;
1541 }
1542
1543 BOOL rc = FALSE;
1544 size_t astride = 0;
1545 size_t bstride = 0;
1546 BYTE* dataA = convert(imageA, &astride, flags);
1547 BYTE* dataB = convert(imageA, &bstride, flags);
1548 if (dataA && dataB && (astride == bstride))
1549 {
1550 rc = TRUE;
1551 for (size_t y = 0; y < imageA->height; y++)
1552 {
1553 const BYTE* lineA = &dataA[astride * y];
1554 const BYTE* lineB = &dataB[bstride * y];
1555
1556 for (size_t x = 0; x < imageA->width; x++)
1557 {
1558 const BYTE* pa = &lineA[x * 4ull];
1559 const BYTE* pb = &lineB[x * 4ull];
1560
1561 if (!compare_pixel(pa, pb, flags))
1562 rc = FALSE;
1563 }
1564 }
1565 }
1566 free(dataA);
1567 free(dataB);
1568 return rc;
1569}
1570
1571const char* winpr_image_format_mime(UINT32 format)
1572{
1573 switch (format)
1574 {
1575 case WINPR_IMAGE_BITMAP:
1576 return "image/bmp";
1577 case WINPR_IMAGE_PNG:
1578 return "image/png";
1579 case WINPR_IMAGE_WEBP:
1580 return "image/webp";
1581 case WINPR_IMAGE_JPEG:
1582 return "image/jpeg";
1583 default:
1584 return nullptr;
1585 }
1586}
1587
1588const char* winpr_image_format_extension(UINT32 format)
1589{
1590 switch (format)
1591 {
1592 case WINPR_IMAGE_BITMAP:
1593 return "bmp";
1594 case WINPR_IMAGE_PNG:
1595 return "png";
1596 case WINPR_IMAGE_WEBP:
1597 return "webp";
1598 case WINPR_IMAGE_JPEG:
1599 return "jpg";
1600 default:
1601 return nullptr;
1602 }
1603}
1604
1605void* winpr_image_write_buffer(wImage* image, UINT32 format, size_t* psize)
1606{
1607 WINPR_ASSERT(image);
1608 switch (format)
1609 {
1610 case WINPR_IMAGE_BITMAP:
1611 {
1612 UINT32 outsize = 0;
1613 size_t size = 1ull * image->height * image->scanline;
1614 void* data = winpr_bitmap_write_buffer(image->data, size, image->width, image->height,
1615 image->scanline, image->bitsPerPixel, &outsize);
1616 *psize = outsize;
1617 return data;
1618 }
1619 case WINPR_IMAGE_WEBP:
1620 {
1621 UINT32 outsize = 0;
1622 size_t size = 1ull * image->height * image->scanline;
1623 void* data = winpr_convert_to_webp(image->data, size, image->width, image->height,
1624 image->scanline, image->bitsPerPixel, &outsize);
1625 *psize = outsize;
1626 return data;
1627 }
1628 case WINPR_IMAGE_JPEG:
1629 {
1630 UINT32 outsize = 0;
1631 size_t size = 1ull * image->height * image->scanline;
1632 void* data = winpr_convert_to_jpeg(image->data, size, image->width, image->height,
1633 image->scanline, image->bitsPerPixel, &outsize);
1634 *psize = outsize;
1635 return data;
1636 }
1637 case WINPR_IMAGE_PNG:
1638 {
1639 UINT32 outsize = 0;
1640 size_t size = 1ull * image->height * image->scanline;
1641 void* data = winpr_convert_to_png(image->data, size, image->width, image->height,
1642 image->scanline, image->bitsPerPixel, &outsize);
1643 *psize = outsize;
1644 return data;
1645 }
1646 default:
1647 *psize = 0;
1648 return nullptr;
1649 }
1650}