Search code examples
c++pngcairo

What to do when cairo asks to read more data than I have when creating and ImageSurface from png?


I'm using cairomm version 1.12.0 with cairo version 1.14.6.

I'm trying to create an ImageSurface in cairo from a png which I have stored in memory as a vector of bytes. Most of the time this is working, but sometimes Cairo passes my read function (the lambda) a length to read (length parameter) greater than what's left in the vector. When I'm debugging this in visual studio of course I get a failed debug assertion Expression: cannot seek vector iterator after end

Cairo::RefPtr<Cairo::ImageSurface> someFunc(const std::vector<BYTE>& src)
{
  Cairo::RefPtr<Cairo::ImageSurface> ret;
  if(src.size() > 0)
  {
    unsigned int read = 0;
    ret = Cairo::ImageSurface::create_from_png_stream([&src, &read](unsigned char* data, unsigned int length) {
      std::copy_n(src.begin() + read, length, data);
      read += length;
      return CAIRO_STATUS_SUCCESS;
    });
  }
  return ret;
}

If I try modifying my read function to copy the minimum of the remaining data and the length Cairo has asked to read, I'm getting CAIRO_STATUS_NO_MEMORY, which causes a std::bad_alloc exception to be thrown.

Cairo::RefPtr<Cairo::ImageSurface> someFunc(const std::vector<BYTE>& src)
{
  Cairo::RefPtr<Cairo::ImageSurface> ret;
  if(src.size() > 0)
  {
    unsigned int read = 0;
    ret = Cairo::ImageSurface::create_from_png_stream([&src, &read](unsigned char* data, unsigned int length) {
      length = std::min((unsigned int)src.size() - read, length); // added
      std::copy_n(src.begin() + read, length, data);
      read += length;
      return CAIRO_STATUS_SUCCESS;
    });
  }
  return ret;
}

I think this has something to do with the call to _cairo_output_stream_write in stream_read_func in cairo-png.c, as it passes it the same size as it passes my read func, so it is likely expecting some data to be there.

...
status = png_closure->read_func (png_closure->closure, data, size);
...
_cairo_output_stream_write (png_closure->png_data, data, size);

Should I be writing null or something to pad the difference in bytes?


Solution

  • If cairo asks for more data than what you have, your PNG is broken. Cairo asks for as many bytes as libpng asks for and supposedly libpng Knows What It Is Doing (tm) and asks for the right amount of bytes. If you do not have enough bytes, you should return CAIRO_STATUS_READ_ERROR from your read function.