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 /* allocate or grow buffer */
1081 if (p->buffer)
1082 {
1083 char* tmp = realloc(p->buffer, nsize);
1084 if (tmp)
1085 p->buffer = tmp;
1086 }
1087 else
1088 p->buffer = malloc(nsize);
1089
1090 if (!p->buffer)
1091 png_error(png_ptr, "Write Error");
1092
1093 /* copy new bytes to end of buffer */
1094 memcpy(p->buffer + p->size, data, length);
1095 p->size += length;
1096}
1097
1098/* This is optional but included to show how png_set_write_fn() is called */
1099static void png_flush(WINPR_ATTR_UNUSED png_structp png_ptr)
1100{
1101}
1102
1103WINPR_ATTR_NODISCARD
1104static SSIZE_T save_png_to_buffer(UINT32 bpp, UINT32 width, uint32_t stride, UINT32 height,
1105 const uint8_t* data, size_t size, void** pDstData)
1106{
1107 SSIZE_T rc = -1;
1108 png_structp png_ptr = nullptr;
1109 png_infop info_ptr = nullptr;
1110 png_byte** row_pointers = nullptr;
1111 struct png_mem_encode state = WINPR_C_ARRAY_INIT;
1112
1113 *pDstData = nullptr;
1114
1115 if (!data || (size == 0))
1116 return 0;
1117
1118 if ((width == 0) || (height == 0) || (bpp == 0))
1119 return -1;
1120
1121 WINPR_ASSERT(pDstData);
1122
1123 if (size / height < stride)
1124 goto fail;
1125
1126 /* Initialize the write struct. */
1127 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
1128 if (png_ptr == nullptr)
1129 goto fail;
1130
1131 /* Initialize the info struct. */
1132 info_ptr = png_create_info_struct(png_ptr);
1133 if (info_ptr == nullptr)
1134 goto fail;
1135
1136 /* Set up error handling. */
1137 if (setjmp(png_jmpbuf(png_ptr)))
1138 goto fail;
1139
1140 /* Set image attributes. */
1141 int colorType = PNG_COLOR_TYPE_PALETTE;
1142 if (bpp > 8)
1143 colorType = PNG_COLOR_TYPE_RGB;
1144 if (bpp > 16)
1145 colorType = PNG_COLOR_TYPE_RGB;
1146 if (bpp > 24)
1147 colorType = PNG_COLOR_TYPE_RGBA;
1148
1149 const UINT32 pngChannels = (bpp > 24) ? 4 : ((bpp > 8) ? 3 : 1);
1150 const UINT32 srcBytesPerPixel = (bpp + 7) / 8;
1151 /* The row copy below emits exactly srcBytesPerPixel bytes per pixel; reject
1152 * inputs where that does not match the PNG channel count (e.g. 16bpp
1153 * sources), otherwise libpng reads past the (narrower) row buffer. */
1154 if ((srcBytesPerPixel != pngChannels) || (width > stride / srcBytesPerPixel))
1155 {
1156 WLog_ERR(TAG,
1157 "unsupported conversion to PNG: bpp=%" PRIu32 ", stride=%" PRIu32
1158 ", min expected stride=%llu",
1159 bpp, stride, 1ull * width * srcBytesPerPixel);
1160 goto fail;
1161 }
1162
1163 png_set_IHDR(png_ptr, info_ptr, width, height, 8, colorType, PNG_INTERLACE_NONE,
1164 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1165
1166 /* Initialize rows of PNG. */
1167#if SIZE_MAX <= UINT32_MAX
1168 if (height > SIZE_MAX / sizeof(png_byte*))
1169 goto fail;
1170#endif
1171
1172 row_pointers = (png_byte**)png_malloc(png_ptr, height * sizeof(png_byte*));
1173 for (size_t y = 0; y < height; ++y)
1174 {
1175 const uint8_t* line = &data[y * stride];
1176
1177 if (width > SIZE_MAX / sizeof(uint8_t) / pngChannels)
1178 goto fail;
1179
1180 uint8_t* row = png_malloc(png_ptr, sizeof(uint8_t) * width * pngChannels);
1181 row_pointers[y] = (png_byte*)row;
1182 for (size_t x = 0; x < width; ++x)
1183 {
1184
1185 *row++ = *line++;
1186 if (bpp > 8)
1187 *row++ = *line++;
1188 if (bpp > 16)
1189 *row++ = *line++;
1190 if (bpp > 24)
1191 *row++ = *line++;
1192 }
1193 }
1194
1195 /* Actually write the image data. */
1196 png_set_write_fn(png_ptr, &state, png_write_data, png_flush);
1197 png_set_rows(png_ptr, info_ptr, row_pointers);
1198 png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, nullptr);
1199
1200 /* Cleanup. */
1201 for (size_t y = 0; y < height; y++)
1202 png_free(png_ptr, row_pointers[y]);
1203 png_free(png_ptr, (void*)row_pointers);
1204
1205 /* Finish writing. */
1206 if (state.size > SSIZE_MAX)
1207 goto fail;
1208 rc = (SSIZE_T)state.size;
1209 *pDstData = state.buffer;
1210fail:
1211 png_destroy_write_struct(&png_ptr, &info_ptr);
1212 if (rc < 0)
1213 free(state.buffer);
1214 return rc;
1215}
1216
1217typedef struct
1218{
1219 png_bytep buffer;
1220 png_uint_32 bufsize;
1221 png_uint_32 current_pos;
1222} MEMORY_READER_STATE;
1223
1224static void read_data_memory(png_structp png_ptr, png_bytep data, size_t length)
1225{
1226 MEMORY_READER_STATE* f = png_get_io_ptr(png_ptr);
1227 if (length > (f->bufsize - f->current_pos))
1228 png_error(png_ptr, "read error in read_data_memory (loadpng)");
1229 else
1230 {
1231 memcpy(data, f->buffer + f->current_pos, length);
1232 f->current_pos += length;
1233 }
1234}
1235
1236WINPR_ATTR_MALLOC(free, 1)
1237static void* winpr_read_png_from_buffer(const void* data, size_t SrcSize, size_t* pSize,
1238 UINT32* pWidth, UINT32* pHeight, UINT32* pBpp,
1239 UINT32* pScanline)
1240{
1241 void* rc = nullptr;
1242 png_uint_32 width = 0;
1243 png_uint_32 height = 0;
1244 int bit_depth = 0;
1245 int color_type = 0;
1246 int interlace_type = 0;
1247 int transforms = PNG_TRANSFORM_IDENTITY;
1248 MEMORY_READER_STATE memory_reader_state = WINPR_C_ARRAY_INIT;
1249 png_bytepp row_pointers = nullptr;
1250 png_infop info_ptr = nullptr;
1251
1252 *pSize = 0;
1253 *pWidth = 0;
1254 *pHeight = 0;
1255 *pBpp = 0;
1256 *pScanline = 0;
1257
1258 if (SrcSize > UINT32_MAX)
1259 return nullptr;
1260
1261 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
1262 if (!png_ptr)
1263 goto fail;
1264 info_ptr = png_create_info_struct(png_ptr);
1265 if (!info_ptr)
1266 goto fail;
1267
1268 /* Set up error handling. */
1269 if (setjmp(png_jmpbuf(png_ptr)))
1270 goto fail;
1271
1272 memory_reader_state.buffer = WINPR_CAST_CONST_PTR_AWAY(data, png_bytep);
1273 memory_reader_state.bufsize = (UINT32)SrcSize;
1274 memory_reader_state.current_pos = 0;
1275
1276 png_set_read_fn(png_ptr, &memory_reader_state, read_data_memory);
1277
1278 transforms |= PNG_TRANSFORM_BGR;
1279 png_read_png(png_ptr, info_ptr, transforms, nullptr);
1280
1281 if (png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type,
1282 nullptr, nullptr) != 1)
1283 goto fail;
1284
1285 WINPR_ASSERT(bit_depth >= 0);
1286 const png_byte channelcount = png_get_channels(png_ptr, info_ptr);
1287 const size_t bpp = channelcount * (size_t)bit_depth;
1288
1289 row_pointers = png_get_rows(png_ptr, info_ptr);
1290 if (row_pointers)
1291 {
1292 if ((width == 0) || (width > SIZE_MAX / bpp))
1293 goto fail;
1294
1295 const size_t png_stride = png_get_rowbytes(png_ptr, info_ptr);
1296 if ((png_stride == 0) || (height == 0) || (png_stride > SIZE_MAX / height))
1297 goto fail;
1298 const size_t size = 1ULL * png_stride * height;
1299
1300 rc = calloc(png_stride, height);
1301 if (rc)
1302 {
1303 char* cur = rc;
1304 for (png_uint_32 i = 0; i < height; i++)
1305 {
1306 memcpy(cur, row_pointers[i], png_stride);
1307 cur += png_stride;
1308 }
1309 *pSize = size;
1310 *pWidth = width;
1311 *pHeight = height;
1312 WINPR_ASSERT(bpp <= UINT32_MAX);
1313 *pBpp = (UINT32)bpp;
1314 *pScanline = (UINT32)png_stride;
1315 }
1316 }
1317fail:
1318
1319 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
1320 return rc;
1321}
1322#endif
1323
1324WINPR_ATTR_MALLOC(free, 1)
1325static void* winpr_convert_to_png(WINPR_ATTR_UNUSED const void* data, WINPR_ATTR_UNUSED size_t size,
1326 UINT32 width, UINT32 height, UINT32 stride, UINT32 bpp,
1327 UINT32* pSize)
1328{
1329 WINPR_ASSERT(data || (size == 0));
1330 WINPR_ASSERT(pSize);
1331
1332 *pSize = 0;
1333
1334 if ((width == 0) || (height == 0) || (stride == 0) || (bpp == 0))
1335 return nullptr;
1336 if (height > UINT32_MAX / stride)
1337 return nullptr;
1338
1339#if defined(WINPR_UTILS_IMAGE_PNG)
1340 void* dst = nullptr;
1341 SSIZE_T rc = save_png_to_buffer(bpp, width, stride, height, data, size, &dst);
1342 if (rc <= 0)
1343 return nullptr;
1344 *pSize = (UINT32)rc;
1345 return dst;
1346#elif defined(WITH_LODEPNG)
1347 {
1348 BYTE* dst = nullptr;
1349 size_t dstsize = 0;
1350 unsigned rc = 1;
1351
1352 switch (bpp)
1353 {
1354 case 32:
1355 rc = lodepng_encode32(&dst, &dstsize, data, width, height);
1356 break;
1357 case 24:
1358 rc = lodepng_encode24(&dst, &dstsize, data, width, height);
1359 break;
1360 default:
1361 break;
1362 }
1363 if (rc)
1364 return nullptr;
1365 *pSize = (UINT32)dstsize;
1366 return dst;
1367 }
1368#else
1369 WLog_WARN(TAG, "PNG not supported in this build");
1370 return nullptr;
1371#endif
1372}
1373
1374SSIZE_T winpr_convert_from_png(WINPR_ATTR_UNUSED const BYTE* comp_data,
1375 WINPR_ATTR_UNUSED size_t comp_data_bytes, UINT32* width,
1376 UINT32* height, UINT32* bpp, BYTE** ppdecomp_data, UINT32* scanline)
1377{
1378 WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
1379 WINPR_ASSERT(width);
1380 WINPR_ASSERT(height);
1381 WINPR_ASSERT(bpp);
1382 WINPR_ASSERT(ppdecomp_data);
1383 WINPR_ASSERT(scanline);
1384
1385 *width = 0;
1386 *height = 0;
1387 *bpp = 0;
1388 *ppdecomp_data = nullptr;
1389 *scanline = 0;
1390
1391#if defined(WINPR_UTILS_IMAGE_PNG)
1392 size_t len = 0;
1393 *ppdecomp_data =
1394 winpr_read_png_from_buffer(comp_data, comp_data_bytes, &len, width, height, bpp, scanline);
1395 if (!*ppdecomp_data)
1396 return -1;
1397 return (SSIZE_T)len;
1398#elif defined(WITH_LODEPNG)
1399 *bpp = 32;
1400 if (*width > UINT32_MAX / 4)
1401 return -1;
1402
1403 SSIZE_T rc =
1404 lodepng_decode32((unsigned char**)ppdecomp_data, width, height, comp_data, comp_data_bytes);
1405 *scanline = *width * 4;
1406 return rc;
1407#else
1408 WLog_WARN(TAG, "PNG not supported in this build");
1409 return -1;
1410#endif
1411}
1412
1413BOOL winpr_image_format_is_supported(UINT32 format)
1414{
1415 switch (format)
1416 {
1417 case WINPR_IMAGE_BITMAP:
1418#if defined(WINPR_UTILS_IMAGE_PNG) || defined(WITH_LODEPNG)
1419 case WINPR_IMAGE_PNG:
1420#endif
1421#if defined(WINPR_UTILS_IMAGE_JPEG)
1422 case WINPR_IMAGE_JPEG:
1423#endif
1424#if defined(WINPR_UTILS_IMAGE_WEBP)
1425 case WINPR_IMAGE_WEBP:
1426#endif
1427 return TRUE;
1428 default:
1429 return FALSE;
1430 }
1431}
1432
1433WINPR_ATTR_MALLOC(free, 1)
1434static BYTE* convert(const wImage* image, size_t* pstride, WINPR_ATTR_UNUSED UINT32 flags)
1435{
1436 WINPR_ASSERT(image);
1437 WINPR_ASSERT(pstride);
1438
1439 *pstride = 0;
1440 if (image->bitsPerPixel < 24)
1441 return nullptr;
1442
1443 const size_t stride = image->width * 4ull;
1444 BYTE* data = calloc(stride, image->height);
1445 if (data)
1446 {
1447 for (size_t y = 0; y < image->height; y++)
1448 {
1449 const BYTE* srcLine = &image->data[image->scanline * y];
1450 BYTE* dstLine = &data[stride * y];
1451 if (image->bitsPerPixel == 32)
1452 memcpy(dstLine, srcLine, stride);
1453 else
1454 {
1455 for (size_t x = 0; x < image->width; x++)
1456 {
1457 const BYTE* src = &srcLine[image->bytesPerPixel * x];
1458 BYTE* dst = &dstLine[4ull * x];
1459 BYTE b = *src++;
1460 BYTE g = *src++;
1461 BYTE r = *src++;
1462
1463 *dst++ = b;
1464 *dst++ = g;
1465 *dst++ = r;
1466 *dst++ = 0xff;
1467 }
1468 }
1469 }
1470 *pstride = stride;
1471 }
1472 return data;
1473}
1474
1475WINPR_ATTR_NODISCARD
1476static BOOL compare_byte_relaxed(BYTE a, BYTE b, UINT32 flags)
1477{
1478 if (a != b)
1479 {
1480 if ((flags & WINPR_IMAGE_CMP_FUZZY) != 0)
1481 {
1482 const int diff = abs((int)a) - abs((int)b);
1483 /* filter out quantization errors */
1484 if (diff > 6)
1485 return FALSE;
1486 }
1487 else
1488 {
1489 return FALSE;
1490 }
1491 }
1492 return TRUE;
1493}
1494
1495WINPR_ATTR_NODISCARD
1496static BOOL compare_pixel(const BYTE* pa, const BYTE* pb, UINT32 flags)
1497{
1498 WINPR_ASSERT(pa);
1499 WINPR_ASSERT(pb);
1500
1501 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1502 return FALSE;
1503 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1504 return FALSE;
1505 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1506 return FALSE;
1507 if ((flags & WINPR_IMAGE_CMP_IGNORE_ALPHA) == 0)
1508 {
1509 if (!compare_byte_relaxed(*pa++, *pb++, flags))
1510 return FALSE;
1511 }
1512 return TRUE;
1513}
1514
1515BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB, UINT32 flags)
1516{
1517 if (imageA == imageB)
1518 return TRUE;
1519 if (!imageA || !imageB)
1520 return FALSE;
1521
1522 if (imageA->height != imageB->height)
1523 return FALSE;
1524 if (imageA->width != imageB->width)
1525 return FALSE;
1526
1527 if ((flags & WINPR_IMAGE_CMP_IGNORE_DEPTH) == 0)
1528 {
1529 if (imageA->bitsPerPixel != imageB->bitsPerPixel)
1530 return FALSE;
1531 if (imageA->bytesPerPixel != imageB->bytesPerPixel)
1532 return FALSE;
1533 }
1534
1535 BOOL rc = FALSE;
1536 size_t astride = 0;
1537 size_t bstride = 0;
1538 BYTE* dataA = convert(imageA, &astride, flags);
1539 BYTE* dataB = convert(imageA, &bstride, flags);
1540 if (dataA && dataB && (astride == bstride))
1541 {
1542 rc = TRUE;
1543 for (size_t y = 0; y < imageA->height; y++)
1544 {
1545 const BYTE* lineA = &dataA[astride * y];
1546 const BYTE* lineB = &dataB[bstride * y];
1547
1548 for (size_t x = 0; x < imageA->width; x++)
1549 {
1550 const BYTE* pa = &lineA[x * 4ull];
1551 const BYTE* pb = &lineB[x * 4ull];
1552
1553 if (!compare_pixel(pa, pb, flags))
1554 rc = FALSE;
1555 }
1556 }
1557 }
1558 free(dataA);
1559 free(dataB);
1560 return rc;
1561}
1562
1563const char* winpr_image_format_mime(UINT32 format)
1564{
1565 switch (format)
1566 {
1567 case WINPR_IMAGE_BITMAP:
1568 return "image/bmp";
1569 case WINPR_IMAGE_PNG:
1570 return "image/png";
1571 case WINPR_IMAGE_WEBP:
1572 return "image/webp";
1573 case WINPR_IMAGE_JPEG:
1574 return "image/jpeg";
1575 default:
1576 return nullptr;
1577 }
1578}
1579
1580const char* winpr_image_format_extension(UINT32 format)
1581{
1582 switch (format)
1583 {
1584 case WINPR_IMAGE_BITMAP:
1585 return "bmp";
1586 case WINPR_IMAGE_PNG:
1587 return "png";
1588 case WINPR_IMAGE_WEBP:
1589 return "webp";
1590 case WINPR_IMAGE_JPEG:
1591 return "jpg";
1592 default:
1593 return nullptr;
1594 }
1595}
1596
1597void* winpr_image_write_buffer(wImage* image, UINT32 format, size_t* psize)
1598{
1599 WINPR_ASSERT(image);
1600 switch (format)
1601 {
1602 case WINPR_IMAGE_BITMAP:
1603 {
1604 UINT32 outsize = 0;
1605 size_t size = 1ull * image->height * image->scanline;
1606 void* data = winpr_bitmap_write_buffer(image->data, size, image->width, image->height,
1607 image->scanline, image->bitsPerPixel, &outsize);
1608 *psize = outsize;
1609 return data;
1610 }
1611 case WINPR_IMAGE_WEBP:
1612 {
1613 UINT32 outsize = 0;
1614 size_t size = 1ull * image->height * image->scanline;
1615 void* data = winpr_convert_to_webp(image->data, size, image->width, image->height,
1616 image->scanline, image->bitsPerPixel, &outsize);
1617 *psize = outsize;
1618 return data;
1619 }
1620 case WINPR_IMAGE_JPEG:
1621 {
1622 UINT32 outsize = 0;
1623 size_t size = 1ull * image->height * image->scanline;
1624 void* data = winpr_convert_to_jpeg(image->data, size, image->width, image->height,
1625 image->scanline, image->bitsPerPixel, &outsize);
1626 *psize = outsize;
1627 return data;
1628 }
1629 case WINPR_IMAGE_PNG:
1630 {
1631 UINT32 outsize = 0;
1632 size_t size = 1ull * image->height * image->scanline;
1633 void* data = winpr_convert_to_png(image->data, size, image->width, image->height,
1634 image->scanline, image->bitsPerPixel, &outsize);
1635 *psize = outsize;
1636 return data;
1637 }
1638 default:
1639 *psize = 0;
1640 return nullptr;
1641 }
1642}