9

I have code which generates a Cairo ImageSurface, and I expose it like so:

def preview(...):
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
    ...
    cherrypy.response.headers['Content-Type'] = "image/png"
    return surface.get_data()
preview.exposed = True

This doesn't work (browsers report that the image has errors).

I've tested that surface.write_to_png('test.png') works, but I'm not sure what to dump the data into to return it. I'm guessing some file-like object? According to the pycairo documentation, get_data() returns a buffer. I've also now tried:

tempf = os.tmpfile()
surface.write_to_png(tempf)
return tempf

Also, is it better to create and hold this image in memory (like I'm trying to do) or write it to disk as a temp file and serve it from there? I only need the image once, then it can be discarded.

colinmarc
  • 2,421
  • 1
  • 22
  • 34
  • How about `write_to_png_stream`? http://cairographics.org/documentation/cairomm/reference/classCairo_1_1Surface.html#b3eca5bc13abe27f470fdf08134269bb – Pekka Jun 17 '10 at 18:18
  • pycairo doesn't seem to expose that method... http://cairographics.org/documentation/pycairo/2/reference/surfaces.html#class-imagesurface-surface – colinmarc Jun 17 '10 at 18:23

4 Answers4

18

Add these imports:

from cherrypy.lib import file_generator
import StringIO

and then go like this:

def index(self):
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
    cherrypy.response.headers['Content-Type'] = "image/png"

    buffer = StringIO.StringIO()
    surface.write_to_png(buffer)
    buffer.seek(0)

    return file_generator(buffer)

Additionaly, if you're serving standalone file (i.e. it's not a part of a web page) and you don't want it to be rendered into browser but rather treated as a file to save on a disk then you need one more header:

cherrypy.response.headers['Content-Disposition'] = 'attachment; filename="file.png"'

Also, is it better to create and hold this image in memory (like I'm trying to do) or write it to disk as a temp file and serve it from there? I only need the image once, then it can be discarded.

If the only thing you want to do is to serve this file to a browser there is no reason to create it on a disk on the server. Quite the contrary - remember that accessing hard disk brings performance penalty.

zifot
  • 2,688
  • 20
  • 21
  • Works great! For the bounty, could you explain to me why? – colinmarc Jun 21 '10 at 04:57
  • Great answer, zifot - I think the key is using "write_to_png" which converts the raw data to PNG format. Just using surface.get_data() won't work because it's not stored internally as PNG data (it's ARGB_32). – Marc Novakowski Jun 21 '10 at 05:01
  • @Marc Novakowski - Thanks. You're right, ImageSurface holds data in memory in one of the formats described here: http://cairographics.org/manual/cairo-image-surface.html#cairo-format-t. Format of output file is a different story. – zifot Jun 21 '10 at 17:01
  • @colinmarc - AFAIK if you question has a bounty then both awarding it and accepting the answer (which are now separate actions) are possible no sooner than two days after creating a bounty. – zifot Jun 21 '10 at 17:07
  • Thanks a bunch, I was really stuck for a while there. =) – colinmarc Jun 21 '10 at 17:13
2

You're failing because of not understand the working of surface.get_data(). You are trying to return mime-type image/png but surface.get_data() returns plain bitmap image (is not a Windows Bitmap file .BMP with header) which is plain image dump from "virtual screen" (surface)

Like this:

0000010000
0000101000
0001000100
0010000010
0001000100
0000101000
0000010000
Mike Scotty
  • 10,530
  • 5
  • 38
  • 50
Vitold S.
  • 402
  • 4
  • 13
0

Have you tried return str(surface.get_data())?

fumanchu
  • 14,419
  • 6
  • 31
  • 36
0

Try this for 'file in memory' approach

return StringIO.StringIO(surface.get_data())
estin
  • 3,051
  • 1
  • 24
  • 31