Rectangle Rotation in Python/Pygame - python

Hey I'm trying to rotate a rectangle around its center and when I try to rotate the rectangle, it moves up and to the left at the same time. Does anyone have any ideas on how to fix this?
def rotatePoint(self, angle, point, origin):
sinT = sin(radians(angle))
cosT = cos(radians(angle))
return (origin[0] + (cosT * (point[0] - origin[0]) - sinT * (point[1] - origin[1])),
origin[1] + (sinT * (point[0] - origin[0]) + cosT * (point[1] - origin[1])))
def rotateRect(self, degrees):
center = (self.collideRect.centerx, self.collideRect.centery)
self.collideRect.topleft = self.rotatePoint(degrees, self.collideRect.topleft, center)
self.collideRect.topright = self.rotatePoint(degrees, self.collideRect.topright, center)
self.collideRect.bottomleft = self.rotatePoint(degrees, self.collideRect.bottomleft, center)
self.collideRect.bottomright = self.rotatePoint(degrees, self.collideRect.bottomright, center)

The rotation code looks to be fine - but, you are aware that pygame's internals don't work with rotated rectangles, do you?
Unless you have some code you wrote yourself with the new rectangle corners, what this does is to define a new rectangle, with sides parallel to the Surface edges, where the original rectangle, when rotated, could be inscribed to, not a rectangle at the same size than the original at a skewed angle. Any Pygame function to which you pass the "self.collideRect" object after the rotation will just do that: treat the rectangle as aligned to the surface,
just as if it has been created with the corners it has now.
If your code requires you to check for things, or even draw, inside a rotated rectangle, you have to perform all the calculations as they where prior to the rotation, and just perform the coordinate rotation at the time of displaying what you want. That is, you work with a global coordinate transform, that is applied in the last step of rendering.

Maybe this can help you:
#load image
image1 = pygame.image.load(file)
#get width and height of unrotated image
width1,height1 = image1.get_size()
#rotate image
image2 = pygame.transform.rotate(image1, angle)
#get width,height of rotated image
width2,height2 = image2.get_size()
#blit rotated image (positon - difference of width or height /2)
display.blit(image2,[round(x - (width1 - width2)/2),round(y - (height1 - height2)/2)])

Related

PyCairo : how to resize & rotate an image by its center to a final canvas

