"Fossies" - the Fresh Open Source Software Archive 
Member "PhotoCollage-1.4.5/photocollage/render.py" (9 Jul 2021, 10198 Bytes) of package /linux/privat/PhotoCollage-1.4.5.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "render.py" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
1.4.4_vs_1.4.5.
1 # Copyright (C) 2014 Adrien Vergé
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
17 import random
18 from threading import Thread
19 import time
20
21 import PIL.Image
22 import PIL.ImageDraw
23 import PIL.ImageFile
24
25 from photocollage.collage import Photo
26
27
28 QUALITY_SKEL = 0
29 QUALITY_FAST = 1
30 QUALITY_BEST = 2
31
32
33 # Try to continue even if the input file is corrupted.
34 # See issue at https://github.com/adrienverge/PhotoCollage/issues/65
35 PIL.ImageFile.LOAD_TRUNCATED_IMAGES = True
36
37
38 class PIL_SUPPORTED_EXTS:
39 """File extensions supported by PIL
40
41 Compiled from:
42 - http://pillow.readthedocs.org/en/2.3.0/handbook/image-file-formats.html
43 - https://github.com/python-imaging/Pillow/blob/master/PIL/*ImagePlugin.py
44
45 """
46 RW = {
47 "BMP": ("bmp",),
48 # "EPS": ("ps", "eps",), # doesn't seem to work
49 "GIF": ("gif",),
50 "IM": ("im",),
51 "JPEG": ("jfif", "jpe", "jpg", "jpeg",),
52 "MSP": ("msp",),
53 "PCX": ("pcx",),
54 "PNG": ("png",),
55 "PPM": ("pbm", "pgm", "ppm",),
56 "TGA": ("tga",),
57 "TIFF": ("tif", "tiff",),
58 "WebP": ("webp",),
59 "XBM": ("xbm",),
60 }
61 RO = {
62 "CUR": ("cur",),
63 "DCX": ("dcx",),
64 "FLI": ("fli", "flc",),
65 "FPX": ("fpx",),
66 "GBR": ("gbr",),
67 "ICO": ("ico",),
68 "IPTC/NAA": ("iim",),
69 "PCD": ("pcd",),
70 "PSD": ("psd",),
71 "SGI": ("bw", "rgb", "rgba", "sgi",),
72 "XPM": ("xpm",),
73 }
74 WO = {
75 # "PALM": ("palm",), # doesn't seem to work
76 # "PDF": ("pdf",), # doesn't seem to work
77 }
78
79
80 def random_color():
81 r = random.randrange(256)
82 g = random.randrange(256)
83 b = random.randrange(256)
84 if r + g + b > 0.7 * 3 * 256:
85 r -= 50
86 g -= 50
87 b -= 50
88 return (r, g, b)
89
90
91 class BadPhoto(Exception):
92 def __init__(self, photoname):
93 self.photoname = photoname
94
95
96 def build_photolist(filelist):
97 ret = []
98
99 for name in filelist:
100 try:
101 img = PIL.Image.open(name)
102 except OSError:
103 raise BadPhoto(name)
104 w, h = img.size
105
106 orientation = 0
107 try:
108 exif = img._getexif()
109 if 274 in exif: # orientation tag
110 orientation = exif[274]
111 if orientation == 6 or orientation == 8:
112 w, h = h, w
113 except Exception:
114 pass
115
116 ret.append(Photo(name, w, h, orientation))
117 return ret
118
119
120 cache = {}
121
122
123 class RenderingTask(Thread):
124 """Execution thread to do the actual poster rendering
125
126 Image computation is a heavy task, that can take several seconds. During
127 this, the program might be unresponding. To avoid this, rendering is done
128 is a separated thread.
129
130 """
131 def __init__(self, page, border_width=0.01, border_color=(0, 0, 0),
132 quality=QUALITY_FAST, output_file=None,
133 on_update=None, on_complete=None, on_fail=None):
134 super().__init__()
135
136 self.page = page
137 self.border_width = border_width
138 self.border_color = border_color
139 self.quality = quality
140
141 self.output_file = output_file
142
143 self.on_update = on_update
144 self.on_complete = on_complete
145 self.on_fail = on_fail
146
147 self.canceled = False
148
149 def abort(self):
150 self.canceled = True
151
152 def draw_skeleton(self, canvas):
153 for col in self.page.cols:
154 for c in col.cells:
155 if c.is_extension():
156 continue
157 color = random_color()
158 x, y, w, h = c.content_coords()
159 xy = (x, y)
160 xY = (x, y + h - 1)
161 Xy = (x + w - 1, y)
162 XY = (x + w - 1, y + h - 1)
163
164 draw = PIL.ImageDraw.Draw(canvas)
165 draw.line(xy + Xy, fill=color)
166 draw.line(xy + xY, fill=color)
167 draw.line(xY + XY, fill=color)
168 draw.line(Xy + XY, fill=color)
169 draw.line(xy + XY, fill=color)
170 draw.line(xY + Xy, fill=color)
171 return canvas
172
173 def draw_borders(self, canvas):
174 if self.border_width == 0:
175 return
176
177 W = self.page.w - 1
178 H = self.page.h - 1
179 border = self.border_width - 1
180 color = self.border_color
181
182 draw = PIL.ImageDraw.Draw(canvas)
183 draw.rectangle((0, 0) + (border, H), color)
184 draw.rectangle((W - border, 0) + (W, H), color)
185 draw.rectangle((0, 0) + (W, border), color)
186 draw.rectangle((0, H - border) + (W, H), color)
187
188 for col in self.page.cols:
189 # Draw horizontal borders
190 for c in col.cells[1:]:
191 xy = (col.x, c.y - border / 2)
192 XY = (col.x + col.w, c.y + border / 2)
193 draw.rectangle(xy + XY, color)
194 # Draw vertical borders
195 if col.x > 0:
196 for c in col.cells:
197 if not c.is_extension():
198 xy = (col.x - border / 2, c.y)
199 XY = (col.x + border / 2, c.y + c.h)
200 draw.rectangle(xy + XY, color)
201 return canvas
202
203 def resize_photo(self, cell, use_cache=False):
204 # If a thumbnail is already in cache, let's use it. But only if it is
205 # bigger than what we need, because we don't want to lose quality.
206 if (use_cache and cell.photo.filename in cache and
207 cache[cell.photo.filename].size[0] >= int(round(cell.w)) and
208 cache[cell.photo.filename].size[1] >= int(round(cell.h))):
209 img = cache[cell.photo.filename].copy()
210 else:
211 img = PIL.Image.open(cell.photo.filename)
212
213 # Rotate image is EXIF says so
214 if cell.photo.orientation == 3:
215 img = img.rotate(180, expand=True)
216 elif cell.photo.orientation == 6:
217 img = img.rotate(270, expand=True)
218 elif cell.photo.orientation == 8:
219 img = img.rotate(90, expand=True)
220
221 if self.quality == QUALITY_FAST:
222 method = PIL.Image.NEAREST
223 else:
224 method = PIL.Image.ANTIALIAS
225
226 shape = img.size[0] * cell.h - img.size[1] * cell.w
227 if shape > 0: # image is too thick
228 img = img.resize((int(round(cell.h * img.size[0] / img.size[1])),
229 int(round(cell.h))), method)
230 elif shape < 0: # image is too tall
231 img = img.resize((int(round(cell.w)),
232 int(round(cell.w * img.size[1] / img.size[0]))),
233 method)
234 else:
235 img = img.resize((int(round(cell.w)), int(round(cell.h))), method)
236
237 # Save this new image to cache (if it is larger than the previous one)
238 if (use_cache and (cell.photo.filename not in cache or
239 cache[cell.photo.filename].size[0] < img.size[0])):
240 cache[cell.photo.filename] = img
241
242 if shape > 0: # image is too thick
243 width_to_crop = img.size[0] - cell.w
244 img = img.crop((
245 int(round(width_to_crop * cell.photo.offset_w)),
246 0,
247 int(round(img.size[0] - width_to_crop *
248 (1 - cell.photo.offset_w))),
249 int(round(cell.h))
250 ))
251 elif shape < 0: # image is too tall
252 height_to_crop = img.size[1] - cell.h
253 img = img.crop((
254 0,
255 int(round(height_to_crop * cell.photo.offset_h)),
256 int(round(cell.w)),
257 int(round(img.size[1] - height_to_crop *
258 (1 - cell.photo.offset_h)))
259 ))
260
261 return img
262
263 def paste_photo(self, canvas, cell, img):
264 canvas.paste(img, (int(round(cell.x)), int(round(cell.y))))
265 return canvas
266
267 def run(self):
268 try:
269 canvas = PIL.Image.new(
270 "RGB", (int(self.page.w), int(self.page.h)), "white")
271
272 self.draw_skeleton(canvas)
273 self.draw_borders(canvas)
274
275 if self.quality != QUALITY_SKEL:
276 n = sum([len([cell for cell in col.cells if not
277 cell.is_extension()]) for col in self.page.cols])
278 i = 0.0
279 if self.on_update:
280 self.on_update(canvas, 0.0)
281 last_update = time.time()
282
283 for col in self.page.cols:
284 for c in col.cells:
285 if self.canceled: # someone clicked "abort"
286 return
287
288 if c.is_extension():
289 continue
290
291 img = self.resize_photo(c, use_cache=True)
292 self.paste_photo(canvas, c, img)
293
294 # Only needed for interactive rendering
295 if self.on_update:
296 self.draw_borders(canvas)
297
298 i += 1
299 now = time.time()
300 if self.on_update and now > last_update + 0.1:
301 self.on_update(canvas, i / n)
302 last_update = now
303
304 self.draw_borders(canvas)
305
306 if self.output_file:
307 canvas.save(self.output_file)
308
309 if self.on_complete:
310 self.on_complete(canvas)
311 except Exception as e:
312 if self.on_fail:
313 self.on_fail(e)