| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #include "write_png_file.hpp" |
| |
| #include "png.h" |
| #include "zlib.h" |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <vector> |
| |
| void WritePNGFile(uint8_t* pixels, |
| int width, |
| int height, |
| bool flipY, |
| const char* file_name, |
| PNGCompression compression) |
| { |
| FILE* fp = fopen(file_name, "wb"); |
| if (!fp) |
| { |
| fprintf(stderr, |
| "WritePNGFile: File %s could not be opened for writing " |
| "(errno=%i)\n", |
| file_name, |
| errno); |
| abort(); |
| } |
| |
| auto png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| |
| if (!png) |
| { |
| fprintf(stderr, "WritePNGFile: png_create_write_struct failed\n"); |
| abort(); |
| } |
| |
| if (compression == PNGCompression::fast_rle) |
| { |
| // RLE with SUB gets best performance with our content. |
| png_set_compression_strategy(png, Z_RLE); |
| png_set_filter(png, 0, PNG_FILTER_SUB); |
| } |
| |
| auto info = png_create_info_struct(png); |
| if (!info) |
| { |
| fprintf(stderr, "WritePNGFile: png_create_info_struct failed\n"); |
| abort(); |
| } |
| |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during init_io\n"); |
| abort(); |
| } |
| |
| png_init_io(png, fp); |
| |
| // Write header. |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during writing header\n"); |
| abort(); |
| } |
| |
| png_set_IHDR(png, |
| info, |
| width, |
| height, |
| 8, |
| PNG_COLOR_TYPE_RGB_ALPHA, |
| PNG_INTERLACE_NONE, |
| PNG_COMPRESSION_TYPE_BASE, |
| PNG_FILTER_TYPE_BASE); |
| |
| png_write_info(png, info); |
| |
| // Write bytes. |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during writing bytes\n"); |
| abort(); |
| } |
| |
| std::vector<uint8_t*> rows(height); |
| for (int y = 0; y < height; ++y) |
| { |
| rows[y] = pixels + (flipY ? height - 1 - y : y) * width * 4; |
| } |
| png_write_image(png, rows.data()); |
| |
| // End write. |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during end of write"); |
| abort(); |
| } |
| |
| png_write_end(png, NULL); |
| png_destroy_write_struct(&png, &info); |
| |
| fclose(fp); |
| } |
| |
| static void append_png_data_chunk(png_structp png, |
| png_bytep data, |
| png_size_t length) |
| { |
| auto encodedData = |
| reinterpret_cast<std::vector<uint8_t>*>(png_get_io_ptr(png)); |
| encodedData->insert(encodedData->end(), data, data + length); |
| } |
| |
| static void flush_png_data(png_structp png) {} |
| |
| std::vector<uint8_t> EncodePNGToBuffer(uint32_t width, |
| uint32_t height, |
| uint8_t* imageDataRGBA, |
| PNGCompression compression) |
| { |
| // Send the image name. |
| auto png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| |
| if (!png) |
| { |
| fprintf(stderr, "WritePNGFile: png_create_write_struct failed\n"); |
| abort(); |
| } |
| |
| if (compression == PNGCompression::fast_rle) |
| { |
| // RLE with SUB gets best performance with our content. |
| png_set_compression_level(png, 6); |
| png_set_compression_strategy(png, Z_RLE); |
| png_set_compression_strategy(png, Z_RLE); |
| png_set_filter(png, 0, PNG_FILTER_SUB); |
| } |
| |
| auto info = png_create_info_struct(png); |
| if (!info) |
| { |
| fprintf(stderr, "WritePNGFile: png_create_info_struct failed\n"); |
| abort(); |
| } |
| |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during init_io\n"); |
| abort(); |
| } |
| |
| std::vector<uint8_t> encodedData; |
| png_set_write_fn(png, |
| &encodedData, |
| &append_png_data_chunk, |
| &flush_png_data); |
| |
| // Write header. |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during writing header\n"); |
| abort(); |
| } |
| |
| png_set_IHDR(png, |
| info, |
| width, |
| height, |
| 8, |
| PNG_COLOR_TYPE_RGB_ALPHA, |
| PNG_INTERLACE_NONE, |
| PNG_COMPRESSION_TYPE_BASE, |
| PNG_FILTER_TYPE_BASE); |
| |
| png_write_info(png, info); |
| |
| // Write bytes. |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during writing bytes\n"); |
| abort(); |
| } |
| |
| std::vector<uint8_t*> rows(height); |
| for (uint32_t y = 0; y < height; ++y) |
| { |
| rows[y] = imageDataRGBA + (height - 1 - y) * width * 4; |
| } |
| png_write_image(png, rows.data()); |
| |
| // End write. |
| if (setjmp(png_jmpbuf(png))) |
| { |
| fprintf(stderr, "WritePNGFile: Error during end of write"); |
| abort(); |
| } |
| |
| png_write_end(png, NULL); |
| png_destroy_write_struct(&png, &info); |
| |
| return encodedData; |
| } |