Using PyCairo, I want to be able to have a method that can put, resize & rotate a given ImageSurface on a context, but rotating by the center of the image (not the top-left)
Okay I've tried the examples I've found here but without any success.
Let's introduce the "context" in details.
I've got a "finale" ImageSurface (say A) on which some other images & texts are written.
I want to put on it another ImageSurface (say B), at a specified position where this position is the top-left where to put B on A. Then I need to resize B (reduce its size) and to rotate it by its center instead of by its top-left corner.
Here is an illustration of the wanted result :
I've tried the following but without success:
def draw_rotated_image(ctx, image_surface, left, top, width, height, angle):
ctx.save()
w = image_surface.get_width()
h = image_surface.get_height()
cl = left / (width/w)
ct = top / (height/h)
ctx.rotate(angle*3.1415927/180)
ctx.scale(width/w, height/h)
ctx.translate(cl + (-0.5*w),ct + (-0.5*h) )
ctx.set_source_surface(image_surface, 0, 0)
ctx.paint()
ctx.restore()
return
Thanks a lot for your help :)
Well, I finally made it ! (thanks to my 14 year old son who made me revise my trigonometry)
I'm trying to explain here my solution.
First, I AM NOT A MATHEMATICIAN. So there is probably a best way, and surely my explanation have errors, but I'm just explaining the logical way I have used to get the good result.
The best idea for that is to first draw a circle around the rectangle, because we need to move the top-left corner of this rectangle, around its own circle, according to the desired angle.
So to get the radius of the rectangle circle, we need to compute its hypothenuse, then to divide by 2 :
hypothenuse = math.hypot(layerWidth,layerHeight)
radius = hypothenuse / 2
Then we will be able to draw a circle around the rectangle.
Second, we need to know at which angle, on this circle, is the actual top-left corner of the rectangle.
So for that, we need compute the invert tangent of the rectangle, which is arc-tan(height/width).
But because we want to know how many degrees are we far from 0°, we need to compute the opposite so arc-tan(width/height).
Finally, another singularity is that Cairo 0° is in fact at 90°, so we will have to rotate again.
This can be shown by this simple graphic :
So finally, what is necessary to understand ?
If you want to draw a layer, with an angle, rotated by its center, the top-left point will move around the circle according to the desired angle.
The top-left position with a given angle of 0 needs to be "the reference".
So we need to get the new X-Y position where to start putting the layer to be able to rotate it :
Now, we can write a function that will return the X-Y pos of the top left rectangle where to draw it with a given angle :
def getTopLeftForRectangleAtAngle(layerLeft,layerTop,layerWidth,layerHeight,angleInDegrees):
# now we need to know the angle of the top-left corner
# for that, we need to compute the arc tangent of the triangle-rectangle:
layerAngleRad = math.atan((layerWidth / layerHeight))
layerAngle = math.degrees(layerAngleRad)
# 0° is 3 o'clock. So we need to rotate left to 90° first
# Then we want that 0° will be the top left corner which is "layerAngle" far from 0
if (angleInDegrees >= (90 + layerAngle)):
angleInDegrees -= (90 + layerAngle)
else:
angleInDegrees = 360 - ((90 + layerAngle) - angleInDegrees)
angle = (angleInDegrees * math.pi / 180.0)
centerLeft = layerLeft + (layerWidth / 2)
centerTop = layerTop + (layerHeight / 2)
# hypothenuse will help us knowing the circle radius
hypothenuse = math.hypot(layerWidth,layerHeight)
radius = hypothenuse / 2
pointX = centerLeft + radius * math.cos(angle)
pointY = centerTop + radius * math.sin(angle)
return (pointX,pointY)
And finally, here is how to use it with an image we want to resize, rotate and write on a context:
def draw_rotated_image(ctx, image_surface, left, top, width, height, angle=0.0, alpha=1.0):
ctx.save()
w = image_surface.get_width()
h = image_surface.get_height()
# get the new top-left position according to the given angle
newTopLeft = getTopLeftForRectangleAtAngle(left, top, width, height, angle)
# translate
ctx.translate(newTopLeft[0], newTopLeft[1])
# rotate
ctx.rotate(angle * math.pi / 180)
# scale & write
ctx.scale(width/w, height/h)
ctx.set_source_surface(image_surface, 0, 0)
ctx.paint_with_alpha(alpha)
ctx.restore()
return

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...

Python how to change colour of individual square of grid on mouse click

How do I change the color of an individual square of the grid. This grid is drawn over an image. I calculated the center point of each square of the grid. When a user clicks on a specific square of the grid, I get the centroid of that square (already implemented). How can I change the color of the square of the grid. I have centroid of the clicked square in variable "closest_centroid".
# Get closes centroid
def closest_node(node, nodes):
nodes = np.asarray(nodes)
dist = np.sum((nodes - node)**2, axis=1)
return np.argmin(dist)
# print co-ordinate function
def get_coords(event):
mouse_xy = (event.x, event.y)
closest_centroid = centers[closest_node(mouse_xy, centers)]
print(closest_centroid)
My idea is to change the color of pixels of an image contained in the clicked square of the grid.
My libraries are:
import math
from Tkinter import *
from PIL import Image, ImageDraw
import numpy as np
You already did the hard work so you just need a simple function like the following:
def change_color(center):
step = step_size/2
center_x, center_y = center
canvas.create_rectangle(
[
center_x - step,
center_y - step,
center_x + step,
center_y + step
],
fill='red'
)
Rather than printing the centroid, you'd just call this function at that point.
John showed in his full code (removed in an edit) that canvas was already setup to draw the grid. To setup canvas John did:
filename = ImageTk.PhotoImage(img)
canvas = tk.Canvas(root,height=img.size[0],width=img.size[0])
canvas.image = filename
canvas.create_image(0,0,anchor='nw',image=filename)
canvas.pack()
Also he has step_size as:
step_size = int(img.width / step_count)
If the image isn't square, you'll need a step_size_x and a step_size_y

