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).
Related
I'm trying to solve a problem and am quite stuck on how to approach it. I have an image and I want to get all the pixels inside the circle area of interest.
The radius of the circle is 150 pixels and the centre point of the ROI is [256, 256].
I have tried to draw the circle on the image, but I couldn't find a way to access those pixels from that.
img = imread('Model/m1.png') * 255
row = size(img, 0) #imgWidth
col = size(img, 1) #imgHeight
px = row * col
circle = Circle((256, 256), 150, fill = False)
fig, ax = subplots()
ax.add_patch(circle)
imshow(img, cmap='gray')
show()
I was thinking maybe using the equation of a circle:
(x - a)**2 + (y - b)**2 == r**2
But I'm not quite sure as wouldn't that only give you the pixels of the circle edge?
Any ideas or tips would be much appreciated.
Inequality (x - a)**2 + (y - b)**2 <= r**2 allows to walk through all pixels inside circle.
In practice you can scan Y-coordinates from b-r to b+r and walk through horizontal lines of corresponding X-range a - sqrt(r**2 - (y-b)**2) .. a + sqrt(r**2 - (y-b)**2)
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
I need to make a circle by adjusting conditions on the heights, this program using a lot of random circles but I am unsure where to go from here? I am trying to use the following equation d = (sqrt)((x1 –x2)^2 +(y1 – y2)^2). Right now the program draws many random circles, so adjusting the formula i should be able to manipulate it so that certain circles are red in the centre (like the japan flag).
# using the SimpleGraphics library
from SimpleGraphics import *
# use the random library to generate random numbers
import random
diameter = 15
##
# returns a valid colour based on the input coordinates
#
# #param x is an x-coordinate
# #param y is a y-coordinate
# #return a colour based on the input x,y values for the given flag
##
def define_colour(x,y):
##
if y < (((2.5 - 0)**2) + ((-0.5 - 0)**2)**(1/2)):
c = 'red'
else:
c = 'white'
return c
return None
# repeat until window is closed
while not closed():
# generate random x and y values
x = random.randint(0, getWidth())
y = random.randint(0, getHeight())
# set colour for current circle
setFill( define_colour(x,y) )
# draw the current circle
ellipse(x, y, diameter, diameter)
Here's some code that endlessly draws circles. Circles that are close to the centre of the screen will be drawn in red, all other circles will be drawn in white. Eventually, this will create an image similar to the flag of Japan, although the edge of the inner red "circle" will not be smooth.
I have not tested this code because I don't have the SimpleGraphics module, and I'm not having much success locating it via Google or pip.
from SimpleGraphics import *
import random
diameter = 15
width, height = getWidth(), getHeight()
cx, cy = width // 2, height // 2
# Adjust the multiplier (10) to control the size of the central red portion
min_dist_squared = (10 * diameter) ** 2
def define_colour(x, y):
#Calculate distance squared from screen centre
r2 = (x - cx) ** 2 + (y - cy) ** 2
if r2 <= min_dist_squared:
return 'red'
else:
return 'white'
# repeat until window is closed
while not closed():
# generate random x and y values
x = random.randrange(0, width)
y = random.randrange(0, height)
# set colour for current circle
setFill(define_colour(x, y))
# draw the current circle
ellipse(x, y, diameter, diameter)
I've got a problem for which I want to draw a circle/object at a certain position in an image, and then I want to draw rays emanating from that image--with each ray being separated by 1 degree. The rays would only be cast on a 145 degree segment, though--so it doesn't form a full circle. I am using Python PIL (to which I am a novice) to accomplish that task--although I'm not strict on language requirements.
def drawSunshine(im):
draw = ImageDraw.Draw(im)
x, y = im.size
draw.ellipse((370,200, 400,230), fill='red',outline='black')
draw.line((370,205,390,218), fill='black',width=3)
draw.point((100,100),'red')
im.show()
I was thinking that I could start by iterating over every pixel around the circle. And then I could just change the colour of that pixel.
Edit This approach made sense to me because I was planning on drawing this image on a black-and-white PNG file. If the current pixel was white, I was going to draw another pixel of the line, if the current pixel was black: I would consider it to be an obstacle and terminate the line at that point.
However, the main questions:
How do I iterate around an object at an arbitrary position in an image such that the area around said object is a circle or a circle segment?
And, how do I ensure that each "ray" I'm drawing per pixel is separated by a 1 degree?
EDIT Is there a way to allow for these "rays" to be interrupted by black pixels?
Here's a little function you can adjust;
import Image, ImageDraw
from math import sin, cos, pi
width, height = 400, 400
skyBlue = (135, 206, 235)
im = Image.new("RGBA", (width, height), skyBlue)
#Draw Sun
draw = ImageDraw.Draw(im)
def drawSun(draw, centre, radius, rays=False, startAngle=0, finishAngle=360, rayAngle=10, rayGap=10, rayLength=1000, rayColour="Yellow", rayOutline="Orange"):
x1,x2 = centre[0] - radius, centre[0] + radius
y1,y2 = centre[1] - radius, centre[1] + radius
if rays:
for rayStart in range(startAngle, finishAngle, rayAngle+rayGap):
rayEnd = (rayStart+rayAngle) * pi/180
rayStart *= pi/180
corner1 = centre[0] + rayLength*cos(rayStart), centre[1] + rayLength*sin(rayStart)
corner2 = centre[0] + rayLength*cos(rayEnd), centre[1] + rayLength*sin(rayEnd)
print [centre, corner1, corner2]
draw.polygon([centre, corner1, corner2], fill="Yellow", outline="Orange")
draw.ellipse((x1, y1, x2, y2), fill="Yellow", outline="Orange")
drawSun(draw, (100, 100), 40, rays=True, startAngle=0, finishAngle=145, rayAngle=3, rayGap=5)
im.save("example.png")
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)])