57 int width,
int height,
const std::vector<uint8_t>& indices,
58 const std::vector<std::array<uint8_t, 4>>& palette) {
59 if (width <= 0 || height <= 0 ||
60 indices.size() !=
static_cast<size_t>(width) * height) {
61 return absl::InvalidArgumentError(
"PNG size does not match pixel count");
63 if (palette.empty() || palette.size() > 256) {
64 return absl::InvalidArgumentError(
"PNG palette needs 1-256 entries");
66 for (uint8_t index : indices) {
67 if (index >= palette.size()) {
68 return absl::InvalidArgumentError(
69 absl::StrFormat(
"Pixel index %d is past the %zu-entry palette", index,
74 std::vector<uint8_t> raw;
75 raw.reserve(
static_cast<size_t>(height) * (width + 1));
76 for (
int y = 0; y < height; ++y) {
78 raw.insert(raw.end(), indices.begin() + y * width,
79 indices.begin() + (y + 1) * width);
81 mz_ulong compressed_size = mz_compressBound(raw.size());
82 std::vector<uint8_t> compressed(compressed_size);
83 if (mz_compress2(compressed.data(), &compressed_size, raw.data(), raw.size(),
84 MZ_BEST_COMPRESSION) != MZ_OK) {
85 return absl::InternalError(
"PNG deflate failed");
87 compressed.resize(compressed_size);
89 std::vector<uint8_t> out(kSignature, kSignature + 8);
90 std::vector<uint8_t> header;
91 PutU32(header,
static_cast<uint32_t
>(width));
92 PutU32(header,
static_cast<uint32_t
>(height));
93 header.insert(header.end(), {8, 3, 0, 0, 0});
94 PutChunk(out,
"IHDR", header);
95 std::vector<uint8_t> plte;
96 std::vector<uint8_t> trns;
97 bool any_alpha =
false;
98 for (
const auto& entry : palette) {
99 plte.insert(plte.end(), {entry[0], entry[1], entry[2]});
100 trns.push_back(entry[3]);
101 any_alpha |= entry[3] != 255;
103 PutChunk(out,
"PLTE", plte);
105 PutChunk(out,
"tRNS", trns);
107 PutChunk(out,
"IDAT", compressed);
108 PutChunk(out,
"IEND", {});
112absl::StatusOr<PngImage>
DecodePng(
const std::vector<uint8_t>& bytes) {
113 if (bytes.size() < 8 || std::memcmp(bytes.data(), kSignature, 8) != 0) {
114 return absl::InvalidArgumentError(
"Not a PNG file");
119 std::vector<uint8_t> idat;
120 std::vector<uint8_t> trns;
122 bool seen_end =
false;
123 while (pos + 12 <= bytes.size()) {
124 const uint32_t length = GetU32(&bytes[pos]);
125 if (length > bytes.size() - pos - 12) {
126 return absl::InvalidArgumentError(
"Truncated PNG chunk");
128 const std::string type(
reinterpret_cast<const char*
>(&bytes[pos + 4]), 4);
129 const uint8_t* data = &bytes[pos + 8];
130 const mz_ulong crc = mz_crc32(MZ_CRC32_INIT, &bytes[pos + 4], length + 4);
131 if (crc != GetU32(data + length)) {
132 return absl::InvalidArgumentError(
"PNG chunk " + type +
" fails CRC");
134 if (type ==
"IHDR") {
136 return absl::InvalidArgumentError(
"Bad PNG header");
138 image.
width =
static_cast<int>(GetU32(data));
139 image.
height =
static_cast<int>(GetU32(data + 4));
141 color_type = data[9];
143 return absl::UnimplementedError(
144 "Interlaced PNGs are not supported; save without interlacing");
146 }
else if (type ==
"PLTE") {
147 for (uint32_t i = 0; i + 2 < length; i += 3) {
148 image.
palette.push_back({data[i], data[i + 1], data[i + 2], 255});
150 }
else if (type ==
"tRNS") {
151 trns.assign(data, data + length);
152 }
else if (type ==
"IDAT") {
153 idat.insert(idat.end(), data, data + length);
154 }
else if (type ==
"IEND") {
160 if (!seen_end || image.
width <= 0 || image.
height <= 0) {
161 return absl::InvalidArgumentError(
"Incomplete PNG");
163 if (
static_cast<uint64_t
>(image.
width) * image.
height > kMaxPixels) {
164 return absl::InvalidArgumentError(
"PNG is too large");
168 switch (color_type) {
185 return absl::InvalidArgumentError(
"Unknown PNG color type");
187 const bool indexed = color_type == 3;
188 if (indexed ? (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 &&
191 return absl::UnimplementedError(
192 absl::StrFormat(
"PNG bit depth %d is not supported for color type %d",
193 bit_depth, color_type));
195 if (indexed && image.
palette.empty()) {
196 return absl::InvalidArgumentError(
"Indexed PNG has no palette");
198 for (
size_t i = 0; i < trns.size() && i < image.
palette.size(); ++i) {
202 const size_t bits_per_pixel =
static_cast<size_t>(bit_depth) * channels;
203 const size_t row_bytes = (image.
width * bits_per_pixel + 7) / 8;
204 const size_t pixel_bytes = std::max<size_t>(1, bits_per_pixel / 8);
205 mz_ulong raw_size =
static_cast<mz_ulong
>((row_bytes + 1) * image.
height);
206 std::vector<uint8_t> raw(raw_size);
207 if (mz_uncompress(raw.data(), &raw_size, idat.data(), idat.size()) != MZ_OK ||
208 raw_size != raw.size()) {
209 return absl::InvalidArgumentError(
"PNG image data does not inflate");
212 std::vector<uint8_t> previous(row_bytes, 0);
213 std::vector<uint8_t> row(row_bytes);
218 image.
rgba.resize(
static_cast<size_t>(image.
width) * image.
height * 4);
220 for (
int y = 0; y < image.
height; ++y) {
221 const uint8_t* line = &raw[y * (row_bytes + 1)];
222 const uint8_t filter = line[0];
223 for (
size_t x = 0; x < row_bytes; ++x) {
224 const uint8_t a = x >= pixel_bytes ? row[x - pixel_bytes] : 0;
225 const uint8_t b = previous[x];
226 const uint8_t c = x >= pixel_bytes ? previous[x - pixel_bytes] : 0;
227 uint8_t value = line[1 + x];
238 value +=
static_cast<uint8_t
>((int(a) + int(b)) / 2);
241 value += Paeth(a, b, c);
244 return absl::InvalidArgumentError(
"Unknown PNG row filter");
248 for (
int x = 0; x < image.
width; ++x) {
249 const size_t out =
static_cast<size_t>(y) * image.
width + x;
251 const size_t bit =
static_cast<size_t>(x) * bit_depth;
252 const uint8_t packed = row[bit / 8];
253 const int shift = 8 - bit_depth -
static_cast<int>(bit % 8);
255 static_cast<uint8_t
>((packed >> shift) & ((1 << bit_depth) - 1));
258 const uint8_t* p = &row[
static_cast<size_t>(x) * channels];
259 uint8_t* dst = &image.
rgba[out * 4];
260 switch (color_type) {
262 dst[0] = dst[1] = dst[2] = p[0];
266 dst[0] = dst[1] = dst[2] = p[0];
276 std::memcpy(dst, p, 4);
280 std::swap(previous, row);