Edge detection in Python - python

I am trying to write a program where a user enters a number, and it draws that many rectangles to the screen, however the triangles cannot overlap. I have had a problem with that last part, and I am looking for some help. I borrowed the edge detection methods from an Al Sweigart book, and the full program he wrote can be found here:
http://inventwithpython.com/chapter18.html
Here is the program I am working on:
http://pastebin.com/EQJVH6xr
import pygame, sys, random
from pygame.locals import *
def doRectsOverlap(rect1, rect2):
for a, b in [(rect1, rect2)]:
# Check if a's corners are inside b
if ((isPointInsideRect(a.left, a.top, b)) or
(isPointInsideRect(a.left, a.bottom, b)) or
(isPointInsideRect(a.right, a.top, b)) or
(isPointInsideRect(a.right, a.bottom, b))):
return True
return False
def isPointInsideRect(x, y, rect):
if (x > rect.left) and (x < rect.right) and (y > rect.top) and (y < rect.bottom):
return True
else:
return False
# set up pygame
pygame.init()
# set up the window
WINDOWWIDTH = 600
WINDOWHEIGHT = 600
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)
pygame.display.set_caption('Rectangles')
# set up the colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
from random import choice
foo = [BLACK, RED, GREEN, BLUE]
# draw the background
windowSurface.fill(WHITE)
print('Please enter a number:')
number = input()
x = 0
array = []
for i in array:
while int(number) > x:
x = x+1
x1 = random.randint(1, 400)
y1 = random.randint(1, 400)
x2 = random.randint(1, 400)
y2 = random.randint(1, 400)
x3 = random.randint(1, 400)
y3 = random.randint(1, 400)
x4 = random.randint(1, 400)
y4 = random.randint(1, 400)
box = pygame.draw.rect(windowSurface,random.choice(foo), (x1, y1, x2, y2))
if doRectsOverlap(box, box) == False:
box
else:
x = x-1
# draw the window onto the screen
pygame.display.update()
Any help would be greatly appreciated. Thank you!

Well as a general answer, you are going to have to plot four co ordinance for each rectangle.
There are a few ways you can do this:
1) Just have the rectangle randomly placed and test if any of the new rectangle's points are inside of any of the existing rectangles. If they are, just keep generating until they are not. This will be very slow and inefficient though.
2) You can number crunch by restricting all of your possible random's to only available positions. This is can be done a variety of ways, it will be semi slow and probably fairly difficult to implement.
3) You can have the rectangles generate like in option 1, but in the case that the co-ordinance of the 4 points overlap, you can push the points off. To do this you simply have to set the violating co-ordinance to the co-ordinance of one of the corners and then add say, (5,5) or subtract or whatever. If you don't want to skew the rectangles too much you can regenerate the violating rectangle based on the modified point, or push all of the points an equivalent distance as the violating point.
I think option 3 is probably the best unless you are very strict on your random principles.
If you want me to clarify any of the above options let me know specifically what you want me to explain and I will do so. I can not however explain all of the possibilities for each option because it would take to long and way too many lines.
Cheers

Related

I can't figure out how to make this function work properly

