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.
Related
NOTE: This is a school-related assignment and I am in no capacity looking for a direct answer. Looking for support on coming up with algorithms as this is my first time with such a question
Programs Intended Purpose: Take a command line provided image and scale it up by a unit of 5. Use the RGB values from the original image to re-create it in a randomized fashion.
Algorithm Attempts:
Test image is 250x250 scaling up to 1250x1250. I've tried to break this into sections of 50(original image side divided by 5) and then use (r + g + b)/5 number of circles to generate the color needed. Ex: Color: (100,50,5) would use 20 red circles, 10 green circles, 1 blue circle in the 50x50 space, (100+50+5)/5 = 31, 20 + 10 + 1 = 31. The x and y coordinates of these circles inside the 50x50 space should be random.
My main issue here has been putting this into code.
Code Attempt 1: Is not related to algorithm, is simply attempt to print image using pygame.draw.circle(this is what I am required to use to make the circle)
import pygame
import sys
import random
image_name = sys.argv[1]
#Import Image
src_image = pygame.image.load(image_name)
(w,h) = src_image.get_size()
window = pygame.display.set_mode((w*5,h*5))
#Nested Loop To Iterate Through Rows And Columns Of Pixels
for y in range(h):
for x in range(w):
(r,g,b,_) = src_image.get_at((x,y))
print(f"{r},{g},{b},x:{x},y:{y}")
pygame.draw.circle(window,(r,g,b),(x*5,y*5),2)
pygame.display.update()
pygame.time.delay(5000)
Possible Solution, works as intended but is still a bit messy due to the randomization:
By dividing the r, g and b values by the scaling value, in this case 5, we get the number of circles needed to be drawn of each color in each block which is broken down to increments of 15. (reduced from 50 to allow more curves and edges of images to be shown as they were hidden with a randomization area of 50). Further luminance calculation is used to find any dark spots, specifically black, and prevent color from being printed in that section. 3 while loops are used to draw each colors separately as they are all drawn a different amount of times.
import pygame
import sys
import random
pygame.init()
image_name = sys.argv[1]
#Import Image
src_image = pygame.image.load(image_name)
#Get image size
(w,h) = src_image.get_size()
#Scale up and display window
window = pygame.display.set_mode((w*5,h*5))
#Nested Loop To Iterate Through Rows And Columns Of Pixels
for y in range(h):
for x in range(w):
#Get rgb values at x and y of image
(r,g,b,_) = src_image.get_at((x,y))
#chck if area is black
lum = (0.2126 * r + 0.7152 * g + 0.0788 * b)*255
#Calculate required number of circles
a = int(r/5)
k = int(g/5)
d = int(b/5)
#draw required number of circles
while(a > 0):
if(lum > 0.625):
pygame.draw.circle(window,(255,0,0),(random.randint((x*5)-15,(x*5)),random.randint((y*5)-15,(y*5))),1)
a-=1
while(k > 0):
if(lum > 0.625):
pygame.draw.circle(window,(0,255,0),(random.randint((x*5)-15,(x*5)),random.randint((y*5)-15,(y*5))),1)
k-=1
while(d > 0):
if(lum > 0.625):
pygame.draw.circle(window,(0,0,255),(random.randint((x*5)-15,(x*5)),random.randint((y*5)-15,(y*5))),1)
d-=1
#update screen
pygame.display.update()
#Keep window open until closed by user
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
You have to initialize pygame, before you can use any pygame feature:
pygame.init()
src_image = pygame.image.load(image_name)
You have to handle the events in the application loop. See pygame.event.get() respectively pygame.event.pump():
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system.
It is recommended to use an application loop.
Create a list of colors and shuffle it with random.shuffle:
import pygame
import sys
import random
pygame.init()
#Import Image
image_name = sys.argv[1]
src_image = pygame.image.load(image_name)
(w,h) = src_image.get_size()
window = pygame.display.set_mode((w*5,h*5))
for y in range(h):
for x in range(w):
(r, g, b, _) = src_image.get_at((x,y))
lum = (0.2126 * r + 0.7152 * g + 0.0788 * b)*255
if lum > 0.625:
a, k, d = r // 5, g // 5, b // 5
colors = [(255,0,0) for _ in range(a)] + [(0,255,0) for _ in range(k)] + [(0,0,255) for _ in range(d)]
random.shuffle(colors)
for c in colors:
pygame.draw.circle(window,c,(random.randint((x*5)-15,(x*5)),random.randint((y*5)-15,(y*5))),1)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.update()
pygame.time.delay(100)
pygame.quit()
I am just getting started with Pygame, and I did a little test of just printing pixels in random spots. However, I noticed that the pixels don't seem to be single pixels at all, but 'fuzzy' blobs of a few pixels, as shown in the image. Here's the code I used to draw the pixels:
Is there any way to just display single pixels?
Edit: Here's the whole code I used:
import pygame.gfxdraw
import pygame
import random
width = 1000
height = 1000
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
running = True
while running:
x = random.randint(0,1000)
y = random.randint(0,1000)
pygame.gfxdraw.pixel(screen, x, y, (225,225,225))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
clock.tick(240)
more fuzzy pixels
pygame.gfxdraw.pixel(surface, x, y, color) will draw a single pixel on the given surface.
Also you will need to add import pygame.gfxdraw.
EDIT: Full code:
import pygame.gfxdraw
import pygame
import random
width = 1680
height = 1050
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
running = True
x = [i for i in range(width - 10)]
x_full = [i for i in range(width)]
y = 100
y_full = [i for i in range(height // 2)]
while running:
for i in x:
for j in y_full:
pygame.gfxdraw.pixel(screen, i, j, pygame.Color("red"))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
clock.tick(1)
Try to test it this way, I set the window size to fit my monitor resolution so it goes fullscreen. x_full and y should give you horizontal line. And if you subtract, for example, 10 you will get slightly shorter line, and vice-versa with y_full and some random x. Also using (width//2, height//2) will cover exactly quarter of the screen. I think it is accurate and that pygame.gfxdraw.pixel(screen, i, j, pygame.Color("red")) displays only single pixel as it should.
In your code you are using random to display pixels and it adds them 240 per second so you are very fast ending up with bunch of pixels at random positions resulting to have pixels close to each other looking as a "bigger one". I think this is what was happening here. Please someone correct me if I am wrong.
Also make small window e.g. (100, 100) and draw one pixel at (50, 50) this way it can be more easily seen. If you are on windows use magnifier to test it.
IMPORTANT:
While testing this with huge number of pixels do it OUTSIDE of the loop because it will consume much processor power to display them.
Hope this answers your question
I need to draw a circle filled with random gray colors and a black outline using pygame. This is what it should look like:
The radius increases by expansion_speed * dt every frame and the surface is updated 60 times per second, so however this is achieved (if even possible) needs to be fast. I tried masking an stored texture but that was too slow. My next idea was to read the pixels from this stored texture and only replace the difference between the last and current surfaces. I tried this too but was unable to translate the idea to code.
So how can this be done?
See my update to your previous related question. It has some info about performance. You could try to enable hardware acceleration in fullscreen mode, but I never personally tried it, so can't give good advice how to do it properly. Just use two differnt colorkeys for extracting circle from noise and putting the whole surface to the display. Note that if your Noise surface has pixels same as colorkey color then they also become transparent.
This example I think is what you are trying to get, move the circle with mouse and hold CTRL key to change radius.
Images:
import os, pygame
pygame.init()
w = 800
h = 600
DISP = pygame.display.set_mode((w, h), 0, 24)
clock = pygame.time.Clock( )
tile1 = pygame.image.load("2xtile1.png").convert()
tile2 = pygame.image.load("2xtile2.png").convert()
tw = tile1.get_width()
th = tile1.get_height()
Noise = pygame.Surface ((w,h))
Background = pygame.Surface ((w,h))
for py in range(0, h/th + 2) :
for px in range(0, w/tw + 2):
Noise.blit(tile1, (px*(tw-1), py*(th-1) ) )
Background.blit(tile2, (px*(tw-1), py*(th-1) ) )
color_key1 = (0, 0, 0)
color_key2 = (1, 1, 1)
Circle = pygame.Surface ((w,h))
Circle.set_colorkey(color_key1)
Mask = pygame.Surface ((w,h))
Mask.fill(color_key1)
Mask.set_colorkey(color_key2)
strokecolor = (10, 10, 10)
DISP.blit(Background,(0,0))
def put_circle(x0, y0, r, stroke):
pygame.draw.circle(Mask, strokecolor, (x0,y0), r, 0)
pygame.draw.circle(Mask, color_key2, (x0,y0), r - stroke, 0)
Circle.blit(Noise,(0,0))
Circle.blit(Mask,(0,0))
dirtyrect = (x0 - r, y0 - r, 2*r, 2*r)
Mask.fill(color_key1, dirtyrect)
DISP.blit(Circle, (0,0))
X = w/2
Y = h/2
R = 100
stroke = 2
FPS = 25
MainLoop = True
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
while MainLoop :
clock.tick(FPS)
pygame.event.pump()
Keys = pygame.key.get_pressed()
MR = pygame.mouse.get_rel() # get mouse shift
if Keys [pygame.K_ESCAPE] :
MainLoop = False
if Keys [pygame.K_LCTRL] :
R = R + MR[0]
if R <= stroke : R = stroke
else :
X = X + MR[0]
Y = Y + MR[1]
DISP.blit(Background,(0,0))
put_circle(X, Y, R, stroke)
pygame.display.flip( )
pygame.mouse.set_visible(True)
pygame.event.set_grab(False)
pygame.quit( )
Many years ago we had a font rendering challenge with the Pygame project.
Someone created an animated static text for the contest but it was far too slow.
We put our heads together and made a much quicker version. Step one was to create a smallish image with random noise. Something like 64x64. You may need a bigger image if your final image is large enough to notice the tiling.
Every frame you blit the tiled noise using a random offset. Then you take an image with the mask, in your case an inverted circle, and draw that on top. That should give you a final image containing just the unmasked noise.
The results were good. In our case it was not noticeable that the noise was just jittering around. That may be because the text did not have a large unobstrcted area. I'd be concerned your large circle would make the trick appear obvious. i guess if you really had a large enough tiled image it would still work.
The results and final source code are still online at the Pygame website,
http://www.pygame.org/pcr/static_text/index.php
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.
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