def cover_with(self, cover_color): """ Mix the two colors respecting their alpha value. Puts cover_color over itself compositing the colors using the alpha values. """ # fastpath for solid colors if cover_color.alpha == 255: return Color(cover_color.red, cover_color.green, cover_color.blue, cover_color.alpha) srca = fdiv(cover_color.alpha, 255) dsta = fdiv(self.alpha, 255) outa = srca + dsta * (1 - srca) srcr, srcg, srcb = cover_color.red, cover_color.green, cover_color.blue dstr, dstg, dstb = self.red, self.green, self.blue outr = (srcr * srca + dstr * dsta * (1 - srca)) / outa outg = (srcg * srca + dstg * dsta * (1 - srca)) / outa outb = (srcb * srca + dstb * dsta * (1 - srca)) / outa red = int(round(outr)) green = int(round(outg)) blue = int(round(outb)) alpha = int(round(outa * 255)) return Color(red, green, blue, alpha)
def resize(self, source, width, height, resize_canvas=True): if not resize_canvas: # this optimised implementation doesn't deal with this. # so delegate to affine() return super(Nearest, self).resize(source, width, height, resize_canvas=resize_canvas) pixels = array.array('B') pixelsize = source.pixelsize x_ratio = fdiv(source.width, width) # get the x-axis ratio y_ratio = fdiv(source.height, height) # get the y-axis ratio y_range = range( height) # an iterator over the indices of all lines (y-axis) x_range = range( width) # an iterator over the indices of all rows (x-axis) for y in y_range: y += 0.5 # use the center of each pixel source_y = int(y * y_ratio) # get the source line for x in x_range: x += 0.5 # use the center of each pixel source_x = int(x * x_ratio) # get the source row pixels.extend(source.pixels.get(source_x, source_y)) return get_pixel_array(pixels, width, height, pixelsize)
def __init__(self, start_x, start_y, end_x, end_y): self.start_x = start_x self.start_y = start_y self.end_x = end_x self.end_y = end_y steep = abs(self.end_y - self.start_y) > abs(self.end_x - self.start_x) if steep: x0, y0 = self.start_y, self.start_x x1, y1 = self.end_y, self.end_x else: x0, y0 = self.start_x, self.start_y x1, y1 = self.end_x, self.end_y if x0 > x1: x0, x1 = x1, x0 y0, y1 = y1, y0 self.x0, self.x1, self.y0, self.y1 = x0, x1, y0, y1 delta_x = x1 - x0 delta_y = abs(y1 - y0) self.error = 0.0 self.delta_error = fdiv(delta_y, delta_x) if y0 < y1: self.ystep = 1 else: self.ystep = -1 self.y = y0 self.iterator = self.steep_iterator if steep else self.normal_iterator
def cover_with(self, base): """ Mix the two colors respecting their alpha value. """ srca = fdiv(base.alpha, 255) dsta = fdiv(self.alpha, 255) outa = srca + dsta * (1 - srca) srcr, srcg, srcb = base.red, base.green, base.blue dstr, dstg, dstb = self.red, self.green, self.blue outr = (srcr * srca + dstr * dsta * (1 - srca)) / outa outg = (srcg * srca + dstg * dsta * (1 - srca)) / outa outb = (srcb * srca + dstb * dsta * (1 - srca)) / outa return Color(*map(int, [outr, outg, outb, outa * 255]))
def nearest(source, width, height, pixelsize): assert pixelsize == 1, "yea... gotta implement this generically" pixels = [] pixelappend = pixels.append # cache for cpython x_ratio = fdiv(source.width, width) # get the x-axis ratio y_ratio = fdiv(source.height, height) # get the y-axis ratio y_range = range(height) # an iterator over the indices of all lines (y-axis) x_range = range(width) # an iterator over the indices of all rows (x-axis) for y in y_range: source_y = int(round(y * y_ratio)) # get the source line line = array.array('B') # initialize a new line lineappend = line.append # cache for cypthon for x in x_range: source_x = int(round(x * x_ratio)) # get the source row lineappend(source.pixels[source_y][source_x]) pixelappend(line) return pixels
def nearest(source, width, height, pixelsize): pixels = [] pixelappend = pixels.append # cache for cpython x_ratio = fdiv(source.width, width) # get the x-axis ratio y_ratio = fdiv(source.height, height) # get the y-axis ratio y_range = range(height) # an iterator over the indices of all lines (y-axis) x_range = range(width) # an iterator over the indices of all rows (x-axis) for y in y_range: source_y = int(round(y * y_ratio)) # get the source line line = array.array('B') # initialize a new line lineextend = line.extend # cache for cypthon for x_coord in x_range: source_x_coord = int(round(x_coord * x_ratio)) # get the source row source_x_start = source_x_coord * pixelsize source_x_end = source_x_start + pixelsize lineextend(source.pixels[source_y][source_x_start:source_x_end]) pixelappend(line) return pixels
def iter_pixels(self, color): """ Use Bresenham Line Algorithm (http://en.wikipedia.org/wiki/Bresenham's_line_algorithm): function line(x0, x1, y0, y1) boolean steep := abs(y1 - y0) > abs(x1 - x0) if steep then swap(x0, y0) swap(x1, y1) if x0 > x1 then swap(x0, x1) swap(y0, y1) int deltax := x1 - x0 int deltay := abs(y1 - y0) real error := 0 real deltaerr := deltay / deltax int ystep int y := y0 if y0 < y1 then ystep := 1 else ystep := -1 for x from x0 to x1 if steep then plot(y,x) else plot(x,y) error := error + deltaerr if error ≥ 0.5 then y := y + ystep error := error - 1.0 """ steep = abs(self.end_y - self.start_y) > abs(self.end_x - self.start_x) if steep: x0, y0 = self.start_y, self.start_x x1, y1 = self.end_y, self.end_x else: x0, y0 = self.start_x, self.start_y x1, y1 = self.end_x, self.end_y if x0 > x1: x0, x1 = x1, x0 y0, y1 = y1, y0 delta_x = x1 - x0 delta_y = abs(y1 - y0) error = 0.0 delta_error = fdiv(delta_y, delta_x) if y0 < y1: ystep = 1 else: ystep = -1 y = y0 for x in range(x0, x1): if steep: yield y, x, color else: yield x, y, color error += delta_error if error >= 0.5: y = y + ystep error = error - 1.0
def resize(self, source, width, height, resize_canvas=True): if not resize_canvas: # this optimised implementation doesn't deal with this. # so delegate to affine() return super(Nearest, self).resize( source, width, height, resize_canvas=resize_canvas ) pixels = array.array('B') pixelsize = source.pixelsize x_ratio = fdiv(source.width, width) # get the x-axis ratio y_ratio = fdiv(source.height, height) # get the y-axis ratio y_range = range(height) # an iterator over the indices of all lines (y-axis) x_range = range(width) # an iterator over the indices of all rows (x-axis) for y in y_range: y += 0.5 # use the center of each pixel source_y = int(y * y_ratio) # get the source line for x in x_range: x += 0.5 # use the center of each pixel source_x = int(x * x_ratio) # get the source row pixels.extend(source.pixels.get(source_x, source_y)) return get_pixel_array(pixels, width, height, pixelsize)
def init(self): if self.current_pass > self.LAST_PASS: self.done = True return self.xstart, self.ystart, self.xstep, self.ystep = self.passes[self.current_pass] self.pixels_per_row = int(math.ceil(fdiv(self.reader.width - self.xstart, self.xstep))) self.row_bytes = int(math.ceil(self.reader.psize * self.pixels_per_row)) self.reader.scanline_length = self.get_scanline_length() if self.ystart >= self.reader.height: # empty pass self.next_pass() elif self.xstart >= self.reader.width: # empty pass self.next_pass() else: self.yiter = irange(self.ystart, self.reader.height, self.ystep) self.current_y = next(self.yiter)
def init(self): if self.current_pass > self.LAST_PASS: self.done = True return self.xstart, self.ystart, self.xstep, self.ystep = self.passes[ self.current_pass] self.pixels_per_row = int( math.ceil(fdiv(self.reader.width - self.xstart, self.xstep))) self.row_bytes = int( math.ceil(self.reader.pixelsize * self.pixels_per_row)) self.reader.scanline_length = self.get_scanline_length() if self.ystart >= self.reader.height: # empty pass self.next_pass() elif self.xstart >= self.reader.width: # empty pass self.next_pass() else: self.yiter = irange(self.ystart, self.reader.height, self.ystep) self.current_y = next(self.yiter)
def _mixin_alpha(colors, alpha): from pymaging.utils import fdiv ratio = fdiv(alpha, 255) return [int(round(color * ratio)) for color in colors]
def iter_pixels(self, color): """ Use Xiaolin Wu's line algorithm: http://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm function plot(x, y, c) is plot the pixel at (x, y) with brightness c (where 0 ≤ c ≤ 1) function ipart(x) is return integer part of x function round(x) is return ipart(x + 0.5) function fpart(x) is return fractional part of x function rfpart(x) is return 1 - fpart(x) function drawLine(x1,y1,x2,y2) is dx = x2 - x1 dy = y2 - y1 if abs(dx) < abs(dy) then swap x1, y1 swap x2, y2 swap dx, dy end if if x2 < x1 swap x1, x2 swap y1, y2 end if gradient = dy / 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(xpxl1, ypxl1, rfpart(yend) * xgap) plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap) 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 (xpxl2, ypxl2, rfpart (yend) * xgap) plot (xpxl2, ypxl2 + 1, fpart (yend) * xgap) // main loop for x from xpxl1 + 1 to xpxl2 - 1 do plot (x, ipart (intery), rfpart (intery)) plot (x, ipart (intery) + 1, fpart (intery)) intery = intery + gradient end function """ def _plot(x, y, c): """ plot the pixel at (x, y) with brightness c (where 0 ≤ c ≤ 1) """ return int(x), int(y), color.get_for_brightness(c) dx = self.end_x - self.start_x dy = self.end_y - self.start_y x1, x2, y1, y2 = self.start_x, self.end_x, self.start_y, self.end_y if abs(dx) > abs(dy): x1, y1 = y1, x1 x2, y2 = y2, x2 dx, dy = dy, dx if x2 < x1: x1, x2 = x2, x1 y1, y2 = y2, y1 gradient = fdiv(dy, dx) xend = round(x1) yend = y1 + gradient * (xend - x1) xgap = _rfpart(x1 + 0.5) xpxl1 = xend ypxl1 = _ipart(yend) yield _plot(xpxl1, ypxl1, _rfpart(yend) * xgap) yield _plot(xpxl1, ypxl1 + 1, _fpart(yend) * xgap) intery = yend + gradient xend = _round(x2) yend = y2 + gradient * (xend - x2) xgap = _fpart(x2 + 0.5) xpxl2 = xend ypxl2 = _ipart(yend) yield _plot(xpxl2, ypxl2, _rfpart(yend) * xgap) yield _plot(xpxl2, ypxl2 + 1, _fpart(yend) * xgap) for x in range(xpxl1 + 1, xpxl2 - 1): yield _plot(x, _ipart(intery), _rfpart(intery)) yield _plot(x, _ipart(intery) + 1, _fpart(intery)) intery += gradient
def _mixin_alpha(colors, alpha): ratio = fdiv(alpha, 255) return [int(round(color * ratio)) for color in colors]
def handle_chunk_IHDR(self, chunk, length): # http://www.w3.org/TR/PNG/#11IHDR if length != 13: raise ChunkError('IHDR chunk has incorrect length %s, should be 13.' % length) (self.width, self.height, self.bit_depth, self.color_type, self.compression_method, self.filter_method, self.interlace_method) = struct.unpack("!2I5B", chunk) # Check that the header specifies only valid combinations. if self.bit_depth not in ALLOWED_BIT_DEPTHS: raise PNGReaderError("invalid bit depth %d" % self.bit_depth) if self.color_type not in ALLOWED_COLOR_TYPES: raise PNGReaderError("invalid colour type %d" % self.color_type) # Check indexed (palettized) images have 8 or fewer bits # per pixel; check only indexed or greyscale images have # fewer than 8 bits per pixel. if ((self.color_type & 1 and self.bit_depth > 8) or (self.bit_depth < 8 and self.color_type not in (0,3))): raise PNGReaderError("Illegal combination of bit depth (%d)" " and colour type (%d)." " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." % (self.bit_depth, self.color_type)) if self.compression_method != 0: raise PNGReaderError("unknown compression method %d" % self.compression_method) if self.filter_method != 0: raise PNGReaderError("Unknown filter method %d," " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ." % self.filter_method) if self.interlace_method not in (0, 1): raise PNGReaderError("Unknown interlace method %d," " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ." % self.interlace_method) # Derived values # http://www.w3.org/TR/PNG/#6Colour-values colormap = bool(self.color_type & 1) greyscale = not (self.color_type & 2) alpha = bool(self.color_type & 4) if greyscale or colormap: color_planes = 1 else: color_planes = 3 planes = color_planes + alpha self.colormap = colormap self.greyscale = greyscale self.alpha = alpha self.color_planes = color_planes self.planes = planes self.psize = fdiv(self.bit_depth, 8) * planes if int(self.psize) == self.psize: self.psize = int(self.psize) self.filter_unit = max(1, self.psize) self.row_bytes = int(math.ceil(self.width * self.psize)) # scanline stuff self.scanline = array('B') if self.bit_depth == 16: array_code = 'H' else: array_code = 'B' if self.interlace_method: self.adam7 = Adam7(self) self.pixels = [array(array_code, [0] * self.width * self.psize) for _ in range(self.height)] self.scanline_length = self.adam7.get_scanline_length() self._process_scanline = self._process_interlaced_scanline else: self.previous_scanline = None self.pixels = [] self.scanline_length = self.row_bytes + 1 self._process_scanline = self._process_straightlaced_scanline
def handle_chunk_IHDR(self, chunk, length): # http://www.w3.org/TR/PNG/#11IHDR if length != 13: raise ChunkError( 'IHDR chunk has incorrect length %s, should be 13.' % length) (self.width, self.height, self.bit_depth, self.color_type, self.compression_method, self.filter_method, self.interlace_method) = struct.unpack("!2I5B", chunk) # Check that the header specifies only valid combinations. if self.bit_depth not in ALLOWED_BIT_DEPTHS: raise PNGReaderError("invalid bit depth %d" % self.bit_depth) if self.color_type not in ALLOWED_COLOR_TYPES: raise PNGReaderError("invalid colour type %d" % self.color_type) # Check indexed (palettized) images have 8 or fewer bits # per pixel; check only indexed or greyscale images have # fewer than 8 bits per pixel. if ((self.color_type & 1 and self.bit_depth > 8) or (self.bit_depth < 8 and self.color_type not in (0, 3))): raise PNGReaderError( "Illegal combination of bit depth (%d)" " and colour type (%d)." " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." % (self.bit_depth, self.color_type)) if self.compression_method != 0: raise PNGReaderError("unknown compression method %d" % self.compression_method) if self.filter_method != 0: raise PNGReaderError( "Unknown filter method %d," " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ." % self.filter_method) if self.interlace_method not in (0, 1): raise PNGReaderError( "Unknown interlace method %d," " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ." % self.interlace_method) self.pixelsize = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4, }[self.color_type] # Derived values # http://www.w3.org/TR/PNG/#6Colour-values colormap = bool(self.color_type & 1) greyscale = not (self.color_type & 2) alpha = bool(self.color_type & 4) if greyscale or colormap: color_planes = 1 else: color_planes = 3 planes = color_planes + alpha self.colormap = colormap self.greyscale = greyscale self.alpha = alpha self.mode = RGBA if self.alpha else RGB self.color_planes = color_planes self.planes = planes self.psize = fdiv(self.bit_depth, 8) * planes if int(self.psize) == self.psize: self.psize = int(self.psize) self.filter_unit = max(1, self.psize) self.row_bytes = int(math.ceil(self.width * self.psize)) # scanline stuff self.scanline = array.array('B') if self.bit_depth == 16: array_code = 'H' else: array_code = 'B' data = array.array(array_code, [0] * self.width * self.height * self.pixelsize) self.pixels = get_pixel_array(data, self.width, self.height, self.pixelsize) if self.interlace_method: self.adam7 = Adam7(self) self.scanline_length = self.adam7.get_scanline_length() self._process_scanline = self._process_interlaced_scanline else: self.previous_scanline = None self.scanline_length = self.row_bytes + 1 self.current_y = 0 self._process_scanline = self._process_straightlaced_scanline
def resize(self, source, width, height, resize_canvas=True): if not resize_canvas: # this optimised implementation doesn't deal with this. # so delegate to affine() return super(Bilinear, self).resize( source, width, height, resize_canvas=resize_canvas ) x_ratio = fdiv(source.width, width) # get the x-axis ratio y_ratio = fdiv(source.height, height) # get the y-axis ratio pixelsize = source.pixelsize pixels = array.array('B') if source.palette: raise NotImplementedError("Resampling of paletted images is not yet supported") if x_ratio < 1 and y_ratio < 1: if not (width % source.width) and not (height % source.height): # optimisation: if doing a perfect upscale, # can just use nearest neighbor (it's much faster) return nearest.resize(source, width, height) has_alpha = source.mode.alpha color_channels_range = range(pixelsize - 1 if has_alpha else pixelsize) y_range = range(height) # an iterator over the indices of all lines (y-axis) x_range = range(width) # an iterator over the indices of all rows (x-axis) for y in y_range: src_y = (y + 0.5) * y_ratio - 0.5 # use the center of each pixel src_y_i = int(src_y) weight_y0 = 1 - abs(src_y - src_y_i) for x in x_range: src_x = (x + 0.5) * x_ratio - 0.5 src_x_i = int(src_x) weight_x0 = 1 - abs(src_x - src_x_i) channel_sums = [0.0] * pixelsize # populate <=4 nearest src_pixels, taking care not to go off # the edge of the image. src_pixels = [source.get_color(src_y_i, src_x_i), None, None, None] if src_x_i + 1 < source.width: src_pixels[1] = source.get_color(src_y_i, src_x_i + 1) else: weight_x0 = 1 if src_y_i + 1 < source.height: src_pixels[2] = source.get_color(src_y_i + 1, src_x_i) if src_x_i + 1 < source.height: src_pixels[3] = source.get_color(src_y_i + 1, src_x_i + 1) else: weight_y0 = 1 for i, src_pixel in enumerate(src_pixels): if src_pixel is None: continue src_pixel = src_pixel.to_pixel(pixelsize) weight_x = (1 - weight_x0) if (i % 2) else weight_x0 weight_y = (1 - weight_y0) if (i // 2) else weight_y0 alpha_weight = weight_x * weight_y color_weight = alpha_weight alpha = 255 if has_alpha: alpha = src_pixel[-1] if not alpha: continue color_weight *= (alpha / 255.0) for channel_index, channel_value in zip(color_channels_range, src_pixel): channel_sums[channel_index] += color_weight * channel_value if has_alpha: channel_sums[-1] += alpha_weight * alpha if has_alpha: total_alpha_multiplier = channel_sums[-1] / 255.0 if total_alpha_multiplier: # (avoid div/0) for channel_index in color_channels_range: channel_sums[channel_index] /= total_alpha_multiplier pixels.extend([int(round(s)) for s in channel_sums]) return get_pixel_array(pixels, width, height, pixelsize)
def iter_pixels(self, color): """ Use Xiaolin Wu's line algorithm: http://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm function plot(x, y, c) is plot the pixel at (x, y) with brightness c (where 0 ≤ c ≤ 1) function ipart(x) is return integer part of x function round(x) is return ipart(x + 0.5) function fpart(x) is return fractional part of x function rfpart(x) is return 1 - fpart(x) function drawLine(x1,y1,x2,y2) is dx = x2 - x1 dy = y2 - y1 if abs(dx) < abs(dy) then swap x1, y1 swap x2, y2 swap dx, dy end if if x2 < x1 swap x1, x2 swap y1, y2 end if gradient = dy / 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(xpxl1, ypxl1, rfpart(yend) * xgap) plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap) 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 (xpxl2, ypxl2, rfpart (yend) * xgap) plot (xpxl2, ypxl2 + 1, fpart (yend) * xgap) // main loop for x from xpxl1 + 1 to xpxl2 - 1 do plot (x, ipart (intery), rfpart (intery)) plot (x, ipart (intery) + 1, fpart (intery)) intery = intery + gradient end function """ def _plot(x, y, c): """ plot the pixel at (x, y) with brightness c (where 0 ≤ c ≤ 1) """ return int(x), int(y), color.get_for_brightness(c) dx = self.end_x - self.start_x dy = self.end_y - self.start_y x1, x2, y1, y2 = self.start_x, self.end_x, self.start_y, self.end_y if abs(dx) > abs(dy): x1, y1 = y1, x1 x2, y2 = y2, x2 dx, dy = dy, dx if x2 < x1: x1, x2 = x2, x1 y1, y2 = y2, y1 gradient = fdiv(dy, dx) xend = round(x1) yend = y1 + gradient * (xend - x1) xgap = _rfpart(x1 + 0.5) xpxl1 = xend ypxl1 = _ipart(yend) yield _plot(xpxl1, ypxl1, _rfpart(yend) * xgap) yield _plot(xpxl1, ypxl1 + 1, _fpart(yend) * xgap) intery = yend + gradient xend = _round(x2) yend = y2 + gradient * (xend - x2) xgap = _fpart(x2 + 0.5) xpxl2 = xend ypxl2 = _ipart(yend) yield _plot(xpxl2, ypxl2, _rfpart(yend) * xgap) yield _plot(xpxl2, ypxl2 + 1, _fpart(yend) * xgap) for x in range (xpxl1 + 1, xpxl2 - 1): yield _plot(x, _ipart(intery), _rfpart(intery)) yield _plot(x, _ipart(intery) + 1, _fpart(intery)) intery += gradient