How can I retrieve the center of a rotated ellipse from a pyqtgraph EllipseROI?

I would like to find the center of an ellipse created using the pyqtgraph EllipseROI, generated using the following command:
import pyqtgraph as pg
...
ellipse = pg.EllipseROI(pos = [200, 200], size=125, pen='r', movable=True)
Once a user moves and rotates the ellipse, some information can be retrieved from the handles to determine the new position:
x0=ellipse.pos().x()
y0=ellipse.pos().y()
width=ellipse.size()[0]
height=ellipse.size()[1]
angle=ellipse.angle()
It looks like the width and height values correspond to the rectangular region surrounding the non-rotated ellipse:
In the above images, the black pixels represent the following points (from lower left, proceeding counter-clockwise):
[x0, y0]
[x0+width, y0]
[x0+width, y0+height]
[x0, y0+height]
The center for the ellipse with angle=0 is simply [x0+width/2, y0+height/2]. For a rotated ellipse, I can find the center by rotating the center of the ellipse corresponding to angle=0 about the point [x0, y0]:
import numpy as np
...
center_x0 = x0+width/2.0
center_y0 = y0+height/2.0
center_x = x0 + np.cos(np.radians(angle)) * (center_x0 - x0) - np.sin(np.radians(angle)) * (x0 - center_x0)
center_y = y0 + np.sin(np.radians(angle)) * (center_x0 - x0) + np.cos(np.radians(angle)) * (center_y - y0)
Am I missing a simple solution to quickly retrieve the center position from the EllipseROI (or any pyqtgraph ROI) that does not first require retrieving the bounding box points of an non-rotated ROI and then rotating their center? I would like to minimize the delay between when the user adjusts the ROI and subsequent displays in the viewbox (that rely on the center point), and the method I am currently using seems like an inefficient way to calculate the center.

Pycairo scale distortion

I am struggling to draw a circle onto a rectangular png background. Whenever I try to draw a circle, it is stretched according to the scale of my image--making it an ellipse instead of a circle.
Here is the set up:
#set background image
surface = cairo.ImageSurface.create_from_png (image)
context = cairo.Context (surface)
WIDTH = cairo.ImageSurface.get_width(surface)
HEIGHT = cairo.ImageSurface.get_height(surface)
context.scale (WIDTH, HEIGHT)
And here is the circle drawing function, (x coord, y coord, radius, color value) and functions used for positioning:
def draw_node(cx,cy,r, rgb):
context.arc(x_scale(cx), y_scale(cy), r, 0, 2 * math.pi)
context.set_source_rgb(rgb[0]/255.0,rgb[1]/255.0,rgb[2]/255.0)
context.fill()
context.stroke()
def x_scale (x):
width = 1000.0
return (x + .5) / width
def y_scale (y):
height = 1000.0 * HEIGHT / WIDTH
return (y + .5) / height
I would particularly appreciate any advice on how to resolve the systemic scaling issue, but I would also appreciate advice on how to draw a circle within the confines of my existing setup.
Thank you very much.
there is probably no way to avoid that context.arc will draw an ellipse when your scaling is not symmetric. with your x_scale (x) function you only scale the center of the circle to be where it should be.
as a workaround i'd suggest to scale both directions by the same amount; sacrifice therefore that x_max and y_max will both be equal to 1.
i'd change in your code:
context.scale(HEIGHT, HEIGHT)
print('surface = 1 x {:1.2f}'.format(WIDTH/HEIGHT))
def draw_node(cx,cy,r, rgb):
context.arc(cx, cy, r, 0, 2 * math.pi)
context.set_source_rgb(rgb[0]/255.0,rgb[1]/255.0,rgb[2]/255.0)
context.fill()
context.stroke()
and not do any scalings (or scale by 1000 if that's what you prefer).

Categories