Wrapping and extending python cairo library with cython - python

I am writing a python program to do graphic animations, I am using the python cairo/rsvg libraries. I have reached a speed issue and wanted to accelerate some parts of the code by moving some of the rendering code in a cython library.
Ideally I would like to augment the cairo library methods to add a few that are optimized for my needs.
For example i have a function that draws an svg on screen centered at a point with a bounding box that forces its size, usually this function is called by an external loop which draws tens of svgs, and this function is one of the most expensive in my code:
def svg(ctx, path, pos, angle, width, height):
"""Draws an svg file at coordinates pos with at a specific angle, and a
maximum bounding box of size width x height"""
if width == 0 or height == 0:
return
svg = rsvg.Handle(file=path) #any svg file
ctx.save()
#put in position
ctx.translate(pos.x, pos.y)
#rotate
ctx.rotate(angle)
#resize
if svg.props.width != width or svg.props.height != height:
ratiow = (width *1.0) / (svg.props.width*1.0)
ratioh = (height * 1.0) / (svg.props.height * 1.0)
ctx.scale(ratiow, ratioh)
#move center to 0,0 of image
ctx.translate(-svg.props.width / 2, - svg.props.height / 2)
#draw image
svg.render_cairo(ctx)
ctx.restore()
What I would like to do is to write a cython function that given the list of svgs will draw them all at once on the screen.
Could this kind of optimization be done with cython?
For what I understand it would be extremely beneficial to define a type for the cairo context object (ctx), but how is the correct way to do it?

Related

Weird things happening when making image get bigger on mouse hover in Pygame

