Skip to content
This repository was archived by the owner on Mar 7, 2025. It is now read-only.

Commit cda407b

Browse files
qmfrederikakoeplinger
authored andcommitted
PNG codec: support most pixel formats (#593)
GDI+ converts a lot of pixel formats to PixelFormat32bppARGB. It turns out libpng supports transformations which allow you to convert from one image format to another (including to 32bppARGB). Use these functions instead and support a bunch of new formats. Re-enable unit tests which were disabled on libgdiplus. Fixes #533 Fixes #378 Fixes #184 Fixes #176 Fixes #175 Fixes #174 Fixes #173 Fixes #172
1 parent 5a6f3e5 commit cda407b

File tree

2 files changed

+68
-146
lines changed

2 files changed

+68
-146
lines changed

src/pngcodec.c

+65-124
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ gdip_load_png_image_from_file_or_stream (FILE *fp, GetBytesDelegate getBytesFunc
253253
int bit_depth;
254254
int channels;
255255
BYTE color_type;
256+
BYTE original_color_type;
256257
int num_palette = 0;
257258
png_colorp png_palette = NULL;
258259

@@ -283,21 +284,51 @@ gdip_load_png_image_from_file_or_stream (FILE *fp, GetBytesDelegate getBytesFunc
283284
png_set_read_fn (png_ptr, (void *) getBytesFunc, _gdip_png_stream_read_data);
284285
}
285286

286-
/* Pass PNG_TRANSFORM_STRIP_16, which basically reduces the color palette from 16-bits to 8-bits
287+
png_read_info(png_ptr, info_ptr);
288+
289+
bit_depth = png_get_bit_depth(png_ptr, info_ptr);
290+
original_color_type = png_get_color_type(png_ptr, info_ptr);
291+
channels = png_get_channels(png_ptr, info_ptr);
292+
293+
/* Apply png_set_strip_16, which basically reduces the color palette from 16-bits to 8-bits
287294
* for 16-bit color depths. The current implementation of libgdiplus doesn't handle bit depths > 8,
288295
* so this acts as a workaround. Net impact is that the quality of the image is slightly reduced instead
289296
* of refusing to process the image (and potentially crashing the application) altogether;
290297
* proper support would mean supporting 16-bit color channels.
291298
* Partially fixes http://bugzilla.ximian.com/show_bug.cgi?id=80693 */
292-
png_read_png (png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16, NULL);
299+
if (bit_depth == 16) {
300+
png_set_strip_16 (png_ptr);
301+
png_set_gray_to_rgb (png_ptr);
302+
png_set_bgr (png_ptr);
303+
png_set_add_alpha (png_ptr, 0xFF, PNG_FILLER_AFTER);
304+
channels = 4;
305+
}
306+
307+
if (bit_depth == 2
308+
|| (bit_depth == 4 && original_color_type != PNG_COLOR_TYPE_PALETTE)
309+
|| (bit_depth == 8 && original_color_type != PNG_COLOR_TYPE_PALETTE)) {
310+
png_set_expand (png_ptr);
311+
png_set_gray_to_rgb (png_ptr);
312+
png_set_bgr (png_ptr);
313+
png_set_add_alpha (png_ptr, 0xFF, PNG_FILLER_AFTER);
314+
}
315+
316+
if (bit_depth == 8 && !(channels == 1 && (original_color_type == PNG_COLOR_TYPE_PALETTE || original_color_type == PNG_COLOR_TYPE_GRAY))) {
317+
png_set_bgr (png_ptr);
318+
png_set_add_alpha (png_ptr, 0xFF, PNG_FILLER_AFTER);
319+
}
320+
321+
// Update the image properties after the transformations have been applied.
322+
// The channels property is not refreshed; the original value will be used
323+
// later to set the pixel format.
324+
png_read_update_info (png_ptr, info_ptr);
293325

294326
bit_depth = png_get_bit_depth (png_ptr, info_ptr);
295-
channels = png_get_channels (png_ptr, info_ptr);
296327
color_type = png_get_color_type (png_ptr, info_ptr);
297328
png_get_PLTE( png_ptr, info_ptr, &png_palette, &num_palette );
298329

299330
/* 2bpp is a special case (promoted to 32bpp ARGB by MS GDI+) */
300-
if ((bit_depth <= 8) && (bit_depth != 2) && (channels == 1) &&
331+
if ((bit_depth <= 8) && (bit_depth != 2) && (channels == 1) &&
301332
((color_type == PNG_COLOR_TYPE_PALETTE) || (color_type == PNG_COLOR_TYPE_GRAY))) {
302333
int width;
303334
int height;
@@ -318,24 +349,32 @@ gdip_load_png_image_from_file_or_stream (FILE *fp, GetBytesDelegate getBytesFunc
318349
gdip_align_stride (dest_stride);
319350

320351
/* Copy image data. */
321-
row_pointers = png_get_rows (png_ptr, info_ptr);
322-
323352
unsigned long long int size = (unsigned long long int)dest_stride * height;
324353
if (size > G_MAXINT32) {
325354
status = OutOfMemory;
326355
goto error;
327356
}
328357

329-
rawdata = GdipAlloc(size);
358+
row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
359+
if (!row_pointers) {
360+
status = OutOfMemory;
361+
goto error;
362+
}
363+
364+
rawdata = GdipAlloc(dest_stride * height);
330365
if (!rawdata) {
331366
status = OutOfMemory;
367+
free(row_pointers);
332368
goto error;
333369
}
334370

335371
for (i=0; i < height; i++) {
336-
memcpy (rawdata + i * dest_stride, row_pointers[i], source_stride);
372+
row_pointers[i] = rawdata + i * dest_stride;
337373
}
338374

375+
png_read_image(png_ptr, row_pointers);
376+
free(row_pointers);
377+
339378
/* Copy palette. */
340379
num_colours = 1 << bit_depth;
341380

@@ -443,18 +482,13 @@ gdip_load_png_image_from_file_or_stream (FILE *fp, GetBytesDelegate getBytesFunc
443482
result->active_bitmap->dpi_horz = 0;
444483
result->active_bitmap->dpi_vert = 0;
445484
result->active_bitmap->palette = palette;
446-
}
447-
448-
/* 2bpp needs to enter here too */
449-
if (!result) {
485+
} else if (bit_depth == 8 && (color_type == PNG_COLOR_TYPE_RGBA || color_type == PNG_COLOR_TYPE_RGB)) {
450486
int width;
451487
int height;
452488
BYTE bit_depth;
453489
int stride;
454490
png_bytep *row_pointers;
455-
BYTE *rawptr;
456-
int i, j;
457-
BYTE alpha[4] = {0xFF, 0xFF, 0xFF, 0xFF}; /* Transparency values for 2bpp - default to fully opaque. */
491+
int i;
458492

459493
width = png_get_image_width (png_ptr, info_ptr);
460494
height = png_get_image_height (png_ptr, info_ptr);
@@ -470,7 +504,12 @@ gdip_load_png_image_from_file_or_stream (FILE *fp, GetBytesDelegate getBytesFunc
470504
stride = (width * 4);
471505
gdip_align_stride (stride);
472506

473-
row_pointers = png_get_rows (png_ptr, info_ptr);
507+
/* Copy image data. */
508+
row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
509+
if (!row_pointers) {
510+
status = OutOfMemory;
511+
goto error;
512+
}
474513

475514
unsigned long long int size = (unsigned long long int)stride * height;
476515
if (size > G_MAXINT32) {
@@ -481,118 +520,17 @@ gdip_load_png_image_from_file_or_stream (FILE *fp, GetBytesDelegate getBytesFunc
481520
rawdata = GdipAlloc (size);
482521
if (!rawdata) {
483522
status = OutOfMemory;
523+
free(row_pointers);
484524
goto error;
485525
}
486526

487-
rawptr = rawdata;
488-
switch (channels) {
489-
case 4: {
490-
for (i = 0; i < height; i++) {
491-
png_bytep rowp = row_pointers[i];
492-
for (j = 0; j < width; j++) {
493-
BYTE b = rowp[2];
494-
BYTE g = rowp[1];
495-
BYTE r = rowp[0];
496-
BYTE a = rowp[3];
497-
498-
set_pixel_bgra (rawptr, 0, b, g, r, a);
499-
rowp += 4;
500-
rawptr += 4;
501-
}
502-
}
503-
break;
504-
}
505-
506-
case 3: {
507-
for (i = 0; i < height; i++) {
508-
png_bytep rowp = row_pointers[i];
509-
for (j = 0; j < width; j++) {
510-
set_pixel_bgra (rawptr, 0, rowp[2], rowp[1], rowp[0], 0xff);
511-
rowp += 3;
512-
rawptr += 4;
513-
}
514-
}
515-
break;
516-
}
527+
for (i = 0; i < height; i++) {
528+
row_pointers[i] = rawdata + i * stride;
529+
}
517530

518-
case 2: {
519-
for (i = 0; i < height; i++) {
520-
png_bytep rowp = row_pointers[i];
521-
for (j = 0; j < width; j++) {
522-
set_pixel_bgra (rawptr, 0, rowp[0], rowp[0], rowp[0], rowp[1]);
523-
rowp += 2;
524-
rawptr += 4;
525-
}
526-
}
527-
break;
528-
}
531+
png_read_image(png_ptr, row_pointers);
529532

530-
case 1:
531-
if (bit_depth == 2) {
532-
/* Make sure transparency is respected for 2bpp images. */
533-
int num_trans = 0;
534-
BYTE * trans = NULL;
535-
png_color_16p dummy = NULL;
536-
if (png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &dummy)) {
537-
if (num_trans > 0 && trans != NULL) {
538-
memcpy(alpha, trans, MIN(num_trans, 4));
539-
}
540-
}
541-
}
542-
for (i = 0; i < height; i++) {
543-
png_bytep rowp = row_pointers[i];
544-
rawptr = rawdata + i * stride; /* Ensure each output row starts at the right place. */
545-
if (bit_depth == 2) {
546-
// 4 pixels for each byte
547-
for (j = 0; j < width; j++) {
548-
png_byte palette = 0;
549-
png_byte pix = *rowp++;
550-
551-
palette = (pix >> 6) & 0x03;
552-
set_pixel_bgra (rawptr, 0,
553-
png_palette[palette].blue,
554-
png_palette[palette].green,
555-
png_palette[palette].red,
556-
alpha[palette]);
557-
if (++j >= width)
558-
break;
559-
560-
palette = (pix >> 4) & 0x03;
561-
set_pixel_bgra (rawptr, 4,
562-
png_palette[palette].blue,
563-
png_palette[palette].green,
564-
png_palette[palette].red,
565-
alpha[palette]);
566-
if (++j >= width)
567-
break;
568-
569-
palette = (pix >> 2) & 0x03;
570-
set_pixel_bgra (rawptr, 8,
571-
png_palette[palette].blue,
572-
png_palette[palette].green,
573-
png_palette[palette].red,
574-
alpha[palette]);
575-
if (++j >= width)
576-
break;
577-
578-
palette = pix & 0x03;
579-
set_pixel_bgra (rawptr, 12,
580-
png_palette[palette].blue,
581-
png_palette[palette].green,
582-
png_palette[palette].red,
583-
alpha[palette]);
584-
rawptr += 16;
585-
}
586-
} else {
587-
for (j = 0; j < width; j++) {
588-
png_byte pix = *rowp++;
589-
set_pixel_bgra (rawptr, 0, pix, pix, pix, 0xff);
590-
rawptr += 4;
591-
}
592-
}
593-
}
594-
break;
595-
}
533+
free(row_pointers);
596534

597535
result = gdip_bitmap_new_with_frame (&gdip_image_frameDimension_page_guid, TRUE);
598536
if (!result) {
@@ -626,12 +564,15 @@ gdip_load_png_image_from_file_or_stream (FILE *fp, GetBytesDelegate getBytesFunc
626564
result->active_bitmap->image_flags |= ImageFlagsHasAlpha;
627565
}
628566

629-
if (color_type & PNG_COLOR_MASK_ALPHA)
567+
if (original_color_type & PNG_COLOR_MASK_ALPHA)
630568
result->active_bitmap->image_flags |= ImageFlagsHasAlpha;
631569

632570
result->active_bitmap->image_flags |= ImageFlagsReadOnly | ImageFlagsHasRealPixelSize;
633571
result->active_bitmap->dpi_horz = 0;
634572
result->active_bitmap->dpi_vert = 0;
573+
} else {
574+
status = InvalidParameter;
575+
goto error;
635576
}
636577

637578
status = gdip_load_png_properties(png_ptr, info_ptr, end_info_ptr, result->active_bitmap);

tests/testpngcodec.c

+3-22
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ static void test_valid1bpp()
9898

9999
static void test_valid2bpp ()
100100
{
101-
#if defined(USE_WINDOWS_GDIPLUS)
102101
BYTE grayscale2bpp1x1Interlaced[] = {
103102
/* Signature */ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
104103
/* IHDR */ 0x00, 0x00, 0x00, 0x0D, 'I', 'H', 'D', 'R', 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x01, 0x07, 0xC9, 0xB3, 0x62,
@@ -118,7 +117,6 @@ static void test_valid2bpp ()
118117
/* IDAT */ 0x00, 0x00, 0x00, 0x0A, 'I', 'D', 'A', 'T', 0x18, 0xD3, 0x63, 0x70, 0x00, 0x00, 0x00, 0x42, 0x00, 0x41, 0xF9, 0xFB, 0x3C, 0x49,
119118
/* IEND */ 0x00, 0x00, 0x00, 0x00, 'I', 'E', 'N', 'D', 0xAE, 0x42, 0x60, 0x82
120119
};
121-
#endif
122120
BYTE indexed2bpp1x1PaletteFirst[] = {
123121
/* Signature */ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
124122
/* IHDR */ 0x00, 0x00, 0x00, 0x0D, 'I', 'H', 'D', 'R', 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x15, 0x7C, 0x1C, 0x8C,
@@ -136,13 +134,9 @@ static void test_valid2bpp ()
136134
};
137135
#endif
138136

139-
// FIXME: this causes an AV in libgdiplus when trying to convert this image to 32bpp as we assume
140-
// the image has a palette.
141-
#if defined(USE_WINDOWS_GDIPLUS)
142137
createFileSuccess (grayscale2bpp1x1Interlaced, PixelFormat32bppARGB, 1, 1, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
143138
createFileSuccess (grayscale2bpp6x4NotInterlaced, PixelFormat32bppARGB, 6, 4, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
144139
createFileSuccess (grayscale2bpp1x1WithPalette, PixelFormat32bppARGB, 1, 1, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
145-
#endif
146140
createFileSuccess (indexed2bpp1x1PaletteFirst, PixelFormat32bppARGB, 1, 1, ImageFlagsColorSpaceRGB | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
147141
// FIXME: GDI+ allows indexed images with palettes last.
148142
#if defined(USE_WINDOWS_GDIPLUS)
@@ -188,12 +182,8 @@ static void test_valid4bpp()
188182
};
189183
#endif
190184

191-
// FIXME: GDI+ converts grayscale alpha 4bpp images to 32bpp.
192-
#if defined(USE_WINDOWS_GDIPLUS)
185+
// GDI+ converts grayscale alpha 4bpp images to 32bpp.
193186
PixelFormat expectedGrayscalePixelFormat = PixelFormat32bppARGB;
194-
#else
195-
PixelFormat expectedGrayscalePixelFormat = PixelFormat4bppIndexed;
196-
#endif
197187
createFileSuccess (grayscaleWithAlpha1x1Interlaced, expectedGrayscalePixelFormat, 1, 1, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
198188
createFileSuccess (grayscaleWithAlpha6x4NotInterlaced, expectedGrayscalePixelFormat, 6, 4, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
199189
createFileSuccess (grayscaleWithAlpha1x1WithPalette, expectedGrayscalePixelFormat, 1, 1, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
@@ -280,12 +270,8 @@ static void test_valid8bpp ()
280270
/* IEND */ 0x00, 0x00, 0x00, 0x00, 'I', 'E', 'N', 'D', 0xAE, 0x42, 0x60, 0x82
281271
};
282272

283-
// FIXME: GDI+ converts grayscale 8bpp images to 32bpp.
284-
#if defined(USE_WINDOWS_GDIPLUS)
273+
// GDI+ converts grayscale 8bpp images to 32bpp.
285274
PixelFormat expectedGrayscalePixelFormat = PixelFormat32bppARGB;
286-
#else
287-
PixelFormat expectedGrayscalePixelFormat = PixelFormat8bppIndexed;
288-
#endif
289275
createFileSuccess (grayscale1x1Interlaced, expectedGrayscalePixelFormat, 1, 1, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
290276
createFileSuccess (grayscale6x4NotInterlaced, expectedGrayscalePixelFormat, 6, 4, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
291277
createFileSuccess (grayscale1x1WithPalette, expectedGrayscalePixelFormat, 1, 1, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
@@ -362,14 +348,9 @@ static void test_valid16bpp ()
362348
/* IEND */ 0x00, 0x00, 0x00, 0x00, 'I', 'E', 'N', 'D', 0xAE, 0x42, 0x60, 0x82
363349
};
364350

365-
// FIXME: GDI+ converts grayscale 16bpp images to 32bpp.
366-
#if defined(USE_WINDOWS_GDIPLUS)
351+
// GDI+ converts grayscale 16bpp images to 32bpp.
367352
PixelFormat expectedGrayscalePixelFormat = PixelFormat32bppARGB;
368353
PixelFormat expectedTrueColorPixelFormat = PixelFormat32bppARGB;
369-
#else
370-
PixelFormat expectedGrayscalePixelFormat = PixelFormat8bppIndexed ;
371-
PixelFormat expectedTrueColorPixelFormat = PixelFormat24bppRGB ;
372-
#endif
373354

374355
createFileSuccess (grayscale1x1Interlaced, expectedGrayscalePixelFormat, 1, 1, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);
375356
createFileSuccess (grayscale6x4NotInterlaced, expectedGrayscalePixelFormat, 6, 4, ImageFlagsColorSpaceGRAY | ImageFlagsHasRealPixelSize | ImageFlagsHasAlpha | ImageFlagsReadOnly, 3);

0 commit comments

Comments
 (0)