Coverage Report

Created: 2026-10-15 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/utils/image.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Image Utils
4
 *
5
 * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 * Copyright 2016 Inuvika Inc.
7
 * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
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)
45
typedef 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). */
54
WINPR_NORETURN(static void winpr_jpeg_error_exit(j_common_ptr cinfo));
55
56
void 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
1
#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
83
WINPR_ATTR_NODISCARD
84
static 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
88
WINPR_ATTR_NODISCARD
89
static 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
93
WINPR_ATTR_NODISCARD
94
static 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
98
BOOL writeBitmapFileHeader(wStream* s, const WINPR_BITMAP_FILE_HEADER* bf)
99
3.92k
{
100
3.92k
  if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_FILE_HEADER)))
101
0
    return FALSE;
102
103
3.92k
  Stream_Write_UINT8(s, bf->bfType[0]);
104
3.92k
  Stream_Write_UINT8(s, bf->bfType[1]);
105
3.92k
  Stream_Write_UINT32(s, bf->bfSize);
106
3.92k
  Stream_Write_UINT16(s, bf->bfReserved1);
107
3.92k
  Stream_Write_UINT16(s, bf->bfReserved2);
108
3.92k
  Stream_Write_UINT32(s, bf->bfOffBits);
109
3.92k
  return TRUE;
110
3.92k
}
111
112
BOOL readBitmapFileHeader(wStream* s, WINPR_BITMAP_FILE_HEADER* bf)
113
3.43k
{
114
3.43k
  static wLog* log = nullptr;
115
3.43k
  if (!log)
116
1
    log = WLog_Get(TAG);
117
118
3.43k
  if (!s || !bf ||
119
3.43k
      (!Stream_CheckAndLogRequiredLengthWLog(log, s, sizeof(WINPR_BITMAP_FILE_HEADER))))
120
8
    return FALSE;
121
122
3.42k
  Stream_Read_UINT8(s, bf->bfType[0]);
123
3.42k
  Stream_Read_UINT8(s, bf->bfType[1]);
124
3.42k
  Stream_Read_UINT32(s, bf->bfSize);
125
3.42k
  Stream_Read_UINT16(s, bf->bfReserved1);
126
3.42k
  Stream_Read_UINT16(s, bf->bfReserved2);
127
3.42k
  Stream_Read_UINT32(s, bf->bfOffBits);
128
129
3.42k
  if (bf->bfSize < sizeof(WINPR_BITMAP_FILE_HEADER))
130
16
  {
131
16
    WLog_Print(log, WLOG_ERROR, "Invalid bitmap::bfSize=%" PRIu32 ", require at least %" PRIuz,
132
16
               bf->bfSize, sizeof(WINPR_BITMAP_FILE_HEADER));
133
16
    return FALSE;
134
16
  }
135
136
3.41k
  if ((bf->bfType[0] != 'B') || (bf->bfType[1] != 'M'))
137
0
  {
138
0
    WLog_Print(log, WLOG_ERROR, "Invalid bitmap header [%c%c], expected [BM]", bf->bfType[0],
139
0
               bf->bfType[1]);
140
0
    return FALSE;
141
0
  }
142
3.41k
  return Stream_CheckAndLogRequiredLengthWLog(log, s,
143
3.41k
                                              bf->bfSize - sizeof(WINPR_BITMAP_FILE_HEADER));
144
3.41k
}
145
146
BOOL writeBitmapInfoHeader(wStream* s, const WINPR_BITMAP_INFO_HEADER* bi)
147
623
{
148
623
  if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_INFO_HEADER)))
149
0
    return FALSE;
150
151
623
  Stream_Write_UINT32(s, bi->biSize);
152
623
  Stream_Write_INT32(s, bi->biWidth);
153
623
  Stream_Write_INT32(s, bi->biHeight);
154
623
  Stream_Write_UINT16(s, bi->biPlanes);
155
623
  Stream_Write_UINT16(s, bi->biBitCount);
156
623
  Stream_Write_UINT32(s, bi->biCompression);
157
623
  Stream_Write_UINT32(s, bi->biSizeImage);
158
623
  Stream_Write_INT32(s, bi->biXPelsPerMeter);
159
623
  Stream_Write_INT32(s, bi->biYPelsPerMeter);
160
623
  Stream_Write_UINT32(s, bi->biClrUsed);
161
623
  Stream_Write_UINT32(s, bi->biClrImportant);
162
623
  return TRUE;
163
623
}
164
165
BOOL readBitmapInfoHeader(wStream* s, WINPR_BITMAP_INFO_HEADER* bi, size_t* poffset)
166
8.27k
{
167
8.27k
  if (!s || !bi || (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(WINPR_BITMAP_INFO_HEADER))))
168
16
    return FALSE;
169
170
8.25k
  const size_t start = Stream_GetPosition(s);
171
8.25k
  Stream_Read_UINT32(s, bi->biSize);
172
8.25k
  Stream_Read_INT32(s, bi->biWidth);
173
8.25k
  Stream_Read_INT32(s, bi->biHeight);
174
8.25k
  Stream_Read_UINT16(s, bi->biPlanes);
175
8.25k
  Stream_Read_UINT16(s, bi->biBitCount);
176
8.25k
  Stream_Read_UINT32(s, bi->biCompression);
177
8.25k
  Stream_Read_UINT32(s, bi->biSizeImage);
178
8.25k
  Stream_Read_INT32(s, bi->biXPelsPerMeter);
179
8.25k
  Stream_Read_INT32(s, bi->biYPelsPerMeter);
180
8.25k
  Stream_Read_UINT32(s, bi->biClrUsed);
181
8.25k
  Stream_Read_UINT32(s, bi->biClrImportant);
182
183
8.25k
  switch (bi->biBitCount)
