Create PDF with (resized) PNG images using Pycairo - rescaling Surface issue
Asked Answered
E

3

2

I have som PNG image links that I want to download, "convert to thumbnails" and save to PDF using Python and Cairo.

Now, I have a working code, but I don't know how to control image size on paper. Is there a way to resize a PyCairo Surface to the dimensions I want (which happens to be smaller than the original)? I want the original pixels to be "shrinked" to a higher resolution (on paper).

Also, I tried Image.rescale() function from PIL, but it gives me back a 20x20 pixel output (out of a 200x200 pixel original image, which is not the banner example on the code). What I want is a 200x200 pixel image plotted inside a 20x20 mm square on paper (instead of a 200x200 mm square as I am getting now)

My current code is:

#!/usr/bin/python

import cairo, urllib, StringIO, Image   # could I do it without Image module?

paper_width = 210
paper_height = 297
margin = 20

point_to_milimeter = 72/25.4

pdfname = "out.pdf" 
pdf = cairo.PDFSurface(pdfname , paper_width*point_to_milimeter, paper_height*point_to_milimeter)
cr = cairo.Context(pdf)
cr.scale(point_to_milimeter, point_to_milimeter)

f=urllib.urlopen("http://cairographics.org/cairo-banner.png")
i=StringIO.StringIO(f.read())
im=Image.open(i)

# are these StringIO operations really necessary?

imagebuffer = StringIO.StringIO()  
im.save(imagebuffer, format="PNG")
imagebuffer.seek(0)
imagesurface = cairo.ImageSurface.create_from_png(imagebuffer)


### EDIT: best answer from Jeremy, and an alternate answer from mine:
best_answer = True      # put false to use my own alternate answer
if best_answer:
    cr.save()
    cr.scale(0.5, 0.5)
    cr.set_source_surface(imagesurface, margin, margin)
    cr.paint()
    cr.restore()
else:
    cr.set_source_surface(imagesurface, margin, margin)
    pattern = cr.get_source()
    scalematrix = cairo.Matrix()    # this can also be used to shear, rotate, etc.
    scalematrix.scale(2,2)          # matrix numbers seem to be the opposite - the greater the number, the smaller the source
    scalematrix.translate(-margin,-margin)  # this is necessary, don't ask me why - negative values!!
    pattern.set_matrix(scalematrix)  
    cr.paint()  

pdf.show_page()

Note that the beautiful Cairo banner does not even fit the page... The ideal result would be that I could control the width and height of this image in user space units (milimeters, in this case), to create a nice header image, for example.

Thanks for reading and for any help or comment!!

Eberhardt answered 17/8, 2011 at 21:19 Comment(0)
R
3

Try scaling the context when you draw the image.

E.g.

cr.save()    # push a new context onto the stack
cr.scale(0.5, 0.5)    # scale the context by (x, y)
cr.set_source_surface(imagesurface, margin, margin)
cr.paint()
cr.restore()    # pop the context

See: http://cairographics.org/documentation/pycairo/2/reference/context.html for more details.

Roddy answered 18/8, 2011 at 4:20 Comment(1)
You got it! I tried that before, but I was setting the imagesurface as source AND ONLY THEN doing thre scaling operation. So, it is necessary to scale FIRST and only then set the source. Thanks!Eberhardt
F
1

This is not answering the question, I just wanted to share heltonbiker's current code edited to run with Python 3.2:

import cairo, urllib.request, io
from PIL import Image

paper_width = 210
paper_height = 297
margin = 20

point_to_millimeter = 72/25.4

pdfname = "out.pdf" 
pdf = cairo.PDFSurface( pdfname, 
                        paper_width*point_to_millimeter, 
                        paper_height*point_to_millimeter
                        )

cr = cairo.Context(pdf)
cr.scale(point_to_millimeter, point_to_millimeter)

# load image
f = urllib.request.urlopen("http://cairographics.org/cairo-banner.png")
i = io.BytesIO(f.read())
im = Image.open(i)
imagebuffer = io.BytesIO()  
im.save(imagebuffer, format="PNG")
imagebuffer.seek(0)
imagesurface = cairo.ImageSurface.create_from_png(imagebuffer)

cr.save()
cr.scale(0.5, 0.5)
cr.set_source_surface(imagesurface, margin, margin)
cr.paint()
cr.restore()

pdf.show_page()
Foldaway answered 29/5, 2013 at 9:34 Comment(0)
E
0

Jeremy Flores solved my problem very well by scaling the target surface before setting the imagesurface as source. Even though, perhaps some day you actually NEED to resize a Surface (or transform it in any way), so I will briefly describe the rationale used in my alternate answer (already included in the question), deduced after thoroughly reading the docs:

  1. Set your surface as the context's source - it implicitly creates a cairo.Pattern!!
  2. Use Context.get_source() to get the pattern back;
  3. Create a cairo.Matrix;
  4. Apply this matrix (with all its transforms) to the pattern;
  5. Paint!

The only problem seems to be the transformations working always around the origin, so that scaling and rotation must be preceeded and followed by complementary translations to the origin (bleargh).

Eberhardt answered 18/8, 2011 at 4:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.