from PIL import ImageDraw, Image as PilImage from . import Layout, ImageMode, ImageAnchor from .internal.helpers import max_with_none, min_with_none class Image(Layout): def __init__( self, image=None, mode=ImageMode.ORIGINAL, scale=1, position=ImageAnchor.TOP_LEFT, offset_x=0, offset_y=0, **kwargs ): super().__init__(**kwargs) self.image = image self.mode = mode self.scale = scale self.position = position self.offset_x = offset_x self.offset_y = offset_y def get_scale(self): if isinstance(self.scale, (tuple, list)): if len(self.scale) == 0: return 1, 1 elif len(self.scale) == 1: return self.scale[0], self.scale[0] else: return self.scale[0], self.scale[1] elif isinstance(self.scale, (int, float)): return self.scale, self.scale else: return 1, 1 def get_image_width(self): if self.image is None: return 0 return self.image.size[0] def get_image_height(self): if self.image is None: return 0 return self.image.size[1] def get_min_inner_width(self, max_height=None): if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y): width = self.get_image_width() else: width = 0 return max_with_none(width, self.min_width) def get_min_inner_height(self, max_width=None): if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X): height = self.get_image_height() else: height = 0 return max_with_none(height, self.min_height) def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None): width, height = 0, 0 if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y): width = self.get_image_width() if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X): height = self.get_image_height() if self.width is not None: width = self.width if self.height is not None: height = self.height width = min_with_none(width, available_width, self.max_width) height = min_with_none(height, available_height, self.max_height) width = max_with_none(width, min_width, self.min_width) height = max_with_none(height, min_height, self.min_height) return width, height def render_content(self, rect): image = self.make_canvas() if self.image is None: return image x1, y1, x2, y2 = rect width = x2 - x1 + 1 height = y2 - y1 + 1 if width == 0 or height == 0: return image image_width = int(self.image.size[0] * self.get_scale()[0]) image_height = int(self.image.size[1] * self.get_scale()[1]) offset_x = self.offset_x offset_y = self.offset_y if self.mode == ImageMode.ORIGINAL: if image_width != self.image.size[0] or image_height != self.image.size[1]: img = self.image.resize((image_width, image_height), reducing_gap=3.0) else: img = self.image elif self.mode == ImageMode.STRETCH: img = self.image.resize((width, height), reducing_gap=3.0) elif self.mode == ImageMode.STRETCH_X: img = self.image.resize((width, image_height), reducing_gap=3.0) elif self.mode == ImageMode.STRETCH_Y: img = self.image.resize((image_width, height), reducing_gap=3.0) elif self.mode == ImageMode.CONTAIN: ratio_width = width / image_width ratio_height = height / image_height if ratio_width < ratio_height: w = width h = int(image_height * width / image_width) elif ratio_height < ratio_width: w = int(image_width * height / image_height) h = height else: w = width h = height img = self.image.resize((w, h), reducing_gap=3.0) elif self.mode == ImageMode.COVER: ratio_width = width / image_width ratio_height = height / image_height if ratio_width > ratio_height: w = width h = int(image_height * width / image_width) elif ratio_height > ratio_width: w = int(image_width * height / image_height) h = height else: w = width h = height img = self.image.resize((w, h), reducing_gap=3.0) if self.mode == ImageMode.REPEAT_X_FILL: repeat_width = int(image_width * height / image_height) repeat_height = height elif self.mode == ImageMode.REPEAT_Y_FILL: repeat_width = width repeat_height = int(image_height * width / image_width) else: repeat_width = image_width repeat_height = image_height if self.mode in (ImageMode.REPEAT, ImageMode.REPEAT_X, ImageMode.REPEAT_X_FILL, ImageMode.REPEAT_X_STRETCH): if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.MIDDLE_LEFT, ImageAnchor.BOTTOM_LEFT): repeat_offset_x = offset_x elif self.position in (ImageAnchor.TOP_CENTER, ImageAnchor.MIDDLE_CENTER, ImageAnchor.BOTTOM_CENTER): repeat_offset_x = (width // 2) - repeat_width // 2 + offset_x elif self.position in (ImageAnchor.TOP_RIGHT, ImageAnchor.MIDDLE_RIGHT, ImageAnchor.BOTTOM_RIGHT): repeat_offset_x = width - repeat_width + offset_x else: raise Exception('invalid image position') if self.mode in (ImageMode.REPEAT, ImageMode.REPEAT_Y, ImageMode.REPEAT_Y_FILL, ImageMode.REPEAT_Y_STRETCH): if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.TOP_CENTER, ImageAnchor.TOP_RIGHT): repeat_offset_y = offset_y elif self.position in (ImageAnchor.MIDDLE_LEFT, ImageAnchor.MIDDLE_CENTER, ImageAnchor.MIDDLE_RIGHT): repeat_offset_y = (height // 2) - repeat_height // 2 + offset_y elif self.position in (ImageAnchor.BOTTOM_LEFT, ImageAnchor.BOTTOM_CENTER, ImageAnchor.BOTTOM_RIGHT): repeat_offset_y = height - repeat_height + offset_y else: raise Exception('invalid image position') if self.mode == ImageMode.REPEAT: if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]: img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0) else: img_part = self.image img = PilImage.new('RGBA', (width, height), (0,0,0,0)) num_x = width // repeat_width + 1 num_y = height // repeat_height + 1 ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width) oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height) for i in range(-1, num_x): for j in range(-1, num_y): img.paste(img_part, (i * repeat_width + ox, j * repeat_height + oy)) offset_x = 0 offset_y = 0 elif self.mode == ImageMode.REPEAT_X: if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]: img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0) else: img_part = self.image img = PilImage.new('RGBA', (width, repeat_height), (0,0,0,0)) num_x = width // repeat_width + 1 ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width) for i in range(-1, num_x): img.paste(img_part, (i * repeat_width + ox, 0)) offset_x = 0 elif self.mode == ImageMode.REPEAT_Y: if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]: img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0) else: img_part = self.image img = PilImage.new('RGBA', (repeat_width, height), (0,0,0,0)) num_y = height // repeat_height + 1 oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height) for i in range(-1, num_y): img.paste(img_part, (0, i * repeat_height + oy)) offset_y = 0 elif self.mode == ImageMode.REPEAT_X_STRETCH: if repeat_width != self.image.size[0] or height != self.image.size[1]: img_part = self.image.resize((repeat_width, height), reducing_gap=3.0) else: img_part = self.image img = PilImage.new('RGBA', (width, height), (0,0,0,0)) num_x = width // repeat_width + 1 ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width) for i in range(-1, num_x): img.paste(img_part, (i * repeat_width + ox, 0)) offset_x = 0 elif self.mode == ImageMode.REPEAT_Y_STRETCH: if width != self.image.size[0] or repeat_height != self.image.size[1]: img_part = self.image.resize((width, repeat_height), reducing_gap=3.0) else: img_part = self.image img = PilImage.new('RGBA', (width, height), (0,0,0,0)) num_y = height // repeat_height + 1 oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height) for i in range(-1, num_y): img.paste(img_part, (0, i * repeat_height + oy)) offset_y = 0 elif self.mode == ImageMode.REPEAT_X_FILL: if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]: img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0) else: img_part = self.image img = PilImage.new('RGBA', (width, height), (0,0,0,0)) num_x = width // repeat_width + 1 ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -w) for i in range(-1, num_x): img.paste(img_part, (i * repeat_width + ox, 0)) offset_x = 0 elif self.mode == ImageMode.REPEAT_Y_FILL: if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]: img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0) else: img_part = self.image img = PilImage.new('RGBA', (width, height), (0,0,0,0)) num_y = height // repeat_height + 1 oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height) for i in range(-1, num_y): img.paste(img_part, (0, i * repeat_height + oy)) offset_y = 0 if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.MIDDLE_LEFT, ImageAnchor.BOTTOM_LEFT): x = x1 + offset_x elif self.position in (ImageAnchor.TOP_CENTER, ImageAnchor.MIDDLE_CENTER, ImageAnchor.BOTTOM_CENTER): x = x1 + (width // 2) - img.size[0] // 2 + offset_x elif self.position in (ImageAnchor.TOP_RIGHT, ImageAnchor.MIDDLE_RIGHT, ImageAnchor.BOTTOM_RIGHT): x = x1 + width - img.size[0] + offset_x else: raise Exception('invalid image position') if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.TOP_CENTER, ImageAnchor.TOP_RIGHT): y = y1 + offset_y elif self.position in (ImageAnchor.MIDDLE_LEFT, ImageAnchor.MIDDLE_CENTER, ImageAnchor.MIDDLE_RIGHT): y = y1 + (height // 2) - img.size[1] // 2 + offset_y elif self.position in (ImageAnchor.BOTTOM_LEFT, ImageAnchor.BOTTOM_CENTER, ImageAnchor.BOTTOM_RIGHT): y = y1 + height - img.size[1] + offset_y else: raise Exception('invalid image position') image.paste(img, (x, y)) return image