1
0
Fork 0
mirror of https://git.lynn.is/Gwen/python-layout.git synced 2024-01-13 01:31:55 +01:00
python-layout/pillow_layout/image.py
2023-02-07 23:56:17 +01:00

267 lines
12 KiB
Python

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