Skip to content

Instantly share code, notes, and snippets.

@t-mat
Last active October 9, 2023 14:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t-mat/fed60b83735a80896fa182a77d5259d6 to your computer and use it in GitHub Desktop.
Save t-mat/fed60b83735a80896fa182a77d5259d6 to your computer and use it in GitHub Desktop.
[C++]Generate uncompressed PNG file
// Uncompressed PNG generator
//
// This is a header-only C++17 library which generates an uncompressed PNG.
// It may be useful for a program which requires low complexity but also
// needs to use PNG as a container.
//
// Usage:
// #include "UncompressedPngGenerator.hpp"
// int main() {
// int width = 256, height = 256;
// std::vector<uint8_t> buf(width * height * 4); // {R,G,B,A} pixels
//
// // ... write your content to buf[] here ...
//
// FILE* fp = fopen("out-rgba-uncompressed.png", "wb");
// UncompressedPngGenerator::makeRgba32UncompressedPngFile(
// width, height,
// [&](int x, int y) -> std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> {
// const auto o = (y * width + x) * 4;
// const uint8_t r = buf[o + 0];
// const uint8_t g = buf[o + 1];
// const uint8_t b = buf[o + 2];
// const uint8_t a = buf[o + 3];
// return { r, g, b, a };
// },
// [&](uint8_t c) {
// fputc(c, fp);
// }
// );
// fclose(fp);
// }
//
// Functions:
// void makeRgba32UncompressedPngFile(int w, int h, const GetRgbaPixelFunc& get, const PutOneByteFunc& put);
// Generates uncompressed PNG file which contains RGBA (32bit) pixels.
//
// void makeRgb24UncompressedPngFile(int w, int h, const GetRgbaPixelFunc& get, const PutOneByteFunc& put);
// Generates uncompressed PNG file which contains RGB (24bit) pixels.
//
// License:
// Copyright (C) 2021, Takayuki Matsuoka.
// SPDX-License-Identifier: CC0-1.0
#pragma once
#include <stdint.h>
#include <assert.h>
#include <functional>
#include <array>
namespace UncompressedPngGenerator
{
using GetRgbaPixelFunc = std::function<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>(int, int)>;
using GetRgbPixelFunc = std::function<std::tuple<uint8_t, uint8_t, uint8_t>(int, int)>;
using PutOneByteFunc = std::function<void(uint8_t)>;
inline void makeRgbaUncompressedPngFile(
int bitsPerPixel, int width, int height, GetRgbaPixelFunc const &getRgbaPixel, PutOneByteFunc const &putOneByte)
{
class Crc32
{
public:
// Using CRC32 algorithm to hash string at compile-time
// https://stackoverflow.com/a/36522355 by Deus Sum
constexpr std::array<uint32_t, 256> gen_crc32_table()
{
// 0xEDB88320 = CRC-32 Reversed Polynomial.
// See
// https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Polynomial_representations_of_cyclic_redundancy_checks
constexpr uint32_t polynomial = 0xEDB88320;
auto crc32_table = std::array<uint32_t, 256>{};
for (uint32_t byte = 0; byte < crc32_table.size(); ++byte) {
uint32_t crc = byte;
for (int i = 0; i < 8; ++i) {
auto const m = crc & 1;
crc >>= 1;
if (m != 0) {
crc ^= polynomial;
}
}
crc32_table[byte] = crc;
}
return crc32_table;
}
void reset()
{
crc = 0xffffffff;
}
uint32_t get() const
{
return ~crc;
}
void update(uint8_t v)
{
static auto const crc32Table = gen_crc32_table();
crc = crc32Table[(crc ^ v) & 0xff] ^ (crc >> 8);
};
private:
uint32_t crc = 0xffffffff;
};
Crc32 crc;
// Output functions also calculate CRC-32 automatically.
auto const put8 = [&](uint8_t v) {
putOneByte(v);
crc.update(v);
};
auto const put32be = [&](uint32_t v) {
put8(static_cast<uint8_t>(v >> 24));
put8(static_cast<uint8_t>(v >> 16));
put8(static_cast<uint8_t>(v >> 8));
put8(static_cast<uint8_t>(v));
};
auto const putFourcc = [&](const char *v) {
put8(v[0]);
put8(v[1]);
put8(v[2]);
put8(v[3]);
};
auto const put16le = [&](uint16_t v) {
put8(static_cast<uint8_t>(v));
put8(static_cast<uint8_t>(v >> 8));
};
{ // File header
static uint8_t constexpr pngFileHeader[8] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
for (uint8_t v : pngFileHeader) {
put8(v);
}
// IHDR : http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
put32be(13); // Chunk length (big endian)
crc.reset(); // CRC calculation must ignore length but include chunk name.
putFourcc("IHDR"); // Chunk name
put32be(static_cast<uint32_t>(width)); // Width (big endian)
put32be(static_cast<uint32_t>(height)); // Height (big endian)
put8(8); // Bit depth (8: 8-bits per component)
put8(bitsPerPixel == 32 ? 6 : 2); // Color type (2:{R,G,B}, 6:{R,G,B,A})
put8(0); // Compresison method (0:Deflate)
put8(0); // Filter method (0:adaptive filtering with five basic filter types)
put8(0); // Interlace method (0:no interlace)
put32be(crc.get()); // CRC-32 (big endian)
}
{ // IDAT
const uint32_t bytesPerPixel = bitsPerPixel / 8;
const uint32_t sizeofZlibHeader = 2; // 1(CMF) + 1(FLG)
const uint32_t sizeofZlibOverhead = height * (1 + 2 + 2); // 1(BFINAL, BTYPE) + 2(LEN) + 2(NLEN)
const uint32_t sizeofZlibFooter = 5 + 4; // 5(Final Empty Block) + 4(Adler32)
const uint32_t sizeofRow = 1 + width * bytesPerPixel; // 1(Filter Type) + width * sizeof({R,G,B,A})
const uint32_t length = sizeofZlibHeader + sizeofZlibOverhead + sizeofZlibFooter + sizeofRow * height;
assert(sizeofRow < 65536); // This restriction is due to single deflate block size for simplicity.
uint32_t adler32_lo = 1, adler32_hi = 0;
const auto updateAdler32 = [&](uint8_t x) {
const uint32_t MOD_ADLER = 65521;
adler32_lo = (adler32_lo + x) % MOD_ADLER;
adler32_hi = (adler32_lo + adler32_hi) % MOD_ADLER;
};
const auto getAdler32 = [&]() { return (adler32_hi << 16) | adler32_lo; };
// For Adler-32 calculation, we use zput8() for deflate content.
const auto zput8 = [&](uint8_t x) {
updateAdler32(x);
put8(x);
};
put32be(length); // Chunk length (big endian)
crc.reset(); // CRC calculation must ignore length but include chunk name.
putFourcc("IDAT"); // Chunk name
// Beginning of the deflate stream
put8(0x78); // deflate: CM=8, CINFO=7
put8(0x01); // deflate: FLG=1
for (int y = 0; y < height; ++y) {
put8(0); // deflate: 0:not a last block, 00:uncompressed block
put16le(sizeofRow); // deflate: length of the block (little endian)
put16le(sizeofRow ^ 0xffff); // deflate: length of the block (little endian, inverted)
zput8(0); // filter type: 0=none
for (int x = 0; x < width; ++x) {
const auto [r, g, b, a] = getRgbaPixel(x, y);
zput8(r);
zput8(g);
zput8(b);
if (bitsPerPixel == 32) {
zput8(a);
}
}
}
// Empty terminator block
put8(1); // deflate: 1:last block, 00:uncompressed block
put16le(0); // deflate: length of the block (little endian)
put16le(0 ^ 0xffff); // deflate: length of the block (little endian, inverted)
// End of the deflate stream
put32be(getAdler32()); // deflate: Adler-32 (big endian)
// End of the IDAT chunk
put32be(crc.get()); // CRC-32 (big endian)
}
// IEND
put32be(0); // Chunk length (big endian)
putFourcc("IEND");
put32be(0xae426082); // CRC-32 (big endian). Since IEND doesn't have content, its CRC-32 is constant.
}
inline void makeRgba32UncompressedPngFile(int width,
int height,
const GetRgbaPixelFunc &getRgbaPixel,
const PutOneByteFunc &putOneByte)
{
return makeRgbaUncompressedPngFile(32, width, height, getRgbaPixel, putOneByte);
}
inline void makeRgb24UncompressedPngFile(int width,
int height,
const GetRgbPixelFunc &getRgbPixel,
const PutOneByteFunc &putOneByte)
{
return makeRgbaUncompressedPngFile(
24,
width,
height,
[&](int x, int y) -> std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> {
const auto [r, g, b] = getRgbPixel(x, y);
return {r, g, b, 0xff};
},
putOneByte);
}
} // namespace UncompressedPngGenerator
#if defined(MY_UNCOMPRESSED_PNG_GENERATOR_TEST)
# include <stdio.h>
# include <stdlib.h>
# include <math.h>
# if defined(MY_UNCOMPRESSED_PNG_GENERATOR_TEST_USE_LODEPNG)
# include "lodepng/lodepng.cpp" // https://lodev.org/lodepng/
# endif
int main()
{
int const width = 256, height = 256;
std::vector<uint8_t> buf(width * height * 4);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
auto const o = (y * width + x) * 4;
uint8_t const r = static_cast<uint8_t>(x);
uint8_t const g = static_cast<uint8_t>(y);
uint8_t const b = static_cast<uint8_t>(sqrt(x * x + y * y));
uint8_t const a = (((x / 64) ^ (y / 64)) & 1) == 0 ? 0x80 : 0xff;
buf[o + 0] = r;
buf[o + 1] = g;
buf[o + 2] = b;
buf[o + 3] = a;
}
}
FILE *fp = fopen("out.png", "wb");
UncompressedPngGenerator::makeRgba32UncompressedPngFile(
width,
height,
[&](int x, int y) -> std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> {
auto const o = (y * width + x) * 4;
uint8_t const r = buf[o + 0];
uint8_t const g = buf[o + 1];
uint8_t const b = buf[o + 2];
uint8_t const a = buf[o + 3];
return {r, g, b, a};
},
[&](uint8_t c) { fputc(c, fp); });
fclose(fp);
# if defined(MY_UNCOMPRESSED_PNG_GENERATOR_TEST_USE_LODEPNG)
{
const char *filename = "out.png";
unsigned char *out = nullptr;
unsigned w, h;
auto const lpr = lodepng_decode32_file(&out, &w, &h, filename);
printf("lodepng result = 0x%08x\n", lpr);
if (lpr != 0) {
printf("lodepng error : %s\n", lodepng_error_text(lpr));
}
}
# endif
}
#endif // defined(MY_UNCOMPRESSED_PNG_GENERATOR_TEST)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment