PillowImage.pillow.PillowImage.correct_extension()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 4
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 3
dl 4
loc 4
rs 10
c 0
b 0
f 0
cc 2
nop 1
1
import os
2
from tempfile import NamedTemporaryFile, TemporaryDirectory
3
from pathlib import Path
4
5
from PIL import Image, ImageDraw, ImageFont
6
from PyBundle import bundle_dir, resource_path
7
8
from PillowImage.font import FONT
9
from PillowImage.utils import img_adjust
10
11
12 View Code Duplication
class PillowImage:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
13
    def __init__(self, img=None, size=(792, 612), mode='RGBA', color=(255, 255, 255, 0)):
14
        """
15
        Construct an image composition using Pillow.
16
17
        :param img: Image path
18
        :param size: Size of new image (if img is None)
19
        :param mode: Mode of the new image (if img is None)
20
        :param color: Color of the new image (if img is None)
21
        """
22
        if img:
23
            # Open img and convert to RGBA color space
24
            self.img = Image.open(img)
25
            if self.img.mode != 'RGBA':
26
                self.img = self.img.convert('RGBA')
27
            else:
28
                self.img = self.img.copy()
29
        else:
30
            # Create a black image
31
            self.img = Image.new(mode, size, color=color)    # 2200, 1700 for 200 DPI
32
        self._tempdir = None
33
34
    def __enter__(self):
35
        return self
36
37
    def __exit__(self, exc_type, exc_val, exc_tb):
38
        self.cleanup()
39
40
    @property
41
    def tempdir(self):
42
        if not self._tempdir:
43
            self._tempdir = TemporaryDirectory(prefix='pillowimg_')
44
        return self._tempdir
45
46
    @property
47
    def size(self):
48
        """Return a tuple (Width, Height) with image dimensions."""
49
        return self.img.size
50
51
    @property
52
    def width(self):
53
        """Return the width value of the image's dimensions."""
54
        return self.size[0]
55
56
    @property
57
    def height(self):
58
        """Return the height value of the image's dimensions."""
59
        return self.size[1]
60
61
    @property
62
    def mode(self):
63
        """Return the images mode."""
64
        return self.img.mode
65
66
    @property
67
    def correct_extension(self):
68
        """Return the images mode."""
69
        return '.jpg' if self.mode != 'RGBA' else '.png'
70
71
    @property
72
    def longest_side(self):
73
        """Return the longest side value (width or height) of the image."""
74
        return max(self.height, self.width)
75
76
    def _text_centered_x(self, text, drawing, font_type):
77
        """
78
        Retrieve a 'x' value that centers the text in the canvas.
79
80
        :param text: String to be centered
81
        :param drawing: PIL.ImageDraw.Draw instance
82
        :param font_type: Registered font family type
83
        :return: X coordinate value
84
        """
85
        # ('Page Width' - 'Text Width') / 2
86
        return (self.width - drawing.textsize(text, font=font_type)[0]) / 2
87
88
    def _text_centered_y(self, font_size):
89
        """
90
        Retrieve a 'y' value that centers the image in the canvas.
91
92
        :param font_size: Font size
93
        :return: Y coordinate value
94
        """
95
        # ('Image Size' / 2) - 'Font Size'
96
        return (self.height / 2) - font_size
97
98
    def _img_centered_x(self, image):
99
        """Retrieve an 'x' value that horizontally centers the image in the canvas."""
100
        return int((self.width / 2) - (image.size[0] / 2))
101
102
    def _img_centered_y(self, image):
103
        """Retrieve an 'y' value that vertically centers the image in the canvas."""
104
        return int((self.height / 2) - (image.size[1] / 2))
105
106
    def image_bound(self, image, x, y):
107
        """
108
        Calculate the image bounds.
109
110
        If 'center' is found in x or y, a value that centers the image is calculated.
111
        If a x or y value is negative, values are calculated as that distance from the right/bottom.
112
113
114
        :param image: Image to-be pasted
115
        :param x:
116
        :param y:
117
        :return: X and Y values
118
        """
119
        def calculator(value, img_size, center_func):
120
            """Helper function to perform bound calculations for either x or y values."""
121
            # Center the image
122
            if 'center' in str(value).lower():
123
                return center_func(image)
124
125
            # Percentage value, calculate based on percentages
126
            elif 0 < float(value) < 1:
127
                return int(img_size * float(value))
128
129
            # Negative value, calculate distance from far edge (Right, Bottom
130
            elif int(value) < 0:
131
                return int(img_size - abs(value))
132
            else:
133
                return int(value)
134
135
        return (abs(calculator(x, self.width,
136
                               self._img_centered_x)), abs(calculator(y, self.height, self._img_centered_y)))
137
138
    def scale_to_fit(self, img, func='min', scale=None, multiplier=float(1)):
139
        """
140
        Scale an image to fit the Pillow canvas.
141
142
        :param img: Image object
143
        :param func: Scale calculation function
144
        :param scale: Specific scale
145
        :param multiplier: Value to multiple calculated scale by
146
        :return:
147
        """
148
        im = img if isinstance(img, Image.Image) else Image.open(img)
149
150
        # Use either the shortest edge (min) or the longest edge (max) to determine scale factor
