summaryrefslogtreecommitdiffstats
path: root/source/opendingux/imageio.c
blob: aa4e3c8110999a3a921c11e4fc392576d2fcca68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Written by Maarten ter Huurne in 2011.
// Based on the libpng example.c source, with some additional inspiration
// from SDL_image and openMSX.
// License: GPL version 2 or later.


#include "imageio.h"

#include <png.h>
#include <assert.h>

SDL_Surface *loadPNG(const char* Path, uint32_t MaxWidth, uint32_t MaxHeight) {
	// Declare these with function scope and initialize them to NULL,
	// so we can use a single cleanup block at the end of the function.
	SDL_Surface *surface = NULL;
	FILE *fp = NULL;
	png_structp png = NULL;
	png_infop info = NULL;
	png_bytep* rowPointers = NULL;

	// Create and initialize the top-level libpng struct.
	png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png) goto cleanup;
	// Create and initialize the image information struct.
	info = png_create_info_struct(png);
	if (!info) goto cleanup;
	// Setup error handling for errors detected by libpng.
	if (setjmp(png_jmpbuf(png))) {
		// Note: This gets executed when an error occurs.
		if (surface) {
			SDL_FreeSurface(surface);
			surface = NULL;
		}
		goto cleanup;
	}

	fp = fopen(Path, "rb");
	if (!fp) goto cleanup;

	// Set up the input control if you are using standard C streams.
	png_init_io(png, fp);

	// The call to png_read_info() gives us all of the information from the
	// PNG file before the first IDAT (image data chunk).
	png_read_info(png, info);
	png_uint_32 width, height;
	int bitDepth, colorType;
	png_get_IHDR(
		png, info, &width, &height, &bitDepth, &colorType, NULL, NULL, NULL);

	// Select ARGB pixel format:
	// (ARGB is the native pixel format for the JZ47xx frame buffer in 24bpp)
	// - strip 16 bit/color files down to 8 bits/color
	png_set_strip_16(png);
	// - convert 1/2/4 bpp to 8 bpp
	png_set_packing(png);
	// - expand paletted images to RGB
	// - expand grayscale images of less than 8-bit depth to 8-bit depth
	// - expand tRNS chunks to alpha channels
	png_set_expand(png);
	// - convert grayscale to RGB
	png_set_gray_to_rgb(png);
	// - add alpha channel
	png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER);
	// - convert RGBA to ARGB
	if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
		png_set_swap_alpha(png);
	} else {
		png_set_bgr(png); // BGRA in memory becomes ARGB in register
	}

	// Update the image info to the post-conversion state.
	png_read_update_info(png, info);
	png_get_IHDR(
		png, info, &width, &height, &bitDepth, &colorType, NULL, NULL, NULL);
	assert(bitDepth == 8);
	assert(colorType == PNG_COLOR_TYPE_RGB_ALPHA);

	// Refuse to load outrageously large images.
	if (width > MaxWidth) {
		goto cleanup;
	}
	if (height > MaxHeight) {
		goto cleanup;
	}

	// Allocate ARGB surface to hold the image.
	surface = SDL_CreateRGBSurface(
		SDL_SWSURFACE | SDL_SRCALPHA, width, height, 32,
		0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000
		);
	if (!surface) {
		// Failed to create surface, probably out of memory.
		goto cleanup;
	}

	// Compute row pointers.
	rowPointers = malloc(height * sizeof(png_bytep));
	if (!rowPointers) {
		// Probably out of memory.
		if (surface) {
			SDL_FreeSurface(surface);
			surface = NULL;
		}
		goto cleanup;
	}
	png_uint_32 y;
	for (y = 0; y < height; y++) {
		rowPointers[y] =
			(png_bytep) (surface->pixels) + y * surface->pitch;
	}

	// Read the entire image in one go.
	png_read_image(png, rowPointers);

	// Read rest of file, and get additional chunks in the info struct.
	// Note: We got all we need, so skip this step.
	//png_read_end(png, info);

cleanup:
	// Clean up.
	if (rowPointers) {
		free(rowPointers);
		rowPointers = NULL;
	}
	png_destroy_read_struct(&png, &info, NULL);
	if (fp) fclose(fp);

	return surface;
}