| /* | |
| * 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; | |
| } |