I was wondering if would be possible to load a svg format like shape and with loop to repeat with for loop to get some kind a generic pattern. Most what I have founded researching online, is to convert from svg to png or some else format but I was wondering is it possible to manipulate before converting to some format (jpg)?
I have tried to combine cairosvg and PIL but I have not gone too far.
from cairosvg import svg2png
from PIL import Image, ImageDraw
white = (255,255,255)
img = Image.new('RGB', (300,300), white)
draw = ImageDraw.Draw(img)
svg_code = """
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="8" x2="12" y2="12"/>
<line x1="12" y1="16" x2="12" y2="16"/>
</svg>
"""
Usually I would use something like this...
for x in range(0, 300, 25):
draw.svg_code??
But this doesn't work... Any idea how can I load svg format and use it with other modules?
Thanks in advance!
Requirements
Skia
Google Chrome, Android, Flutter and others use the open source 2D graphics library Skia, for which there is also a Python binding.
Skia allows to create a shader with tile mode rules to fill a drawn area. Additionally a matrix can be supplied which allows e.g. rotation. Advantage is that this is performed very efficiently even if the target image is large and the number of elements that make up the pattern is very high.
Such a pattern can therefore be easily created, e.g. with the following code:
def pattern(canvas, image_element, rotation):
matrix = skia.Matrix()
matrix.preRotate(rotation)
canvas.drawPaint({
'Shader': image_element.makeShader(
skia.TileMode.kRepeat,
skia.TileMode.kRepeat,
matrix,
)
})
So a complete approach might look like this:
An important feature for many use cases is that SVG drawings are done on transparent background.
However, JPEG does not support transparency. You can convert the image from a type with transparency (RBGA
) to a type without transparency by using .convert('RGB')
. But in this case you will have black elements on a black background. When converting, it is possible to create a new image with the background preset and paste the transparent image into it.
Self-contained Python example
A self-contained example that takes into account the above points might look like the following program.
It simply creates a pattern based on your SVG example and draws a red text on a gray rectangle on it. It outputs two images: once the desired JPEG but also PNG to be able to display the mentioned transparency option.
import io
import skia
from PIL import Image
def image_from_svg(svg, element_size):
stream = skia.MemoryStream()
stream.setMemory(bytes(svg, 'UTF-8'))
svg = skia.SVGDOM.MakeFromStream(stream)
width, height = svg.containerSize()
surface = skia.Surface(element_size, element_size)
with surface as canvas:
canvas.scale(element_size / width, element_size / height)
svg.render(canvas)
return surface.makeImageSnapshot()
def pattern_image_with_title(image_element, width, height, rotation, title):
surface = skia.Surface(width, height)
with surface as canvas:
pattern(canvas, image_element, rotation)
rectangle(canvas, width)
draw_title(canvas, title)
return surface.makeImageSnapshot()
def draw_title(canvas, title):
paint = skia.Paint(AntiAlias=True, Color=skia.ColorRED)
canvas.drawString(title, 48, 76, skia.Font(None, 32), paint)
def rectangle(canvas, width):
rect = skia.Rect(32, 32, width - 32, 96)
paint = skia.Paint(
Color=skia.ColorGRAY,
Style=skia.Paint.kFill_Style)
canvas.drawRect(rect, paint)
def pattern(canvas, image_element, rotation):
matrix = skia.Matrix()
matrix.preRotate(rotation)
canvas.drawPaint({
'Shader': image_element.makeShader(
skia.TileMode.kRepeat,
skia.TileMode.kRepeat,
matrix,
)
})
def write_png(file_name, skia_image):
with io.BytesIO(skia_image.encodeToData()) as f:
pil_image = Image.open(f)
pil_image.save(file_name, 'PNG')
def write_jpeg(file_name, skia_image, background):
with io.BytesIO(skia_image.encodeToData()) as f:
pil_image = Image.open(f)
new_image = Image.new("RGBA", pil_image.size, background)
new_image.paste(pil_image, (0, 0), pil_image)
new_image = new_image.convert('RGB')
new_image.save(file_name, 'JPEG')
svg_code = """
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="8" x2="12" y2="12"/>
<line x1="12" y1="16" x2="12" y2="16"/>
</svg>
"""
if __name__ == "__main__":
img = image_from_svg(svg_code, 50)
img = pattern_image_with_title(img, 300, 300, 45, 'hello world!')
write_png('result.png', img)
write_jpeg('result.jpg', img, 'WHITE')
Result
As a result of the above program you get two images. On the left is the JPEG image with white background and on the right is the PNG image with transparency.