"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)