184
8.25k
  {
185
4.69k
    case 1:
186
4.84k
    case 4:
187
5.55k
    case 8:
188
6.99k
    case 16:
189
7.46k
    case 24:
190
8.05k
    case 32:
191
8.05k
      break;
192
206
    default:
193
206
      WLog_WARN(TAG, "invalid biBitCount=%" PRIu32, bi->biBitCount);
194
206
      return FALSE;
195
8.25k
  }
196
197
8.05k
  if (bi->biWidth < 0)
198
162
  {
199
162
    WLog_WARN(TAG, "negative biWidth=%" PRId32, bi->biWidth);
200
162
    return FALSE;
201
162
  }
202
203
  /* https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader */
204
7.88k
  size_t offset = 0;
205
7.88k
  switch (bi->biCompression)
206
7.88k
  {
207
7.44k
    case BI_RGB:
208
7.44k
      if (bi->biBitCount <= 8)
209
4.97k
      {
210
4.97k
        DWORD used = bi->biClrUsed;
211
4.97k
        if (used == 0)
212
2.23k
          used = 1u << bi->biBitCount;
213
4.97k
        offset += sizeof(RGBQUAD) * used;
214
4.97k
      }
215
7.44k
      if (bi->biSizeImage == 0)
216
1.89k
      {
217
1.89k
        const UINT64 rawustride =
218
1.89k
            WINPR_ASSERTING_INT_CAST(UINT64, bi->biWidth) * bi->biBitCount;
219
1.89k
        const UINT64 ustride = ((rawustride + 31) & ~31u) >> 3;
220
1.89k
        if (ustride > UINT32_MAX)
221
0
        {
222
0
          WLog_ERR(TAG, "bi->biWidth * bi->biBitCount > UINT32_MAX");
223
0
          return FALSE;
224
0
        }
225
226
1.89k
        const UINT32 stride = WINPR_ASSERTING_INT_CAST(uint32_t, ustride);
227
1.89k
        const UINT64 usize = WINPR_ASSERTING_INT_CAST(UINT64, llabs(bi->biHeight)) * stride;
228
1.89k
        if (usize > UINT32_MAX)
229
162
        {
230
162
          WLog_ERR(TAG, "abs(bi->biHeight) * stride > UINT32_MAX");
231
162
          return FALSE;
232
162
        }
233
1.73k
        bi->biSizeImage = WINPR_ASSERTING_INT_CAST(uint32_t, usize);
234
1.73k
      }
235
7.28k
      break;
236
7.28k
    case BI_BITFIELDS:
237
      /* BITMAPV4HEADER and BITMAPV5HEADER include the color masks in biSize. */
238
404
      if (bi->biSize == sizeof(WINPR_BITMAP_INFO_HEADER))
239
38
        offset += sizeof(DWORD) * 3; // 3 DWORD color masks
240
404
      break;
241
42
    default:
242
42
      WLog_ERR(TAG, "unsupported biCompression %" PRIu32, bi->biCompression);
243
42
      return FALSE;
244
7.88k
  }
245
246
7.68k
  if (bi->biSizeImage == 0)
247
42
  {
248
42
    WLog_ERR(TAG, "invalid biSizeImage %" PRIu32, bi->biSizeImage);
249
42
    return FALSE;
250
42
  }
251
252
7.64k
  const size_t pos = Stream_GetPosition(s) - start;
253
7.64k
  if (bi->biSize < pos)
254
72
  {
255
72
    WLog_ERR(TAG, "invalid biSize %" PRIu32 " < (actual) offset %" PRIuz, bi->biSize, pos);
256
72
    return FALSE;
257
72
  }
258
259
7.57k
  *poffset = offset;
260
7.57k
  return Stream_SafeSeek(s, bi->biSize - pos);
261
7.64k
}
262
263
WINPR_ATTR_MALLOC(Stream_Free, 1)
264
static wStream* winpr_bitmap_construct_header_stream(size_t width, size_t height, size_t bpp,
265
                                                     BOOL bottomUp)
