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