#include "TinyPngOut.hpp"
#include <algorithm>
#include <cassert>
#include <limits>
#include <stdexcept>
#include "adler32.hpp"
#include "crc32.hpp"
#include "Endian.hpp"
NAMESPACE_SOUP
{
TinyPngOut::TinyPngOut(uint32_t w, uint32_t h, Writer& out)
: width(w),
height(h),
output(out),
positionX(0),
positionY(0),
deflateFilled(0),
adler(1)
{
SOUP_ASSERT(width != 0 && height != 0);
uint64_t lineSz = static_cast<uint64_t>(width) * 3 + 1;
SOUP_ASSERT(lineSz <= UINT32_MAX); lineSize = static_cast<uint32_t>(lineSz);
uint64_t uncompRm = lineSize * height;
SOUP_ASSERT(uncompRm <= UINT32_MAX); uncompRemain = static_cast<uint32_t>(uncompRm);
uint32_t numBlocks = uncompRemain / DEFLATE_MAX_BLOCK_SIZE;
if (uncompRemain % DEFLATE_MAX_BLOCK_SIZE != 0)
{
numBlocks++; }
uint64_t idatSize = static_cast<uint64_t>(numBlocks) * 5 + 6;
idatSize += uncompRemain;
SOUP_ASSERT(idatSize <= static_cast<uint32_t>(INT32_MAX));
uint8_t header[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0x00, 0x00, 0x00, 0x0D,
0x49, 0x48, 0x44, 0x52,
0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x02, 0x00, 0x00, 0x00,
0, 0, 0, 0, 0, 0, 0, 0, 0x49, 0x44, 0x41, 0x54,
0x08, 0x1D,
};
putBigUint32(width, &header[16]);
putBigUint32(height, &header[20]);
putBigUint32(static_cast<uint32_t>(idatSize), &header[33]);
crc = 0;
updateCrc(&header[12], 17);
putBigUint32(crc, &header[29]);
write(header);
crc = 0;
updateCrc(&header[37], 6); }
void TinyPngOut::write(const Rgb* pixels, size_t count)
{
write(reinterpret_cast<const uint8_t*>(pixels), count);
}
void TinyPngOut::write(const uint8_t* pixels, size_t count)
{
SOUP_ASSERT(count <= SIZE_MAX / 3);
count *= 3; while (count > 0)
{
SOUP_ASSERT(positionY < height);
if (deflateFilled == 0) {
uint16_t size = DEFLATE_MAX_BLOCK_SIZE;
if (uncompRemain < size)
size = static_cast<uint16_t>(uncompRemain);
const uint8_t header[] = { static_cast<uint8_t>(uncompRemain <= DEFLATE_MAX_BLOCK_SIZE ? 1 : 0),
static_cast<uint8_t>(size >> 0),
static_cast<uint8_t>(size >> 8),
static_cast<uint8_t>((size >> 0) ^ 0xFF),
static_cast<uint8_t>((size >> 8) ^ 0xFF),
};
write(header);
updateCrc(header, sizeof(header) / sizeof(header[0]));
}
assert(positionX < lineSize && deflateFilled < DEFLATE_MAX_BLOCK_SIZE);
if (positionX == 0)
{
uint8_t b[] = { 0 };
write(b);
updateCrc(b, 1);
updateAdler(b, 1);
positionX++;
uncompRemain--;
deflateFilled++;
}
else
{
uint16_t n = DEFLATE_MAX_BLOCK_SIZE - deflateFilled;
if (lineSize - positionX < n)
{
n = static_cast<uint16_t>(lineSize - positionX);
}
if (count < n)
{
n = static_cast<uint16_t>(count);
}
if (static_cast<std::make_unsigned<size_t>::type>(std::numeric_limits<size_t>::max()) < std::numeric_limits<decltype(n)>::max())
{
n = std::min(n, static_cast<decltype(n)>(std::numeric_limits<size_t>::max()));
}
assert(n > 0);
output.raw(const_cast<uint8_t*>(pixels), static_cast<size_t>(n));
updateCrc(pixels, n);
updateAdler(pixels, n);
count -= n;
pixels += n;
positionX += n;
uncompRemain -= n;
deflateFilled += n;
}
if (deflateFilled >= DEFLATE_MAX_BLOCK_SIZE)
{
deflateFilled = 0; }
if (positionX == lineSize) {
positionX = 0;
positionY++;
if (positionY == height) {
uint8_t footer[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x00,
0x49, 0x45, 0x4E, 0x44,
0xAE, 0x42, 0x60, 0x82,
};
putBigUint32(adler, &footer[0]);
updateCrc(&footer[0], 4);
putBigUint32(crc, &footer[4]);
write(footer);
}
}
}
}
void TinyPngOut::updateCrc(const uint8_t* data, size_t size)
{
crc = crc32::hash(data, size, crc);
}
void TinyPngOut::updateAdler(const uint8_t* data, size_t size)
{
adler = adler32::hash(data, size, adler);
}
void TinyPngOut::putBigUint32(uint32_t val, uint8_t arr[4])
{
if constexpr (ENDIAN_NATIVE == ENDIAN_LITTLE)
{
*reinterpret_cast<uint32_t*>(&arr[0]) = Endianness::invert(val);
}
else
{
*reinterpret_cast<uint32_t*>(&arr[0]) = val;
}
}
}