How to create a filled in circle within an array of pixels - python
I am having trouble creating a method that can create a FILLED IN circle within a 2d array of pixels. So far, the Image class I have created can create a 2d array of pixels, change individual pixel values, etc. What I have been able to complete so far is use Bresenham's circle algorithm to create a hollow circle of any radius around any given point in the array. I cannot, however, figure out how to make this circle filled in.
I am open to any solution! I have tried creating a "flood fill" method, only to be greeted by various recursion errors. I have also tried just calling the circleBres method several times with decrementing radii, but this does not work either. For the sake of space, just assume that the writePixel method works.
class Image:
def drawCircle(self, centerX, centerY, x, y):
self.writePixel(centerX + x, centerY + y, 50.0)
self.writePixel(centerX - x, centerY + y, 50.0)
self.writePixel(centerX + x, centerY - y, 50.0)
self.writePixel(centerX - x, centerY - y, 50.0)
self.writePixel(centerX + y, centerY + x, 50.0)
self.writePixel(centerX - y, centerY + x, 50.0)
self.writePixel(centerX + y, centerY - x, 50.0)
self.writePixel(centerX - y, centerY - x, 50.0)
def circleBres(self, xc, yc, r):
x = 0
y = r
d = 3 - (2*r)
self.drawCircle(xc, yc, x, y)
while(y>=x):
x+=1
if(d>0):
y-=1
d = d+4*(x-y)+10
else:
d = d + 4 * x + 6
self.drawCircle(xc, yc, x, y)
time.sleep(.06)
obj = Image(50, 50, 51.0)
obj.circleBres(35, 35, 10)
The Image constructor's third parameter is the value that all pixels are assigned upon creation (51.0), and the third parameter of the writePixel method is the value that the pixel is being changed to (50.0).
Any help is greatly appreciated. Thanks!
When you write
self.writePixel(centerX + x, centerY + y, 50.0)
self.writePixel(centerX - x, centerY + y, 50.0)
you draw the leftmost and the rightmost pixels of some scanline.
To fill it, just make for loops instead of these 8 lines:
for xx in range(centerX - x, centerX + x + 1):
self.writePixel(xx, centerY + y, 50.0)
self.writePixel(xx, centerY - y, 50.0)
for xx in range(centerX - y, centerX + y):
self.writePixel(centerX + y, centerY + x, 50.0)
self.writePixel(centerX - y, centerY - x, 50.0)
Here's an example of how I would handle a problem like this:
Assuming you have a correct outline of circle, just iterate through the rows and fill all pixels between edges of the circle.
grid = [
[0,0,0,1,1,1,0,0,0],
[0,0,1,0,0,0,1,0,0],
[0,1,0,0,0,0,0,1,0],
[0,1,0,0,0,0,0,1,0],
[0,0,1,0,0,0,1,0,0],
[0,0,0,1,1,1,0,0,0],
]
def fill_circle(grid):
for r in grid: # For each row
j1 = None # left endpoint
j2 = None # right endpoint
for j, v in enumerate(r):
if v == 1 and j1 is None:
j1 = j
continue
if v == 1 and j2 is None:
j2 = j
break
else: # Did not find j1 AND j2
continue
for j in range(j1, j2): # Fill all points between
r[j] = 1
fill_circle(grid)
grid
[[0,0,0,1,1,1,0,0,0],
[0,0,1,1,1,1,1,0,0],
[0,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,0],
[0,0,1,1,1,1,1,0,0],
[0,0,0,1,1,1,0,0,0]]
Related
Using uniform cost search on a matrix in python
Good day, I have an 11x11 matrix (shown below) where the 0s represent open spaces and the 1s represent walls. The horizontal and vertical movements are weighted at 1 and the diagonal movements are weighted at sqrt(2) The matrix looks as follows: `board = [[0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,1,1,1,0,1,1,0], [0,1,0,0,0,0,1,1,0,1,1,0], [0,1,1,0,0,0,0,0,0,1,1,0], [0,1,1,1,0,0,0,0,0,1,1,0], [0,1,1,1,1,0,0,0,0,1,1,0], [0,1,1,1,1,1,1,1,1,1,1,0], [1,1,1,1,1,1,1,1,1,1,1,0], [0,0,0,0,0,0,0,0,0,0,1,0], [0,0,0,0,0,0,0,0,0,0,1,0], [0,0,1,1,1,1,1,1,1,1,1,0], [0,0,0,0,0,0,0,0,0,0,0,0]]` My goal is to write a Uniform cost search code in python to find the most cost effective path from a starting point (e.g [1,1]) to an end point (e.g [5,1]). Most of the code I have come across works with graphs and not matrices. I need help with working around this with a matrix. I am fairly new at python and all help and advice will be highly appreciated. I am using python 3.
Since nobody seems to know an easy answer to this question I will post my (hopefully correct) answer. The used approach is not really efficient and based on a flood fill like algorithm. First we define a list with all possible directions. Those are represented by a lambda function which return the new indices (xand y), the current weight and the current path: from math import sqrt dirs = [ lambda x, y, z, p: (x, y - 1, z + 1, p + [(x, y)]), # up lambda x, y, z, p: (x, y + 1, z + 1, p + [(x, y)]), # down lambda x, y, z, p: (x - 1, y, z + 1, p + [(x, y)]), # left lambda x, y, z, p: (x + 1, y, z + 1, p + [(x, y)]), # right lambda x, y, z, p: (x - 1, y - 1, z + sqrt(2), p + [(x, y)]), # up left lambda x, y, z, p: (x + 1, y - 1, z + sqrt(2), p + [(x, y)]), # up right lambda x, y, z, p: (x - 1, y + 1, z + sqrt(2), p + [(x, y)]), # down left lambda x, y, z, p: (x + 1, y + 1, z + sqrt(2), p + [(x, y)]) # down right ] Then we create some functions. The first one checks if the indices calculated by the directions are valid indices for the matrix and that there is no wall. def valid(grid, x, y): return 0 <= x < len(grid) and 0 <= y < len(grid[0]) and grid[x][y] == 0 The adjacent function yields every direction for every cell at the frontier (imagine it like a wave) and the flood function moves the wave one step forwards and replaces the old step with walls (1). def adjacent(grid, frontier): for (x, y, z, p) in frontier: for d in dirs: nx, ny, nz, np = d(x, y, z, p) if valid(grid, nx, ny): yield (nx, ny, nz, np) def flood(grid, lst): res = list(adjacent(grid, frontier)) for (x, y, z, p) in frontier: grid[x][y] = 1 return res In the following funtion we call the defined functions and return a tuple of the weight of the shortest path and the shortest path. def shortest(grid, start, end): start, end = tuple(start), tuple(end) frontier = [(start[0], start[1], 0, [])] res = [] while frontier and grid[end[0]][end[1]] == 0: frontier = flood(grid, frontier) for (x, y, z, p) in frontier: if (x, y) == end: res.append((z, p + [(x, y)])) if not res: return () return sorted(res)[0] I tested it for (0, 0) to (8, 8) and the output seems plausable. It will probably fail if the cost for two horizontal / vertical steps is lower than the cost for the equal diagonal step. EDIT: Result for (0, 0) to (8, 8) with P as path: [[P,P,P,P,P,P,P,P,P,P,P,0], [0,0,0,0,0,1,1,1,0,1,1,P], [0,1,0,0,0,0,1,1,0,1,1,P], [0,1,1,0,0,0,0,0,0,1,1,P], [0,1,1,1,0,0,0,0,0,1,1,P], [0,1,1,1,1,0,0,0,0,1,1,P], [0,1,1,1,1,1,1,1,1,1,1,P], [1,1,1,1,1,1,1,1,1,1,1,P], [0,0,0,P,P,P,P,P,P,P,1,P], [0,0,P,0,0,0,0,0,0,0,1,P], [0,P,1,1,1,1,1,1,1,1,1,P], [0,0,P,P,P,P,P,P,P,P,P,0]] Weight: 39.071067811865476 EDIT 2: Add copy paste version. from math import sqrt board = [[0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,1,1,1,0,1,1,0], [0,1,0,0,0,0,1,1,0,1,1,0], [0,1,1,0,0,0,0,0,0,1,1,0], [0,1,1,1,0,0,0,0,0,1,1,0], [0,1,1,1,1,0,0,0,0,1,1,0], [0,1,1,1,1,1,1,1,1,1,1,0], [1,1,1,1,1,1,1,1,1,1,1,0], [0,0,0,0,0,0,0,0,0,0,1,0], [0,0,0,0,0,0,0,0,0,0,1,0], [0,0,1,1,1,1,1,1,1,1,1,0], [0,0,0,0,0,0,0,0,0,0,0,0]] dirs = [ lambda x, y, z, p: (x, y - 1, z + 1, p + [(x, y)]), # up lambda x, y, z, p: (x, y + 1, z + 1, p + [(x, y)]), # down lambda x, y, z, p: (x - 1, y, z + 1, p + [(x, y)]), # left lambda x, y, z, p: (x + 1, y, z + 1, p + [(x, y)]), # right lambda x, y, z, p: (x - 1, y - 1, z + sqrt(2), p + [(x, y)]), # up left lambda x, y, z, p: (x + 1, y - 1, z + sqrt(2), p + [(x, y)]), # up right lambda x, y, z, p: (x - 1, y + 1, z + sqrt(2), p + [(x, y)]), # down left lambda x, y, z, p: (x + 1, y + 1, z + sqrt(2), p + [(x, y)]) # down right ] def valid(grid, x, y): return 0 <= x < len(grid) and 0 <= y < len(grid[0]) and grid[x][y] == 0 def adjacent(grid, frontier): for (x, y, z, p) in frontier: for d in dirs: nx, ny, nz, np = d(x, y, z, p) if valid(grid, nx, ny): yield (nx, ny, nz, np) def flood(grid, frontier): res = list(adjacent(grid, frontier)) for (x, y, z, p) in frontier: grid[x][y] = 1 return res def shortest(grid, start, end): start, end = tuple(start), tuple(end) frontier = [(start[0], start[1], 0, [])] res = [] while frontier and grid[end[0]][end[1]] == 0: frontier = flood(grid, frontier) for (x, y, z, p) in frontier: if (x, y) == end: res.append((z, p + [(x, y)])) if not res: return () return sorted(res)[0] print(shortest(board, (0, 0), (8, 8)))
python find vector direction between two 3D points
I'm trying to calculate the direction of a 3D vector starting at point (x, y, z) and ending at point (a, b, c) for the navigation in my spaceship game, but I have been unable to find anything helpful. So far I have tried using two circles, one for figure out x and y and another for z, to figure it out and the code only works if the two vector's distances are very similar. Here is what I'm using: def setcourse(self, destination): x1, y1, z1 = self.coords x2, y2, z2 = destination dx = x2 - x1 dy = y2 - y1 dz = z2 - z1 self.heading = math.atan2(dy, dx) self.heading2 = math.atan2(dz, dy) self.distance = int(math.sqrt((dx) ** 2 + (dy) ** 2)) self.distance2 = int(math.sqrt((dy) ** 2 + (dz) ** 2)) def move(self): if self.distance > 0 and self.distance2 > 0: if self.atwarp == True: x, y, z = self.coords x += math.cos(self.heading) * self.speed y += math.sin(self.heading) * self.speed z += math.sin(self.heading2) * self.speed self.coords = (x, y, z) print(str(self.coords)) self.distance -= self.speed self.distance2 -= self.speed elif self.distance <= 0 and self.distance2 <= 0 and self.atwarp == True: self.atwarp = False self.speed = 0 print("Reached Destination") else: self.atwarp = False I'm not sure how much of it is a math error and how much is a programming one, but the z winds up way off and I'm not sure how to go about fixing it. No matter what I do the z is always off if its input more than slightly different from the others. Here is examples starting from (0, 0, 0). I'm trying to get the output to be similar if not the same as the input. Input: (100, 200, -200) Vector1 heading in radians: 1.1071487177940904 Vector2 heading: 2.356194490192345 Vector1 distance: 223 Vector2 distance: 282 Output: (99.7286317964909, 199.4572635929818, 157.68481220460077) The x and y are fine, but the z is off. Input: (-235, 634, -21) Vector1 heading in radians: 1.9257588105240444 Vector2 heading: 1.6039072496758664 Vector1 distance: 676 Vector2 distance: 634 Output: (-220.3499891866359, 594.4761410396925, 633.6524941214135) The z off.
The direction of the movement is the trio dx, dy, dz you calculated. This vector is not pure: it contains distance and direction. If you want direction alone, you have to normalize this: The distance is sqrt(dx^2 + dy^2 + dz^2). For the normalized direction, you divide each dx, dy, and dz by this number. If you want to move in that direction, the new position is the old position plus the the direction vector times the distance you want to travel: newpos = oldpos + dist * dirvector I'm not sure what you mean by input: (100, 200, -200) if that is the direction, your direction vector would be 300 long, and the actual direction vector is 100/300, 200/300, and -200/300 (so 0.333, 0.667 and -0.667) If you want to travel 500 along that direction, the new position is 0+166.67, 0+333.33, and 0-333.33
Python checking if a point is in sphere with center x, y ,z
I'm trying to check if a point is within a sphere with a center point of (x, y, z) where (x, y, z) is not (0, 0, 0). This code I'm using to generate the points I want to check: def generatecoords(self, i): x, y, z = generatepoint() if i >= 1: valid = False while valid == False: coords = self.checkpoint(x, y, z) for b in world.starlist: if coords == world.starlist[b].coords: coords = self.checkpoint(x, y, z) else: valid = True else: coords = self.checkpoint(x, y, z) return coords def checkpoint(self, x, y, z): d = math.sqrt(x * x + y * y + z * z) while d >= self.radius: x, y, z = generatepoint() d = math.sqrt(x * x + y * y + z * z) coords = (int(x), int(y), int(z)) return coords def generatepoint(): x, y, z = [int(random.uniform(-self.radius, self.radius)) \ for b in range(3)] return x, y, z These function are called in a for loop to generate the points in a dictionary, while also checking the unlikely chance that points aren't placed on top of another(mostly because I can). I trying to figure out what I need to add to math.sqrt(x * x + y * y + z * z) so that it accounts for a center that isn't (0, 0, 0). I do know of one way to do it, but it would require several lines of code and I'd rather do it in one. I would have asked this in the comments of the answer in another question, but I'm not allowed to comment on answers yet.
The formula is: A point (x,y,z) is inside the sphere with center (cx,cy,cz) and radius r if (x - cx)^2 + (y - cy)^2 + (z - cz)^2 < r^2
Here is a very short function that returns True if the point is in the sphere, and False if not. The inputs are two numpy arrays: point = [x,y,z] and ref = [x,y,z] and the radius should be a float. import numpy as np def inSphere(self, point, ref, radius): # Calculate the difference between the reference and measuring point diff = np.subtract(point, ref) # Calculate square length of vector (distance between ref and point)^2 dist = np.sum(np.power(diff, 2)) # If dist is less than radius^2, return True, else return False return dist < radius ** 2
X rotation doesn't work
In python, I have written a 3D rendering program. The Y rotation works fine, but the X rotation zooms in for some obscure reason. I couldn't spot it, so I put it up here. def plotLine(W, H, (x, y, z), (x2, y2, z2), rotX, rotY, FOV=1.0): try: x = float(x) y = float(y) z = float(z) x2 = float(x2) y2 = float(y2) z2 = float(z2) if z == 0: z = 0.01 if z2 == 0: z2 = 0.01 x, y, z = rotateY((x, y, z), rotY) x, y, z = rotateX((x, y, z), rotX) x2, y2, z2 = rotateY((x2, y2, z2), rotY) x2, y2, z2 = rotateX((x2, y2, z2), rotX) scX = (x/z)*FOV scY = (y/z)*FOV scX *= min(W, H) scY *= min(W, H) scX += W/2 scY += H/2 scX2 = (x2/z2)*FOV scY2 = (y2/z2)*FOV scX2 *= min(W, H) scY2 *= min(W, H) scX2 += W/2 scY2 += H/2 pygame.draw.aaline(display, (0, 255, 0), (scX, scY), (scX2, scY2)) except (OverflowError, ZeroDivisionError): return def rotateY((x, y, z), degrees): # Looking left and right. x, y, z = float(x), float(y), float(z) rads = math.radians(degrees) newX = (math.cos(rads)*x)+(math.sin(rads)*z) newY = y newZ = (-math.sin(rads)*x)+(math.cos(rads)*z) return (newX, newY, newZ) def rotateX((x, y, z), degrees): x, y, z = float(x), float(y), float(z) rads = math.radians(degrees) newX = x newY = (math.cos(rads)*y)+(math.sin(rads)*z) newZ = (math.sin(rads)*y)+(math.cos(rads)*z) return (newX, newY, newZ) Any help would be appreciated! BTW, I have looked up the matrix rotations on Wikipedia. Either Wikipedia got the matrices wrong, or I multiplied the matrices wrong, which is not likely. I have looked over them several times.
I think you have an error in your rotateX function newY = (math.cos(rads)*y)+(math.sin(rads)*z) newZ = (math.sin(rads)*y)+(math.cos(rads)*z) should be newY = (math.cos(rads)*y)+(math.sin(rads)*z) newZ = (-math.sin(rads)*y)+(math.cos(rads)*z) ^ ^ without the negative sign you will not get a rotation. You have done this correctly in your rotateY function but not the rotateX function. If you look at the 2D submatrix of your currently coded 3D rotation you have [cos(rads) sin(rads)] [sin(rads) cos(rads)] and the determinant of this is 1/(cos(rads)*cos(rads) - sin(rads)sin(rads)) = 1/cos(2*rads) This is not equal to 1 for all angles rads and hence is not a rotation for all values of rads. Note also that this rotation angle would be in the negative sense to what is usually associated with a rotation. You can see more information about this here on wikepedia
Drawing an anti-aliased line with the Python Imaging Library (PIL)
I'm drawing a bunch of lines with the Python Imaging Library's ImageDraw.line(), but they look horrid since I can't find a way to anti-alias them. How can I anti-alias lines in PIL?
This is a really quickly hacked together function to draw an anti-aliased line with PIL that I wrote after googling for the same issue, seeing this post and failing to install aggdraw and being on a tight deadline. It's an implementation of Xiaolin Wu's line algorithm. I hope it helps anyone googling for the same thing!! :) """Library to draw an antialiased line.""" # http://stackoverflow.com/questions/3122049/drawing-an-anti-aliased-line-with-thepython-imaging-library # https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm import math def plot(draw, img, x, y, c, col, steep, dash_interval): """Draws an antiliased pixel on a line.""" if steep: x, y = y, x if x < img.size[0] and y < img.size[1] and x >= 0 and y >= 0: c = c * (float(col[3]) / 255.0) p = img.getpixel((x, y)) x = int(x) y = int(y) if dash_interval: d = dash_interval - 1 if (x / dash_interval) % d == 0 and (y / dash_interval) % d == 0: return draw.point((x, y), fill=( int((p[0] * (1 - c)) + col[0] * c), int((p[1] * (1 - c)) + col[1] * c), int((p[2] * (1 - c)) + col[2] * c), 255)) def iround(x): """Rounds x to the nearest integer.""" return ipart(x + 0.5) def ipart(x): """Floors x.""" return math.floor(x) def fpart(x): """Returns the fractional part of x.""" return x - math.floor(x) def rfpart(x): """Returns the 1 minus the fractional part of x.""" return 1 - fpart(x) def draw_line_antialiased(draw, img, x1, y1, x2, y2, col, dash_interval=None): """Draw an antialised line in the PIL ImageDraw. Implements the Xialon Wu antialiasing algorithm. col - color """ dx = x2 - x1 if not dx: draw.line((x1, y1, x2, y2), fill=col, width=1) return dy = y2 - y1 steep = abs(dx) < abs(dy) if steep: x1, y1 = y1, x1 x2, y2 = y2, x2 dx, dy = dy, dx if x2 < x1: x1, x2 = x2, x1 y1, y2 = y2, y1 gradient = float(dy) / float(dx) # handle first endpoint xend = round(x1) yend = y1 + gradient * (xend - x1) xgap = rfpart(x1 + 0.5) xpxl1 = xend # this will be used in the main loop ypxl1 = ipart(yend) plot(draw, img, xpxl1, ypxl1, rfpart(yend) * xgap, col, steep, dash_interval) plot(draw, img, xpxl1, ypxl1 + 1, fpart(yend) * xgap, col, steep, dash_interval) intery = yend + gradient # first y-intersection for the main loop # handle second endpoint xend = round(x2) yend = y2 + gradient * (xend - x2) xgap = fpart(x2 + 0.5) xpxl2 = xend # this will be used in the main loop ypxl2 = ipart(yend) plot(draw, img, xpxl2, ypxl2, rfpart(yend) * xgap, col, steep, dash_interval) plot(draw, img, xpxl2, ypxl2 + 1, fpart(yend) * xgap, col, steep, dash_interval) # main loop for x in range(int(xpxl1 + 1), int(xpxl2)): plot(draw, img, x, ipart(intery), rfpart(intery), col, steep, dash_interval) plot(draw, img, x, ipart(intery) + 1, fpart(intery), col, steep, dash_interval) intery = intery + gradient
I had a similar problem, my lines had rough edges where changing directions. I took a clue from how lines are drawn in IOS and came up with this code. It puts rounded line caps on the ends of the lines and really cleans things up. Not exactly anti-aliasing, but am totally new to PIL and had such a hard time finding an answer I figured I would share. Needs some tweaking and there is probably a better way but does what I need :) from PIL import Image import ImageDraw class Point: def __init__(self, x, y): self.x = x self.y = y class DrawLines: def draw(self, points, color, imageName): img = Image.new("RGBA", [1440,1080], (255,255,255,0)) draw = ImageDraw.Draw(img) linePoints = [] for point in points: draw.ellipse((point.x-7, point.y-7, point.x+7, point.y+7), fill=color) linePoints.append(point.x) linePoints.append(point.y) draw.line(linePoints, fill=color, width=14) img.save(imageName) p1 = Point(100,200) p2 = Point(190,250) points = [p1,p2] red = (255,0,0) drawLines = DrawLines() drawLines.draw(points, red, "C:\\test.png")