# # $Id: geometry.py,v 1.24 2003/05/12 22:13:11 mrnolta Exp $ # # Copyright (C) 2000-2001 Mike Nolta # # 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 2 # 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, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # import math, Numeric # pt_* functions -------------------------------------------------------------- def pt_add( u, v ): return u[0] + v[0], u[1] + v[1] def pt_sub( u, v ): return u[0] - v[0], u[1] - v[1] def pt_mul( a, u ): return a * u[0], a * u[1] def pt_rot( u, angle ): c, s = math.cos(angle), math.sin(angle) return c*u[0] - s*u[1], s*u[0] + c*u[1] def pt_len( u ): return math.hypot( u[0], u[1] ) def pt_angle( u ): return math.atan2( u[1], u[0] ) def pt_unit( u ): r = pt_len(u) return u[0]/r, u[1]/r def pt_min( a, b ): if a is None: return b if b is None: return a return min(a[0],b[0]), min(a[1],b[1]) def pt_max( a, b ): if a is None: return b if b is None: return a x = max( a[0], b[0] ) if x is None: x = min( a[0], b[0] ) y = max( a[1], b[1] ) if y is None: y = min( a[1], b[1] ) return x, y # BoundingBox ----------------------------------------------------------------- class BoundingBox: def __init__( self, *args ): if len(args) > 0: self.p0 = reduce( pt_min, args ) self.p1 = reduce( pt_max, args ) else: self.p0 = None self.p1 = None def __str__( self ): return "(%s,%s)" % (str(self.p0), str(self.p1)) def copy( self ): return BoundingBox( self.p0, self.p1 ) def is_null( self ): return self.p0 is None or self.p1 is None def width( self ): if self.is_null(): return None else: return abs( self.p0[0] - self.p1[0] ) def height( self ): if self.is_null(): return None else: return abs( self.p0[1] - self.p1[1] ) def diagonal( self ): if self.is_null(): return None else: return math.hypot( self.width(), self.height() ) def aspect_ratio( self ): if self.is_null(): return None else: return self.height()/self.width() def xrange( self ): if self.is_null(): return None else: return self.p0[0], self.p1[0] def yrange( self ): if self.is_null(): return None else: return self.p0[1], self.p1[1] def lowerleft( self ): if self.is_null(): return None else: return self.p0 def upperleft( self ): if self.is_null(): return None else: return self.p0[0], self.p1[1] def upperright( self ): if self.is_null(): return None else: return self.p1 def lowerright( self ): if self.is_null(): return None else: return self.p1[0], self.p0[1] def center( self ): x = self.xrange() y = self.yrange() return (x[0]+x[1])/2., (y[0]+y[1])/2. def union( self, other ): self.p0 = pt_min( self.p0, other.p0 ) self.p1 = pt_max( self.p1, other.p1 ) def deform( self, dt, db, dl, dr ): self.p0 = pt_sub( self.p0, (dl,db) ) self.p1 = pt_add( self.p1, (dr,dt) ) def shift( self, dp ): self.p0 = pt_add( self.p0, dp ) self.p1 = pt_add( self.p1, dp ) def expand( self, factor ): dp = pt_mul( factor/2., (self.width(), self.height()) ) self.p0 = pt_sub( self.p0, dp ) self.p1 = pt_add( self.p1, dp ) def rotate( self, angle, p ): a = pt_add(pt_rot(pt_sub( self.lowerleft(), p), angle), p) b = pt_add(pt_rot(pt_sub(self.lowerright(), p), angle), p) c = pt_add(pt_rot(pt_sub( self.upperleft(), p), angle), p) d = pt_add(pt_rot(pt_sub(self.upperright(), p), angle), p) self.p0 = pt_min( a, pt_min( b, pt_min( c, d ) ) ) self.p1 = pt_max( a, pt_max( b, pt_max( c, d ) ) ) def make_aspect_ratio( self, ratio ): if ratio < self.aspect_ratio(): dh = self.height() - ratio * self.width() self.p0 = self.p0[0], self.p0[1] + dh/2 self.p1 = self.p1[0], self.p1[1] - dh/2 else: dw = self.width() - self.height() / ratio self.p0 = self.p0[0] + dw/2, self.p0[1] self.p1 = self.p1[0] - dw/2, self.p1[1] def contains( self, q ): if self.p0[0] <= q[0] and \ q[0] <= self.p1[0] and \ self.p0[1] <= q[1] and \ q[1] <= self.p1[1]: return 1 else: return 0 # AffineTransform ------------------------------------------------------------- def _matrix_multipy( A, B ): C00 = A[0][0] * B[0][0] + A[0][1] * B[1][0] C01 = A[0][0] * B[0][1] + A[0][1] * B[1][1] C10 = A[1][0] * B[0][0] + A[1][1] * B[1][0] C11 = A[1][0] * B[0][1] + A[1][1] * B[1][1] return (C00, C01), (C10, C11) class AffineTransform: def __init__( self ): self.t = 0., 0. self.m = (1., 0.), (0., 1.) def __call__( self, x, y ): p = self.t[0] + self.m[0][0] * x + self.m[0][1] * y q = self.t[1] + self.m[1][0] * x + self.m[1][1] * y return p, q def call_vec( self, x, y ): x_ = Numeric.asarray( x ) y_ = Numeric.asarray( y ) p = self.t[0] + self.m[0][0] * x_ + self.m[0][1] * y_ q = self.t[1] + self.m[1][0] * x_ + self.m[1][1] * y_ return p, q def compose( self, other ): self.t = self( other.t[0], other.t[1] ) self.m = _matrix_multiply( self.m, other.m ) class RectilinearMap( AffineTransform ): def __init__( self, src, dest ): AffineTransform.__init__( self ) sx = dest.width() / src.width() sy = dest.height() / src.height() p, q = dest.lowerleft(), src.lowerleft() tx = p[0] - sx * q[0] ty = p[1] - sy * q[1] self.t = tx, ty self.m = ( sx, 0. ), ( 0., sy )