PNG.h 9.2 KB

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