Source code for mountain_tapir.region_maker

# -*- coding: utf-8 -*-
#
# Mountain Tapir Collage Maker is a tool for combining images into collages.
# Copyright (c) 2016, tttppp
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import

from math import sqrt
from random import randrange
from itertools import product

from mountain_tapir.algorithm import Algorithm

TARGET_RATIO = 2.0/3
UNACCEPTABLE_WIDTH = 50
UNACCEPTABLE_HEIGHT = 50


[docs]class RegionMaker: """A region is a tuple (left, top, width, height).""" @staticmethod
[docs] def makeRegions(model): """Split the preview pane into disjoint regions.""" if model.getAlgorithm() == Algorithm.COLLAGE: return RegionMaker.makeCollageRegions(model) elif model.getAlgorithm() == Algorithm.GRID: return RegionMaker.makeGridRegions(model) elif model.getAlgorithm() == Algorithm.FRAME: return RegionMaker.makeFrameRegions(model) else: print('Unsupported algorithm: {0}'.format(model.getAlgorithm()))
@staticmethod
[docs] def makeCollageRegions(model): """Start with the whole area as a region. Each iteration pick the 'worst' region and split it either horizontally or vertically at a random point. Remove the old region and add the two new regions. The worst region is defined to be the region furthest from the TARGET_RATIO. """ def randNearMid(maximum): return (randrange(maximum) + randrange(maximum)) // 2 regions = [(0, 0, model.getWidth(), model.getHeight())] while len(regions) < model.getRegionCount(): worstRegion, worstRegionDiff = RegionMaker.findWorstRegion(regions) for region in regions: regionDiff = RegionMaker.ratioDiff(region[2], region[3]) if regionDiff > worstRegionDiff: worstRegion = region worstRegionDiff = regionDiff relativeSplitPoint = (randNearMid(worstRegion[2]), randNearMid(worstRegion[3])) worstHorizontalSplitDiff = max(RegionMaker.ratioDiff(relativeSplitPoint[0], worstRegion[3]), RegionMaker.ratioDiff(worstRegion[2]-relativeSplitPoint[0], worstRegion[3])) worstVerticalSplitDiff = max(RegionMaker.ratioDiff(worstRegion[2], relativeSplitPoint[1]), RegionMaker.ratioDiff(worstRegion[2], worstRegion[3]-relativeSplitPoint[1])) regions.remove(worstRegion) if worstHorizontalSplitDiff < worstVerticalSplitDiff: # Split vertically regions.append((worstRegion[0], worstRegion[1], relativeSplitPoint[0], worstRegion[3])) regions.append((worstRegion[0]+relativeSplitPoint[0], worstRegion[1], worstRegion[2]-relativeSplitPoint[0], worstRegion[3])) else: # Split horizontally regions.append((worstRegion[0], worstRegion[1], worstRegion[2], relativeSplitPoint[1])) regions.append((worstRegion[0], worstRegion[1]+relativeSplitPoint[1], worstRegion[2], worstRegion[3]-relativeSplitPoint[1])) # Restart if any width or height is unacceptable if regions[-2][2] <= UNACCEPTABLE_WIDTH or regions[-2][3] <= UNACCEPTABLE_HEIGHT or \ regions[-1][2] <= UNACCEPTABLE_WIDTH or regions[-1][3] <= UNACCEPTABLE_HEIGHT: regions = [(0, 0, model.getWidth(), model.getHeight())] return regions
@staticmethod
[docs] def makeGridRegions(model): shortSide = int(sqrt(model.getRegionCount())) while model.getRegionCount() % shortSide != 0: shortSide -= 1 longSide = model.getRegionCount() // shortSide if model.getWidth() > model.getHeight(): columns = longSide rows = shortSide else: columns = shortSide rows = longSide # Need to set width and height of each cell programmatically to avoid rounding errors in calculation of # cellWidth and cellHeight return [(x*model.getWidth()//columns, y*model.getHeight()//rows, (x+1)*model.getWidth()//columns - x*model.getWidth()//columns, (y+1)*model.getHeight()//rows - y*model.getHeight()//rows) for x in range(columns) for y in range(rows)]
@staticmethod
[docs] def makeFrameRegions(model): if model.getRegionCount() < 5: return RegionMaker.makeGridRegions(model) width, height = model.getWidth(), model.getHeight() halfWidth, halfHeight = model.getWidth() // 2, model.getHeight() // 2 quarterWidth, quarterHeight = model.getWidth() // 4, model.getHeight() // 4 centerRegion = (quarterWidth, quarterHeight, halfWidth, halfHeight) # Decide where to draw lines near each corner bestWorstScore = 999999 bestRegions = None for joinCornerHorizontal in product((True, False), repeat=4): usedByCorner = ((quarterWidth if joinCornerHorizontal[0] else 0, 0 if joinCornerHorizontal[0] else quarterHeight), ((width - halfWidth - quarterWidth) if joinCornerHorizontal[1] else 0, 0 if joinCornerHorizontal[1] else quarterHeight), (quarterWidth if joinCornerHorizontal[2] else 0, 0 if joinCornerHorizontal[2] else (height - halfHeight - quarterHeight)), ((width - halfWidth - quarterWidth) if joinCornerHorizontal[3] else 0, 0 if joinCornerHorizontal[3] else (height - halfHeight - quarterHeight))) top = (quarterWidth - usedByCorner[0][0], 0, halfWidth + usedByCorner[0][0] + usedByCorner[1][0], quarterHeight) left = (0, quarterHeight - usedByCorner[0][1], quarterWidth, halfHeight + usedByCorner[0][1] + usedByCorner[2][1]) right = (halfWidth + quarterWidth, quarterHeight - usedByCorner[1][1], quarterWidth, halfHeight + usedByCorner[1][1] + usedByCorner[3][1]) bottom = (quarterWidth - usedByCorner[2][0], halfHeight + quarterHeight, halfWidth + usedByCorner[2][0] + usedByCorner[3][0], quarterHeight) regions = [centerRegion, top, left, right, bottom] perimeter = (top[2]//2, right[3], bottom[2], left[3], top[2]-top[2]//2) splits = {'top': [0, top[2]], 'right': [0, right[3]], 'bottom': [0, bottom[2]], 'left': [0, left[3]]} for i in range(model.getRegionCount() - 5): # Find the distance around the perimeter to split a frame perimeterDistance = (sum(perimeter) * (i)) // (model.getRegionCount() - 5) if perimeterDistance < perimeter[0]: splits['top'].append(perimeterDistance + top[2]//2) elif perimeterDistance < sum(perimeter[:2]): perimeterDistance -= perimeter[0] splits['right'].append(perimeterDistance) elif perimeterDistance < sum(perimeter[:3]): perimeterDistance -= sum(perimeter[:2]) splits['bottom'].append(bottom[2] - perimeterDistance) elif perimeterDistance < sum(perimeter[:4]): perimeterDistance -= sum(perimeter[:3]) splits['left'].append(left[3] - perimeterDistance) else: perimeterDistance -= sum(perimeter[:4]) splits['top'].append(perimeterDistance) topRegions = [] for x1, x2 in zip(sorted(splits['top'])[:-1], sorted(splits['top'])[1:]): topRegions.append((x1 + top[0], top[1], x2 - x1, top[3])) rightRegions = [] for y1, y2 in zip(sorted(splits['right'])[:-1], sorted(splits['right'])[1:]): rightRegions.append((right[0], y1 + right[1], right[2], y2 - y1)) bottomRegions = [] for x1, x2 in zip(sorted(splits['bottom'])[:-1], sorted(splits['bottom'])[1:]): bottomRegions.append((x1 + bottom[0], bottom[1], x2 - x1, bottom[3])) leftRegions = [] for y1, y2 in zip(sorted(splits['left'])[:-1], sorted(splits['left'])[1:]): leftRegions.append((left[0], y1 + left[1], left[2], y2 - y1)) regions = [centerRegion] + topRegions + rightRegions + bottomRegions + leftRegions worstRegion, worstRegionDiff = RegionMaker.findWorstRegion(regions) if worstRegionDiff < bestWorstScore: bestRegions = regions bestWorstScore = worstRegionDiff return bestRegions
@staticmethod
[docs] def findWorstRegion(regions): worstRegion = None worstRegionDiff = -1 for region in regions: regionDiff = RegionMaker.ratioDiff(region[2], region[3]) if regionDiff > worstRegionDiff: worstRegion = region worstRegionDiff = regionDiff return worstRegion, worstRegionDiff
@staticmethod
[docs] def ratioDiff(width, height): if width == 0 or height == 0: return 0 return min(abs((width*1.0/height) - TARGET_RATIO), abs((height*1.0/width) - TARGET_RATIO))