266
623
{
267
623
  WINPR_BITMAP_FILE_HEADER bf = WINPR_C_ARRAY_INIT;
268
623
  WINPR_BITMAP_INFO_HEADER bi = WINPR_C_ARRAY_INIT;
269
270
623
  size_t stride = (width * bpp + 7) / 8;
271
623
  if ((stride % 4) != 0)
272
410
    stride += 4 - (stride % 4);
273
274
623
  size_t imgSize = stride * height;
275
623
  if ((width > INT32_MAX) || (height > INT32_MAX) || (bpp > UINT16_MAX) || (imgSize > UINT32_MAX))
276
0
    return nullptr;
277
278
623
  wStream* s = Stream_New(nullptr, WINPR_IMAGE_BMP_HEADER_LEN);
279
623
  if (!s)
280
0
    return nullptr;
281
282
623
  bf.bfType[0] = 'B';
283
623
  bf.bfType[1] = 'M';
284
623
  bf.bfReserved1 = 0;
285
623
  bf.bfReserved2 = 0;
286
623
  bi.biSize = (UINT32)sizeof(WINPR_BITMAP_INFO_HEADER);
287
623
  bf.bfOffBits = (UINT32)sizeof(WINPR_BITMAP_FILE_HEADER) + bi.biSize;
288
623
  bi.biSizeImage = (UINT32)imgSize;
289
623
  bf.bfSize = bf.bfOffBits + bi.biSizeImage;
290
623
  bi.biWidth = (INT32)width;
291
623
  bi.biHeight = bottomUp ? (INT32)height : -1 * (INT32)height;
292
623
  bi.biPlanes = 1;
293
623
  bi.biBitCount = (UINT16)bpp;
294
623
  bi.biCompression = BI_RGB;
295
623
  bi.biXPelsPerMeter = (INT32)width;
296
623
  bi.biYPelsPerMeter = (INT32)height;
297
623
  bi.biClrUsed = 0;
298
623
  bi.biClrImportant = 0;
299
300
623
  size_t offset = 0;
301
623
  switch (bi.biCompression)
302
623
  {
303
623
    case BI_RGB:
304
623
      if (bi.biBitCount <= 8)
305
191
      {
306
191
        DWORD used = bi.biClrUsed;
307
191
        if (used == 0)
308
191
          used = 1u << bi.biBitCount;
309
191
        offset += sizeof(RGBQUAD) * used;
310
191
      }
311
623
      if (bi.biSizeImage == 0)
312
0
      {
313
0
        const UINT64 rawustride =
314
0
            WINPR_ASSERTING_INT_CAST(UINT64, bi.biWidth) * bi.biBitCount;
315
0
        const UINT64 ustride = ((rawustride + 31) & ~31u) >> 3;
316
0
        if (ustride > UINT32_MAX)
317
0
        {
318
0
          WLog_ERR(TAG, "bi->biWidth * bi->biBitCount > UINT32_MAX");
319
0
          goto fail;
320
0
        }
321
322
0
        const UINT32 xstride = WINPR_ASSERTING_INT_CAST(uint32_t, ustride);
323
0
        const UINT64 usize = WINPR_ASSERTING_INT_CAST(UINT64, llabs(bi.biHeight)) * xstride;
324
0
        if (usize > UINT32_MAX)
325
0
        {
326
0
          WLog_ERR(TAG, "abs(bi->biHeight) * stride > UINT32_MAX");
327
0
          goto fail;
328
0
        }
329
0
        bi.biSizeImage = WINPR_ASSERTING_INT_CAST(uint32_t, usize);
330
0
      }
331
623
      break;
332
623
    case BI_BITFIELDS:
333
0
      offset += sizeof(DWORD) * 3; // 3 DWORD color masks
334
0
      break;
335
0
    default:
336
0
      return nullptr;
337
623
  }
338
339
623
  if (offset > UINT32_MAX - bf.bfOffBits)
340
0
    goto fail;
341
623
  bf.bfOffBits += WINPR_ASSERTING_INT_CAST(UINT32, offset);
342
343
623
  if (!writeBitmapFileHeader(s, &bf))
344
0
    goto fail;
345
346
623
  if (!writeBitmapInfoHeader(s, &bi))
347
0
    goto fail;
348
349
623
  if (!Stream_EnsureRemainingCapacity(s, offset))
350
0
    goto fail;
351
352
623
  Stream_Zero(s, offset);
353
623
  const size_t pos = Stream_GetPosition(s);
354
623
  if (pos != bf.bfOffBits)
355
0
    goto fail;
356
623
  return s;
357
0
fail:
358
0
  Stream_Free(s, TRUE);
359
0
  return nullptr;
360
623
}
361
362
BYTE* winpr_bitmap_construct_header(size_t width, size_t height, size_t bpp)
363
0
{
364
0
  wStream* s = winpr_bitmap_construct_header_stream(width, height, bpp, FALSE);
365
0
  if (!s)
366
0
    return nullptr;
367
0
  void* data = Stream_Buffer(s);
368
0
  Stream_Free(s, FALSE);
369
0
  return data;
370
0
}
371
372
/**
373
 * Refer to "Compressed Image File Formats: JPEG, PNG, GIF, XBM, BMP" book
374
 */
375
376
WINPR_ATTR_MALLOC(free, 1)
377
static void* winpr_bitmap_write_buffer(const BYTE* data, size_t size, UINT32 width, UINT32 height,
378
                                       UINT32 stride, UINT32 bpp, UINT32* pSize)
379
623
{
380
623
  WINPR_ASSERT(data || (size == 0));
381
623
  WINPR_ASSERT(pSize);
382
383
623
  void* result = nullptr;
384
623
  if ((height == 0) || (width == 0) || (bpp == 0) || (width > SIZE_MAX / height) ||
385
623
      (width > SIZE_MAX / bpp))
386
0
    return nullptr;
387
388
623
  size_t bpp_stride = 1ull * width * bpp;
389
623
  if (bpp < 8)
390
191
  {
391
191
    if ((bpp_stride % 8) != 0)
392
173
      bpp_stride += 8 - (bpp_stride % 8);
393
191
  }
394
623
  bpp_stride /= 8ull;
395
396
623
  if ((bpp_stride % 4) != 0)
397
410
    bpp_stride += 4 - (bpp_stride % 4);
398
399
623
  if ((bpp_stride < 4) || (bpp_stride > UINT32_MAX))
400
0
    return nullptr;
401
402
  /* the pixels of a row, without the padding to 4 bytes BMP requires */
403
623
  if (width > (SIZE_MAX - 7ull) / bpp)
404
0
    return nullptr;
405
623
  const size_t row = (1ull * width * bpp + 7ull) / 8ull;
406
407
623
  if (stride == 0)
408
0
    stride = (UINT32)bpp_stride;
409
410
623
  if (stride > SIZE_MAX / height)
411
0
    return nullptr;
412
413
623
  if (stride > size / height)
414
0
    return nullptr;
415
416
623
  if (row > stride)
417
0
    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
623
  wStream* s = winpr_bitmap_construct_header_stream(width, height, bpp, TRUE);
422
623
  if (!s)
423
0
    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
848k
  for (size_t y = height; y > 0; y--)
428
847k
  {
429
847k
    if (!Stream_EnsureRemainingCapacity(s, bpp_stride))
430
0
      goto fail;
431
432
847k
    const size_t offset = (y - 1) * stride;
433
847k
    Stream_Write(s, &data[offset], row);
434
847k
    Stream_Zero(s, bpp_stride - row);
435
847k
  }
436
437
623
  result = Stream_Buffer(s);
438
623
  {
439
623
    const size_t pos = Stream_GetPosition(s);
440
623
    if (pos > UINT32_MAX)
441
0
      goto fail;
442
623
    *pSize = (UINT32)pos;
443
623
  }
444
623
fail:
445
623
  Stream_Free(s, result == nullptr);
446
623
  return result;
447
623
}
448
449
int winpr_bitmap_write(const char* filename, const BYTE* data, size_t width, size_t height,
450
                       size_t bpp)
