rotate text around its center in pycairo - python

community.
I know that there are many answers here, manuals, tutorials and references over the internets and amny more about this question. Also I know that knowledge of linear algebra is required.
But when I think about time to figuring out all the theory and solving exercises in practice - my head is blowing off and I can't do the simplest things :(
Please, if you know a little fast solution how to make rotation of text over its center before rendering it - tell me, pleeease.
For now I have:
#...
cr.move_to(*text_center)
myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)
cr.save()
cr.translate(myX, myY)
cr.rotate(radians(text_angle))
cr.show_text(letter)
cr.restore()
#...
But my letter isn't rotating around itself. It's just like falling down to the right side :(
I know that my code isn't right. Maybe I miss transformation but I don't know how to make it right.
UPDATE: Unfortunately, text are not affected by translations, so
cr.translate(10000, 10000)
cr.rotate(radians(15))
cr.show_text("hello")
will be exactly the same as
cr.rotate(radians(15))
cr.show_text("hello")
And I don't know how to make text rotation over its center without making new surface or something (like new layer in graphic processor) :(

At least on the version of cairo available on my machine (1.8.8), the following approach works for me:
def text(ctx, string, pos, theta = 0.0, face = 'Georgia', font_size = 18):
ctx.save()
# build up an appropriate font
ctx.select_font_face(face , cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(font_size)
fascent, fdescent, fheight, fxadvance, fyadvance = ctx.font_extents()
x_off, y_off, tw, th = ctx.text_extents(string)[:4]
nx = -tw/2.0
ny = fheight/2
ctx.translate(pos[0], pos[1])
ctx.rotate(theta)
ctx.translate(nx, ny)
ctx.move_to(0,0)
ctx.show_text(string)
ctx.restore()
Which can be used in the following way:
width = 500
height = 500
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
ctx = cairo.Context(surface)
ctx.set_source_rgb(1,1,1)
rect(ctx, (0,0), (width, height), stroke=False)
ctx.set_source_rgb(0,0,0)
for i in xrange(5):
for j in xrange(5):
x = 100 * i + 20
y = 100 * j + 20
theta = math.pi*0.25*(5*i+j)
text(ctx, 'hello world', (x, y), theta, font_size=15)
surface.write_to_png('text-demo.png')

OK so cairo allows for text move_to and rotate. This means that what you want is to figure out (x,y) for move_to (T), such that when you rotate (R), the center point of your text is at your desired location, c=(cx,cy):
So you have to solve the equation Mv = c, where v is the text center relative to the text origin:
M = T*R
T = (1 0 x)
(0 1 y)
(0 0 1)
R = (cos r -sin r 0)
(sin r cos r 0)
(0 0 1)
v = (w/2, h', 1)
c = (cx, cy, 1)
h' = h/2 - (h - y_bearing)
Sanity checks:
when r is 0 (no rotation), you get x=cx-w/2, y=cy-h', which you know
is the correct answer
when r=-90 (text sideways, with "up" towards the right), you get what you expect,
ie x = cx - h' and y = cy + w/2
For python code, you will have to rewrite the above equation so you end up with A*t=b, where t=(x,y), and you will compute t = inv(A)*b. Then, you will simply do
cr.move_to(x, y)
cr.rotate(r)
cr.show_text(yourtext)
Note that the coordinate system in cairo has +y going down so there will be a couple signs to fix, and maybe y_bearing is not correct, but you get the idea.

Class function based on above input with multi-line text support.
def text(self, text, x, y, rotation=0, fontName="Arial", fontSize=10, verticalPadding=0):
rotation = rotation * math.pi / 180
self.ctx.select_font_face(fontName, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
self.ctx.set_font_size(fontSize)
fascent, fdescent, fheight, fxadvance, fyadvance = self.ctx.font_extents()
self.ctx.save()
self.ctx.translate(x, y)
self.ctx.rotate(rotation)
lines = text.split("\n")
for i in xrange(len(lines)):
line = lines[i]
xoff, yoff, textWidth, textHeight = self.ctx.text_extents(line)[:4]
offx = -textWidth / 2.0
offy = (fheight / 2.0) + (fheight + verticalPadding) * i
self.ctx.move_to(offx, offy)
self.ctx.show_text(line)
self.ctx.restore()

Should
myX, myY = text_center[0] + (height / 2), text_center[1] - (width / 2)
be
myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)
?
That might explain why it's falling down to the right side.

Related

Draw a line on an image using angle and center point using Python

I'm facing a little issue in my code. I have a dataframe of coords and angels.
I want to draw a line from a certain xy coords to the edge of the image on a certain angle (say 45 degree).
How can I do that using PIL? Looping over x2 = x + length*cos(angle) doesn't look like a good solution (but I might be wrong here).
Thanks in advance.
Thanks for posting your solution. I've found a good one.
import math
def get_coords(x, y, angle, imwidth, imheight):
x1_length = (x-imwidth) / math.cos(angle)
y1_length = (y-imheight) / math.sin(angle)
length = max(abs(x1_length), abs(y1_length))
endx1 = x + length * math.cos(math.radians(angle))
endy1 = y + length * math.sin(math.radians(angle))
x2_length = (x-imwidth) / math.cos(angle+180)
y2_length = (y-imheight) / math.sin(angle+180)
length = max(abs(x2_length), abs(y2_length))
endx2 = x + length * math.cos(math.radians(angle+180))
endy2 = y + length * math.sin(math.radians(angle+180))
return endx1, endy1, endx2, endy2
Then I just draw a line between (endx1, endy1) and (endx2, endy2).
If you have a better solution, I would be very interested to see it.

Graphics.py: Check if point is in Object

In short, I want to make a radar of sorts, using specifically the graphics.py library, that detects if pre-drawn Shape exists at a Point.
Below I've drafted up a code that will show me all the points within a radar (and return it as an array), but I want to be able to take a point/coordinate and check if it is inside a Circle or Rectangle etc without knowing anything else (i.e only x and y parameters, no knowledge of other existing Shapes). Something like:
if Point(x,y).getObject() is None:
return False
or even
if Point(x,y).getObject().config["fill"] == "#f00":
return True
Here's what I've done so far:
from graphics import *
def getAllPointsInRadius(circle, win):
cx = int(circle.getCenter().getX())
cy = int(circle.getCenter().getY())
r = circle.getRadius()# + 2
points = []
print cx, cy
for i in range(cx-r, cx+r):
for j in range(cy-r, cy+r):
x = i-circle.getRadius()
y = j - circle.getRadius()
if ((i - cx) * (i - cx) + (j - cy) * (j - cy) <= r * r):
points.append(Point(i,j)) #if within, append as Point
p = Point(i, j)
p.setFill('#551A8B')
p.draw(win) #illustrate all points inside radar
else:
p = Point(i, j)
p.setFill('#0000ff')
p.draw(win) #points outside
return points
win = GraphWin('Radar', width = 500, height = 500)
win.setCoords(0, 0, 30, 30)
win.setBackground('black')
#object to be detected
objectCircle = Circle(Point(11, 12), 2)
objectCircle.setFill('#f00')
objectCircle.draw(win)
#radius within objects may be detected
radiusCircle = Circle(Point(10, 10), 3)
radiusCircle.setOutline('#fff')
radiusCircle.draw(win)
print getAllPointsInRadius(radiusCircle, win)
#print Point(11, 12).config["outline"]
win.getMouse()
Addendum: I think maybe by making Point(x,y) I'm actually creating a point and not returning the coordinates. I'm not sure how to do it otherwise, though.

VTK cannot construct a proper closed surface with vtkClipClosedSurface

Here's a rough explanation of what I do in vtk:
Create a surface (a minimal surface, not too relevant what it is, the geometry is important though: the gyroid has two labyrinths that are completely shut off from each other).
use vtkClipClosedSurface to shut off one of the labyrinths so that I get an object that has no open surfaces anymore. A regular surface looks like this, with a closed surface it looks like this.
Here's my problem: For more complicated versions of my structure, I get this:
Can you see how on the top left it works fine and near the bottom right it stops creating surfaces? Sometimes I also get really weird triangles in that last part.
To my understanding vtkClipClosedSurface knows from the surface normals where to close a surface and where not. The thing is: The normals of my structure are fine and they all point in the right direction. If you take a closer look on the structure you will notice that the lower part is basically an inversion of the top part that changes gradually, all in one surface.
I tried to modify my structure before cutting with many things like vtkSmoothPolyDataFilter, vtkCleanPolyData or vtkPolyDataNormals. I even tried extracting the boundary surfaces with vtkFeatureEdges, which led to an even worse result. Even vtkFillHolesFilter didn't yield any acceptable results. My surface seems flawless and easy enough to create a boundary.
I have no idea what else to try. This happens for other structures, too. Fixing it with a CAD tool is out of question, because it is supposed to work out of box. Please help me!
Here's another example of a geometry that doesn't close the surface properly. This time I used vtkFillHolesFilter which results in surfaces on the inside of the structure, while they should only occupy the boundary of te object.
In case you need a more detailed rundown of my pipeline, here goes:
create surface using mayavi.mlab.contour3d
get the PolyData by extracting the actor.mapper.input
convert format from tvtk to regular vtk
vtkClipClosedSurface with a plane collection that cuts away part of the structure (errors occur when the plane collection is the same as the structure boundary)
visualize it
Edit: Okay, this did not receive enough attention, so I constructed a minimal, complete and verifiable working example that reproduces the behaviour:
import numpy as np
import vtk # VTK version 7.0
from mayavi import mlab # mayavi version 4.4.4
from mayavi.api import Engine, OffScreenEngine
from tvtk.api import tvtk
def schwarz_D(x, y, z, linear_term=0):
"""This is the function for the Schwarz Diamond level surface."""
return (np.sin(x) * np.sin(y) * np.sin(z) + np.sin(x) * np.cos(y) * np.cos(z) +
np.cos(x) * np.sin(y) * np.cos(z) + np.cos(x) * np.cos(y) * np.sin(z)) - linear_term * z
def plane_collection(xn, x, yn, y, zn, z):
"""Defines the 6 planes for cutting rectangular objects to the right size."""
plane1 = vtk.vtkPlane()
plane1.SetOrigin(x, 0, 0)
plane1.SetNormal(-1, 0, 0)
plane2 = vtk.vtkPlane()
plane2.SetOrigin(0, y, 0)
plane2.SetNormal(0, -1, 0)
plane3 = vtk.vtkPlane()
plane3.SetOrigin(0, 0, z)
plane3.SetNormal(0, 0, -1)
plane4 = vtk.vtkPlane()
plane4.SetOrigin(xn, 0, 0)
plane4.SetNormal(1, 0, 0)
plane5 = vtk.vtkPlane()
plane5.SetOrigin(0, yn, 0)
plane5.SetNormal(0, 1, 0)
plane6 = vtk.vtkPlane()
plane6.SetOrigin(0, 0, zn)
plane6.SetNormal(0, 0, 1)
plane_list = [plane4, plane1, plane5, plane2, plane6, plane3]
planes = vtk.vtkPlaneCollection()
for item in plane_list:
planes.AddItem(item)
return planes
[nx, ny, nz] = [2, 2, 8] # amount of unit cells
cell_size = 1
gradient_value = 0.04 # only values below 0.1 produce the desired geometry; this term is essential
x, y, z = np.mgrid[-cell_size*(nx + 1)/2:cell_size*(nx + 1)/2:100j,
-cell_size*(ny + 1)/2:cell_size*(ny + 1)/2:100j,
-cell_size*(nz + 1)/2:cell_size*(nz + 1)/2:100*2j] * np.pi / (cell_size/2)
# engine = Engine()
engine = OffScreenEngine() # do not start mayavi GUI
engine.start()
fig = mlab.figure(figure=None, engine=engine)
contour3d = mlab.contour3d(x, y, z, schwarz_D(x, y, z, gradient_value), figure=fig)
scene = engine.scenes[0]
actor = contour3d.actor.actors[0]
iso_surface = scene.children[0].children[0].children[0]
iso_surface.contour.minimum_contour = 0
iso_surface.contour.number_of_contours = 1
iso_surface.compute_normals = False
iso_surface.contour.auto_update_range = False
mlab.draw(fig)
# mlab.show() # enable if you want to see the mayavi GUI
polydata = tvtk.to_vtk(actor.mapper.input) # convert tvtkPolyData to vtkPolyData
# Move object to the coordinate center to make clipping easier later on.
center_coords = np.array(polydata.GetCenter())
center = vtk.vtkTransform()
center.Translate(-center_coords[0], -center_coords[1], -center_coords[2])
centerFilter = vtk.vtkTransformPolyDataFilter()
centerFilter.SetTransform(center)
centerFilter.SetInputData(polydata)
centerFilter.Update()
# Reverse normals in order to receive a closed surface after clipping
reverse = vtk.vtkReverseSense()
reverse.SetInputConnection(centerFilter.GetOutputPort())
reverse.ReverseNormalsOn()
reverse.ReverseCellsOn()
reverse.Update()
bounds = np.asarray(reverse.GetOutput().GetBounds())
clip = vtk.vtkClipClosedSurface()
clip.SetInputConnection(reverse.GetOutputPort())
clip.SetTolerance(10e-3)
# clip.TriangulationErrorDisplayOn() # enable to see errors for not watertight surfaces
clip.SetClippingPlanes(plane_collection(bounds[0] + cell_size/2, bounds[1] - cell_size/2,
bounds[2] + cell_size/2, bounds[3] - cell_size/2,
bounds[4] + cell_size/2, bounds[5] - cell_size/2))
clip.Update()
# Render the result
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(clip.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
renderer = vtk.vtkRenderer()
renderWindow = vtk.vtkRenderWindow()
renderWindow.AddRenderer(renderer)
renderWindowInteractor = vtk.vtkRenderWindowInteractor()
renderWindowInteractor.SetRenderWindow(renderWindow)
renderer.AddActor(actor)
renderWindow.Render()
renderWindowInteractor.Start()
This really is a short as it gets, I stripped as much as I could. The problem still persists and I can't figure out a solution.
Try using pymeshfix. I had a very similar problem with some low-res mandelbulbs I was generating.
You may also want ot check out pyvista, it's a nice python wrapper for vtk.
Great problem and thanks for the example.
I was able to get this to work in pyvista with some modifications:
import numpy as np
import pyvista as pv
def schwarz_D(x, y, z, linear_term=0):
"""This is the function for the Schwarz Diamond level surface."""
return (np.sin(x) * np.sin(y) * np.sin(z) + np.sin(x) * np.cos(y) * np.cos(z) +
np.cos(x) * np.sin(y) * np.cos(z) + np.cos(x) * np.cos(y) * np.sin(z)) - linear_term * z
# Create the grid
[nx, ny, nz] = [2, 2, 8] # amount of unit cells
cell_size = 1
gradient_value = 0.04 # only values below 0.1 produce the desired geometry; this term is essential
x, y, z = np.mgrid[-cell_size*(nx + 1)/2:cell_size*(nx + 1)/2:100j,
-cell_size*(ny + 1)/2:cell_size*(ny + 1)/2:100j,
-cell_size*(nz + 1)/2:cell_size*(nz + 1)/2:100*2j] * np.pi / (cell_size/2)
# make a grid and exclude cells below 0.1
grid = pv.StructuredGrid(x, y, z)
grid['scalars'] = schwarz_D(x, y, z, gradient_value).ravel(order='F')
contour = grid.clip_scalar(value=0.1)
# make a bunch of clips
# bounds = contour.bounds
# contour.clip(origin=(bounds[0] + cell_size/2, 0, 0), normal='-x', inplace=True)
# contour.clip(origin=(0, bounds[1] - cell_size/2, 0), normal='-y', inplace=True)
# contour.clip(origin=(0, 0, bounds[2] + cell_size/2), normal='-z', inplace=True)
# contour.clip(origin=(bounds[3] - cell_size/2, 0, 0), normal='x', inplace=True)
# contour.clip(origin=(0, bounds[4] + cell_size/2, 0), normal='y', inplace=True)
# contour.clip(origin=(0, 0, bounds[5] - cell_size/2), normal='z', inplace=True)
contour.plot(smooth_shading=True, color='w')
I'm not sure why you're using clipping planes, and I think that you would be better off simply limiting your x, y, and z ranges put into creating the grids. That way, you wouldn't have to clip the final mesh.

Replicating this design in Python

I've been trying to replicate this design using Python.
I am using the Graphics module, obtained here. I can't use any other Graphic Module at the moment.
This code here allows me to draw 5 circles in a line repeated by a loop.
def fdShape():
win = GraphWin(199,199)
centre = Point(20,100)
for y in range(5):
for x in range(5):
centre = Point(x * 40 + 20, y * 40 + 60)
circle = Circle(centre, 20)
circle.setOutline("red")
circle.draw(win)
One problem that I've found with this code is that it leaves a blank line at the top of the window and place the last circle line at the bottom, beyond the borders of the window. Thats the first problem.
The second is using the code to display semi circles displayed in red. As you can see in the image at the top of this page. I'm unsure of how to replicate this picture using Python.
Thank you!
This looks pretty close:
from graphic import *
def main():
repeat = 5
diameter = 40
radius = diameter // 2
offset = radius // 2
win = GraphWin("Graphic Design", diameter*repeat + offset, diameter*repeat)
win.setBackground('white')
for i in range(repeat):
for j in range(repeat):
draw_symbol(win, i % 2,
Point(i*diameter + offset, j*diameter), radius, 'red')
win.getMouse()
win.close()
def draw_symbol(win, kind, lower_left, radius, colour):
centre = Point(lower_left.x+radius, lower_left.y+radius)
circle = Circle(centre, radius)
circle.setOutline('')
circle.setFill(colour)
circle.draw(win)
if kind == 0:
rectangle = Rectangle(lower_left,
Point(lower_left.x+radius, lower_left.y+radius*2))
else:
rectangle = Rectangle(lower_left,
Point(lower_left.x+radius*2, lower_left.y+radius))
rectangle.setOutline('white')
rectangle.setFill('white')
rectangle.draw(win)
circle = Circle(centre, radius)
circle.setWidth(1)
circle.setOutline(colour)
circle.setFill('')
circle.draw(win)
main()
Two issues I see.
GraphWin should be initialized to 200x200, not 199x199.
This line:
centre = Point(x * 40 + 20, y * 40 + 60)
Should most likely be:
centre = Point(x * 40 + 20, y * 40 + 20)

Distance between two objects

So I've been trying to figure out how to find the distance between two objects on a canvas and I've exhausted most relevant links on Google with little success.
I'm trying to make it so that it calculates the distance between the drawn ovals and the line on the canvas.
from __future__ import division
from Tkinter import *
import tkMessageBox
class MyApp(object):
def __init__(self):
self.root = Tk()
self.root.wm_title("Escape")
self.canvas = Canvas(self.root, width=800, height=800, bg='white')
self.canvas.pack()
self.canvas.create_line(100, 100, 200, 200, fill='black')
self.canvas.bind("<B1-Motion>", self.tracer)
self.root.mainloop()
def tracer(self, e):
self.canvas.create_oval(e.x-5, e.y-5, e.x+5, e.y+5, fill='blue', outline='blue')
rx = "%d" % (e.x)
ry = "%d" % (e.y)
print rx, ry
MyApp()
Two circles:
dist = math.sqrt((circle1.x-circle2.x)**2 + (circle1.y-circle2.y)**2) - circle1.r - circle2.r
Kind of obvious, their Euclid distance is calculated using the Pythagorean theorem.
Point/segment:
a = segment[1].y - segment[0].y
b = segment[0].x - segment[1].x
c = - segment[0].x * a - segment[0].y * b
dx = segment[1].x - segment[0].x
dy = segment[1].y - segment[0].y
nc0 = - segment[0].x * dx - segment[0].y * dy
nc1 = - segment[1].x * dx - segment[1].y * dy
if ((dx * x + dy * y + nc0) < 0) dist = math.sqrt((x-segment[0].x)**2 + (y-segment[0].y)**2)
elif((dx * x + dy * y + nc1) < 0) dist = math.sqrt((x-segment[1].x)**2 + (y-segment[1].y)**2)
else dist = (a*x + b*y + c) / math.sqrt(a**2 + b**2)
Circle/segment - same as point/segment, just substract circle's radius
Polygon/polygon - Loop through each vertex of polygon 1 and segment of polygon 2, then loop through each vertex of polygon 2 and segment of polygon 1, then find the smallest.
Don't use magic numbers inside your code. That radius of 5 isn't good.
dist = math.sqrt((oval.x-point.x)**2 + (oval.y-point.y)**2)
not really sure if this answers your question but this is the distance formula
there are several problems with the original question.
at what point are you trying to find the distance?
you are not saving the oval positions so it will be very hard to look up later
why are you trying to get the distance?/what are you planning on doing with it?
are you trying to find distance to edge or distance to center?
in general I would guess you want to get it in the def trace function
step 1. Point of interest is (e.x,e.y)
step 2. Find the closest point on the line (line = (100,100) to (200,200) ) (this is probably a question in its own right. http://nic-gamedev.blogspot.com/2011/11/using-vector-mathematics-and-bit-of_08.html )
step 3. Apply distance method to point of interest and closest point on line
def dist(pt1,pt2):
return ((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)**.5
on another side note ... your ovals look an awful lot like circles ...

Categories