summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/png.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/util/png.cpp')
-rw-r--r--src/lib/util/png.cpp732
1 files changed, 372 insertions, 360 deletions
diff --git a/src/lib/util/png.cpp b/src/lib/util/png.cpp
index 9069348c843..17574481aea 100644
--- a/src/lib/util/png.cpp
+++ b/src/lib/util/png.cpp
@@ -8,14 +8,19 @@
***************************************************************************/
-#include <math.h>
-#include <stdlib.h>
-#include <assert.h>
+#include "png.h"
#include <zlib.h>
-#include "png.h"
+#include <cstdint>
+#include <list>
+#include <memory>
#include <new>
+#include <utility>
+
+#include <math.h>
+#include <stdlib.h>
+#include <assert.h>
/***************************************************************************
@@ -24,19 +29,10 @@
struct image_data_chunk
{
- image_data_chunk * next;
- int length;
- uint8_t * data;
-};
-
+ image_data_chunk(std::uint32_t l, std::unique_ptr<std::uint8_t []> &&d) : length(l), data(std::move(d)) { }
-struct png_private
-{
- png_info * pnginfo;
- image_data_chunk * idata;
- image_data_chunk ** idata_next;
- uint8_t bpp;
- uint32_t rowbytes;
+ std::uint32_t length = 0;
+ std::unique_ptr<std::uint8_t []> data;
};
@@ -91,15 +87,9 @@ static inline void put_32bit(uint8_t *v, uint32_t data)
}
-static inline int compute_bpp(const png_info *pnginfo)
+static inline int compute_rowbytes(const png_info &pnginfo)
{
- return samples[pnginfo->color_type] * pnginfo->bit_depth / 8;
-}
-
-
-static inline int compute_rowbytes(const png_info *pnginfo)
-{
- return (pnginfo->width * samples[pnginfo->color_type] * pnginfo->bit_depth + 7) / 8;
+ return (pnginfo.width * samples[pnginfo.color_type] * pnginfo.bit_depth + 7) / 8;
}
@@ -113,247 +103,374 @@ static inline int compute_rowbytes(const png_info *pnginfo)
pnginfo structure
-------------------------------------------------*/
-void png_free(png_info *pnginfo)
+static void png_free(png_info &pnginfo)
{
- while (pnginfo->textlist != nullptr)
+ while (pnginfo.textlist)
{
- png_text *temp = pnginfo->textlist;
- pnginfo->textlist = temp->next;
- if (temp->keyword != nullptr)
- free((void *)temp->keyword);
+ png_text *const temp = pnginfo.textlist;
+ pnginfo.textlist = temp->next;
+ if (temp->keyword)
+ delete[] (std::uint8_t *)temp->keyword;
free(temp);
}
- if (pnginfo->palette != nullptr)
- free(pnginfo->palette);
- pnginfo->palette = nullptr;
+ if (pnginfo.palette)
+ delete[] pnginfo.palette;
+ pnginfo.palette = nullptr;
- if (pnginfo->trans != nullptr)
- free(pnginfo->trans);
- pnginfo->trans = nullptr;
+ if (pnginfo.trans)
+ delete[] pnginfo.trans;
+ pnginfo.trans = nullptr;
- if (pnginfo->image != nullptr)
- free(pnginfo->image);
- pnginfo->image = nullptr;
+ if (pnginfo.image)
+ delete[] pnginfo.image;
+ pnginfo.image = nullptr;
}
+void png_free(png_info *pnginfo) { png_free(*pnginfo); } // TODO: make external interface use reference
-/***************************************************************************
- PNG READING FUNCTIONS
-***************************************************************************/
-/*-------------------------------------------------
- verify_header - verify the PNG
- header at the current file location
--------------------------------------------------*/
+namespace {
-static png_error verify_header(util::core_file &fp)
+class png_private
{
- uint8_t signature[8];
+public:
+ png_private(png_info &info) : pnginfo(info)
+ {
+ }
- /* read 8 bytes */
- if (fp.read(signature, 8) != 8)
- return PNGERR_FILE_TRUNCATED;
+ png_error expand_buffer_8bit()
+ {
+ /* nothing to do if we're at 8 or greater already */
+ if (pnginfo.bit_depth >= 8)
+ return PNGERR_NONE;
+
+ /* calculate the offset for each pass of the interlace on the input and output */
+ unsigned const pass_count(get_pass_count());
+ std::uint32_t inp_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ std::uint32_t outp_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ for (unsigned pass = 0; pass_count > pass; ++pass)
+ {
+ inp_offset[pass + 1] = inp_offset[pass] + get_pass_bytes(pass);
+ outp_offset[pass + 1] = outp_offset[pass] + get_pass_bytes(pass, 8);
+ }
- /* return an error if we don't match */
- if (memcmp(signature, PNG_Signature, 8) != 0)
- return PNGERR_BAD_SIGNATURE;
+ /* allocate a new buffer at 8-bit */
+ std::unique_ptr<std::uint8_t []> outbuf;
+ try { outbuf.reset(new std::uint8_t [outp_offset[pass_count]]); }
+ catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
- return PNGERR_NONE;
-}
+ for (unsigned pass = 0; pass_count > pass; ++pass)
+ {
+ std::pair<std::uint32_t, std::uint32_t> const dimensions(get_pass_dimensions(pass));
+ std::uint8_t const *inp(&pnginfo.image[inp_offset[pass]]);
+ std::uint8_t *outp(&outbuf[outp_offset[pass]]);
+ for (std::uint32_t y = 0; dimensions.second > y; ++y)
+ {
+ for (std::uint32_t i = 0; (dimensions.first / (8 / pnginfo.bit_depth)); ++i, ++inp)
+ {
+ for (std::int8_t j = (8 / pnginfo.bit_depth) - 1; 0 <= j; --j)
+ *outp++ = (*inp >> (j * pnginfo.bit_depth)) & (0xffU >> (8 - pnginfo.bit_depth));
+ }
+ if (pnginfo.width % (8 / pnginfo.bit_depth))
+ {
+ for (std::int8_t j = (pnginfo.width % (8 / pnginfo.bit_depth)) - 1; 0 <= j; --j)
+ *outp++ = (*inp >> (j * pnginfo.bit_depth)) & (0xffU >> (8 - pnginfo.bit_depth));
+ inp++;
+ }
+ }
+ }
-/*-------------------------------------------------
- read_chunk - read the next PNG chunk
--------------------------------------------------*/
+ delete[] pnginfo.image;
+ pnginfo.image = outbuf.release();
+ pnginfo.bit_depth = 8;
+ return PNGERR_NONE;
+ }
-static png_error read_chunk(util::core_file &fp, uint8_t **data, uint32_t *type, uint32_t *length)
-{
- uint32_t crc, chunk_crc;
- uint8_t tempbuff[4];
+ png_error read_file(util::core_file &fp)
+ {
+ /* initialize the data structures */
+ png_error error = PNGERR_NONE;
+ std::memset(&pnginfo, 0, sizeof(pnginfo));
- /* fetch the length of this chunk */
- if (fp.read(tempbuff, 4) != 4)
- return PNGERR_FILE_TRUNCATED;
- *length = fetch_32bit(tempbuff);
+ /* verify the signature at the start of the file */
+ error = verify_header(fp);
+ if (error != PNGERR_NONE)
+ goto handle_error;
- /* fetch the type of this chunk */
- if (fp.read(tempbuff, 4) != 4)
- return PNGERR_FILE_TRUNCATED;
- *type = fetch_32bit(tempbuff);
+ /* loop until we hit an IEND chunk */
+ for ( ; ; )
+ {
+ /* read a chunk */
+ std::unique_ptr<std::uint8_t []> chunk_data;
+ std::uint32_t chunk_type, chunk_length;
+ error = read_chunk(fp, chunk_data, chunk_type, chunk_length);
+ if (error != PNGERR_NONE)
+ goto handle_error;
+
+ /* stop when we hit an IEND chunk */
+ if (chunk_type == PNG_CN_IEND)
+ break;
+
+ /* process the chunk */
+ error = process_chunk(std::move(chunk_data), chunk_type, chunk_length);
+ if (error != PNGERR_NONE)
+ goto handle_error;
+ }
- /* stop when we hit an IEND chunk */
- if (*type == PNG_CN_IEND)
- return PNGERR_NONE;
+ /* finish processing the image */
+ error = process();
- /* start the CRC with the chunk type (but not the length) */
- crc = crc32(0, tempbuff, 4);
+ handle_error:
+ /* no need for the intermediate data any more */
+ idata.clear();
- /* read the chunk itself into an allocated memory buffer */
- *data = nullptr;
- if (*length != 0)
+ /* if we have an error, free all the other data as well */
+ if (error != PNGERR_NONE)
+ {
+ png_free(pnginfo);
+ memset(&pnginfo, 0, sizeof(pnginfo));
+ }
+ return error;
+ }
+
+ png_info & pnginfo;
+
+private:
+ png_error process()
{
- /* allocate memory for this chunk */
- *data = (uint8_t *)malloc(*length);
- if (*data == nullptr)
- return PNGERR_OUT_OF_MEMORY;
+ png_error error = PNGERR_NONE;
+
+ /* calculate the offset for each pass of the interlace */
+ unsigned const pass_count(get_pass_count());
+ std::uint32_t pass_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ for (unsigned pass = 0; pass_count > pass; ++pass)
+ pass_offset[pass + 1] = pass_offset[pass] + get_pass_bytes(pass);
- /* read the data from the file */
- if (fp.read(*data, *length) != *length)
+ /* allocate memory for the filtered image */
+ try { pnginfo.image = new std::uint8_t [pass_offset[pass_count]]; }
+ catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
+
+ /* decompress image data */
+ error = decompress(pass_offset[pass_count]);
+ for (unsigned pass = 0; (pass_count > pass) && (PNGERR_NONE == error); ++pass)
{
- free(*data);
- *data = nullptr;
- return PNGERR_FILE_TRUNCATED;
+ /* compute some basic parameters */
+ std::pair<std::uint32_t, std::uint32_t> const dimensions(get_pass_dimensions(pass));
+ std::uint32_t const bpp(get_bytes_per_pixel());
+ std::uint32_t const rowbytes(get_row_bytes(dimensions.first));
+
+ /* we de-filter in place */
+ uint8_t *dst = pnginfo.image + pass_offset[pass];
+ uint8_t const *src = dst;
+
+ /* iterate over rows */
+ for (std::uint32_t y = 0; (dimensions.second > y) && (PNGERR_NONE == error); ++y)
+ {
+ /* first byte of each row is the filter type */
+ uint8_t const filter(*src++);
+ error = unfilter_row(filter, src, dst, y ? &dst[-rowbytes] : nullptr, bpp, rowbytes);
+ src += rowbytes;
+ dst += rowbytes;
+ }
}
- /* update the CRC */
- crc = crc32(crc, *data, *length);
+ /* if we errored, free the image data */
+ if (error != PNGERR_NONE)
+ {
+ delete[] pnginfo.image;
+ pnginfo.image = nullptr;
+ }
+ return error;
}
- /* read the CRC */
- if (fp.read(tempbuff, 4) != 4)
+ png_error decompress(std::uint32_t expected)
{
- free(*data);
- *data = nullptr;
- return PNGERR_FILE_TRUNCATED;
- }
- chunk_crc = fetch_32bit(tempbuff);
+ /* only deflate is permitted */
+ if (pnginfo.compression_method != 0)
+ return PNGERR_DECOMPRESS_ERROR;
+
+ /* allocate zlib stream */
+ z_stream stream;
+ int zerr;
+ std::memset(&stream, 0, sizeof(stream));
+ stream.zalloc = Z_NULL;
+ stream.zfree = Z_NULL;
+ stream.opaque = Z_NULL;
+ stream.avail_in = 0;
+ stream.next_in = Z_NULL;
+ zerr = inflateInit(&stream);
+ if (Z_OK != zerr)
+ return PNGERR_DECOMPRESS_ERROR;
+
+ /* decompress IDAT blocks */
+ stream.next_out = pnginfo.image;
+ stream.avail_out = expected;
+ stream.avail_in = 0;
+ std::list<image_data_chunk>::const_iterator it = idata.begin();
+ while ((idata.end() != it) && ((Z_OK == zerr) || (Z_BUF_ERROR == zerr)) && !stream.avail_in)
+ {
+ stream.avail_in = it->length;
+ stream.next_in = it->data.get();
+ do
+ {
+ zerr = inflate(&stream, Z_NO_FLUSH);
+ }
+ while (stream.avail_in && (Z_OK == zerr));
+ if (!stream.avail_in)
+ ++it;
+ }
- /* validate the CRC */
- if (crc != chunk_crc)
- {
- free(*data);
- *data = nullptr;
- return PNGERR_FILE_CORRUPT;
+ /* it's all good if we got end-of-stream or we have with no data remaining */
+ if ((Z_OK == inflateEnd(&stream)) && ((Z_STREAM_END == zerr) || ((Z_OK == zerr) && (idata.end() == it) && !stream.avail_in)))
+ return PNGERR_NONE;
+ else
+ return PNGERR_DECOMPRESS_ERROR;
}
- return PNGERR_NONE;
-}
-
-/*-------------------------------------------------
- process_chunk - process a PNG chunk
--------------------------------------------------*/
-
-static png_error process_chunk(png_private *png, uint8_t *data, uint32_t type, uint32_t length, bool *keepmem)
-{
- /* default to not keeping memory */
- *keepmem = false;
-
- /* switch off of the type */
- switch (type)
+ png_error process_chunk(std::unique_ptr<std::uint8_t []> &&data, uint32_t type, uint32_t length)
{
+ /* switch off of the type */
+ switch (type)
+ {
/* image header */
case PNG_CN_IHDR:
- png->pnginfo->width = fetch_32bit(data);
- png->pnginfo->height = fetch_32bit(data + 4);
- png->pnginfo->bit_depth = fetch_8bit(data + 8);
- png->pnginfo->color_type = fetch_8bit(data + 9);
- png->pnginfo->compression_method = fetch_8bit(data + 10);
- png->pnginfo->filter_method = fetch_8bit(data + 11);
- png->pnginfo->interlace_method = fetch_8bit(data + 12);
+ pnginfo.width = fetch_32bit(&data[0]);
+ pnginfo.height = fetch_32bit(&data[4]);
+ pnginfo.bit_depth = fetch_8bit(&data[8]);
+ pnginfo.color_type = fetch_8bit(&data[9]);
+ pnginfo.compression_method = fetch_8bit(&data[10]);
+ pnginfo.filter_method = fetch_8bit(&data[11]);
+ pnginfo.interlace_method = fetch_8bit(&data[12]);
break;
/* palette */
case PNG_CN_PLTE:
- png->pnginfo->num_palette = length / 3;
- png->pnginfo->palette = data;
- *keepmem = true;
+ pnginfo.num_palette = length / 3;
+ pnginfo.palette = data.release();
break;
/* transparency information */
case PNG_CN_tRNS:
- png->pnginfo->num_trans = length;
- png->pnginfo->trans = data;
- *keepmem = true;
+ pnginfo.num_trans = length;
+ pnginfo.trans = data.release();
break;
/* image data */
case PNG_CN_IDAT:
- /* allocate a new image data descriptor */
- *png->idata_next = (image_data_chunk *)malloc(sizeof(**png->idata_next));
- if (*png->idata_next == nullptr)
- return PNGERR_OUT_OF_MEMORY;
-
- /* add it to the tail of the list */
- (*png->idata_next)->next = nullptr;
- (*png->idata_next)->length = length;
- (*png->idata_next)->data = data;
- png->idata_next = &(*png->idata_next)->next;
- *keepmem = true;
+ /* allocate a new image data descriptor and add it to the tail of the list */
+ try { idata.emplace_back(length, std::move(data)); }
+ catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
+
break;
/* gamma */
case PNG_CN_gAMA:
- png->pnginfo->source_gamma = fetch_32bit(data) / 100000.0;
+ pnginfo.source_gamma = fetch_32bit(data.get()) / 100000.0;
break;
/* physical information */
case PNG_CN_pHYs:
- png->pnginfo->xres = fetch_32bit(data);
- png->pnginfo->yres = fetch_32bit(data + 4);
- png->pnginfo->resolution_unit = fetch_8bit(data + 8);
+ pnginfo.xres = fetch_32bit(&data[0]);
+ pnginfo.yres = fetch_32bit(&data[4]);
+ pnginfo.resolution_unit = fetch_8bit(&data[8]);
break;
/* text */
case PNG_CN_tEXt:
- {
- png_text *text, *pt, *ct;
-
- /* allocate a new text item */
- text = (png_text *)malloc(sizeof(*text));
- if (text == nullptr)
- return PNGERR_OUT_OF_MEMORY;
-
- /* set the elements */
- text->keyword = (char *)data;
- text->text = text->keyword + strlen(text->keyword) + 1;
- text->next = nullptr;
-
- /* add to the end of the list */
- for (pt = nullptr, ct = png->pnginfo->textlist; ct != nullptr; pt = ct, ct = ct->next) { }
- if (pt == nullptr)
- png->pnginfo->textlist = text;
- else
- pt->next = text;
+ {
+ /* allocate a new text item */
+ png_text *const text = (png_text *)malloc(sizeof(*text));
+ if (!text)
+ return PNGERR_OUT_OF_MEMORY;
+
+ /* set the elements */
+ text->keyword = (char *)data.release();
+ text->text = text->keyword + strlen(text->keyword) + 1;
+ text->next = nullptr;
+
+ /* add to the end of the list */
+ png_text *pt, *ct;
+ for (pt = nullptr, ct = pnginfo.textlist; ct != nullptr; pt = ct, ct = ct->next) { }
+ if (pt == nullptr)
+ pnginfo.textlist = text;
+ else
+ pt->next = text;
- *keepmem = true;
- break;
- }
+ break;
+ }
/* anything else */
default:
if ((type & 0x20000000) == 0)
return PNGERR_UNKNOWN_CHUNK;
break;
+ }
+ return PNGERR_NONE;
}
- return PNGERR_NONE;
-}
+ unsigned get_pass_count() const
+ {
+ return (1 == pnginfo.interlace_method) ? 7 : 1;
+ }
-/*-------------------------------------------------
- unfilter_row - unfilter a single row of pixels
--------------------------------------------------*/
+ std::pair<uint32_t, uint32_t> get_pass_dimensions(unsigned pass) const
+ {
+ static constexpr unsigned x_bias[7] = { 7, 3, 3, 1, 1, 0, 0 };
+ static constexpr unsigned y_bias[7] = { 7, 7, 3, 3, 1, 1, 0 };
+ static constexpr unsigned x_shift[7] = { 3, 3, 2, 2, 1, 1, 0 };
+ static constexpr unsigned y_shift[7] = { 3, 3, 3, 2, 2, 1, 1 };
+ if (0 == pnginfo.interlace_method)
+ return std::make_pair(pnginfo.width, pnginfo.height);
+ else
+ return std::make_pair((pnginfo.width + x_bias[pass]) >> x_shift[pass], (pnginfo.height + y_bias[pass]) >> y_shift[pass]);
+ }
-static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dstprev, int bpp, int rowbytes)
-{
- int x;
+ std::uint32_t get_pass_bytes(unsigned pass) const
+ {
+ return get_pass_bytes(pass, pnginfo.bit_depth);
+ }
+
+ std::uint32_t get_pass_bytes(unsigned pass, uint8_t bit_depth) const
+ {
+ std::pair<std::uint32_t, std::uint32_t> const dimensions(get_pass_dimensions(pass));
+ return (get_row_bytes(dimensions.first, bit_depth) + 1) * dimensions.second;
+ }
+
+ std::uint32_t get_row_bytes(std::uint32_t width) const
+ {
+ return get_row_bytes(width, pnginfo.bit_depth);
+ }
+
+ std::uint32_t get_row_bytes(std::uint32_t width, uint8_t bit_depth) const
+ {
+ return ((width * samples[pnginfo.color_type] * bit_depth) + 7) >> 3;
+ }
- /* switch off of it */
- switch (type)
+ std::uint32_t get_bytes_per_pixel() const
{
+ return (samples[pnginfo.color_type] * pnginfo.bit_depth) / 8; // FIXME: won't work for 4bpp greyscale etc.
+ }
+
+ static png_error unfilter_row(int type, uint8_t const *src, uint8_t *dst, uint8_t const *dstprev, int bpp, int rowbytes)
+ {
+ /* switch off of it */
+ switch (type)
+ {
/* no filter, just copy */
case PNG_PF_None:
- for (x = 0; x < rowbytes; x++)
+ for (int x = 0; x < rowbytes; x++)
*dst++ = *src++;
break;
/* SUB = previous pixel */
case PNG_PF_Sub:
- for (x = 0; x < bpp; x++)
+ for (int x = 0; x < bpp; x++)
*dst++ = *src++;
- for (x = bpp; x < rowbytes; x++, dst++)
+ for (int x = bpp; x < rowbytes; x++, dst++)
*dst = *src++ + dst[-bpp];
break;
@@ -361,7 +478,7 @@ static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dst
case PNG_PF_Up:
if (dstprev == nullptr)
return unfilter_row(PNG_PF_None, src, dst, dstprev, bpp, rowbytes);
- for (x = 0; x < rowbytes; x++, dst++)
+ for (int x = 0; x < rowbytes; x++, dst++)
*dst = *src++ + *dstprev++;
break;
@@ -369,23 +486,23 @@ static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dst
case PNG_PF_Average:
if (dstprev == nullptr)
{
- for (x = 0; x < bpp; x++)
+ for (int x = 0; x < bpp; x++)
*dst++ = *src++;
- for (x = bpp; x < rowbytes; x++, dst++)
+ for (int x = bpp; x < rowbytes; x++, dst++)
*dst = *src++ + dst[-bpp] / 2;
}
else
{
- for (x = 0; x < bpp; x++, dst++)
+ for (int x = 0; x < bpp; x++, dst++)
*dst = *src++ + *dstprev++ / 2;
- for (x = bpp; x < rowbytes; x++, dst++)
+ for (int x = bpp; x < rowbytes; x++, dst++)
*dst = *src++ + (*dstprev++ + dst[-bpp]) / 2;
}
break;
/* PAETH = special filter */
case PNG_PF_Paeth:
- for (x = 0; x < rowbytes; x++)
+ for (int x = 0; x < rowbytes; x++)
{
int32_t pa = (x < bpp) ? 0 : dst[-bpp];
int32_t pc = (x < bpp || dstprev == nullptr) ? 0 : dstprev[-bpp];
@@ -406,171 +523,99 @@ static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dst
/* unknown filter type */
default:
return PNGERR_UNKNOWN_FILTER;
- }
-
- return PNGERR_NONE;
-}
-
-
-/*-------------------------------------------------
- process_image - post-process a loaded iamge
--------------------------------------------------*/
-
-static png_error process_image(png_private *png)
-{
- int rowbytes, bpp, imagesize;
- png_error error = PNGERR_NONE;
- image_data_chunk *idat;
- uint8_t *src, *dst;
- z_stream stream;
- int zerr, y;
-
- /* compute some basic parameters */
- bpp = compute_bpp(png->pnginfo);
- rowbytes = compute_rowbytes(png->pnginfo);
- imagesize = png->pnginfo->height * (rowbytes + 1);
-
- /* allocate memory for the filtered image */
- png->pnginfo->image = (uint8_t *)malloc(imagesize);
- if (png->pnginfo->image == nullptr)
- return PNGERR_OUT_OF_MEMORY;
+ }
- /* initialize the stream */
- memset(&stream, 0, sizeof(stream));
- stream.next_out = png->pnginfo->image;
- stream.avail_out = imagesize;
- zerr = inflateInit(&stream);
- if (zerr != Z_OK)
- {
- error = PNGERR_DECOMPRESS_ERROR;
- goto handle_error;
+ return PNGERR_NONE;
}
- /* loop over IDAT and decompress each as part of a larger stream */
- for (idat = png->idata; idat != nullptr; idat = idat->next)
+ static png_error verify_header(util::core_file &fp)
{
- /* decompress this chunk */
- stream.next_in = idat->data;
- stream.avail_in = idat->length;
- zerr = inflate(&stream, Z_NO_FLUSH);
+ uint8_t signature[8];
- /* stop at the end of the stream */
- if (zerr == Z_STREAM_END)
- break;
+ /* read 8 bytes */
+ if (fp.read(signature, 8) != 8)
+ return PNGERR_FILE_TRUNCATED;
- /* other errors are fatal */
- if (zerr != Z_OK)
- {
- error = PNGERR_DECOMPRESS_ERROR;
- goto handle_error;
- }
+ /* return an error if we don't match */
+ if (memcmp(signature, PNG_Signature, 8) != 0)
+ return PNGERR_BAD_SIGNATURE;
+
+ return PNGERR_NONE;
}
- /* clean up */
- zerr = inflateEnd(&stream);
- if (zerr != Z_OK)
+ static png_error read_chunk(util::core_file &fp, std::unique_ptr<std::uint8_t []> &data, std::uint32_t &type, std::uint32_t &length)
{
- error = PNGERR_DECOMPRESS_ERROR;
- goto handle_error;
- }
+ std::uint8_t tempbuff[4];
- /* we de-filter in place */
- src = dst = png->pnginfo->image;
+ /* fetch the length of this chunk */
+ if (fp.read(tempbuff, 4) != 4)
+ return PNGERR_FILE_TRUNCATED;
+ length = fetch_32bit(tempbuff);
- /* iterate over rows */
- for (y = 0; y < png->pnginfo->height && error == PNGERR_NONE; y++)
- {
- /* first byte of each row is the filter type */
- int filter = *src++;
- error = unfilter_row(filter, src, dst, (y == 0) ? nullptr : &dst[-rowbytes], bpp, rowbytes);
- src += rowbytes;
- dst += rowbytes;
- }
+ /* fetch the type of this chunk */
+ if (fp.read(tempbuff, 4) != 4)
+ return PNGERR_FILE_TRUNCATED;
+ type = fetch_32bit(tempbuff);
-handle_error:
- /* if we errored, free the image data */
- if (error != PNGERR_NONE)
- {
- free(png->pnginfo->image);
- png->pnginfo->image = nullptr;
- }
- return error;
-}
+ /* stop when we hit an IEND chunk */
+ if (type == PNG_CN_IEND)
+ return PNGERR_NONE;
+ /* start the CRC with the chunk type (but not the length) */
+ std::uint32_t crc = crc32(0, tempbuff, 4);
-/*-------------------------------------------------
- png_read_file - read a PNG from a core stream
--------------------------------------------------*/
+ /* read the chunk itself into an allocated memory buffer */
+ if (length)
+ {
+ /* allocate memory for this chunk */
+ try { data.reset(new std::uint8_t [length]); }
+ catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
-png_error png_read_file(util::core_file &fp, png_info *pnginfo)
-{
- uint8_t *chunk_data = nullptr;
- png_private png;
- png_error error;
+ /* read the data from the file */
+ if (fp.read(data.get(), length) != length)
+ {
+ data.reset();
+ return PNGERR_FILE_TRUNCATED;
+ }
- /* initialize the data structures */
- memset(&png, 0, sizeof(png));
- memset(pnginfo, 0, sizeof(*pnginfo));
- png.pnginfo = pnginfo;
- png.idata_next = &png.idata;
+ /* update the CRC */
+ crc = crc32(crc, data.get(), length);
+ }
- /* verify the signature at the start of the file */
- error = verify_header(fp);
- if (error != PNGERR_NONE)
- goto handle_error;
+ /* read the CRC */
+ if (fp.read(tempbuff, 4) != 4)
+ {
+ data.reset();
+ return PNGERR_FILE_TRUNCATED;
+ }
+ std::uint32_t const chunk_crc = fetch_32bit(tempbuff);
- /* loop until we hit an IEND chunk */
- for ( ; ; )
- {
- uint32_t chunk_type, chunk_length;
- bool keepmem;
+ /* validate the CRC */
+ if (crc != chunk_crc)
+ {
+ data.reset();
+ return PNGERR_FILE_CORRUPT;
+ }
- /* read a chunk */
- error = read_chunk(fp, &chunk_data, &chunk_type, &chunk_length);
- if (error != PNGERR_NONE)
- goto handle_error;
+ return PNGERR_NONE;
+ }
- /* stop when we hit an IEND chunk */
- if (chunk_type == PNG_CN_IEND)
- break;
+ std::list<image_data_chunk> idata; // FIXME: this could really be a local
+};
- /* process the chunk */
- error = process_chunk(&png, chunk_data, chunk_type, chunk_length, &keepmem);
- if (error != PNGERR_NONE)
- goto handle_error;
+} // anonymous namespace
- /* free memory if we didn't want to keep it */
- if (!keepmem)
- free(chunk_data);
- chunk_data = nullptr;
- }
- /* finish processing the image */
- error = process_image(&png);
- if (error != PNGERR_NONE)
- goto handle_error;
-handle_error:
- /* free all intermediate data */
- while (png.idata != nullptr)
- {
- image_data_chunk *next = png.idata->next;
- if (png.idata->data != nullptr)
- free(png.idata->data);
- free(png.idata);
- png.idata = next;
- }
- if (chunk_data != nullptr)
- free(chunk_data);
+/*-------------------------------------------------
+ png_read_file - read a PNG from a core stream
+-------------------------------------------------*/
- /* if we have an error, free all the other data as well */
- if (error != PNGERR_NONE)
- {
- png_free(pnginfo);
- memset(pnginfo, 0, sizeof(*pnginfo));
- }
- return error;
+png_error png_read_file(util::core_file &fp, png_info *pnginfo)
+{
+ png_private png(*pnginfo);
+ return png.read_file(fp);
}
@@ -656,40 +701,7 @@ png_error png_read_bitmap(util::core_file &fp, bitmap_argb32 &bitmap)
png_error png_expand_buffer_8bit(png_info *pnginfo)
{
- int i,j, k;
- uint8_t *inp, *outp, *outbuf;
-
- /* nothing to do if we're at 8 or greater already */
- if (pnginfo->bit_depth >= 8)
- return PNGERR_NONE;
-
- /* allocate a new buffer at 8-bit */
- outbuf = (uint8_t *)malloc(pnginfo->width * pnginfo->height);
- if (outbuf == nullptr)
- return PNGERR_OUT_OF_MEMORY;
-
- inp = pnginfo->image;
- outp = outbuf;
-
- for (i = 0; i < pnginfo->height; i++)
- {
- for(j = 0; j < pnginfo->width / ( 8 / pnginfo->bit_depth); j++)
- {
- for (k = 8 / pnginfo->bit_depth-1; k >= 0; k--)
- *outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth));
- inp++;
- }
- if (pnginfo->width % (8 / pnginfo->bit_depth))
- {
- for (k = pnginfo->width % (8 / pnginfo->bit_depth)-1; k >= 0; k--)
- *outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth));
- inp++;
- }
- }
- free (pnginfo->image);
- pnginfo->image = outbuf;
-
- return PNGERR_NONE;
+ return png_private(*pnginfo).expand_buffer_8bit();
}
@@ -1041,7 +1053,7 @@ static png_error write_png_stream(util::core_file &fp, png_info *pnginfo, const
goto handle_error;
/* write a single IDAT chunk */
- error = write_deflated_chunk(fp, pnginfo->image, PNG_CN_IDAT, pnginfo->height * (compute_rowbytes(pnginfo) + 1));
+ error = write_deflated_chunk(fp, pnginfo->image, PNG_CN_IDAT, pnginfo->height * (compute_rowbytes(*pnginfo) + 1));
if (error != PNGERR_NONE)
goto handle_error;