How to resize svg image file using librsvg Python binding - python

When rasterizing svg file, I would like to be able to set width and height for the resulting png file. With the following code, only the canvas is set to the desired width and height, the actual image content with the original svg file dimension is rendered in the top left corner on the (500, 600) canvas.
import cairo
import rsvg
WIDTH, HEIGHT = 500, 600
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
ctx = cairo.Context(surface)
svg = rsvg.Handle(file="test.svg")
svg.render_cairo(ctx)
surface.write_to_png("test.png")
What should I do to make the image content same size with cairo canvas? I tried
svg.set_property('width', 500)
svg.set_property('height', 500)
but got
TypeError: property 'width' is not writable
Also documents for librsvg python binding seem to be extremely rare, only some random code snippets on cairo site.

There is a resize function in librsvg, but it is deprecated.
Set up a scale matrix in Cairo to change the size of your drawing:
setup a scale transformation matrix on your cairo context
draw your SVG with the .render_cairo() method
write your surface to PNG

This is the code that works for me.
It implements the answer by Luper above:
import rsvg
import cairo
# Load the svg data
svg_xml = open('topthree.svg', 'r')
svg = rsvg.Handle()
svg.write(svg_xml.read())
svg.close()
# Prepare the Cairo context
img = cairo.ImageSurface(cairo.FORMAT_ARGB32,
WIDTH,
HEIGHT)
ctx = cairo.Context(img)
# Scale whatever is written into this context
# in this case 2x both x and y directions
ctx.scale(2, 2)
svg.render_cairo(ctx)
# Write out into a PNG file
png_io = StringIO.StringIO()
img.write_to_png(png_io)
with open('sample.png', 'wb') as fout:
fout.write(png_io.getvalue())

Resizing svg files programmatically is not obvious. The solution provided here by other answers may be outdated/difficult to implement. I'm using another library svgutils.
The following should work.
import svgutils.transform as sg
import sys
fig = sg.fromfile('myimage.svg')
fig.set_size(('200','200'))
fig.save('myimage2.svg')
You can install svgutils with usual -
pip install svgutils
Once you have properly resized svg file, you can use ffmpeg or any other image converter to save it into png.

Related

Is there any way to make PIL's ImageGrab.grab() save photos with a small size?

