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