/*
* Tiny PNG Output (C++)
*
* Copyright (c) 2018 Project Nayuki
* https://www.nayuki.io/page/tiny-png-output
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program (see COPYING.txt and COPYING.LESSER.txt).
* If not, see .
*/
#include
#include
#include
#include
#include "TinyPngOut.hpp"
using std::uint8_t;
using std::uint16_t;
using std::uint32_t;
using std::uint64_t;
using std::size_t;
TinyPngOut::TinyPngOut(uint32_t w, uint32_t h, std::ostream &out) :
// Set most of the fields
width(w),
height(h),
output(out),
positionX(0),
positionY(0),
deflateFilled(0),
adler(1) {
// Check arguments
if (width == 0 || height == 0)
throw std::domain_error("Zero width or height");
// Compute and check data siezs
uint64_t lineSz = static_cast(width) * 3 + 1;
if (lineSz > UINT32_MAX)
throw std::length_error("Image too large");
lineSize = static_cast(lineSz);
uint64_t uncompRm = lineSize * height;
if (uncompRm > UINT32_MAX)
throw std::length_error("Image too large");
uncompRemain = static_cast(uncompRm);
uint32_t numBlocks = uncompRemain / DEFLATE_MAX_BLOCK_SIZE;
if (uncompRemain % DEFLATE_MAX_BLOCK_SIZE != 0)
numBlocks++; // Round up
// 5 bytes per DEFLATE uncompressed block header, 2 bytes for zlib header, 4 bytes for zlib Adler-32 footer
uint64_t idatSize = static_cast(numBlocks) * 5 + 6;
idatSize += uncompRemain;
if (idatSize > static_cast(INT32_MAX))
throw std::length_error("Image too large");
// Write header (not a pure header, but a couple of things concatenated together)
uint8_t header[] = { // 43 bytes long
// PNG header
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
// IHDR chunk
0x00, 0x00, 0x00, 0x0D,
0x49, 0x48, 0x44, 0x52,
0, 0, 0, 0, // 'width' placeholder
0, 0, 0, 0, // 'height' placeholder
0x08, 0x02, 0x00, 0x00, 0x00,
0, 0, 0, 0, // IHDR CRC-32 placeholder
// IDAT chunk
0, 0, 0, 0, // 'idatSize' placeholder
0x49, 0x44, 0x41, 0x54,
// DEFLATE data
0x08, 0x1D,
};
putBigUint32(width, &header[16]);
putBigUint32(height, &header[20]);
putBigUint32(idatSize, &header[33]);
crc = 0;
crc32(&header[12], 17);
putBigUint32(crc, &header[29]);
write(header);
crc = 0;
crc32(&header[37], 6); // 0xD7245B6B
}
void TinyPngOut::write(const uint8_t pixels[], size_t count) {
if (count > SIZE_MAX / 3)
throw std::length_error("Invalid argument");
count *= 3; // Convert pixel count to byte count
while (count > 0) {
if (pixels == nullptr)
throw std::invalid_argument("Null pointer");
if (positionY >= height)
throw std::logic_error("All image pixels already written");
if (deflateFilled == 0) { // Start DEFLATE block
uint16_t size = DEFLATE_MAX_BLOCK_SIZE;
if (uncompRemain < size)
size = static_cast(uncompRemain);
const uint8_t header[] = { // 5 bytes long
static_cast(uncompRemain <= DEFLATE_MAX_BLOCK_SIZE ? 1 : 0),
static_cast(size >> 0),
static_cast(size >> 8),
static_cast((size >> 0) ^ 0xFF),
static_cast((size >> 8) ^ 0xFF),
};
write(header);
crc32(header, sizeof(header) / sizeof(header[0]));
}
assert(positionX < lineSize && deflateFilled < DEFLATE_MAX_BLOCK_SIZE);
if (positionX == 0) { // Beginning of line - write filter method byte
uint8_t b[] = {0};
write(b);
crc32(b, 1);
adler32(b, 1);
positionX++;
uncompRemain--;
deflateFilled++;
} else { // Write some pixel bytes for current line
uint16_t n = DEFLATE_MAX_BLOCK_SIZE - deflateFilled;
if (lineSize - positionX < n)
n = static_cast(lineSize - positionX);
if (count < n)
n = static_cast(count);
if (static_cast::type>(std::numeric_limits::max()) < std::numeric_limits::max())
n = std::min(n, static_cast(std::numeric_limits::max()));
assert(n > 0);
output.write(reinterpret_cast(pixels), static_cast(n));
// Update checksums
crc32(pixels, n);
adler32(pixels, n);
// Increment positions
count -= n;
pixels += n;
positionX += n;
uncompRemain -= n;
deflateFilled += n;
}
if (deflateFilled >= DEFLATE_MAX_BLOCK_SIZE)
deflateFilled = 0; // End current block
if (positionX == lineSize) { // Increment line
positionX = 0;
positionY++;
if (positionY == height) { // Reached end of pixels
uint8_t footer[] = { // 20 bytes long
0, 0, 0, 0, // DEFLATE Adler-32 placeholder
0, 0, 0, 0, // IDAT CRC-32 placeholder
// IEND chunk
0x00, 0x00, 0x00, 0x00,
0x49, 0x45, 0x4E, 0x44,
0xAE, 0x42, 0x60, 0x82,
};
putBigUint32(adler, &footer[0]);
crc32(&footer[0], 4);
putBigUint32(crc, &footer[4]);
write(footer);
}
}
}
}
void TinyPngOut::crc32(const uint8_t data[], size_t len) {
crc = ~crc;
for (size_t i = 0; i < len; i++) {
for (int j = 0; j < 8; j++) { // Inefficient bitwise implementation, instead of table-based
uint32_t bit = (crc ^ (data[i] >> j)) & 1;
crc = (crc >> 1) ^ ((-bit) & UINT32_C(0xEDB88320));
}
}
crc = ~crc;
}
void TinyPngOut::adler32(const uint8_t data[], size_t len) {
uint32_t s1 = adler & 0xFFFF;
uint32_t s2 = adler >> 16;
for (size_t i = 0; i < len; i++) {
s1 = (s1 + data[i]) % 65521;
s2 = (s2 + s1) % 65521;
}
adler = s2 << 16 | s1;
}
void TinyPngOut::putBigUint32(uint32_t val, uint8_t array[4]) {
for (int i = 0; i < 4; i++)
array[i] = static_cast(val >> ((3 - i) * 8));
}