UNPKG

9.42 kBtext/x-cView Raw
1#pragma once
2
3#include <cairo.h>
4#include "closure.h"
5#include <cmath> // round
6#include <cstdlib>
7#include <cstring>
8#include <png.h>
9#include <pngconf.h>
10
11#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__)
12#define likely(expr) (__builtin_expect (!!(expr), 1))
13#define unlikely(expr) (__builtin_expect (!!(expr), 0))
14#else
15#define likely(expr) (expr)
16#define unlikely(expr) (expr)
17#endif
18
19static void canvas_png_flush(png_structp png_ptr) {
20 /* Do nothing; fflush() is said to be just a waste of energy. */
21 (void) png_ptr; /* Stifle compiler warning */
22}
23
24/* Converts native endian xRGB => RGBx bytes */
25static void canvas_convert_data_to_bytes(png_structp png, png_row_infop row_info, png_bytep data) {
26 unsigned int i;
27
28 for (i = 0; i < row_info->rowbytes; i += 4) {
29 uint8_t *b = &data[i];
30 uint32_t pixel;
31
32 memcpy(&pixel, b, sizeof (uint32_t));
33
34 b[0] = (pixel & 0xff0000) >> 16;
35 b[1] = (pixel & 0x00ff00) >> 8;
36 b[2] = (pixel & 0x0000ff) >> 0;
37 b[3] = 0;
38 }
39}
40
41/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */
42static void canvas_unpremultiply_data(png_structp png, png_row_infop row_info, png_bytep data) {
43 unsigned int i;
44
45 for (i = 0; i < row_info->rowbytes; i += 4) {
46 uint8_t *b = &data[i];
47 uint32_t pixel;
48 uint8_t alpha;
49
50 memcpy(&pixel, b, sizeof (uint32_t));
51 alpha = (pixel & 0xff000000) >> 24;
52 if (alpha == 0) {
53 b[0] = b[1] = b[2] = b[3] = 0;
54 } else {
55 b[0] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
56 b[1] = (((pixel & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
57 b[2] = (((pixel & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
58 b[3] = alpha;
59 }
60 }
61}
62
63/* Converts RGB16_565 format data to RGBA32 */
64static void canvas_convert_565_to_888(png_structp png, png_row_infop row_info, png_bytep data) {
65 // Loop in reverse to unpack in-place.
66 for (ptrdiff_t col = row_info->width - 1; col >= 0; col--) {
67 uint8_t* src = &data[col * sizeof(uint16_t)];
68 uint8_t* dst = &data[col * 3];
69 uint16_t pixel;
70
71 memcpy(&pixel, src, sizeof(uint16_t));
72
73 // Convert and rescale to the full 0-255 range
74 // See http://stackoverflow.com/a/29326693
75 const uint8_t red5 = (pixel & 0xF800) >> 11;
76 const uint8_t green6 = (pixel & 0x7E0) >> 5;
77 const uint8_t blue5 = (pixel & 0x001F);
78
79 dst[0] = ((red5 * 255 + 15) / 31);
80 dst[1] = ((green6 * 255 + 31) / 63);
81 dst[2] = ((blue5 * 255 + 15) / 31);
82 }
83}
84
85struct canvas_png_write_closure_t {
86 cairo_write_func_t write_func;
87 PngClosure* closure;
88};
89
90#ifdef PNG_SETJMP_SUPPORTED
91bool setjmp_wrapper(png_structp png) {
92 return setjmp(png_jmpbuf(png));
93}
94#endif
95
96static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr write_func, canvas_png_write_closure_t *closure) {
97 unsigned int i;
98 cairo_status_t status = CAIRO_STATUS_SUCCESS;
99 uint8_t *data;
100 png_structp png;
101 png_infop info;
102 png_bytep *volatile rows = NULL;
103 png_color_16 white;
104 int png_color_type;
105 int bpc;
106 unsigned int width = cairo_image_surface_get_width(surface);
107 unsigned int height = cairo_image_surface_get_height(surface);
108
109 data = cairo_image_surface_get_data(surface);
110 if (data == NULL) {
111 status = CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
112 return status;
113 }
114 cairo_surface_flush(surface);
115
116 if (width == 0 || height == 0) {
117 status = CAIRO_STATUS_WRITE_ERROR;
118 return status;
119 }
120
121 rows = (png_bytep *) malloc(height * sizeof (png_byte*));
122 if (unlikely(rows == NULL)) {
123 status = CAIRO_STATUS_NO_MEMORY;
124 return status;
125 }
126
127 int stride = cairo_image_surface_get_stride(surface);
128 for (i = 0; i < height; i++) {
129 rows[i] = (png_byte *) data + i * stride;
130 }
131
132#ifdef PNG_USER_MEM_SUPPORTED
133 png = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, NULL, NULL);
134#else
135 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
136#endif
137
138 if (unlikely(png == NULL)) {
139 status = CAIRO_STATUS_NO_MEMORY;
140 free(rows);
141 return status;
142 }
143
144 info = png_create_info_struct (png);
145 if (unlikely(info == NULL)) {
146 status = CAIRO_STATUS_NO_MEMORY;
147 png_destroy_write_struct(&png, &info);
148 free(rows);
149 return status;
150
151 }
152
153#ifdef PNG_SETJMP_SUPPORTED
154 if (setjmp_wrapper(png)) {
155 png_destroy_write_struct(&png, &info);
156 free(rows);
157 return status;
158 }
159#endif
160
161 png_set_write_fn(png, closure, write_func, canvas_png_flush);
162 png_set_compression_level(png, closure->closure->compressionLevel);
163 png_set_filter(png, 0, closure->closure->filters);
164 if (closure->closure->resolution != 0) {
165 uint32_t res = static_cast<uint32_t>(round(static_cast<double>(closure->closure->resolution) * 39.3701));
166 png_set_pHYs(png, info, res, res, PNG_RESOLUTION_METER);
167 }
168
169 cairo_format_t format = cairo_image_surface_get_format(surface);
170
171 switch (format) {
172 case CAIRO_FORMAT_ARGB32:
173 bpc = 8;
174 png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
175 break;
176#ifdef CAIRO_FORMAT_RGB30
177 case CAIRO_FORMAT_RGB30:
178 bpc = 10;
179 png_color_type = PNG_COLOR_TYPE_RGB;
180 break;
181#endif
182 case CAIRO_FORMAT_RGB24:
183 bpc = 8;
184 png_color_type = PNG_COLOR_TYPE_RGB;
185 break;
186 case CAIRO_FORMAT_A8:
187 bpc = 8;
188 png_color_type = PNG_COLOR_TYPE_GRAY;
189 break;
190 case CAIRO_FORMAT_A1:
191 bpc = 1;
192 png_color_type = PNG_COLOR_TYPE_GRAY;
193#ifndef WORDS_BIGENDIAN
194 png_set_packswap(png);
195#endif
196 break;
197 case CAIRO_FORMAT_RGB16_565:
198 bpc = 8; // 565 gets upconverted to 888
199 png_color_type = PNG_COLOR_TYPE_RGB;
200 break;
201 case CAIRO_FORMAT_INVALID:
202 default:
203 status = CAIRO_STATUS_INVALID_FORMAT;
204 png_destroy_write_struct(&png, &info);
205 free(rows);
206 return status;
207 }
208
209 if ((format == CAIRO_FORMAT_A8 || format == CAIRO_FORMAT_A1) &&
210 closure->closure->palette != NULL) {
211 png_color_type = PNG_COLOR_TYPE_PALETTE;
212 }
213
214 png_set_IHDR(png, info, width, height, bpc, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
215
216 if (png_color_type == PNG_COLOR_TYPE_PALETTE) {
217 size_t nColors = closure->closure->nPaletteColors;
218 uint8_t* colors = closure->closure->palette;
219 uint8_t backgroundIndex = closure->closure->backgroundIndex;
220 png_colorp pngPalette = (png_colorp)png_malloc(png, nColors * sizeof(png_colorp));
221 png_bytep transparency = (png_bytep)png_malloc(png, nColors * sizeof(png_bytep));
222 for (i = 0; i < nColors; i++) {
223 pngPalette[i].red = colors[4 * i];
224 pngPalette[i].green = colors[4 * i + 1];
225 pngPalette[i].blue = colors[4 * i + 2];
226 transparency[i] = colors[4 * i + 3];
227 }
228 png_set_PLTE(png, info, pngPalette, nColors);
229 png_set_tRNS(png, info, transparency, nColors, NULL);
230 png_set_packing(png); // pack pixels
231 // have libpng free palette and trans:
232 png_data_freer(png, info, PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_PLTE | PNG_FREE_TRNS);
233 png_color_16 bkg;
234 bkg.index = backgroundIndex;
235 png_set_bKGD(png, info, &bkg);
236 }
237
238 if (png_color_type != PNG_COLOR_TYPE_PALETTE) {
239 white.gray = (1 << bpc) - 1;
240 white.red = white.blue = white.green = white.gray;
241 png_set_bKGD(png, info, &white);
242 }
243
244 /* We have to call png_write_info() before setting up the write
245 * transformation, since it stores data internally in 'png'
246 * that is needed for the write transformation functions to work.
247 */
248 png_write_info(png, info);
249 if (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
250 png_set_write_user_transform_fn(png, canvas_unpremultiply_data);
251 } else if (format == CAIRO_FORMAT_RGB16_565) {
252 png_set_write_user_transform_fn(png, canvas_convert_565_to_888);
253 } else if (png_color_type == PNG_COLOR_TYPE_RGB) {
254 png_set_write_user_transform_fn(png, canvas_convert_data_to_bytes);
255 png_set_filler(png, 0, PNG_FILLER_AFTER);
256 }
257
258 png_write_image(png, rows);
259 png_write_end(png, info);
260
261 png_destroy_write_struct(&png, &info);
262 free(rows);
263 return status;
264}
265
266static void canvas_stream_write_func(png_structp png, png_bytep data, png_size_t size) {
267 cairo_status_t status;
268 struct canvas_png_write_closure_t *png_closure;
269
270 png_closure = (struct canvas_png_write_closure_t *) png_get_io_ptr(png);
271 status = png_closure->write_func(png_closure->closure, data, size);
272 if (unlikely(status)) {
273 cairo_status_t *error = (cairo_status_t *) png_get_error_ptr(png);
274 if (*error == CAIRO_STATUS_SUCCESS) {
275 *error = status;
276 }
277 png_error(png, NULL);
278 }
279}
280
281static cairo_status_t canvas_write_to_png_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PngClosure* closure) {
282 struct canvas_png_write_closure_t png_closure;
283
284 if (cairo_surface_status(surface)) {
285 return cairo_surface_status(surface);
286 }
287
288 png_closure.write_func = write_func;
289 png_closure.closure = closure;
290
291 return canvas_write_png(surface, canvas_stream_write_func, &png_closure);
292}