I'm saving a Cairo::ImageSurface
as part of a binary file. Writing pixels from surface->get_data()
works fine, however it produces a much larger file compared to when I write it with surface->write_to_png(std::string name)
. So I'm trying to use surface->write_to_png_stream(const SlotWriteFunc &write_func);
and Cairo::ImageSurface::create_from_png_stream(const SlotReadFunc &read_func)
;
Initially, the Cairo::Surface::SlotWriteFunc callback made sense to me, it is called several times while writing the surface and provides parameters const unsigned char*
as data and unsigned int
as length, but Cairo::Surface::SlotReadFunc confuses me, as it also provides an unsigned int
that determines how much data it wants to read. Why isn't it a unsigned int&
, so that I can tell the length based on the file? This leads me to a conclusion that I don't understand the SlotWriteFunc
neither.
Please explain how to use SlotWriteFunc
and SlotReadFunc
.
Thank you for your help.
The following example writes to the file fine, but throws an error where I call create_from_png_stream
:
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Aborted
#include <fstream>
#include <iostream>
#include <cairomm/cairomm.h>
class PNGStream {
public:
std::fstream *file;
Cairo::ErrorStatus write(const unsigned char *data, unsigned int length) {
std::cout << length << std::endl;
file->write(reinterpret_cast<char *>(&length), sizeof(length));
file->write(reinterpret_cast<const char*>(data), length);
return Cairo::ErrorStatus::CAIRO_STATUS_SUCCESS;
}
Cairo::ErrorStatus read(unsigned char *data, unsigned int length) {
std::cout << length << std::endl;
// I'm not sure, what comes here
return Cairo::ErrorStatus::CAIRO_STATUS_SUCCESS;
}
};
int main() {
std::fstream file;
PNGStream png_stream;
png_stream.file = &file;
auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, 1920, 1080);
auto cr = Cairo::Context::create(surface);
cr->set_source_rgb(0, 0, 0);
cr->paint();
file.open("test.png", std::ios::binary | std::ios::out);
surface->write_to_png_stream(sigc::mem_fun(png_stream, &PNGStream::write));
file.close();
file.open("test.png", std::ios::binary | std::ios::in);
auto surface2 = Cairo::ImageSurface::create_from_png_stream(sigc::mem_fun(png_stream, &PNGStream::read)); //error
file.close();
return 0;
}
PNG files have a known format, it consists of chunks, with each chunk prefixed by its length, cairo can read it chunk by chunk, until the read fails indicating the end of file.
As for the code you write, you should just read length
bytes from the stream, if the read was unsuccessful then the file stream will have the error flag set.
Cairo::ErrorStatus read(unsigned char *data, unsigned int length) {
std::cout << length << std::endl;
file->read(reinterpret_cast<char*>(data), length);
if (file->fail()) // read failed
{
return Cairo::ErrorStatus::CAIRO_STATUS_READ_ERROR;
}
return Cairo::ErrorStatus::CAIRO_STATUS_SUCCESS;
}
This will work if you have a file stream of size exactly equal to that of a png, but if you have anything else "packed" in the same file (like embedded resources) then you need to count how many bytes were read and manually limit the bytes read, ie: fail if more bytes are requested.
the writer side should just write instead of read
Cairo::ErrorStatus write(const unsigned char* data, unsigned int length) {
std::cout << length << std::endl;
file->write(reinterpret_cast<const char*>(data), length);
if (file->fail()) // write failed
{
return Cairo::ErrorStatus::CAIRO_STATUS_WRITE_ERROR;
}
return Cairo::ErrorStatus::CAIRO_STATUS_SUCCESS;
}