1 /*
2  * SDL_SavePNG -- libpng-based SDL_Surface writer.
3  *
4  * This code is free software, available under zlib/libpng license.
5  * http://www.libpng.org/pub/png/src/libpng-LICENSE.txt
6  */
7 #include <SDL.h>
8 #include <stdlib.h>
9 #include "png.h"
10 
11 #include "savepng.hpp"
12 
13 #define SUCCESS 0
14 #define ERROR -1
15 
16 #define USE_ROW_POINTERS
17 
18 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
19 #define rmask 0xFF000000
20 #define gmask 0x00FF0000
21 #define bmask 0x0000FF00
22 #define amask 0x000000FF
23 #else
24 #define rmask 0x000000FF
25 #define gmask 0x0000FF00
26 #define bmask 0x00FF0000
27 #define amask 0xFF000000
28 #endif
29 
SDL_SavePNG(SDL_Surface * surface,const char * filename)30 int SDL_SavePNG(SDL_Surface *surface, const char *filename)
31 {
32 	SDL_RWops *file = SDL_RWFromFile(filename, "wb");
33 	if (file != NULL)
34 	{
35 		return SDL_SavePNG_RW(surface, file, 1);
36 	}
37 	else
38 	{
39 		return -1;
40 	}
41 }
42 
43 /* libpng callbacks */
png_error_SDL(png_structp ctx,png_const_charp str)44 static void png_error_SDL(png_structp ctx, png_const_charp str)
45 {
46 	SDL_SetError("libpng: %s\n", str);
47 }
png_write_SDL(png_structp png_ptr,png_bytep data,png_size_t length)48 static void png_write_SDL(png_structp png_ptr, png_bytep data, png_size_t length)
49 {
50 	SDL_RWops* rw = (SDL_RWops*)png_get_io_ptr(png_ptr);
51 	SDL_RWwrite(rw, data, sizeof(png_byte), length);
52 }
53 
SDL_PNGFormatAlpha(SDL_Surface * src)54 SDL_Surface* SDL_PNGFormatAlpha(SDL_Surface* src)
55 {
56 	SDL_Surface* surf;
57 	SDL_Rect rect = { 0 };
58 
59 	/* NO-OP for images < 32bpp and 32bpp images that already have Alpha channel */
60 	if (src->format->BitsPerPixel <= 24 || src->format->Amask)
61 	{
62 		src->refcount++;
63 		return src;
64 	}
65 
66 	/* Convert 32bpp alpha-less image to 24bpp alpha-less image */
67 	rect.w = src->w;
68 	rect.h = src->h;
69 	surf = SDL_CreateRGBSurface(src->flags, src->w, src->h, 24,
70 	                            src->format->Rmask, src->format->Gmask, src->format->Bmask, 0);
71 	SDL_LowerBlit(src, &rect, surf, &rect);
72 
73 	return surf;
74 }
75 
SDL_SavePNG_RW(SDL_Surface * surface,SDL_RWops * dst,int freedst)76 int SDL_SavePNG_RW(SDL_Surface* surface, SDL_RWops* dst, int freedst)
77 {
78 	png_structp png_ptr;
79 	png_infop info_ptr;
80 	png_colorp pal_ptr;
81 	SDL_Palette* pal;
82 	int i, colortype;
83 #ifdef USE_ROW_POINTERS
84 	png_bytep* row_pointers;
85 #endif
86 	/* Initialize and do basic error checking */
87 	if (!dst)
88 	{
89 		SDL_SetError("Argument 2 to SDL_SavePNG_RW can't be NULL, expecting SDL_RWops*\n");
90 		if (freedst)
91 		{
92 			SDL_RWclose(dst);
93 		}
94 		return (ERROR);
95 	}
96 	if (!surface)
97 	{
98 		SDL_SetError("Argument 1 to SDL_SavePNG_RW can't be NULL, expecting SDL_Surface*\n");
99 		if (freedst)
100 		{
101 			SDL_RWclose(dst);
102 		}
103 		return (ERROR);
104 	}
105 	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, png_error_SDL, NULL); /* err_ptr, err_fn, warn_fn */
106 	if (!png_ptr)
107 	{
108 		SDL_SetError("Unable to png_create_write_struct on %s\n", PNG_LIBPNG_VER_STRING);
109 		if (freedst)
110 		{
111 			SDL_RWclose(dst);
112 		}
113 		return (ERROR);
114 	}
115 	info_ptr = png_create_info_struct(png_ptr);
116 	if (!info_ptr)
117 	{
118 		SDL_SetError("Unable to png_create_info_struct\n");
119 		png_destroy_write_struct(&png_ptr, NULL);
120 		if (freedst)
121 		{
122 			SDL_RWclose(dst);
123 		}
124 		return (ERROR);
125 	}
126 	if (setjmp(png_jmpbuf(png_ptr)))  	/* All other errors, see also "png_error_SDL" */
127 	{
128 		png_destroy_write_struct(&png_ptr, &info_ptr);
129 		if (freedst)
130 		{
131 			SDL_RWclose(dst);
132 		}
133 		return (ERROR);
134 	}
135 
136 	/* Setup our RWops writer */
137 	png_set_write_fn(png_ptr, dst, png_write_SDL, NULL); /* w_ptr, write_fn, flush_fn */
138 
139 	/* Prepare chunks */
140 	colortype = PNG_COLOR_MASK_COLOR;
141 	if (surface->format->BytesPerPixel > 0
142 	        &&  surface->format->BytesPerPixel <= 8
143 	        && (pal = surface->format->palette))
144 	{
145 		colortype |= PNG_COLOR_MASK_PALETTE;
146 		pal_ptr = (png_colorp)malloc(pal->ncolors * sizeof(png_color));
147 		for (i = 0; i < pal->ncolors; i++)
148 		{
149 			pal_ptr[i].red   = pal->colors[i].r;
150 			pal_ptr[i].green = pal->colors[i].g;
151 			pal_ptr[i].blue  = pal->colors[i].b;
152 		}
153 		png_set_PLTE(png_ptr, info_ptr, pal_ptr, pal->ncolors);
154 		free(pal_ptr);
155 	}
156 	else if (surface->format->BytesPerPixel > 3 || surface->format->Amask)
157 	{
158 		colortype |= PNG_COLOR_MASK_ALPHA;
159 	}
160 
161 	png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype,
162 	             PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
163 
164 //	png_set_packing(png_ptr);
165 
166 	/* Allow BGR surfaces */
167 	if (surface->format->Rmask == bmask
168 	        && surface->format->Gmask == gmask
169 	        && surface->format->Bmask == rmask)
170 	{
171 		png_set_bgr(png_ptr);
172 	}
173 
174 	/* Write everything */
175 	png_write_info(png_ptr, info_ptr);
176 #ifdef USE_ROW_POINTERS
177 	row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * surface->h);
178 	for (i = 0; i < surface->h; i++)
179 	{
180 		row_pointers[i] = (png_bytep)(Uint8*)surface->pixels + i * surface->pitch;
181 	}
182 	png_write_image(png_ptr, row_pointers);
183 	free(row_pointers);
184 #else
185 	for (i = 0; i < surface->h; i++)
186 	{
187 		png_write_row(png_ptr, (png_bytep)(Uint8*)surface->pixels + i * surface->pitch);
188 	}
189 #endif
190 	png_write_end(png_ptr, info_ptr);
191 
192 	/* Done */
193 	png_destroy_write_struct(&png_ptr, &info_ptr);
194 	if (freedst)
195 	{
196 		SDL_RWclose(dst);
197 	}
198 	return (SUCCESS);
199 }
200