so im trying to create a function for my game in pycharm but i cannot figure out how to make this specific function work properly. what im trying to do is as follows: i want to make a functon called wall. this will make a wall tile and a rect appear. i have that done. however, the second part requires me to be able to define a width and a height for it. as it is a small but tileable img, i want to be able to define how many tiles down, and how many tiles right, so it will make a square or a rectangle of some sort. here is the code:
def wall(x, y, length,height,):
length = 0
height = 0
pygame.draw.rect(screen, BLACK, (x, y, 32, 32), 0)
screen.blit(wall_surf, (x, y))
for length in range(length):
wall(x + 32, y, 1, 1)
for height in range(height):
wall(x, y + 32, 1, 1)
and here is the command i want to be able to use:
wall(0, 0, 2, 2)
ideally, this should make a 2 x 2 square at the top corner. the length and height are the second two numbers, the x and y are the first.
unfortunately, the for loop does not work for either of them. all the code does rn is make a single tile in the top left.
please explain simplistically, as i am just starting to code with python.
edit: here is the new code:
length = 0
height = 0
x = 0
y = 0
def wall(x, y, length,height,):
pygame.draw.rect(screen, BLACK, (x, y, 32, 32), 0)
screen.blit(wall_surf, (x, y))
for l in range(length):
wall(x + 32, y + height, 1, 1)
ok, with the new code that #picklepick has given me, i get an error: line 64, in
wall(x, y)
TypeError: wall() missing 2 required positional arguments: 'rows' and 'cols'
i havent changed wall to my_draw_rect though.
Posting as an answer because it's hard to show in comments. This should get you started. You'll need to modify and test it yourself in order to understand how it works. Update your question when you're stuck again.
I changed the variable names for length & height to better reflect what's going on
# draw 5x5 rects
rows = 5
cols = 5
# this should make the nested loops a bit clearer
for x in range(rows):
for y in range(cols):
print(x, y)
Next, instead of just printing the values you want to draw rectangles with the given coordinates:
# this should make the nested loops a bit clearer
for x in range(rows):
for y in range(cols):
my_draw_rect(x, y) # this would be your (improved) wall()
my_draw_rect() would be your function that actually draw's the matrix of rectangles. It takes a x and y position and then draws a square at that position. There are many threads on this website (and many others) that show how to draw a square.
I haven't used pygame in a while but your wall would look something like:
import pygame
from pygame.locals import *
SIZE = 500, 200
RED = (255, 0, 0)
GRAY = (150, 150, 150)
ROWS = 5
COLS = 5
pygame.init()
screen = pygame.display.set_mode(SIZE)
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
screen.fill(GRAY)
for x in range(ROWS):
for y in range(COLS):
pygame.draw.rect(screen, RED, (x * 15, y * 15, 10, 10))
pygame.display.flip()
pygame.quit()
# this should look something like this:
# [][][][][]
# [][][][][]
# [][][][][]
# [][][][][]
# [][][][][]
I quickly wrote this based on the pygame documentation, when you start coding it's usually helpful to first write the same code as in the examples to get you used to it.

Pygame doesn't draw rectangles on the screen after a specific threshold

I'm trying to make a visualisation for various sorting algorithms and I was playing around with Pygame to try and draw the rectangles that I need.
In the below code, the user is given multiples inputs: the lowest value of the list to be sorted, the highest value, and the number of elements the list is going to have. The elements are going to be randomly generated.
Then I'm getting the user's screen size so that I can have an appropriate window for the visualisation. Based on the visualisation window and the user's input, I'm setting up the width and height of the rectangles, so that each rectangle has the same width and that they are scaled based on the highest value.
Almost everything is nice and fine with this approach, but there's one thing that I can't figure out. It seems that setting the number of elements (n, in the code below) too high, the rectangles are not being drawn.
My asumption is that after a specific threshold, RECT_W, which is the width of the rectangles, becomes to small for Pygame to draw it.
What options do I have to solve it, except of having the number of elements smaller than a specific value?
import random
import pygame
import color_constants as colors
import ctypes
import copy
from pygame.locals import *
# Read data based on user's input
def readData():
listOfNumbers = []
data = dict()
print("Lowest Value: ")
numLow = int(input())
print("Highest Value: ")
numHigh = int(input())
print("Length of list: ")
n = int(input())
for i in range(0, n):
listOfNumbers.append(random.randint(numLow, numHigh))
origLst = copy.copy(listOfNumbers)
data.update({'lst': origLst})
data.update({'numLow': numLow})
data.update({'numHigh': numHigh})
data.update({'n': n})
data.update({'sorted': listOfNumbers})
return data
if __name__ == "__main__":
data = readData()
# Getting the user's screen size
user32 = ctypes.windll.user32
SCREENSIZE = user32.GetSystemMetrics(0)-100, user32.GetSystemMetrics(1)-100
SCREEN_W = SCREENSIZE[0]
SCREEN_H = SCREENSIZE[1]
# Setting and scaling the size of rectangle based on the number of elements (n)
# and the highest number (numHigh)
RECT_W = SCREEN_W // data['n']
RECT_H = SCREEN_H / (data['numHigh'])
# Setting up the color literals
RED = (255, 255, 255)
GRAY = (0, 0, 0)
pygame.init()
screen = pygame.display.set_mode(SCREENSIZE)
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
screen.fill(GRAY)
for i in range(data['n']):
rect = Rect(i*RECT_W, 0, RECT_W, RECT_H * data['lst'][i])
rect.bottom = SCREEN_H
pygame.draw.rect(screen, RED, rect)
pygame.display.flip()
If data['n'] is greater than SCREEN_W, RECT_W is 0. A coordinate is truncated when drawing. You cannot draw a fraction of a pixel. The size of the rectangle can only be integral (0, 1, 2 ...). Hence, you cannot draw a rectangle with a size less than 1.
You can draw the rectangles on a large surface and scale down the surface. However, the rectangles get blurred. So, this is no good option.