451
0
{
452
0
  return winpr_bitmap_write_ex(filename, data, 0, width, height, bpp);
453
0
}
454
455
int winpr_bitmap_write_ex(const char* filename, const BYTE* data, size_t stride, size_t width,
456
                          size_t height, size_t bpp)
457
0
{
458
0
  FILE* fp = nullptr;
459
0
  int ret = -1;
460
0
  void* bmpdata = nullptr;
461
0
  const size_t bpp_stride = ((((width * bpp) + 31) & (size_t)~31) >> 3);
462
463
0
  if ((stride > UINT32_MAX) || (width > UINT32_MAX) || (height > UINT32_MAX) ||
464
0
      (bpp > UINT32_MAX))
465
0
    goto fail;
466
467
0
  if (stride == 0)
468
0
    stride = bpp_stride;
469
470
0
  {
471
0
    UINT32 bmpsize = 0;
472
0
    {
473
0
      const size_t size = stride * 1ull * height;
474
0
      bmpdata = winpr_bitmap_write_buffer(data, size, (UINT32)width, (UINT32)height,
475
0
                                          (UINT32)stride, (UINT32)bpp, &bmpsize);
476
0
    }
477
0
    if (!bmpdata)
478
0
      goto fail;
479
480
0
    fp = winpr_fopen(filename, "w+b");
481
0
    if (!fp)
482
0
    {
483
0
      WLog_ERR(TAG, "failed to open file %s", filename);
484
0
      goto fail;
485
0
    }
486
487
0
    if (fwrite(bmpdata, bmpsize, 1, fp) != 1)
488
0
      goto fail;
489
0
  }
490
491
0
  ret = 0;
492
0
fail:
493
0
  if (fp)
494
0
    (void)fclose(fp);
495
0
  free(bmpdata);
496
0
  return ret;
497
0
}
498
499
WINPR_ATTR_NODISCARD
500
static int write_and_free(const char* filename, void* data, size_t size)
501
0
{
502
0
  int status = -1;
503
0
  if (!data)
504
0
    goto fail;
505
506
0
  {
507
0
    FILE* fp = winpr_fopen(filename, "w+b");
508
0
    if (!fp)
509
0
      goto fail;
510
511
0
    {
512
0
      const size_t w = fwrite(data, 1, size, fp);
513
0
      (void)fclose(fp);
514
0
      status = (w == size) ? 1 : -1;
515
0
    }
516
0
  }
517
0
fail:
518
0
  free(data);
519
0
  return status;
520
0
}
521
522
int winpr_image_write(wImage* image, const char* filename)
523
0
{
524
0
  WINPR_ASSERT(image);
525
0
  return winpr_image_write_ex(image, WINPR_ASSERTING_INT_CAST(uint32_t, image->type), filename);
526
0
}
527
528
int winpr_image_write_ex(wImage* image, UINT32 format, const char* filename)
529
0
{
530
0
  WINPR_ASSERT(image);
531
532
0
  size_t size = 0;
533
0
  void* data = winpr_image_write_buffer(image, format, &size);
534
0
  if (!data)
535
0
    return -1;
536
0
  return write_and_free(filename, data, size);
537
0
}
538
539
WINPR_ATTR_NODISCARD
540
static int winpr_image_bitmap_read_buffer(wImage* image, const BYTE* buffer, size_t size)
541
3.41k
{
542
3.41k
  int rc = -1;
543
3.41k
  BOOL vFlip = 0;
544
3.41k
  WINPR_BITMAP_FILE_HEADER bf = WINPR_C_ARRAY_INIT;
545
3.41k
  WINPR_BITMAP_INFO_HEADER bi = WINPR_C_ARRAY_INIT;
546
3.41k
  wStream sbuffer = WINPR_C_ARRAY_INIT;
547
3.41k
  wStream* s = Stream_StaticConstInit(&sbuffer, buffer, size);
548
549
3.41k
  if (!s)
550
0
    return -1;
551
552
3.41k
  size_t bmpoffset = 0;
553
3.41k
  if (!readBitmapFileHeader(s, &bf) || !readBitmapInfoHeader(s, &bi, &bmpoffset))
554
276
    goto fail;
555
556
3.14k
  if ((bf.bfType[0] != 'B') || (bf.bfType[1] != 'M'))
557
0
  {
558
0
    WLog_WARN(TAG, "Invalid bitmap header %c%c", bf.bfType[0], bf.bfType[1]);
559
0
    goto fail;
560
0
  }
561
562
3.14k
  image->type = WINPR_IMAGE_BITMAP;
563
564
3.14k
  {
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
3.14k
    const size_t pos = Stream_GetPosition(s);
568
3.14k
    const size_t expect = bf.bfOffBits;
569
3.14k
    if ((pos > expect) || (bmpoffset > expect - pos))
570
228
    {
571
228
      WLog_WARN(TAG, "pos=%" PRIuz ", expected %" PRIuz ", offset=%" PRIuz, pos, expect,
572
228
                bmpoffset);
573
228
      goto fail;
574
228
    }
575
576
2.91k
    if (!Stream_SafeSeek(s, expect - pos))
577
52
      goto fail;
578
2.91k
  }
579
580
2.86k
  if (!Stream_CheckAndLogRequiredLength(TAG, s, bi.biSizeImage))
581
1.19k
    goto fail;
582
583
1.66k
  if (bi.biWidth <= 0)
584
5
  {
585
5
    WLog_WARN(TAG, "bi.biWidth=%" PRId32, bi.biWidth);
586
5
    goto fail;
587
5
  }
588
589
1.65k
  image->width = (UINT32)bi.biWidth;
590
591
1.65k
  if (bi.biHeight < 0)
592
670
  {
593
670
    vFlip = FALSE;
594
670
    image->height = (UINT32)(-1ll * bi.biHeight);
595
670
  }
596
989
  else
597
989
  {
598
989
    vFlip = TRUE;
599
989
    image->height = (UINT32)bi.biHeight;
600
989
  }
601
602
1.65k
  if (image->width <= 0)
603
0
  {
604
0
    WLog_WARN(TAG, "image->width=%" PRIu32, image->width);
605
0
    goto fail;
606
0
  }
607
608
1.65k
  if (image->height <= 0)
609
5
  {
610
5
    WLog_WARN(TAG, "image->height=%" PRIu32, image->height);
611
5
    goto fail;
612
5
  }
613
614
1.65k
  image->bitsPerPixel = bi.biBitCount;
615
1.65k
  {
616
1.65k
    const size_t bpp = (bi.biBitCount + 7UL) / 8UL;
617
1.65k
    image->bytesPerPixel = WINPR_ASSERTING_INT_CAST(uint32_t, bpp);
618
1.65k
    if (image->bytesPerPixel == 0)
619
0
      goto fail;
620
621
1.65k
    if (image->width > (UINT32_MAX / image->bytesPerPixel) - 4ull)
622
15
      goto fail;
623
624
1.63k
    image->scanline = image->width * image->bytesPerPixel;
625
1.63k
    if ((image->scanline % 4) != 0)
626
1.14k
      image->scanline += 4 - image->scanline % 4;
627
628
1.63k
    if (image->height > UINT32_MAX / image->scanline)
629
506
      goto fail;
630
631
1.13k
    {
632
1.13k
      const size_t bmpsize = 1ULL * image->scanline * image->height;
633
1.13k
      if (bmpsize != bi.biSizeImage)
634
617
        WLog_WARN(TAG, "bmpsize=%" PRIuz " != bi.biSizeImage=%" PRIu32, bmpsize,
635
1.13k
                  bi.biSizeImage);
636
637
1.13k
      {
638
1.13k
        size_t scanline = image->scanline;
639
1.13k
        if (bi.biSizeImage < bmpsize)
640
524
        {
641
          /* Workaround for unaligned bitmaps */
642
524
          const size_t uscanline = image->width * bpp;
643
524
          const size_t unaligned = image->height * uscanline;
644
524
          if (bi.biSizeImage != unaligned)
645
510
            goto fail;
646
14
          scanline = uscanline;
647
14
        }
648
649
623
        image->data = nullptr;
650
623
        {
651
623
          const size_t asize = 1ULL * image->scanline * image->height;
652
623
          if (asize > 0)
653
623
            image->data = (BYTE*)malloc(asize);
654
623
        }
655
656
623
        if (!image->data)
657
0
          goto fail;
658
659
623
        if (!vFlip)
660
180
        {
661
180
          BYTE* pDstData = image->data;
662
663
827k
          for (size_t index = 0; index < image->height; index++)
664
826k
          {
665
826k
            Stream_Read(s, pDstData, scanline);
666
826k
            pDstData += image->scanline;
667
826k
          }
668
180
        }
669
443
        else
670
443
        {
671
443
          BYTE* pDstData = &(image->data[(image->height - 1ull) * image->scanline]);
672
673
21.1k
          for (size_t index = 0; index < image->height; index++)
674
20.7k
          {
675
20.7k
            Stream_Read(s, pDstData, scanline);
676
20.7k
            pDstData -= image->scanline;
677
20.7k
          }
678
443
        }
679
623
      }
680
623
    }
681
623
  }
682
683
0
  rc = 1;
684
3.41k
fail:
685
686
3.41k
  if (rc < 0)
687
2.79k
  {
688
2.79k
    free(image->data);
689
2.79k
    image->data = nullptr;
690
2.79k
  }
691
692
3.41k
  return rc;
693
623
}
694
695
int winpr_image_read(wImage* image, const char* filename)
696
0
{
697
0
  int status = -1;
698
699
0
  FILE* fp = winpr_fopen(filename, "rb");
700
0
  if (!fp)
701
0
  {
702
0
    WLog_ERR(TAG, "failed to open file %s", filename);
703
0
    return -1;
704
0
  }
705
706
0
  (void)fseek(fp, 0, SEEK_END);
707
0
  INT64 pos = _ftelli64(fp);
708
0
  (void)fseek(fp, 0, SEEK_SET);
709
710
0
  if (pos > 0)
711
0
  {
712
0
    BYTE* buffer = malloc((size_t)pos);
713
0
    if (buffer)
714
0
    {
715
0
      size_t r = fread(buffer, 1, (size_t)pos, fp);
716
0
      if (r == (size_t)pos)
717
0
      {
718
0
        status = winpr_image_read_buffer(image, buffer, (size_t)pos);
719
0
      }
720
0
    }
721
0
    free(buffer);
722
0
  }
723
0
  (void)fclose(fp);
724
0
  return status;
725
0
}
726
727
int winpr_image_read_buffer(wImage* image, const BYTE* buffer, size_t size)
728
4.02k
{
729
4.02k
  BYTE sig[12] = WINPR_C_ARRAY_INIT;
730
4.02k
  int status = -1;
731
732
4.02k
  if (size < sizeof(sig))
733
20
    return -1;
734
735
4.00k
  CopyMemory(sig, buffer, sizeof(sig));
736
737
4.00k
  if ((sig[0] == 'B') && (sig[1] == 'M'))
738
3.41k
  {
739
3.41k
    image->type = WINPR_IMAGE_BITMAP;
740
3.41k
    status = winpr_image_bitmap_read_buffer(image, buffer, size);
741
3.41k
  }
742
588
  else if ((sig[0] == 'R') && (sig[1] == 'I') && (sig[2] == 'F') && (sig[3] == 'F') &&
743
104
           (sig[8] == 'W') && (sig[9] == 'E') && (sig[10] == 'B') && (sig[11] == 'P'))
744
0
  {
745
0
    image->type = WINPR_IMAGE_WEBP;
746
0
    const SSIZE_T rc =
747
0
        winpr_convert_from_webp(buffer, size, &image->width, &image->height,
748
0
                                &image->bitsPerPixel, &image->data, &image->scanline);
749
0
    if (rc >= 0)
750
0
    {
751
0
      image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
752
0
      status = 1;
753
0
    }
754
0
  }
755
588
  else if ((sig[0] == 0xFF) && (sig[1] == 0xD8) && (sig[2] == 0xFF) && (sig[3] == 0xE0) &&
756
92
           (sig[6] == 0x4A) && (sig[7] == 0x46) && (sig[8] == 0x49) && (sig[9] == 0x46) &&
757
8
           (sig[10] == 0x00))
758
4
  {
759
4
    image->type = WINPR_IMAGE_JPEG;
760
4
    const SSIZE_T rc =
761
4
        winpr_convert_from_jpeg(buffer, size, &image->width, &image->height,
762
4
                                &image->bitsPerPixel, &image->data, &image->scanline);
763
4
    if (rc >= 0)
764
0
    {
765
0
      image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
766
0
      status = 1;
767
0
    }
768
4
  }
769
584
  else if ((sig[0] == 0x89) && (sig[1] == 'P') && (sig[2] == 'N') && (sig[3] == 'G') &&
770
48
           (sig[4] == '\r') && (sig[5] == '\n') && (sig[6] == 0x1A) && (sig[7] == '\n'))
771
4
  {
772
4
    image->type = WINPR_IMAGE_PNG;
773
4
    const SSIZE_T rc =
774
4
        winpr_convert_from_png(buffer, size, &image->width, &image->height,
775
4
                               &image->bitsPerPixel, &image->data, &image->scanline);
776
4
    if (rc >= 0)
777
0
    {
778
0
      image->bytesPerPixel = (image->bitsPerPixel + 7) / 8;
779
0
      status = 1;
780
0
    }
781
4
  }
782
783
4.00k
  return status;
784
4.02k
}
785
786
wImage* winpr_image_new(void)
787
5.55k
{
788
5.55k
  wImage* image = (wImage*)calloc(1, sizeof(wImage));
789
790
5.55k
  if (!image)
791
0
    return nullptr;
792
793
5.55k
  return image;
794
5.55k
}
795
796
void winpr_image_free(wImage* image, BOOL bFreeBuffer)
797
5.55k
{
798
5.55k
  if (!image)
799
0
    return;
800
801
5.55k
  if (bFreeBuffer)
802
5.55k
    free(image->data);
803
804
5.55k
  free(image);
805
5.55k
}
806
807
WINPR_ATTR_MALLOC(free, 1)
808
static 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
0
{
812
0
  WINPR_ASSERT(data || (size == 0));
813
0
  WINPR_ASSERT(pSize);
814
815
0
  *pSize = 0;
816
0
  if ((width == 0) || (height == 0) || (stride > SIZE_MAX / height) || (bpp < 1) || (bpp > 32))
817
0
    return nullptr;
818
819
0
#if !defined(WINPR_UTILS_IMAGE_JPEG)
820
0
  WLog_WARN(TAG, "JPEG not supported in this build");
821
0
  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
881
fail:
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
0
}
890
891
// NOLINTBEGIN(readability-non-const-parameter)
892
SSIZE_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
4
{
899
4
  WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
900
4
  WINPR_ASSERT(width);
901
4
  WINPR_ASSERT(height);
902
4
  WINPR_ASSERT(bpp);
903
4
  WINPR_ASSERT(ppdecomp_data);
904
4
  WINPR_ASSERT(scanline);
905
906
4
  *width = 0;
907
4
  *height = 0;
908
4
  *bpp = 0;
909
4
  *ppdecomp_data = nullptr;
910
4
  *scanline = 0;
911
912
4
#if !defined(WINPR_UTILS_IMAGE_JPEG)
913
4
  WLog_WARN(TAG, "JPEG not supported in this build");
914
4
  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
965
fail:
966
  jpeg_destroy_decompress(&cinfo);
967
  *ppdecomp_data = decomp_data;
968
  return size;
969
#endif
970
4
}
971
972
WINPR_ATTR_MALLOC(free, 1)
973
static 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
0
{
978
0
  WINPR_ASSERT(data || (size == 0));
979
0
  WINPR_ASSERT(pSize);
980
981
0
  *pSize = 0;
982
983
0
#if !defined(WINPR_UTILS_IMAGE_WEBP)
984
0
  WLog_WARN(TAG, "WEBP not supported in this build");
985
0
  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
0
}
1019
1020
SSIZE_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
0
{
1024
0
  WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
1025
0
  WINPR_ASSERT(width);
1026
0
  WINPR_ASSERT(height);
1027
0
  WINPR_ASSERT(bpp);
1028
0
  WINPR_ASSERT(ppdecomp_data);
1029
0
  WINPR_ASSERT(scanline);
1030
1031
0
  *width = 0;
1032
0
  *height = 0;
1033
0
  *bpp = 0;
1034
0
  *ppdecomp_data = nullptr;
1035
0
  *scanline = 0;
1036
1037
0
#if !defined(WINPR_UTILS_IMAGE_WEBP)
1038
0
  WLog_WARN(TAG, "WEBP not supported in this build");
1039
0
  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
0
}
1065
1066
#if defined(WINPR_UTILS_IMAGE_PNG)
1067
struct png_mem_encode
1068
{
1069
  char* buffer;
1070
  size_t size;
1071
};
1072
1073
static 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 */
1099
static void png_flush(WINPR_ATTR_UNUSED png_structp png_ptr)
1100
{
1101
}
1102
1103
WINPR_ATTR_NODISCARD
1104
static 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;
1210
fail:
1211
  png_destroy_write_struct(&png_ptr, &info_ptr);