I am making a scene where there is a thumbs-up image that is supposed to get bigger on mouse hover, and shrink back to normal size when the mouse is no longer hovering.
This is how I make the thumbs-up image:
thumbs_up_image = pygame.image.load("./plz_like.png")
thumbs_up_rect = thumbs_up_image.get_rect(topleft=(screen.get_width() // 2 - thumbs_up_image.get_width() + 75,
screen.get_height() // 2 + thumbs_up_image.get_height() - 225))
And this is how I make it get bigger:
if thumbs_up_rect.collidepoint(pygame.mouse.get_pos()):
thumbs_up_image = pygame.transform.scale(thumbs_up_image,
[n + 50 for n in thumbs_up_image.get_size()])
thumbs_up_rect = thumbs_up_image.get_rect()
This is how the image is blited:
screen.blit(thumbs_up_image, thumbs_up_rect)
The problem is that when I hover on the thumbs-up image, it first goes to the top-left corner of the screen. Then, when I hover on it again, it gets super big and pixelated.
What am I doing wrong?
I managed to figure it out by myself.
This is how I do it:
First, I prepared a bigger version of the image and it's rect: (as shown below)
big_thumbs_image = pygame.transform.scale(thumbs_up_image, [i + 50 for i in thumbs_up_image.get_size()])
big_thumbs_image_rect = thumbs_up_image.get_rect(
topleft=(screen.get_width() // 2 - thumbs_up_image.get_width() + 55,
screen.get_height() // 2 + thumbs_up_image.get_height() - 250))
Then, when the small image's rect collides with the mouse, blit the bigger image:
if thumbs_up_rect.collidepoint(pygame.mouse.get_pos()):
screen.blit(big_thumbs_image, big_thumbs_image_rect)
You are not showing the code that actually renders the image to the screen.; But basically: you are not saving the original size - at each hover event it will grow and grow (and it will grow once per frame, if that code is run in the mainloop).
You need a variable to hold the original image, one to tell your code the image has already been resized, and an else clause on this if to restore the original image: pygame won't do that for you.
Also, when you use the get_rect for the image, its top-left position will always be "0, 0" - you have to translate this top-left corner to a suitable coordinate- getting the rectangle center of the original sprite (wherever the data of its location on the screen is kept), and setting the same center on the new rect should work.
And finally, prefer "rotozoom" than "scale" - Pygame documentation is clear that the second method uses better algorithms for scaling.
Try using this pygame function:
pygame.transform.rotozoom(Surface, angle, scale)
I also had some issues with pixilation in a game but it seemed to work with this.

Best way to render pixels to the screen in python?

I'm writing an interactive (zoom/pan) mandelbrot set viewer in python, and I'm having some performance issues. I'm currently using pyglet and PyOpenGL to render the pixels since I like how it handles mouse events. I generate the pixel values using numpy, and after some searching on stack exchange/docs/other places, I'm currently using glDrawPixels to draw the pixels. The application is horribly slow, taking ~1.5s to draw. I've heard that using textures is much faster, but I have no experience with them, and learning that much OpenGL seems like it should be unnecessary. Another approach I have considered is using vertex lists and batched rendering with pyglet, but it seems wrong to created a new GL_POINT at every single pixel on the screen. Am I going about this all wrong? Is there a better way to render something to the screen when pixels change so frequently? Code below:
# this code all is in a class that subclasses pyglet.window.Window
# called every 1/10.0 seconds, update 10 frames
def update_region(self):
# this code just computes new mandelbrot detail
if self.i < self.NUM_IT:
for _ in range(10): # do 10 iterations every update, can be customizable
self.z = np.where(np.absolute(self.z) < self.THRESHOLD,
self.z ** 2 + self.reg, self.z)
self.pixels = np.where(
(self.pixels == self.NUM_IT) & (np.absolute(self.z) >
self.THRESHOLD), self.i, self.pixels)
self.i = self.i + 1
def update_frame(self, x, y):
self.update_region()
# color_pixels is what will actually be rendered
self.color_pixels = self.cmap.to_rgba(self.pixels).flatten()
def on_draw(self): # draw method called every update (set to .1s)
start = time.time()
glClear(GL_COLOR_BUFFER_BIT)
glDrawPixels(2 * self.W, 2 * self.H, GL_RGBA, GL_FLOAT, (GLfloat * len(self.color_pixels))(*self.color_pixels))
glutSwapBuffers()
print('on_draw took {0} seconds'.format(time.time() - start))
Are you sure it's the glDrawPixels slowing you down? In your code for update_frame there's a cmap.to_rgba() which I assume is mapping the single value calculated by Mandelbrot into an RGB triple, and then whatever .flatten() does. Copying the entire image, twice, won't help.
For drawing raster images that don't need 3D scaling, pyglet has the image module and .blit()
You are right that a vertex list of points would not help.
Loading the image into a texture would be a bit more OpenGL code, but not too much. You could then zoom in OpenGL, and do the Mandelbrot -> RGB conversion on the GPU as it is drawn in a fragment shader.

Drawing full screen chessboard pattern?

I'm working in OpenCV (camera calibration and then creating 3d model) and till now I always printed a checkerboard pattern on paper and then took pictures needed for calibration. I tried to find a way to draw the pattern on the full screen with pre-defined square sizes (so I could set that square size in the calibration process), but I only found the Python turtle module which seems to only be for drawing on part of screen, and it always draws an arrow on last square. I need to draw the pattern with some small offset from the screen borders and, inside those offsets, draw a checkerboard with uniform squares. Also, I saw some people are drawing patterns in GIMP, but not on the full screen.
OpenCV has the function drawChessboardCorners but it requires founded corners from previous imported images, which need to be calibrated, so I think it doesn't make sense.
If anybody has an idea how to solve this problem, either with some program or module in some programming language (Python if possible), I would be grateful.
Here is a simple code for generating the chessboard pattern. However, the diameter of the chessboard is in pixel unit.
import numpy as np
h = 6
w = 8
size = 100
checkerboard = 255.0 * np.kron([[1, 0] * (w//2), [0, 1] * (w//2)] * (h//2), np.ones((size, size)))
I only found turtle module which seems to be only for drawing on part
of screen and it always draws arrow on last square.
Let's dispense with these two issues by drawing a grid in a window the size of the screen with no arrow on the last square:
from turtle import Screen, Turtle
BLOCK_SIZE = 72 # pixels
CURSOR_SIZE = 20 # pixels
BORDER = 1 # blocks
screen = Screen()
screen.setup(1.0, 1.0) # display size window
width, height = screen.window_width(), screen.window_height()
screen.setworldcoordinates(0, 0, width, height)
block = Turtle('square', visible=False) # hide the cursor completely
block.pencolor('black')
block.shapesize(BLOCK_SIZE / CURSOR_SIZE)
block.penup()
x_count = width // BLOCK_SIZE - BORDER * 2
x_width = x_count * BLOCK_SIZE
x_start = (width - x_width) // 2
x_limit = x_width + (BORDER + 1) * BLOCK_SIZE
y_count = height // BLOCK_SIZE - BORDER * 2
y_height = y_count * BLOCK_SIZE
y_start = (height - y_height) // 2
y_limit = y_height + (BORDER + 1) * BLOCK_SIZE
screen.tracer(False)
for parity_y, y in enumerate(range(y_start, y_limit, BLOCK_SIZE)):
block.sety(y)
for parity_x, x in enumerate(range(x_start, x_limit, BLOCK_SIZE)):
block.fillcolor(['white', 'black'][(parity_y % 2) == (parity_x % 2)])
block.setx(x)
block.stamp()
screen.tracer(True)
screen.mainloop()
(Hide your dock in OS X if you want to cover even more of the screen.)
Unfortunately, this is drawing in pixel units which is arbitrary. See my answer about drawing in a standardized measure using the pixel pitch value of your display.
Prepare your chessboard pattern in your favourite graphics editor, save the file onto the computer you want to use to display it for your calibration, then just display it when needed. I think you might be over-thinking the problem...

pygame - Snap Mouse to Grid

I'm making a little platformer game using pygame, and decided that making a level editor for each level would be easier than typing each blocks' coordinate and size.
I'm using a set of lines, horizontally and vertically to make a grid to make plotting points easier.
Here's the code for my grid:
def makeGrid(surface, width, height, spacing):
for x in range(0, width, spacing):
pygame.draw.line(surface, BLACK, (x,0), (x, height))
for y in range(0, height, spacing):
pygame.draw.line(surface, BLACK, (0,y), (width, y))
I want the user's mouse to move at 10px intervals, to move to only the points of intersection. Here's what I tried to force the mouse to snap to the grid.
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
pygame.mouse.set_pos(roundCoords(mousePos[0],mousePos[1]))
(BTW, roundCoords() returns the coordinates rounded to the nearest ten unit.)
(Also BTW, snapToGrid() is called inside the main game loop (while not done))
...but this happens, the mouse doesn't want to move anywhere else.
Any suggestions on how to fix this? If I need to, I can change the grid code too.
Thanks a bunch.
P.S. This is using the latest version of PyGame on 64 bit Python 2.7
First of all I think you're not far off.
I think the problem is that the code runs quite fast through each game loop, so your mouse doesn't have time to move far before being set to the position return by your function.
What I would have a look into is rather than to pygame.mouse.set_pos() just return the snapped coordinates to a variable and use this to blit a marker to the screen highlighting the intersection of interest (here I use a circle, but you could just blit the image of a mouse ;) ). And hide your actual mouse using pygame.mouse.set_visible(False):
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
return roundCoords(mousePos[0],mousePos[1])
snap_coord = snapToGrid(mousePos)# save snapped coordinates to variable
pygame.draw.circle(Surface, color, snap_coord, radius, 0)# define the remaining arguments, Surface, color, radius as you need
pygame.mouse.set_visible(False)# hide the actual mouse pointer
I hope that works for you !

How can i make all the images on a window scale down or up with the size of the monitor

So i am making a game in python and pygame and i have the indow setup like this
display = pygame.display.set_mode((0,0), pygame.FULLSCREEN)
which makes the size of the window about 1334 X 800 so i based all the sprites and backgrounds on that size of screen but as you know not everyone has the same sized screen as me so my question is how can i make images scale with how big the monitor screen is
(P.S The game is in fullscreen mode)
First, how do you get the resolution and the scaling factor?
This is tricky, because someone's screen may not have the same aspect ratio as your 1334x800. You can letterbox (in various different ways) or stretch the sprites; you need to decide what you want, but I'll show one letterboxing possibility:
NOMINAL_WIDTH, NOMINAL_HEIGHT = 1334., 800.
surface = display.get_surface()
width, height = surface.get_width(), surface.get_height()
xscale = width / NOMINAL_WIDTH
yscale = height / NOMINAL_HEIGHT
if xscale < 1 and yscale < 1:
scale = max(xscale, yscale)
elif xscale > 1 and yscale > 1:
scale = min(xscale, yscale)
else:
scale = 1.0
Now, how do you scale each sprite and background?
Well, first, are you sure you want to? It may be simpler to just transform the whole surface. Whether this is slower or faster is hard to predict without testing (and probably not relevant anyway), but it will definitely look better (because any interpolation, dithering, antialiasing, etc. happens after compositing, instead of before—unless you're going for that 8-bit look, of course, in which case it will destroy the look…). You can do this by compositing everything to an off-screen surface of 1334x800 (or, better, scaling everything up by a constant factor), then transforming that surface for display. (Note that the transform methods include an optional DestSurface argument. You can use this to directly transform from the offscreen surface to the display's surface.)
But let's assume you want to do it the way you asked.
You can do this when loading the sprites. For example:
def rescale(surf, scale):
new_width, new_height = surf.get_width() * scale, surf.get_height() * scale
return pygame.transform.smoothscale(surf, (new_width, new_height))
class ScaledSprite(pygame.sprite.Sprite):
def __init__(self, path, scale):
pygame.sprite.Sprite.__init__(self)
self.image = rescale(pygame.image.load(path), scale)
self.rect = self.image.get_rect()
And the same for the backgrounds.
from this SO question, you can get the size of the monitor with
infoObject = pygame.display.Info()
which gets the height and width of the screen as infoObject.current_w and infoObject.current_h
You can then use these values to scale everything appropriately.

Categories