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 |
|
19 | static void canvas_png_flush(png_structp png_ptr) {
|
20 |
|
21 | (void) png_ptr;
|
22 | }
|
23 |
|
24 |
|
25 | static 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 |
|
42 | static 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 |
|
64 | static void canvas_convert_565_to_888(png_structp png, png_row_infop row_info, png_bytep data) {
|
65 |
|
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 |
|
74 |
|
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 |
|
85 | struct canvas_png_write_closure_t {
|
86 | cairo_write_func_t write_func;
|
87 | PngClosure* closure;
|
88 | };
|
89 |
|
90 | #ifdef PNG_SETJMP_SUPPORTED
|
91 | bool setjmp_wrapper(png_structp png) {
|
92 | return setjmp(png_jmpbuf(png));
|
93 | }
|
94 | #endif
|
95 |
|
96 | static 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;
|
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);
|
231 |
|
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 | |
245 |
|
246 |
|
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 |
|
266 | static 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 |
|
281 | static 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 | }
|