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