mirror of
https://github.com/arsenetar/dupeguru.git
synced 2024-10-31 22:05:58 +00:00
217 lines
5.4 KiB
Python
217 lines
5.4 KiB
Python
# Created By: Virgil Dupras
|
|
# Created On: 2011-08-05
|
|
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
#
|
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
# which should be included with this package. The terms are also available at
|
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
from sys import maxsize as INF
|
|
from math import sqrt
|
|
|
|
VERY_SMALL = 0.0000001
|
|
|
|
|
|
class Point:
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
|
|
def __repr__(self):
|
|
return "<Point {:2.2f}, {:2.2f}>".format(*self)
|
|
|
|
def __iter__(self):
|
|
yield self.x
|
|
yield self.y
|
|
|
|
def distance_to(self, other):
|
|
return Line(self, other).length()
|
|
|
|
|
|
class Line:
|
|
def __init__(self, p1, p2):
|
|
self.p1 = p1
|
|
self.p2 = p2
|
|
|
|
def __repr__(self):
|
|
return "<Line {}, {}>".format(*self)
|
|
|
|
def __iter__(self):
|
|
yield self.p1
|
|
yield self.p2
|
|
|
|
def dx(self):
|
|
return self.p2.x - self.p1.x
|
|
|
|
def dy(self):
|
|
return self.p2.y - self.p1.y
|
|
|
|
def length(self):
|
|
return sqrt(self.dx() ** 2 + self.dy() ** 2)
|
|
|
|
def slope(self):
|
|
if self.dx() == 0:
|
|
return INF if self.dy() > 0 else -INF
|
|
else:
|
|
return self.dy() / self.dx()
|
|
|
|
def intersection_point(self, other):
|
|
# with help from http://paulbourke.net/geometry/lineline2d/
|
|
if abs(self.slope() - other.slope()) < VERY_SMALL:
|
|
# parallel. Even if coincident, we return nothing
|
|
return None
|
|
|
|
A, B = self
|
|
C, D = other
|
|
|
|
denom = (D.y - C.y) * (B.x - A.x) - (D.x - C.x) * (B.y - A.y)
|
|
if denom == 0:
|
|
return None
|
|
numera = (D.x - C.x) * (A.y - C.y) - (D.y - C.y) * (A.x - C.x)
|
|
numerb = (B.x - A.x) * (A.y - C.y) - (B.y - A.y) * (A.x - C.x)
|
|
|
|
mua = numera / denom
|
|
mub = numerb / denom
|
|
if (0 <= mua <= 1) and (0 <= mub <= 1):
|
|
x = A.x + mua * (B.x - A.x)
|
|
y = A.y + mua * (B.y - A.y)
|
|
return Point(x, y)
|
|
else:
|
|
return None
|
|
|
|
|
|
class Rect:
|
|
def __init__(self, x, y, w, h):
|
|
self.x = x
|
|
self.y = y
|
|
self.w = w
|
|
self.h = h
|
|
|
|
def __iter__(self):
|
|
yield self.x
|
|
yield self.y
|
|
yield self.w
|
|
yield self.h
|
|
|
|
def __repr__(self):
|
|
return "<Rect {:2.2f}, {:2.2f}, {:2.2f}, {:2.2f}>".format(*self)
|
|
|
|
@classmethod
|
|
def from_center(cls, center, width, height):
|
|
x = center.x - width / 2
|
|
y = center.y - height / 2
|
|
return cls(x, y, width, height)
|
|
|
|
@classmethod
|
|
def from_corners(cls, pt1, pt2):
|
|
x1, y1 = pt1
|
|
x2, y2 = pt2
|
|
return cls(min(x1, x2), min(y1, y2), abs(x1 - x2), abs(y1 - y2))
|
|
|
|
def center(self):
|
|
return Point(self.x + self.w / 2, self.y + self.h / 2)
|
|
|
|
def contains_point(self, point):
|
|
x, y = point
|
|
(x1, y1), (x2, y2) = self.corners()
|
|
return (x1 <= x <= x2) and (y1 <= y <= y2)
|
|
|
|
def contains_rect(self, rect):
|
|
pt1, pt2 = rect.corners()
|
|
return self.contains_point(pt1) and self.contains_point(pt2)
|
|
|
|
def corners(self):
|
|
return Point(self.x, self.y), Point(self.x + self.w, self.y + self.h)
|
|
|
|
def intersects(self, other):
|
|
r1pt1, r1pt2 = self.corners()
|
|
r2pt1, r2pt2 = other.corners()
|
|
if r1pt1.x < r2pt1.x:
|
|
xinter = r1pt2.x >= r2pt1.x
|
|
else:
|
|
xinter = r2pt2.x >= r1pt1.x
|
|
if not xinter:
|
|
return False
|
|
if r1pt1.y < r2pt1.y:
|
|
yinter = r1pt2.y >= r2pt1.y
|
|
else:
|
|
yinter = r2pt2.y >= r1pt1.y
|
|
return yinter
|
|
|
|
def lines(self):
|
|
pt1, pt4 = self.corners()
|
|
pt2 = Point(pt4.x, pt1.y)
|
|
pt3 = Point(pt1.x, pt4.y)
|
|
l1 = Line(pt1, pt2)
|
|
l2 = Line(pt2, pt4)
|
|
l3 = Line(pt3, pt4)
|
|
l4 = Line(pt1, pt3)
|
|
return l1, l2, l3, l4
|
|
|
|
def scaled_rect(self, dx, dy):
|
|
"""Returns a rect that has the same borders at self, but grown/shrunk by dx/dy on each side."""
|
|
x, y, w, h = self
|
|
x -= dx
|
|
y -= dy
|
|
w += dx * 2
|
|
h += dy * 2
|
|
return Rect(x, y, w, h)
|
|
|
|
def united(self, other):
|
|
"""Returns the bounding rectangle of this rectangle and `other`."""
|
|
# ul=upper left lr=lower right
|
|
ulcorner1, lrcorner1 = self.corners()
|
|
ulcorner2, lrcorner2 = other.corners()
|
|
corner1 = Point(min(ulcorner1.x, ulcorner2.x), min(ulcorner1.y, ulcorner2.y))
|
|
corner2 = Point(max(lrcorner1.x, lrcorner2.x), max(lrcorner1.y, lrcorner2.y))
|
|
return Rect.from_corners(corner1, corner2)
|
|
|
|
# --- Properties
|
|
@property
|
|
def top(self):
|
|
return self.y
|
|
|
|
@top.setter
|
|
def top(self, value):
|
|
self.y = value
|
|
|
|
@property
|
|
def bottom(self):
|
|
return self.y + self.h
|
|
|
|
@bottom.setter
|
|
def bottom(self, value):
|
|
self.y = value - self.h
|
|
|
|
@property
|
|
def left(self):
|
|
return self.x
|
|
|
|
@left.setter
|
|
def left(self, value):
|
|
self.x = value
|
|
|
|
@property
|
|
def right(self):
|
|
return self.x + self.w
|
|
|
|
@right.setter
|
|
def right(self, value):
|
|
self.x = value - self.w
|
|
|
|
@property
|
|
def width(self):
|
|
return self.w
|
|
|
|
@width.setter
|
|
def width(self, value):
|
|
self.w = value
|
|
|
|
@property
|
|
def height(self):
|
|
return self.h
|
|
|
|
@height.setter
|
|
def height(self, value):
|
|
self.h = value
|