Undraw all objects within a certain region Python Graphics

I needed to know whether it is possible to clear all objects within a given set of coordinates regardless of the name of the object. I don't have any code for this at the moment as I've been trying to brainstorm this for a while now but have come up with nothing. An example I can give is that there are squares and circles within the coordinates of (200,100) and (300,200) how would I delete everything within these coordinates?
Yes, this can be done. First, the GraphWin object keeps track of items drawn on it but doesn't officially export this list. So you should keep track of what you draw onto the window.
The items you want (e.g. Rectangle and Circle) belong to the class _BBox which has a getCenter() method you can use to manipulate objects within your boundaries:
from random import randrange
from graphics import *
win = GraphWin("My Example", 400, 400)
boundary = Rectangle(Point(200, 100), Point(300, 200))
boundary.setOutline('blue')
boundary.draw(win)
graphics = []
for _ in range(150):
circle = Circle(Point(randrange(400), randrange(400)), 5)
circle.setOutline('green')
circle.draw(win)
graphics.append(circle)
x, y = randrange(400), randrange(400)
rectangle = Rectangle(Point(x, y), Point(x + 10, y + 10))
rectangle.setOutline('orange')
rectangle.draw(win)
graphics.append(rectangle)
for graphic in graphics:
center = graphic.getCenter()
if 200 < center.getX() < 300 and 100 < center.getY() < 200:
graphic.setFill('red')
graphic.undraw()
win.getMouse()
win.close()

Anti-aliased Arc Pygame