1212
  if (rc < 0)
1213
    free(state.buffer);
1214
  return rc;
1215
}
1216
1217
typedef struct
1218
{
1219
  png_bytep buffer;
1220
  png_uint_32 bufsize;
1221
  png_uint_32 current_pos;
1222
} MEMORY_READER_STATE;
1223
1224
static 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
1236
WINPR_ATTR_MALLOC(free, 1)
1237
static 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
  }
1317
fail:
1318
1319
  png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
1320
  return rc;
1321
}
1322
#endif
1323
1324
WINPR_ATTR_MALLOC(free, 1)
1325
static 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
0
{
1329
0
  WINPR_ASSERT(data || (size == 0));
1330
0
  WINPR_ASSERT(pSize);
1331
1332
0
  *pSize = 0;
1333
1334
0
  if ((width == 0) || (height == 0) || (stride == 0) || (bpp == 0))
1335
0
    return nullptr;
1336
0
  if (height > UINT32_MAX / stride)
1337
0
    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
0
  WLog_WARN(TAG, "PNG not supported in this build");
1370
0
  return nullptr;
1371
0
#endif
1372
0
}
1373
1374
SSIZE_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
4
{
1378
4
  WINPR_ASSERT(comp_data || (comp_data_bytes == 0));
1379
4
  WINPR_ASSERT(width);
1380
4
  WINPR_ASSERT(height);
1381
4
  WINPR_ASSERT(bpp);
1382
4
  WINPR_ASSERT(ppdecomp_data);
1383
4
  WINPR_ASSERT(scanline);
1384
1385
4
  *width = 0;
1386
4
  *height = 0;
1387
4
  *bpp = 0;
1388
4
  *ppdecomp_data = nullptr;
1389
4
  *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
4
  WLog_WARN(TAG, "PNG not supported in this build");
1409
4
  return -1;
1410
4
#endif
1411
4
}
1412
1413
BOOL winpr_image_format_is_supported(UINT32 format)
1414
1.65k
{
1415
1.65k
  switch (format)
1416
1.65k
  {
1417
0
    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
0
      return TRUE;
1428
1.65k
    default:
1429
1.65k
      return FALSE;
1430
1.65k
  }
1431
1.65k
}
1432
1433
WINPR_ATTR_MALLOC(free, 1)
1434
static BYTE* convert(const wImage* image, size_t* pstride, WINPR_ATTR_UNUSED UINT32 flags)
1435
0
{
1436
0
  WINPR_ASSERT(image);
1437
0
  WINPR_ASSERT(pstride);
1438
1439
0
  *pstride = 0;
1440
0
  if (image->bitsPerPixel < 24)
1441
0
    return nullptr;
1442
1443
0
  const size_t stride = image->width * 4ull;
1444
0
  BYTE* data = calloc(stride, image->height);
1445
0
  if (data)
1446
0
  {
1447
0
    for (size_t y = 0; y < image->height; y++)
1448
0
    {
1449
0
      const BYTE* srcLine = &image->data[image->scanline * y];
1450
0
      BYTE* dstLine = &data[stride * y];
1451
0
      if (image->bitsPerPixel == 32)
1452
0
        memcpy(dstLine, srcLine, stride);
1453
0
      else
1454
0
      {
1455
0
        for (size_t x = 0; x < image->width; x++)
1456
0
        {
1457
0
          const BYTE* src = &srcLine[image->bytesPerPixel * x];
1458
0
          BYTE* dst = &dstLine[4ull * x];
1459
0
          BYTE b = *src++;
1460
0
          BYTE g = *src++;
1461
0
          BYTE r = *src++;
1462
1463
0
          *dst++ = b;
1464
0
          *dst++ = g;
1465
0
          *dst++ = r;
1466
0
          *dst++ = 0xff;
1467
0
        }
1468
0
      }
1469
0
    }
1470
0
    *pstride = stride;
1471
0
  }
1472
0
  return data;
1473
0
}
1474
1475
WINPR_ATTR_NODISCARD
1476
static BOOL compare_byte_relaxed(BYTE a, BYTE b, UINT32 flags)
1477
0
{
1478
0
  if (a != b)
1479
0
  {
1480
0
    if ((flags & WINPR_IMAGE_CMP_FUZZY) != 0)
1481
0
    {
1482
0
      const int diff = abs((int)a) - abs((int)b);
1483
      /* filter out quantization errors */
1484
0
      if (diff > 6)
1485
0
        return FALSE;
1486
0
    }
1487
0
    else
1488
0
    {
1489
0
      return FALSE;
1490
0
    }
1491
0
  }
1492
0
  return TRUE;
1493
0
}
1494
1495
WINPR_ATTR_NODISCARD
1496
static BOOL compare_pixel(const BYTE* pa, const BYTE* pb, UINT32 flags)
1497
0
{
1498
0
  WINPR_ASSERT(pa);
1499
0
  WINPR_ASSERT(pb);
1500
1501
0
  if (!compare_byte_relaxed(*pa++, *pb++, flags))
1502
0
    return FALSE;
1503
0
  if (!compare_byte_relaxed(*pa++, *pb++, flags))
1504
0
    return FALSE;
1505
0
  if (!compare_byte_relaxed(*pa++, *pb++, flags))
1506
0
    return FALSE;
1507
0
  if ((flags & WINPR_IMAGE_CMP_IGNORE_ALPHA) == 0)
1508
0
  {
1509
0
    if (!compare_byte_relaxed(*pa++, *pb++, flags))
1510
0
      return FALSE;
1511
0
  }
1512
0
  return TRUE;
1513
0
}
1514
1515
BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB, UINT32 flags)
1516
0
{
1517
0
  if (imageA == imageB)
1518
0
    return TRUE;
1519
0
  if (!imageA || !imageB)
1520
0
    return FALSE;
1521
1522
0
  if (imageA->height != imageB->height)
1523
0
    return FALSE;
1524
0
  if (imageA->width != imageB->width)
1525
0
    return FALSE;
1526
1527
0
  if ((flags & WINPR_IMAGE_CMP_IGNORE_DEPTH) == 0)
1528
0
  {
1529
0
    if (imageA->bitsPerPixel != imageB->bitsPerPixel)
1530
0
      return FALSE;
1531
0
    if (imageA->bytesPerPixel != imageB->bytesPerPixel)
1532
0
      return FALSE;
1533
0
  }
1534
1535
0
  BOOL rc = FALSE;
1536
0
  size_t astride = 0;
1537
0
  size_t bstride = 0;
1538
0
  BYTE* dataA = convert(imageA, &astride, flags);
1539
0
  BYTE* dataB = convert(imageA, &bstride, flags);
1540
0
  if (dataA && dataB && (astride == bstride))
1541
0
  {
1542
0
    rc = TRUE;
1543
0
    for (size_t y = 0; y < imageA->height; y++)
1544
0
    {
1545
0
      const BYTE* lineA = &dataA[astride * y];
1546
0
      const BYTE* lineB = &dataB[bstride * y];
1547
1548
0
      for (size_t x = 0; x < imageA->width; x++)
1549
0
      {
1550
0
        const BYTE* pa = &lineA[x * 4ull];
1551
0
        const BYTE* pb = &lineB[x * 4ull];
1552
1553
0
        if (!compare_pixel(pa, pb, flags))
1554
0
          rc = FALSE;
1555
0
      }
1556
0
    }
1557
0
  }
1558
0
  free(dataA);
1559
0
  free(dataB);
1560
0
  return rc;
1561
0
}
1562
1563
const char* winpr_image_format_mime(UINT32 format)
1564
551
{
1565
551
  switch (format)
1566
551
  {
1567
551
    case WINPR_IMAGE_BITMAP:
1568
551
      return "image/bmp";
1569
0
    case WINPR_IMAGE_PNG:
1570
0
      return "image/png";
1571
0
    case WINPR_IMAGE_WEBP:
1572
0
      return "image/webp";
1573
0
    case WINPR_IMAGE_JPEG:
1574
0
      return "image/jpeg";
1575
0
    default:
1576
0
      return nullptr;
1577
551
  }
1578
551
}
1579
1580
const char* winpr_image_format_extension(UINT32 format)
1581
0
{
1582
0
  switch (format)
1583
0
  {
1584
0
    case WINPR_IMAGE_BITMAP:
1585
0
      return "bmp";
1586
0
    case WINPR_IMAGE_PNG:
1587
0
      return "png";
1588
0
    case WINPR_IMAGE_WEBP:
1589
0
      return "webp";
1590
0
    case WINPR_IMAGE_JPEG:
1591
0
      return "jpg";
1592
0
    default:
1593
0
      return nullptr;
1594
0
  }
1595
0
}
1596
1597
void* winpr_image_write_buffer(wImage* image, UINT32 format, size_t* psize)
1598
623
{
1599
623
  WINPR_ASSERT(image);
1600
623
  switch (format)
1601
623
  {
1602
623
    case WINPR_IMAGE_BITMAP:
1603
623
    {
1604
623
      UINT32 outsize = 0;
1605
623
      size_t size = 1ull * image->height * image->scanline;
1606
623
      void* data = winpr_bitmap_write_buffer(image->data, size, image->width, image->height,
1607
623
                                             image->scanline, image->bitsPerPixel, &outsize);
1608
623
      *psize = outsize;
1609
623
      return data;
1610
0
    }
1611
0
    case WINPR_IMAGE_WEBP:
1612
0
    {
1613
0
      UINT32 outsize = 0;
1614
0
      size_t size = 1ull * image->height * image->scanline;
1615
0
      void* data = winpr_convert_to_webp(image->data, size, image->width, image->height,
1616
0
                                         image->scanline, image->bitsPerPixel, &outsize);
1617
0
      *psize = outsize;
1618
0
      return data;
1619
0
    }
1620
0
    case WINPR_IMAGE_JPEG:
1621
0
    {
1622
0
      UINT32 outsize = 0;
1623
0
      size_t size = 1ull * image->height * image->scanline;
1624
0
      void* data = winpr_convert_to_jpeg(image->data, size, image->width, image->height,
1625
0
                                         image->scanline, image->bitsPerPixel, &outsize);
1626
0
      *psize = outsize;
1627
0
      return data;
1628
0
    }
1629
0
    case WINPR_IMAGE_PNG:
1630
0
    {
1631
0
      UINT32 outsize = 0;
1632
0
      size_t size = 1ull * image->height * image->scanline;
1633
0
      void* data = winpr_convert_to_png(image->data, size, image->width, image->height,
1634
0
                                        image->scanline, image->bitsPerPixel, &outsize);
1635
0
      *psize = outsize;
1636
0
      return data;
1637
0
    }
1638
0
    default:
1639
0
      *psize = 0;
1640
0
      return nullptr;
1641
623
  }
1642
623
}