def canvas_size(image, new_size, centering, background_color, opacity, old_size=None): #because of layer support photo size can be different from image layer size if old_size is None: old_size = image.size #check if image size has changed if old_size == new_size: return image #displacement dx = new_size[0] - old_size[0] dy = new_size[1] - old_size[1] #alignment x = int(centering[0] * dx / 100.0) y = int(centering[1] * dy / 100.0) #mode if image.mode in ['RGBA', 'LA', 'P']: if image.mode != 'RGBA': image = image.convert('RGBA') background_color = HTMLColorToRGBA(background_color, opacity) if image.mode in ['RGB', 'RGBA']: new_canvas = Image.new(image.mode, new_size, background_color) else: new_canvas = Image.new('RGB', new_size, background_color) if opacity > 0: imtools.paste(new_canvas, image, (x, y), mask=image) else: imtools.paste(new_canvas, image, (x, y), force=True) return new_canvas
def put_highlight(image, highlight, resample_highlight, opacity, cache=None): if cache is None: cache = {} resample_highlight = getattr(Image, resample_highlight) id = 'highlight_%s_w%d_h%d_o%d'\ % (highlight, image.size[0], image.size[1], opacity) try: highlight = cache[id] except KeyError: highlight = open_image(highlight)\ .convert('RGBA').resize(image.size, resample_highlight) if opacity < 100: #apply opacity highlight_alpha = imtools.get_alpha(highlight) opacity = (255 * opacity) / 100 highlight.putalpha(ImageMath.eval("convert((a * o) / 255, 'L')", a=highlight_alpha, o=opacity)) #store in cache cache[id] = highlight if not has_transparency(image): image = image.convert('RGBA') else: if has_transparency(image): image = image.convert('RGBA') alpha = imtools.get_alpha(image) highlight = highlight.copy() highlight_alpha = imtools.get_alpha(highlight) highlight.putalpha(ImageMath.eval("convert(min(a, b), 'L')", a=alpha, b=highlight_alpha)) overlay = highlight.convert('RGB') paste(image, overlay, mask=highlight) return image
def border(image, method, border_width=0, left=0, right=0, top=0, bottom=0, color=0, opacity=100): """ """ #set up sizes, and make the target img if method == OPTIONS[0]: left, right, top, bottom = (border_width, ) * 4 else: left, right, top, bottom = [x for x in left, right, top, bottom] #new image size attributes could get really messed up by negatives... new_width = sum([x for x in image.size[0], left, right if x >= 0]) new_height = sum([x for x in image.size[1], top, bottom if x >= 0]) # only need to do conversions when preserving transparency, or when # dealing with transparent overlays negative = [x for x in left, right, top, bottom if x < 0] if (negative and (opacity < 100)) or has_transparency(image): new_image = Image.new('RGBA', (new_width, new_height), color) else: new_image = Image.new('RGB', (new_width, new_height), color) # now for the masking component. The size of the mask needs to be the size # of the original image, and totally opaque. then we will have draw in # negative border values with an opacity scaled appropriately. # NOTE: the technique here is that rotating the image allows me to do # this with one simple draw operation, no need to add and subtract and # otherwise introduce geometry errors if negative: #draw transparent overlays mask = Image.new('L', image.size, 255) drawcolor = int(255 - (opacity / 100.0 * 255)) for val in left, top, right, bottom: if val < 0: mask_draw = ImageDraw.Draw(mask) mask_draw.rectangle((0, 0, abs(val), max(mask.size)), drawcolor) del mask_draw mask = mask.rotate(90) else: mask = None # negative paste position values mess with the result. left = max(left, 0) top = max(top, 0) paste(new_image, image, (left, top), mask) return new_image
def create_rounded_rectangle(size=(600, 400), cache={}, radius=100, opacity=255, pos=ROUNDED_POS): #rounded_rectangle im_x, im_y = size rounded_rectangle_id = ROUNDED_RECTANGLE_ID % (radius, opacity, size, pos) if rounded_rectangle_id in cache: return cache[rounded_rectangle_id] else: #cross cross_id = ROUNDED_RECTANGLE_ID % (radius, opacity, size, CROSS_POS) if cross_id in cache: cross = cache[cross_id] else: cross = cache[cross_id] = Image.new('L', size, 0) draw = ImageDraw.Draw(cross) draw.rectangle((radius, 0, im_x - radius, im_y), fill=opacity) draw.rectangle((0, radius, im_x, im_y - radius), fill=opacity) if pos == CROSS_POS: return cross #corner corner_id = CORNER_ID % (radius, opacity) if corner_id in cache: corner = cache[corner_id] else: corner = cache[corner_id] = create_corner(radius, opacity) #rounded rectangle rectangle = Image.new('L', (radius, radius), 255) rounded_rectangle = cross.copy() for index, angle in enumerate(pos): if angle == CROSS: continue if angle == ROUNDED: element = corner else: element = rectangle if index % 2: x = im_x - radius element = element.transpose(Image.FLIP_LEFT_RIGHT) else: x = 0 if index < 2: y = 0 else: y = im_y - radius element = element.transpose(Image.FLIP_TOP_BOTTOM) imtools.paste(rounded_rectangle, element, (x, y)) cache[rounded_rectangle_id] = rounded_rectangle return rounded_rectangle
def round_image(image, cache={}, round_all=True, rounding_type=None, radius=100, opacity=255, pos=ROUNDED_POS, back_color='#FFFFFF'): if image.mode != 'RGBA': image = image.convert('RGBA') if round_all: pos = 4 * (rounding_type, ) mask = create_rounded_rectangle(image.size, cache, radius, opacity, pos) imtools.paste(image, Image.new('RGB', image.size, back_color), (0, 0), ImageChops.invert(mask)) image.putalpha(mask) return image
def put_contour(image, size=1, offset=0, contour_color=0, fill_color=0, opacity=100, include_image=True): if not has_transparency(image): return put_border(image, size, offset, contour_color, fill_color, opacity, include_image) image = image.convert('RGBA') mask = imtools.get_alpha(image) w, h = image.size outer_mask = mask.resize( (w + 2 * (size + offset), h + 2 * (size + offset)), Image.ANTIALIAS) inner_mask = mask.resize((w + 2 * offset, h + 2 * offset), Image.ANTIALIAS) inner_mask = ImageOps.expand(inner_mask, border=size, fill=0) paste(outer_mask, (255 * opacity) / 100, mask=inner_mask) if include_image: image = ImageOps.expand(image, border=size + offset, fill=(0, 0, 0, 0)) mask = ImageOps.expand(mask, border=size + offset, fill=0) paste(outer_mask, 255, mask=mask) contour = ImageOps.colorize(outer_mask, (255, 255, 255), contour_color) paste(contour, fill_color, mask=inner_mask) if include_image: paste(contour, image, mask=image) contour.putalpha(outer_mask) return contour
def put_contour(image, size=1, offset=0, contour_color=0, fill_color=0, opacity=100, include_image=True): if not has_transparency(image): return put_border( image, size, offset, contour_color, fill_color, opacity, include_image) image = image.convert('RGBA') mask = imtools.get_alpha(image) w, h = image.size outer_mask = mask.resize( (w + 2 * (size + offset), h + 2 * (size + offset)), Image.ANTIALIAS) inner_mask = mask.resize( (w + 2 * offset, h + 2 * offset), Image.ANTIALIAS) inner_mask = ImageOps.expand(inner_mask, border=size, fill=0) paste(outer_mask, (255 * opacity) / 100, mask=inner_mask) if include_image: image = ImageOps.expand(image, border=size + offset, fill=(0, 0, 0, 0)) mask = ImageOps.expand(mask, border=size + offset, fill=0) paste(outer_mask, 255, mask=mask) contour = ImageOps.colorize(outer_mask, (255, 255, 255), contour_color) paste(contour, fill_color, mask=inner_mask) if include_image: paste(contour, image, mask=image) contour.putalpha(outer_mask) return contour
def tile(image, direction): if image.mode == 'P': image = convert_safe_mode(image) result = Image.new(image.mode, get_dimensions(image, direction)) paste(result, image, (0, 0)) if direction == BOTH: x_mirror(image, result) y_mirror(image, result) xy_mirror(image, result) if direction == HORIZONTAL: x_mirror(image, result) if direction == VERTICAL: y_mirror(image, result) return result
def background(image, fill, mark, color, horizontal_offset=None, vertical_offset=None, horizontal_justification=None, vertical_justification=None, orientation=None, method=None, opacity=100): if not has_transparency(image): return image if image.mode == 'P': image = image.convert('RGBA') if fill == FILL_CHOICES[0]: opacity = (255 * opacity) / 100 return fill_background_color(image, HTMLColorToRGBA(color, opacity)) elif fill == FILL_CHOICES[1]: layer = generate_layer(image.size, mark, method, horizontal_offset, vertical_offset, horizontal_justification, vertical_justification, orientation, opacity) paste(layer, image, mask=image) return layer
def reflect(image, depth, opacity, background_color, background_opacity, scale_method, gap=0, scale_reflection=False, blur_reflection=False, cache=None): if has_transparency(image): image = image.convert('RGBA') else: image = image.convert('RGB') if cache is None: cache = {} opacity = (255 * opacity) / 100 background_opacity = (255 * background_opacity) / 100 scale_method = getattr(Image, scale_method) if background_opacity == 255: mode = 'RGB' color = background_color else: mode = 'RGBA' color = HTMLColorToRGBA(background_color, background_opacity) width, height = image.size depth = min(height, depth) #make reflection if has_alpha(image) and background_opacity > 0: reflection = Image.new(mode, image.size, color) paste(reflection, image, (0, 0), image) else: reflection = image reflection = reflection.transpose(Image.FLIP_TOP_BOTTOM) if scale_reflection: reflection = reflection.resize((width, depth), scale_method) else: reflection = reflection.crop((0, 0, width, depth)) if blur_reflection: reflection = reflection.filter(ImageFilter.BLUR) mask = gradient_mask((width, depth), opacity, cache) #composite total_size = (width, height + gap + depth) total = Image.new(mode, total_size, color) paste(total, image, (0, 0), image) paste(total, reflection, (0, height + gap), mask) return total
def make_grid(image, grid, col_line_width=0, row_line_width=0, line_color='#FFFFFF', line_opacity=0, old_size=None, scale=True): # Check if there is any work to do. if grid == (1, 1): return image # Because of layer support photo size can be different # from image layer size if old_size is None: old_size = image.size # Unpack grid cols, rows = grid # Scaling down? if scale: # Keep the same number of pixels in the result s = sqrt(cols * rows) old_size = tuple(map(lambda x: int(x / s), old_size)) # To scale down we need to make the image processing safe. image = imtools.convert_safe_mode(image)\ .resize(old_size, getattr(Image, 'ANTIALIAS')) #displacement dx, dy = old_size dx += col_line_width dy += row_line_width new_size = cols * dx - col_line_width, rows * dy - row_line_width # The main priority is that the new_canvas has the same mode as the image. # Palette images if image.mode == 'P': if 0 < line_opacity < 255: # transparent lines require RGBA image = imtools.convert(image, 'RGBA') else: if 'transparency' in image.info and line_opacity == 0: # Make line color transparent for images # with transparency. line_color_index = image.info['transparency'] palette = None else: line_color_index, palette = imtools.fit_color_in_palette( image, ImageColor.getrgb(line_color), ) if line_color_index != -1: new_canvas = Image.new('P', new_size, line_color_index) imtools.put_palette(new_canvas, image, palette) else: # Convert to non palette image (RGB or RGBA) image = imtools.convert_safe_mode(image) # Non palette images if image.mode != 'P': line_color = ImageColor.getcolor(line_color, image.mode) if imtools.has_alpha(image): # Make line color transparent for images # with an alpha channel. line_color = tuple(list(line_color)[:-1] + [line_opacity]) pass new_canvas = Image.new(image.mode, new_size, line_color) # Paste grid for x in range(cols): for y in range(rows): pos = (x * dx, y * dy) imtools.paste(new_canvas, image, pos, force=True) return new_canvas
def y_mirror(image, result): width, height = image.size paste(result, image.transpose(Image.FLIP_TOP_BOTTOM), (0, height))
def drop_shadow(image, horizontal_offset=5, vertical_offset=5, background_color=(255, 255, 255, 0), shadow_color=0x444444, border=8, shadow_blur=3, force_background_color=False, cache=None): """Add a gaussian blur drop shadow to an image. :param image: The image to overlay on top of the shadow. :param type: PIL Image :param offset: Offset of the shadow from the image as an (x,y) tuple. Can be positive or negative. :type offset: tuple of integers :param background_color: Background color behind the image. :param shadow_color: Shadow color (darkness). :param border: Width of the border around the image. This must be wide enough to account for the blurring of the shadow. :param shadow_blur: Number of times to apply the filter. More shadow_blur produce a more blurred shadow, but increase processing time. """ if cache is None: cache = {} if has_transparency(image) and image.mode != 'RGBA': # Make sure 'LA' and 'P' with trasparency are handled image = image.convert('RGBA') #get info size = image.size mode = image.mode back = None #assert image is RGBA if mode != 'RGBA': if mode != 'RGB': image = image.convert('RGB') mode = 'RGB' #create cache id id = ''.join([str(x) for x in ['shadow_', size, horizontal_offset, vertical_offset, border, shadow_blur, background_color, shadow_color]]) #look up in cache if id in cache: #retrieve from cache back, back_size = cache[id] if back is None: #size of backdrop back_size = (size[0] + abs(horizontal_offset) + 2 * border, size[1] + abs(vertical_offset) + 2 * border) #create shadow mask if mode == 'RGBA': image_mask = imtools.get_alpha(image) shadow = Image.new('L', back_size, 0) else: image_mask = Image.new(mode, size, shadow_color) shadow = Image.new(mode, back_size, background_color) shadow_left = border + max(horizontal_offset, 0) shadow_top = border + max(vertical_offset, 0) paste(shadow, image_mask, (shadow_left, shadow_top, shadow_left + size[0], shadow_top + size[1])) del image_mask # free up memory #blur shadow mask #Apply the filter to blur the edges of the shadow. Since a small #kernel is used, the filter must be applied repeatedly to get a decent #blur. n = 0 while n < shadow_blur: shadow = shadow.filter(ImageFilter.BLUR) n += 1 #create back if mode == 'RGBA': back = Image.new('RGBA', back_size, shadow_color) back.putalpha(shadow) del shadow # free up memory else: back = shadow cache[id] = back, back_size #Paste the input image onto the shadow backdrop image_left = border - min(horizontal_offset, 0) image_top = border - min(vertical_offset, 0) if mode == 'RGBA': paste(back, image, (image_left, image_top), image) if force_background_color: mask = imtools.get_alpha(back) paste(back, Image.new('RGB', back.size, background_color), (0, 0), ImageChops.invert(mask)) back.putalpha(mask) else: paste(back, image, (image_left, image_top)) return back
def border(image, method, border_width=0, left=0, right=0, top=0, bottom=0, color=0, opacity=100): """ """ #set up sizes, and make the target img if method == OPTIONS[0]: left, right, top, bottom = (border_width, ) * 4 else: left, right, top, bottom = [x for x in left, right, top, bottom] #new image size attributes could get really messed up by negatives... new_width = sum([x for x in image.size[0], left, right if x >= 0]) new_height = sum([x for x in image.size[1], top, bottom if x >= 0]) # only need to do conversions when preserving transparency, or when # dealing with transparent overlays negative = [x for x in left, right, top, bottom if x < 0] if (negative and (opacity < 100)) or has_transparency(image): new_image = Image.new('RGBA', (new_width, new_height), color) else: new_image = Image.new('RGB', (new_width, new_height), color) # now for the masking component. The size of the mask needs to be the size # of the original image, and totally opaque. then we will have draw in # negative border values with an opacity scaled appropriately. # NOTE: the technique here is that rotating the image allows me to do # this with one simple draw operation, no need to add and subtract and # otherwise introduce geometry errors if negative: #draw transparent overlays mask = Image.new('L', image.size, 255) drawcolor = int(255 - (opacity / 100.0 * 255)) for val in left, top, right, bottom: if val < 0: mask_draw = ImageDraw.Draw(mask) mask_draw.rectangle((0, 0, abs(val), max(mask.size)), drawcolor) del mask_draw mask = mask.rotate(90) else: mask = None # negative paste position values mess with the result. left = max(left, 0) top = max(top, 0) paste(new_image, image, (left, top), mask) return new_image #---Phatch CHOICES = ['-25', '-10', '-5', '-1', '0', '1', '5', '10', '25'] class Action(models.Action): label = _t('Border') author = 'Erich' email = '*****@*****.**' init = staticmethod(init) pil = staticmethod(border) version = '0.2' tags = [_t('filter')] __doc__ = _t('Draw border inside or outside') def interface(self, fields): fields[_t('Method')] = self.ChoiceField(OPTIONS[0], choices=OPTIONS) fields[_t('Border Width')] = self.PixelField('1px', choices=CHOICES) fields[_t('Left')] = self.PixelField('0px', choices=CHOICES) fields[_t('Right')] = self.PixelField('0px', choices=CHOICES) fields[_t('Top')] = self.PixelField('0px', choices=CHOICES) fields[_t('Bottom')] = self.PixelField('0px', choices=CHOICES) fields[_t('Color')] = self.ColorField('#000000') fields[_t('Opacity')] = self.SliderField(100, 1, 100) def values(self, info): #pixel fields width, height = info['size'] # pass absolute reference for relative pixel values such as % return super(Action, self).values(info, pixel_fields={ 'Border Width': (width + height) / 2, 'Left': width, 'Right': width, 'Top': height, 'Bottom': height, }) def get_relevant_field_labels(self): """If this method is present, Phatch will only show relevant fields. :returns: list of the field labels which are relevant :rtype: list of strings .. note:: It is very important that the list of labels has EXACTLY the same order as defined in the interface method. """ relevant = ['Method', 'Color', 'Opacity'] if self.get_field_string('Method') == OPTIONS[0]: relevant.append('Border Width') else: relevant.extend(['Left', 'Right', 'Top', 'Bottom']) return relevant icon = \ 'x\xda\x01x\x0b\x87\xf4\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x000\x00\
def x_mirror(image, result): width, height = image.size paste(result, image.transpose(Image.FLIP_LEFT_RIGHT), (width, 0))
def xy_mirror(image, result): paste(result, image.transpose(Image.ROTATE_180), image.size)