I am trying to code a simple circle timer in Python using Pygame.
At the moment it looks like this:
As you can see, the blue line is very wavy and has white dots in it. I am achieving this blue line by using pygame.draw.arc() function, but it is not anti-aliased and looks bad. I would like it to be anti-aliased, but gfxdraw module which should let me achieve this, doesn't support arc width selection. Here's code snippet:
pygame.draw.arc(screen, blue, [center[0] - 120, center[1] - 120, 240, 240], pi/2, pi/2+pi*i*koef, 15)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 105, black)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 120, black)
I did it creating the arc with a polygon.
def drawArc(surface, x, y, r, th, start, stop, color):
points_outer = []
points_inner = []
n = round(r*abs(stop-start)/20)
if n<2:
n = 2
for i in range(n):
delta = i/(n-1)
phi0 = start + (stop-start)*delta
x0 = round(x+r*math.cos(phi0))
y0 = round(y+r*math.sin(phi0))
points_outer.append([x0,y0])
phi1 = stop + (start-stop)*delta
x1 = round(x+(r-th)*math.cos(phi1))
y1 = round(y+(r-th)*math.sin(phi1))
points_inner.append([x1,y1])
points = points_outer + points_inner
pygame.gfxdraw.aapolygon(surface, points, color)
pygame.gfxdraw.filled_polygon(surface, points, color)
The for loop could certainly be created more elegantly with a generator, but I am not very sophisticated with python.
The arc definitely looks nicer than pygame.draw.arc, but when I compare it to the screen rendering on my mac, there is room for improvement.
I am not aware of any pygame function that would solve this problem, meaning you basically have to program a solution yourself (or use something other than pygame), since draw is broken as you've noted and gfxdraw won't give you the thickness.
One very ugly but simple solution is to draw multiple times over the arc segments, always slightly shifted to "fill in" the missing gaps. This will still leave some aliasing at the very front of the timer arc, but the rest will be filled in.
import pygame
from pygame.locals import *
import pygame.gfxdraw
import math
# Screen size
SCREEN_HEIGHT = 350
SCREEN_WIDTH = 500
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREY = (150, 150, 150)
RED = (255,0,0)
# initialisation
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
done = False
clock = pygame.time.Clock()
# We need this if we want to be able to specify our
# arc in degrees instead of radians
def degreesToRadians(deg):
return deg/180.0 * math.pi
# Draw an arc that is a portion of a circle.
# We pass in screen and color,
# followed by a tuple (x,y) that is the center of the circle, and the radius.
# Next comes the start and ending angle on the "unit circle" (0 to 360)
# of the circle we want to draw, and finally the thickness in pixels
def drawCircleArc(screen,color,center,radius,startDeg,endDeg,thickness):
(x,y) = center
rect = (x-radius,y-radius,radius*2,radius*2)
startRad = degreesToRadians(startDeg)
endRad = degreesToRadians(endDeg)
pygame.draw.arc(screen,color,rect,startRad,endRad,thickness)
# fill screen with background
screen.fill(WHITE)
center = [150, 200]
pygame.gfxdraw.aacircle(screen, center[0], center[1], 105, BLACK)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 120, BLACK)
pygame.display.update()
step = 10
maxdeg = 0
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
maxdeg = maxdeg + step
for i in range(min(0,maxdeg-30),maxdeg):
drawCircleArc(screen,RED,(150,200),119,i+90,max(i+10,maxdeg)+90,14)
#+90 will shift it from starting at the right to starting (roughly) at the top
pygame.display.flip()
clock.tick(2) # ensures a maximum of 60 frames per second
pygame.quit()
Note that I have copied degreesToRadians and drawCircleArc from https://www.cs.ucsb.edu/~pconrad/cs5nm/08F/ex/ex09/drawCircleArcExample.py
I do not generally recommend this solution, but it might do in a pinch.
You are right, some pygame rendering functions do indeed suck, so you can achieve something like this with PIL instead.
pie_size = (40, 40) # defining constants
pil_img = PIL.Image.new("RGBA", pie_size) # PIL template image
pil_draw = PIL.ImageDraw.Draw(pil_img) # drawable image
pil_draw.pieslice((0, 0, *[ps - 1 for ps in pie_size]), -90, 180, fill=(0, 0, 0)) # args: (x0, y0, x1, y1), start, end, fill
This will create a PIL shape. Now we can convert it to pygame.
data = pil_img.tobytes()
size = pil_img.size
mode = pil_img.mode
pygame_img = pygame.image.fromstring(data, size, mode).convert_alpha()
But don't forget to pip install pillow and
import PIL.Image
import PIL.ImageDraw
Ok, this is really old, but why not try to draw pies instead. For example draw a pie, then an unfilled circle as the outside ring and then a filled circle as the inside and another unfilled circle as the inside ring.
So pie -> unfilled circle -> filled circle -> unfilled.
The order is somewhat arbitrary but if u still have this problem give it a try. (Btw I haven't tried it but I think it will work)
For my own uses, I wrote a simple wrapper function, and to deal with the spotty arc drawing, I used an ugly loop to draw the same arc several times.
def DrawArc(surface, color, center, radius, startAngle, stopAngle, width=1):
width -= 2
for i in range(-2, 3):
# (2pi rad) / (360 deg)
deg2Rad = 0.01745329251
rect = pygame.Rect(
center[0] - radius + i,
center[1] - radius,
radius * 2,
radius * 2
)
pygame.draw.arc(
surface,
color,
rect,
startAngle * deg2Rad,
stopAngle * deg2Rad,
width
)
I'm aware this is not a great solution, but it works alright for my uses.
An important note is I added that "width -= 2" to hopefully preserve the intended size of the arc at least a little more accurately, but this results in increasing the minimum width by 2.
In your case, you might want to consider doing something more to fix the issues this results in.
If the start and end aren't all that important, one can create many circles following an arc trajectory and when done ie small circles drawn 360 time, you finally have a big circle with no wavy effect:
MWE:
#!/usr/bin/env python3
import pygame
import math
# Initialize pygame
pygame.init()
# Set the screen size
screen = pygame.display.set_mode((400, 300))
# Set the center point of the arc
center_x = 200
center_y = 150
arc_radius = 100
circle_radius = 6
# Set the start and stop angles of the arc
start_angle = 0
stop_angle = 360
angle_step = 1
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Clear the screen
screen.fill((0, 0, 0))
# Draw the overlapping circles
for i in range(start_angle, stop_angle, angle_step):
angle = math.radians(i)
x = center_x + arc_radius * math.cos(angle)
y = center_y + arc_radius * math.sin(angle)
pygame.draw.circle(screen, "red", (int(x), int(y)), circle_radius)
# Update the display
pygame.display.flip()
pygame.quit()
Having a start_angle and stop_angle of 0 to 360 respectively yields a fill circle with an output:
To change it to a 1/3 of a circle, one would change the stop_angle from 360 to 120 (1/3 x 360 = 120) and this would then yield:

Colors not varying - python graphics

I'm trying to reproduce something to this effect.
import graphics
from graphics import color_rgb
import random
window= graphics.GraphWin("x", 600, 400)
stripes = input("How many stripes should be on the flag")
stripes = int(stripes)
count = 0
count = int(count)
P1=graphics.Point(0,0) #left corner - anchor point
for x in range(stripes): #loop for number of stripes
col= random.randint(1,255)
stepdim = 400/stripes #size of divisions
stepdim = int(stepdim)
shrink = count*stepdim
shrink = int(shrink)
stepdim = stepdim*10 #enlarge to an increment below the last
stepdim = stepdim-shrink
stepdim = int(stepdim)
P2=graphics.Point(600,stepdim) #bottom right corner - ever shrinking
outsiderec=graphics.Rectangle(P1,P2) #
outsiderec.setFill(color_rgb(100, col, 0))
outsiderec.draw(window)
count= count + 1
count= int(count)
window.getMouse()
window.close()
I'm instead receiving one flat color.
I assume the problem is in my rand(int). I don't really know the ins and outs of it. Is it not running more than once?
Using your code as base I have tried to reproduce the expected result.
import graphics
from graphics import color_rgb
import random
window= graphics.GraphWin("x", 600, 400)
stripes = input("How many stripes should be on the flag")
stripes = int(stripes)
#count = 0
#count = int(count)
#P1=graphics.Point(0,0) #left corner - anchor point
stepdim = 400/stripes #size of divisions
for x in range(stripes): #loop for number of stripes
#col= random.randint(1,255)
#stepdim = int(stepdim)
#shrink = count*stepdim
#shrink = int(shrink)
#stepdim = stepdim*10 #enlarge to an increment below the last
#stepdim = stepdim-shrink
#stepdim = int(stepdim)
#P2=graphics.Point(600,stepdim) #bottom right corner - ever shrinking
P1=graphics.Point(0, stepdim * x) #left corner - anchor point
P2=graphics.Point(600,stepdim * (x + 1)) #bottom right corner - ever shrinking
outsiderec=graphics.Rectangle(P1,P2)
#outsiderec.setFill(color_rgb(100, col, 0))
red = random.randint(1, 255)
green = random.randint(1, 255)
blue = random.randint(1, 255)
outsiderec.setFill(color_rgb(red, green, blue))
outsiderec.draw(window)
#count= count + 1
#count= int(count)
window.getMouse()
window.close()
I have:
commented out all those statement that aren't needed
moved stepdim = 400/stripes from inside the loop to outside because you don't need to compute the same value for every loop
moved the P1=graphics.Point(0,0) inside the loop with a slight modification just to point to the top left corner of the stripe
modified the P2=graphics.Point(600,stepdim) to point to the bottom right corner of the stripe
added a random calculation for the three color components red = random.randint(1, 255), green = random.randint(1, 255), and blue = random.randint(1, 255)
Observations:
I have changed a little bit the way the stripes are drawn. Instead of drawing a shrunk version for every loop, the modified version just draws a fixed size stripe in consecutive positions.

Categories

Resources