I'm using PIL with Python 3.8 to make screenshots of my screeen but the size of them is too large. How can I reduce it?
You can use Image.resize() to shrink the image prior to saving.
from PIL import Image
im = Image.open("hopper.jpg")
# Provide the target width and height of the image
(width, height) = (im.width // 2, im.height // 2)
im_resized = im.resize((width, height))
source PIL Docs

Can you display an image inside of a python program (without using pygame)?

I want to do something like:
import image
image.display_image('http://upload.wikimedia.org/wikipedia/commons/8/84/Example.svg')
And it would come out as an image.
P.S. I want PNG or JPEG, not GIFs.
This question is somewhat old, but as info on how to do this easily isn't easy to find online, I'm posting this answer in hope it can be useful to others in the future.
To raster a SVG and put it into a ImageTk.PhotoImage object you can do this inside a class used for a tkinter gui:
def svgPhotoImage(self,file_path_name):
import Image,ImageTk,rsvg,cairo
"Returns a ImageTk.PhotoImage object represeting the svg file"
# Based on pygame.org/wiki/CairoPygame and http://bit.ly/1hnpYZY
svg = rsvg.Handle(file=file_path_name)
width, height = svg.get_dimension_data()[:2]
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
context = cairo.Context(surface)
#context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
svg.render_cairo(context)
tk_image=ImageTk.PhotoImage('RGBA')
image=Image.frombuffer('RGBA',(width,height),surface.get_data(),'raw','BGRA',0,1)
tk_image.paste(image)
return(tk_image)
Then display the image on a Frame widget (e.g. mainFrame) this way:
tk_image=self.svgPhotoImage(filename)
mainFrame.configure(image=tk_image)
If you want to save the picture you can return image instead of tk_image and save it this way:
image.save(filename) # Image format is autodected via filename extension

Embed .SVG files into PDF using reportlab

I have written a script in python that produces matplotlib graphs and puts them into a pdf report using reportlab.
I am having difficulty embedding SVG image files into my PDF file. I've had no trouble using PNG images but I want to use SVG format as this produces better quality images in the PDF report.
This is the error message I am getting:
IOError: cannot identify image file
Does anyone have suggestions or have you overcome this issue before?
Yesterday I succeeded in using svglib to add a SVG Image as a reportlab Flowable.
so this drawing is an instance of reportlab Drawing, see here:
from reportlab.graphics.shapes import Drawing
a reportlab Drawing inherits Flowable:
from reportlab.platypus import Flowable
Here is a minimal example that also shows how you can scale it correctly (you must only specify path and factor):
from svglib.svglib import svg2rlg
drawing = svg2rlg(path)
sx = sy = factor
drawing.width, drawing.height = drawing.minWidth() * sx, drawing.height * sy
drawing.scale(sx, sy)
#if you want to see the box around the image
drawing._showBoundary = True
As mentioned by skidzo, you can totally do this with the svglib package, which you can find here: https://pypi.python.org/pypi/svglib/
According to the website, Svglib is a pure-Python library for reading SVG files and converting them (to a reasonable degree) to other formats using the ReportLab Open Source toolkit.
You can use pip to install svglib.
Here is a complete example script:
# svg_demo.py
from reportlab.graphics import renderPDF, renderPM
from reportlab.platypus import SimpleDocTemplate
from svglib.svglib import svg2rlg
def svg_demo(image_path, output_path):
drawing = svg2rlg(image_path)
renderPDF.drawToFile(drawing, output_path)
if __name__ == '__main__':
svg_demo('/path/to/image.svg', 'svg_demo.pdf')
skidzo's answer is very helpful, but isn't a complete example of how to use an SVG file as a flowable in a reportlab PDF. Hopefully this is helpful for others trying to figure out the last few steps:
from io import BytesIO
import matplotlib.pyplot as plt
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import SimpleDocTemplate, Paragraph
from svglib.svglib import svg2rlg
def plot_data(data):
# Plot the data using matplotlib.
plt.plot(data)
# Save the figure to SVG format in memory.
svg_file = BytesIO()
plt.savefig(svg_file, format='SVG')
# Rewind the file for reading, and convert to a Drawing.
svg_file.seek(0)
drawing = svg2rlg(svg_file)
# Scale the Drawing.
scale = 0.75
drawing.scale(scale, scale)
drawing.width *= scale
drawing.height *= scale
return drawing
def main():
styles = getSampleStyleSheet()
pdf_path = 'sketch.pdf'
doc = SimpleDocTemplate(pdf_path)
data = [1, 3, 2]
story = [Paragraph('Lorem ipsum!', styles['Normal']),
plot_data(data),
Paragraph('Dolores sit amet.', styles['Normal'])]
doc.build(story)
main()
You need to make sure you are importing PIL (Python Imaging Library) in your code so that ReportLab can use it to handle image types like SVG. Otherwise it can only support a few basic image formats.
That said, I recall having some trouble, even when using PIL, with vector graphics. I don't know if I tried SVG but I remember having a lot of trouble with EPS.

Working with the Python graphics module: is there any way to save the current window as an image?

I'm working with the python graphics module. What I am trying to do is save the current window as an image. In the module there is an option to save an "image" as an image (image.save()). But that isn't helpful because it just saves an image you have already loaded. OR if you load a blank image like I did in hopes drawing over it would change that, surprise, surprise: you get a blank image saved. Here is my code:
from graphics import *
w = 300
h = 300
anchorpoint=Point(150,150)
height=300
width=300
image=Image(anchorpoint, height, width) #creates a blank image in the background
win = GraphWin("Red Circle", w, h)
# circle needs center x, y coordinates and radius
center = Point(150, 150)
radius = 80
circle = Circle(center, radius)
circle.setFill('red')
circle.setWidth(2)
circle.draw(win)
point= circle.getCenter()
print point
pointx= point.getX()
pointy= point.getY()
print pointx
print pointy
findPixel=image.getPixel(150,150)
print findPixel
image.save("blank.gif")
# wait, click mouse to go on/exit
win.getMouse()
win.close()
#######that's it#####
so again here is my problem: How do I save what is now on the screen as "blank.gif"
Thanks!
The objects you are drawing are based on Tkinter. I don't believe you are actually drawing on the base image, but rather simply creating Tkinter objects by using the "graphics" library. I also don't believe you can save a Tkinter to a "gif" file, though you can definitely save them in postscript format, then covert them to a gif format.
In order to do this, you will need python's PIL library.
If all of your objects are actually TKinter objeccts, you can simply save the objects.
Start by replacing this line of code:
image.save("blank.gif")
With the following:
# saves the current TKinter object in postscript format
win.postscript(file="image.eps", colormode='color')
# Convert from eps format to gif format using PIL
from PIL import Image as NewImage
img = NewImage.open("image.eps")
img.save("blank.gif", "gif")
If you need additional information, please check out http://www.daniweb.com/software-development/python/code/216929 - which is where I got the suggested code.
I'm sure there are more elegant solutions available than save/convert, but since I don't know a lot about TKinter - this is the only way I've found.
Hope it helps!

Python Imaging Library - Text rendering

I'm trying to render some text using PIL, but the result that comes out is, frankly, crap.
For example, here's some text I wrote in Photoshop:
and what comes out of PIL:
As you can see, the results from PIL is less than satisfactory. Maybe I'm just being picky, but is there any way to draw text using PIL that gets results more close to my reference image?
Here's the code I'm using on Python 2.7 with PIL 1.1.7
image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
I came up with my own solution that I find acceptable.
What I did was render the text large, like 3x the size it needs to be then scale it resize it down with antialiasing, it's not 100% perfect, but it's a hell of a lot better than default, and doesn't require cairo or pango.
for example,
image = Image.new("RGBA", (600,150), (255,255,255))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", fontsize)
draw.text((10, 0), txt, (0,0,0), font=font)
img_resized = image.resize((188,45), Image.ANTIALIAS)
and you endup with this result,
which is a lot better than what I was getting before with the same font.
Try using pycairo - the python bindings for the Cairo drawing library -- it is usefull for more refined drawing, with antialiased lines,
and such - and you can generate vector based images as well
Correctly handling fonts, and layout is complicated, and requires the use of
the "pango" and "pangocairo" libraries as well. Although they are made
for serious font work (all GTK+ widgets do use pango for font rendering),
the available docuemtnation and examples are extremely poor.
The sample bellow shows the prints available in the system and renders the
sample text in a font family passed as parameter on the command line.
# -*- coding: utf-8 -*-
import cairo
import pango
import pangocairo
import sys
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 320, 120)
context = cairo.Context(surf)
#draw a background rectangle:
context.rectangle(0,0,320,120)
context.set_source_rgb(1, 1, 1)
context.fill()
#get font families:
font_map = pangocairo.cairo_font_map_get_default()
families = font_map.list_families()
# to see family names:
print [f.get_name() for f in font_map.list_families()]
#context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
# Positions drawing origin so that the text desired top-let corner is at 0,0
context.translate(50,25)
pangocairo_context = pangocairo.CairoContext(context)
pangocairo_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
layout = pangocairo_context.create_layout()
fontname = sys.argv[1] if len(sys.argv) >= 2 else "Sans"
font = pango.FontDescription(fontname + " 25")
layout.set_font_description(font)
layout.set_text(u"Travis L.")
context.set_source_rgb(0, 0, 0)
pangocairo_context.update_layout(layout)
pangocairo_context.show_layout(layout)
with open("cairo_text.png", "wb") as image_file:
surf.write_to_png(image_file)
I've never used PIL, but a quick review of the documentation for the Draw method indicates that PIL provides a way to render simple graphics. Photoshop provides a way to render complex graphics. To get anywhere close to Photoshop-like results requires, at a minimum, font hinting and anti-aliasing. PIL's documentation doesn't even hint at having such capabilities. You may want to look at using an external tool that might do a better job of rendering text on images. For example, ImageMagick (you'll want to use the 8-bit version, which handles standard 24-bit RGB). You can find some text drawing samples here: http://www.imagemagick.org/Usage/draw/
Suggestion: use Wand or a different Imaging library
Here's an example with wand -
from wand.color import Color
from wand.image import Image
from wand.drawing import Drawing
from wand.compat import nested
with Drawing() as draw:
with Image(width=1000, height=100, background=Color('lightblue')) as img:
draw.font_family = 'Indie Flower'
draw.font_size = 40.0
draw.push()
draw.fill_color = Color('hsl(0%, 0%, 0%)')
draw.text(0,int(img.height/2 + 20), 'Hello, world!')
draw.pop()
draw(img)
img.save(filename='image.png')
In python3 there is an option for aliased fonts. I couldn't find this answer anywhere, hopefully it helps someone like me who found this question on google and had to dig a long time to find the answer.
draw = ImageDraw.Draw(img)
draw.fontmode = "L"
Mentioned in the docs here
You can also try to write the font two times it increases the quality immense.
image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)

Categories