I am using Turtle Graphics in Python for a larger program. I am able to return the point that the user clicks on using turtle.onscreenclick
However, I would like to extract the RGB color of the point that the user clicks on. Can this even be done in turtle graphics and how can this be accomplished? Thank you!
import turtle
# Global variables specifying the point clicked
xclick = 0
yclick = 0
# Draw a rectangle that is red
height = float(50)
length = height *(1.9)
length = round(length,2)
turtle.begin_fill()
turtle.color("red")
turtle.down()
turtle.forward(length)
turtle.right(90)
turtle.forward(height)
turtle.right(90)
turtle.forward(length)
turtle.right(90)
turtle.forward(height)
turtle.right(90)
turtle.end_fill()
# Gets the click
def getcoordinates():
turtle.onscreenclick(turtle.goto)
turtle.onscreenclick(modifyglobalvariables)
# Modifies the global variables
def modifyglobalvariables(rawx,rawy):
global xclick
global yclick
xclick = int(rawx//1)
yclick = int(rawy//1)
print(xclick)
print(yclick)
getcoordinates()
turtle.done()
turtle doesn't have function to get pixel color. It uses tkinter (and widget tkinter.Canvas - turtle.getcanvas()) to display everything but it doesn't have function to get pixel color too.
Canvas keeps all as objects and second answer for "Get pixel colors of tkinter canvas" shows how to get color of object in position (x,y). Maybe it will work for you.
EDIT: I made working example
canvas uses different coordinates - it needed to change y = -y
import turtle
# --- functions --- (lower_case_names)
def get_pixel_color(x, y):
# canvas use different coordinates
y = -y
canvas = turtle.getcanvas()
ids = canvas.find_overlapping(x, y, x, y)
if ids: # if list is not empty
index = ids[-1]
color = canvas.itemcget(index, "fill")
if color != '':
return color.lower()
return "white" # default color
def modify_global_variables(rawx,rawy):
global xclick
global yclick
xclick = int(rawx)
yclick = int(rawy)
print(get_pixel_color(xclick, yclick))
def draw_rect(x1, y1, width, height, color):
y1 = -y1
canvas = turtle.getcanvas()
canvas.create_rectangle((x1, y1, x1+width, y1+height), fill=color, width=0)
# --- main ---
# Global variables specifying the point clicked
xclick = 0
yclick = 0
# Draw a rectangle that is red
height = 50.0 # now it is float
length = height * 1.9
length = round(length, 2)
turtle.down()
turtle.color("RED")
turtle.begin_fill()
for _ in range(2):
turtle.forward(length)
turtle.right(90)
turtle.forward(height)
turtle.right(90)
turtle.end_fill()
# Use tkinter.Canvas to draw rectangle
draw_rect(100, 100, length, height, 'green')
# Gets the click & Modifies the global variables
turtle.onscreenclick(modify_global_variables)
turtle.done()
Related
it has no error now but i want to draw a chess board but this thing just randomly output something else:)))))
i think there is something wrong with the fill function tho
import turtle
screen = turtle.Screen()
screen.setup(800,800)
screen.bgcolor('pink')
screen.colormode(255)
screen.title("chessboard")
turtle = turtle.Turtle()
def draw(length,angle):
for i in range (4):
turtle.forward(length)
turtle.right(angle)
length = 30
angle = 90
coor = -120
num = 64
def board(length):
draw(length,angle)
turtle.forward(length)
def limit():
for q in range (8):
board(length)
def pos(coor,length):
for w in range(8):
turtle.penup()
turtle.setpos(coor,coor+30*w)
turtle.pendown()
limit()
def fill():
for q in range (8):
for w in range(8):
if (q+w)%2==0:
turtle.fillcolor('#000000')
else:
turtle.fillcolor('#FFFFFF')
def repeat():
for h in range (8):
draw(length,angle)
turtle.forward(length)
def color():
turtle.begin_fill()
repeat()
pos(coor,length)
fill()
turtle.end_fill()
turtle.speed(0)
turtle.penup()
turtle.goto(coor, coor)
turtle.pendown()
turtle.hideturtle()
screen.update()
color()
it has no error now but i want to draw a chess board but this thing just randomly output something else:)))))
i think there is something wrong with the fill function tho
I'm just learning python as well, so I found your problem being an interesting thing to try to do myself.
Please see below a solution that works, using some ideas from your code and my own implementation too.
If anything of the above helps you fix your own code, or have any questions I might be able to answer, please let me know :)
import turtle
# draw a square at given pos
def draw_square(t, fwd, angle):
for _ in range(4):
t.forward(fwd)
t.right(angle)
def changePos(t, startPos, endPos, penUp):
if penUp:
t.pu()
t.goto(startPos, endPos)
if penUp:
t.pd()
# Let's skip the drawing all together?
turtle.tracer(0, 0)
# taking input for the side of the square
squareSize = 100
angle = 90
boardSquares = 8
startPos = -(squareSize * (boardSquares/2)), (squareSize * (boardSquares/2)) #start from top-left
print("Start position: ", startPos)
screen = turtle.Screen()
screen.setup(850, 850)
screen.bgcolor('pink')
screen.title("chessboard")
# create the white squares
squares = turtle.Turtle()
squares.hideturtle()
squares.fillcolor("white")
squares.pensize(1)
squares.pencolor("red")
changePos(squares, startPos[0], startPos[1], True)
for r in range(8):
for c in range(8):
if (r + c) % 2 == 0:
squares.fillcolor('#000000')
else:
squares.fillcolor('#FFFFFF')
changePos(squares, startPos[0] + (squareSize * c), startPos[1] - (squareSize * r), True)
squares.begin_fill()
draw_square(squares, squareSize, angle)
squares.end_fill()
# let's draw the board edge
board = turtle.Turtle()
board.hideturtle()
changePos(board, startPos[0], startPos[1], True)
board.pensize(5)
board.pencolor("brown")
draw_square(board, boardSquares * squareSize, angle)
turtle.update()
turtle.exitonclick()
import turtle as t
from random import randint, random
def draw_star(points, size, col, x, y):
t.penup()
t.goto(x, y)
t.pendown()
angle = 180 - (180 / points)
t.color(col)
t.begin_fill()
for i in range(points):
t.forward(size)
t.right(angle)
t.end_fill()
# Main code
while True:
ranPts = randint(2, 5) * 2 + 1
ranSize = randint(10, 50)
ranCol = (random(), random(), random())
ranX = randint(-350, 300)
ranY = randint(-250, 250)
draw_star(ranPts, ranSize, ranCol, ranX, ranY)
Question:
How could I know the maximum values of coordinates of my screen? So I can have a better idea on how to set the values of ranX and ranY?
Thanks.
You could use t.setworldcoordinates(llx, lly, urx, ury)
The parameters:
llx = x of lower left corner
lly = y of lower left corner
urx= x of upper right corner
ury = y of upper right corner
You can create a function and find the values of coordinates yourself by clicking on the screen like this:
# turtle library
import turtle
#This to make turtle object
tess=turtle.Turtle()
# self defined function to print coordinate
def buttonclick(x,y):
print("You clicked at this coordinate({0},{1})".format(x,y))
#onscreen function to send coordinate
turtle.onscreenclick(buttonclick,1)
turtle.listen() # listen to incoming connections
turtle.speed(10) # set the speed
turtle.done() # hold the screen
This will print everytime you click on the screen and print the coordinates out.
The screensize() function returns the canvas width and the canvas height as a tuple.
You can use this to find the max coordinates of the canvas.
screenSize = t.screensize() #returns (width, height)
# Main code
while True:
ranPts = randint(2, 5) * 2 + 1
ranSize = randint(10, 50)
ranCol = (random(), random(), random())
ranX = randint(50-screenSize[0], screenSize[0] - 100)
ranY = randint(50-screenSize[1], screenSize[1] - 100)
draw_star(ranPts, ranSize, ranCol, ranX, ranY)
I found out this is what I need: t.window_width() and t.window_height().
I created 2 drawing modes but they draw at the same time but i want them to work seperateley so i created a button but dont know how to define the mode change for the button to work
how can I define my mode change so I can change between different drawing modes eg freehandmode, circlemode so they dont work at the same time
from turtle import Screen, Turtle
beni=Screen()
beni.setup(400, 400, 10, 10)
beni.setworldcoordinates(-300, -300, 300, 300)
def mode_change(x, y):
#freehandmode
def freehandmode(x, y):
t.ondrag(None)
t.setheading(t.towards(x, y))
t.goto(x, y)
t.ondrag(freehandmode)
#circlemode
def draw_circle(x, y):
beni.onclick(None)
center = turtle.position()
turtle.setposition(x, y)
turtle.setheading(turtle.towards(center) - 90)
turtle.pendown()
turtle.circle(turtle.distance(center))
turtle.penup()
turtle.clearstamps()
beni.onclick(pick_center)
def pick_center(x, y):
beni.onclick(None)
turtle.setposition(x, y)
turtle.stamp()
beni.onclick(draw_circle)
turtle = Turtle()
turtle.hideturtle()
turtle.shape('circle')
turtle.shapesize(0.5)
turtle.penup()
beni.onclick(pick_center)
#freehand turtle
t = Turtle('circle')
t.shapesize(1)
t.speed('fastest')
t.ondrag(freehandmode)
#modechange turtle
modechange = Turtle('circle')
modechange.pu()
modechange.shapesize(0.55, 1.45)
modechange.color('black')
modechange.setpos(0, 290)
modechange.onclick(mode_change)
#modebutton
modebutton = Turtle()
modebutton.shapesize(0.25)
modebutton.pu()
modebutton.setpos(-20, 300)
modebutton.setheading(270)
modebutton.pd()
modebutton.fd(20)
modebutton.setheading(0)
modebutton.fd(45)
modebutton.setheading(90)
modebutton.fd(20)
modebutton.setheading(180)
modebutton.fd(45)
modebutton.pu()
modebutton.setpos(-10, 263)
modebutton.setheading(0)
modebutton.color('green')
modebutton.write('Modi')
modebutton.hideturtle()
beni.mainloop()
It's mostly a matter of accounting -- you need to explicitly list steps necessary to disable one turtle and enable the other every time you switch modes. And you need to keep track of the current mode. Here's my rework and simplification of your (actually, largely my) code to do this:
from turtle import Screen, Turtle, mainloop
def mode_change(x, y):
global mode
if mode == 'freehand':
# disable freehand
freehand.ondrag(None)
freehand.hideturtle()
# enable circle
screen.ontimer(lambda: screen.onclick(pick_center)) # so screen doesn't inherit *this* click
mode = 'circle'
else:
# disable circle
screen.onclick(None)
# enable freehand
freehand.ondrag(freehandmode)
freehand.showturtle()
mode = 'freehand'
# freehand mode
def freehandmode(x, y):
freehand.ondrag(None)
freehand.setheading(freehand.towards(x, y))
freehand.goto(x, y)
freehand.ondrag(freehandmode)
# circle mode
def draw_circle(x, y):
screen.onclick(None)
center = circle.position()
circle.setposition(x, y)
circle.setheading(circle.towards(center) - 90)
circle.pendown()
circle.circle(circle.distance(center))
circle.penup()
circle.clearstamps()
screen.onclick(pick_center)
def pick_center(x, y):
screen.onclick(None)
circle.setposition(x, y)
circle.stamp()
screen.onclick(draw_circle)
screen = Screen()
screen.setup(400, 400)
mode = 'freehand'
circle = Turtle('circle', visible=False)
circle.shapesize(0.5)
circle.speed('fastest')
circle.penup()
freehand = Turtle('circle')
freehand.speed('fastest')
freehand.ondrag(freehandmode)
# modechange turtle
modechange = Turtle('square')
modechange.penup()
modechange.setposition(0, 155)
modechange.pencolor('green')
modechange.write('Modi', align='center')
modechange.shapesize(2.25, 1.0)
modechange.setposition(0, 190)
modechange.onclick(mode_change)
mainloop()
I want to be able to have both of my turtle onclick events function, but only one of them functions. I have a function that draws a square at the location the user clicks, and I have a close button that closes the program when you click it. Only one of these functions work at a time.
import turtle
import math
turtle.penup()
def square(x, y):
turtle.up()
turtle.goto(x, y)
turtle.down()
for i in range(4):
turtle.forward(50)
turtle.left(90)
def closebutton(location1):
(x,y) = location1
turtle.up()
turtle.setposition(location1)
turtle.down()
for i in range(2):
turtle.forward(40)
turtle.left(90)
turtle.forward(25)
turtle.left(90)
turtle.up()
turtle.forward(7.5)
turtle.left(90)
turtle.forward(5)
turtle.right(90)
turtle.write("close")
def btnclick(x, y):
if x > 100 and x < 141 and y > -100 and y < -75:
quit()
turtle.onscreenclick(btnclick)
closebutton((100,-100))
turtle.onscreenclick(square)
def square(x, y):
# Check area is Close button
if x > 100 and x < 141 and y > -100 and y < -75:
quit()
turtle.up()
turtle.goto(x, y)
turtle.down()
for i in range(4):
turtle.forward(50)
turtle.left(90)
I think, at square function, we can check that is close button's area
We can solve this the way you attempted, but we have to add the screen click handlers in a specific order and take advantage of the little-used add parameter:
from turtle import Screen, Turtle, mainloop
FONT_SIZE = 20
FONT = ('Arial', FONT_SIZE, 'normal')
def draw_square(x, y):
turtle.penup()
turtle.goto(x, y)
turtle.pendown()
for _ in range(4):
turtle.forward(50)
turtle.left(90)
def button_clicked(x, y):
if 100 < x < 100 + text_width and -FONT_SIZE/2 - 100 < y < FONT_SIZE/2 - 100:
quit()
def make_close_button(x, y):
turtle.penup()
turtle.setposition(x, y - FONT_SIZE / 2)
turtle.write("close", move=True, font=FONT)
width = turtle.xcor() - x # pixel width of text we just wrote
turtle.pendown()
for _ in range(2):
turtle.left(90)
turtle.forward(FONT_SIZE)
turtle.left(90)
turtle.forward(width)
turtle.penup()
turtle.home()
return width
turtle = Turtle()
text_width = make_close_button(100, -100)
screen = Screen()
screen.onscreenclick(button_clicked)
screen.onscreenclick(draw_square, add=True)
mainloop()
But this approach makes it hard to disable the onscreenclick() event inside the event handler code. That is, while one square is still being drawn, you can click elsewhere on the screen and start a second, interfering with the completion of the first. To solve this we might try a completely different approach with a single event handler function that we can disable and reenable as needed:
from turtle import Screen, Turtle, mainloop
FONT_SIZE = 20
FONT = ('Arial', FONT_SIZE, 'normal')
def draw_square():
for _ in range(4):
turtle.forward(50)
turtle.left(90)
def button_clicked(x, y):
screen.onscreenclick(None) # disable event handler inside event handler
if button.distance(x, y) < FONT_SIZE:
quit()
turtle.penup()
turtle.goto(x, y)
turtle.pendown()
draw_square()
screen.onscreenclick(button_clicked)
def make_close_button(x, y):
button = Turtle(visible=False)
button.speed('fastest')
button.penup()
button.setposition(x, y - FONT_SIZE / 2)
button.write("close", move=True, font=FONT)
width = button.xcor() - x # pixel width of text we just wrote
button.pendown()
for _ in range(2):
button.left(90)
button.forward(FONT_SIZE)
button.left(90)
button.forward(width)
button.penup()
button.setposition(x + width / 2, y)
return button
button = make_close_button(100, -100)
turtle = Turtle()
screen = Screen()
screen.onscreenclick(button_clicked)
mainloop()
Here we broke up the functionality a bit to have functions with specific duties. And instead of figuring out if we clicked near the button, we left a turtle behind and just check the distance of the click from that turtle.
Both approaches have their advantages and disadvantages.
I am trying to find the color of the canvas under a Python turtle. I use canvas.find_overlapping but it is only successful when I negate the ycor, implying that the y-axis is inverted in the canvas object, compared to what is shown. Is there a problem with my code or is the y-axis inverted?
import turtle
wn = turtle.Screen()
maze_drawer = turtle.Turtle()
maze_drawer.color("purple")
maze_drawer.speed("fastest")
path_width = 15
def get_pixel_color(x, y):
c = turtle.Screen().getcanvas()
# -y should not work??
items = c.find_overlapping(x, -y, x, -y)
if len(items) > 0:
return c.itemcget(items[0], "fill") # get 0 object (canvas)
# draw simplified maze
wall_len = 0
for i in range(10):
maze_drawer.left(90)
wall_len += path_width
maze_drawer.forward(wall_len)
# navigate maze from center
maze_runner = turtle.Turtle()
maze_runner.color("green")
maze_runner.penup()
maze_runner.goto(-path_width, -path_width)
# test in y dir: maze_runner.setheading(90)
clear = True
while(clear):
maze_runner.forward(1)
color_at_turtle = get_pixel_color(maze_runner.xcor(), maze_runner.ycor())
if (color_at_turtle == "purple"):
clear = False
wn.exitonclick()
Neat use of tkinter pixel detection within turtle! If the inverted Y coordinate is bothersome, you can flip it from turtle's perspective:
from turtle import Screen, Turtle
screen = Screen()
width, height = screen.window_width() / 2, screen.window_height() / 2
screen.setworldcoordinates(-width, height, width, -height) # flip Y coordinate
Then your code doesn't have to think about negating Y as long as you know you're drawing upside down:
from turtle import Screen, Turtle
PATH_WIDTH = 15
def get_pixel_color(x, y):
canvas = screen.getcanvas()
items = canvas.find_overlapping(x, y, x, y)
if items:
return canvas.itemcget(items[0], "fill") # get 0 object (canvas)
return None
screen = Screen()
width, height = screen.window_width() / 2, screen.window_height() / 2
screen.setworldcoordinates(-width, height, width, -height)
maze_drawer = Turtle(visible=False)
maze_drawer.color("purple")
maze_drawer.speed("fastest")
# draw simplified maze
wall_len = 0
for _ in range(20):
maze_drawer.left(90)
wall_len += PATH_WIDTH
maze_drawer.forward(wall_len)
# navigate maze from center
maze_runner = Turtle()
maze_runner.color("dark green", "green")
maze_runner.penup()
maze_runner.goto(-PATH_WIDTH, -PATH_WIDTH)
def run_maze():
maze_runner.forward(1)
x, y = maze_runner.position()
color_at_turtle = get_pixel_color(x, y)
if color_at_turtle == "purple":
maze_runner.backward(PATH_WIDTH - 1)
maze_runner.left(90)
x, y = maze_runner.position()
if -width < x < width and -height < y < height:
screen.ontimer(run_maze, 10)
run_maze()
screen.exitonclick()