def angle(self): """angle() -> theta Returns the orientation of the pixels""" xs = ys = tot = 0 # multiplication counters xx = yy = xy = 0 for x in range(self._mask.w): for y in range(self._mask.h): if sdl.bitmask_getbit(self._mask, x, y): ys += y xs += x tot += 1 xx += x * x yy += y * y xy += x * y if tot: xc = xs // tot yc = ys // tot theta = atan2(2 * (xy / tot - xc * yc), (xx / tot - xc * xc) - (yy / tot - yc * yc)) # covert from radians # We copy this logic from pygame upstream, because reasons theta = -90.0 * theta / pi return theta return 0.0
def angle(self): """angle() -> theta Returns the orientation of the pixels""" xs = ys = tot = 0 # multiplication counters xx = yy = xy = 0 for x in range(self._mask.w): for y in range(self._mask.h): if sdl.bitmask_getbit(self._mask, x, y): ys += y xs += x tot += 1 xx += x * x yy += y * y xy += x * y if tot: xc = xs // tot yc = ys // tot theta = math.atan2(2 * (xy / tot - xc * yc), (xx / tot - xc * xc) - (yy / tot - yc * yc)) # covert from radians # We copy this logic from pygame upstream, because reasons theta = -90.0 * theta / math.pi return theta return 0.0
def get_at(self, pos): """get_at((x,y)) -> int Returns nonzero if the bit at (x,y) is set.""" x, y = pos if 0 <= x < self._mask.w and 0 <= y < self._mask.h: val = sdl.bitmask_getbit(self._mask, x, y) else: raise IndexError("%d, %d out of bounds" % pos) return val
def centroid(self): """centroid() -> (x, y) Returns the centroid of the pixels in a Mask""" xs = ys = tot = 0 for x in range(self._mask.w): for y in range(self._mask.h): if sdl.bitmask_getbit(self._mask, x, y): ys += y xs += x tot += 1 if tot: return (xs // tot, ys // tot) return (0, 0)
def connected_component(self, pos=None): """connected_component((x,y) = None) -> Mask Returns a mask of a connected region of pixels.""" output = Mask((self._mask.w, self._mask.h)) # if a coordinate is specified, make sure the pixel there is # actually set x = y = -1 check = True if pos: x, y = pos if not sdl.bitmask_getbit(self._mask, x, y): check = False if check: r = sdl.largest_connected_comp(self._mask, output._mask, x, y) # largest_connected_comp returns -2 on memory errors, 0 otherwise if r == -2: raise MemoryError("Not enough memory to get largest component") return output
def outline(self, every=1): """outline(every = 1) -> [(x,y), (x,y) ...] list of points outlining an object""" # Find the first set pixel in the mask points = [] start_point = None for y in range(self._mask.h): for x in range(self._mask.w): if sdl.bitmask_getbit(self._mask, x, y): # We add 1 because of later padding trick start_point = x + 1, y + 1 points.append((x, y)) break if points: break # If not pixels, break out if not points: return points # Check for corner case around only last pixel being set if start_point == (self._mask.w, self._mask.h): return points # We create a larger mask, to avoid checking edges for every pixel trace = Mask((self._mask.w + 2, self._mask.h + 2)) sdl.bitmask_draw(trace._mask, self._mask, 1, 1) # This walks around a pixel clockwise. # We have doubled this for the logic later offsets = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)] * 2 curr = second = None pos = 0 # We check just the first point for neighbours for p, off in enumerate(offsets[:8]): cand = (start_point[0] + off[0], start_point[1] + off[1]) if sdl.bitmask_getbit(trace._mask, cand[0], cand[1]): curr = second = cand # Scale back to our mask points.append((second[0] - 1, second[1] - 1)) # Set appropriate start point for next loop pos = p + 5 break if not second: # No neighbours return points # Trace the outline next_point = curr while True: for p, off in enumerate(offsets[pos:pos + 8]): cand = (curr[0] + off[0], curr[1] + off[1]) if sdl.bitmask_getbit(trace._mask, cand[0], cand[1]): # The logic here is a little hairy, but we must # make sure we test all other neighbors before we # test going from next_point back to curr_point # For example, if we found next_point using (1, 1) # we need to test (-1, -1) last when checking next_point. pos = (pos + p + 5) % 8 next_point = cand if curr != start_point or next_point != second: # Not yet back at the start points.append((next_point[0] - 1, next_point[1] - 1)) break if curr == start_point and next_point == second: # About to repeat ourselves, so we're done break curr = next_point # Return asked for subset of points return points[::every]