151
        if not scale:
152
            if func == 'min':
153
                scale = min(float(self.width / im.size[0]), float(self.height / im.size[1]))
154
            else:
155
                scale = max(float(self.width / im.size[0]), float(self.height / im.size[1]))
156
        scale = scale * multiplier
157
158
        im.thumbnail((int(im.size[0] * scale), int(im.size[1] * scale)))
159
160
        image = im if isinstance(img, Image.Image) else self.save(img=im)
161
        im.close()
162
        return image
163
164
    def resize(self, longest_side):
165
        """Resize by specifying the longest side length."""
166
        return self.resize_width(longest_side) if self.width > self.height else self.resize_height(longest_side)
167
168
    def resize_width(self, max_width):
169
        """Adjust an images width while proportionately scaling height."""
170
        width_percent = (max_width / float(self.width))
171
        height_size = int((float(self.height)) * float(width_percent))
172
        self.img = self.img.resize((max_width, height_size), Image.ANTIALIAS)
173
        return self.img
174
175
    def resize_height(self, max_height):
176
        """Adjust an images height while proportionately scaling width."""
177
        height_percent = (max_height / float(self.height))
178
        width_size = int((float(self.width) * float(height_percent)))
179
        self.img = self.img.resize((width_size, max_height), Image.ANTIALIAS)
180
        return self.img
181
182
    def draw_text(self, text, x='center', y=140, font=FONT, font_size=40, opacity=25):
183
        """
184
        Draw text onto a Pillow image canvas.
185
186
        :param text: Text string
187
        :param x: X coordinate value
188
        :param y: Y coordinate value
189
        :param font: Registered font family
190
        :param font_size: Font size
191
        :param opacity: Opacity of text to be drawn
192
        :return:
193
        """
194
        # Set drawing context
195
        d = ImageDraw.Draw(self.img)
196
197
        # Set a font
198
        fnt = ImageFont.truetype(font, int(font_size * 1.00))    # multiply size of font if needed
199
200
        # Check if x or y is set to 'center'
201
        x = self._text_centered_x(text, d, fnt) if 'center' in str(x).lower() else x
202
        y = self._text_centered_y(font_size) if 'center' in str(y).lower() else y
203
204
        # Draw text to image
205
        opacity = int(opacity * 100) if opacity < 1 else opacity
206
        d.text((x, y), text, font=fnt, fill=(0, 0, 0, opacity))
207
208
    def draw_img(self,
209
                 img,
210
                 x='center',
211
                 y='center',
212
                 opacity=1.0,
213
                 rotate=0,
214
                 fit=1,
215
                 scale_to_fit=True,
216
                 scale_multiplier=float(1)):
217
        """
218
        Scale an image to fit the canvas then alpha composite paste the image.
219
220
        Optionally place the image (x, y), adjust the images opacity
221
        or apply a rotation.
222
223
        :param img: Path to image to paste
224
        :param x: X coordinates value (Left)
225
        :param y: Y coordinates value (Top)
226
        :param opacity: Opacity value
227
        :param rotate: Rotation degrees
228
        :param fit: When true, expands image canvas size to fit rotated image
229
        :param scale_to_fit: When true, image is scaled to fit canvas size
230
        :param scale_multiplier: Value to multiple calculated scale by
231
        :return:
232
        """
233
        img = img_adjust(img, opacity, rotate, fit, self.tempdir.name)
234
        with Image.open(self.scale_to_fit(img, multiplier=scale_multiplier) if scale_to_fit else img) as image:
235
            x, y = self.image_bound(image, x, y)
236
            self.img.alpha_composite(image, (x, y))
237
238
    def rotate(self, rotate):
239
        # Create transparent image that is the same size as self.img
240
        mask = Image.new('L', self.img.size, 255)
241
242
        # Rotate image and then scale image to fit self.img
243
        front = self.img.rotate(rotate, expand=True)
244
245
        # Rotate mask
246
        mask.rotate(rotate, expand=True)
247
248
        # Determine difference in size between mask and front
249
        y_margin = int((mask.size[1] - front.size[1]) / 3)
250
251
        # Create another new image
252
        rotated = Image.new('RGBA', self.img.size, color=(255, 255, 255, 0))
253
254
        # Paste front into new image and set x offset equal to half
255
        # the difference of front and mask size
256
        rotated.paste(front, (0, y_margin))
257
        self.img = rotated
258
259
    def save(self, img=None, destination=None, file_name='pil', ext='.png'):
260
        img = self.img if not img else img
261
        if destination:
262
            output = os.path.join(destination, Path(file_name).stem + ext)
263
        elif self.tempdir:
264
            tmpimg = NamedTemporaryFile(suffix='.png', dir=self.tempdir.name, delete=False)
265
            output = resource_path(tmpimg.name)
266
            tmpimg.close()
267
        else:
268
            output = os.path.join(bundle_dir(), file_name + ext)
269
270
        # Save image file
271
        img.save(output)
272
        return output
273
274
    def show(self):
275
        """Display a Pillow image on your operating system."""
276
        return self.img.show()
277
278
    def cleanup(self):
279
        """Implicitly delete temporary directories that have been created."""
280
        self.tempdir.cleanup()
281