more details
[PyX/mjg.git] / pyx / bbox.py
blob9fde94b97a6bec34b71a27c7d35ec500c2d0e2a9
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2006 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import math
25 import unit
28 # classes representing bounding boxes
31 class bbox_pt:
33 """class for bounding boxes
35 This variant requires points in the constructor, and is used for internal
36 purposes.
38 A bbox for which llx_pt is None represents an empty bbox, i.e., one containing
39 no points.
40 """
42 def __init__(self, llx_pt, lly_pt, urx_pt, ury_pt):
43 self.llx_pt = llx_pt
44 self.lly_pt = lly_pt
45 self.urx_pt = urx_pt
46 self.ury_pt = ury_pt
48 def __nonzero__(self):
49 return self.llx_pt is not None
51 def __add__(self, other):
52 """join two bboxes"""
53 if self.llx_pt is not None:
54 if other.llx_pt is not None:
55 return bbox_pt(min(self.llx_pt, other.llx_pt), min(self.lly_pt, other.lly_pt),
56 max(self.urx_pt, other.urx_pt), max(self.ury_pt, other.ury_pt))
57 else:
58 return bbox_pt(self.llx_pt, self.lly_pt, self.urx_pt, self.ury_pt)
59 else:
60 return bbox_pt(other.llx_pt, other.lly_pt, other.urx_pt, other.ury_pt)
62 def __iadd__(self, other):
63 """join two bboxes inplace"""
64 if self.llx_pt is not None:
65 if other.llx_pt is not None:
66 self.llx_pt = min(self.llx_pt, other.llx_pt)
67 self.lly_pt = min(self.lly_pt, other.lly_pt)
68 self.urx_pt = max(self.urx_pt, other.urx_pt)
69 self.ury_pt = max(self.ury_pt, other.ury_pt)
70 else:
71 self.llx_pt = other.llx_pt
72 self.lly_pt = other.lly_pt
73 self.urx_pt = other.urx_pt
74 self.ury_pt = other.ury_pt
75 return self
77 def __mul__(self, other):
78 """return intersection of two bboxes"""
79 if self.llx_pt is not None and other.llx_pt is not None:
80 return bbox_pt(max(self.llx_pt, other.llx_pt), max(self.lly_pt, other.lly_pt),
81 min(self.urx_pt, other.urx_pt), min(self.ury_pt, other.ury_pt))
82 else:
83 return empty()
85 def __imul__(self, other):
86 """intersect two bboxes in place"""
87 if self.llx_pt is not None and other.llx_pt is not None:
88 self.llx_pt = max(self.llx_pt, other.llx_pt)
89 self.lly_pt = max(self.lly_pt, other.lly_pt)
90 self.urx_pt = min(self.urx_pt, other.urx_pt)
91 self.ury_pt = min(self.ury_pt, other.ury_pt)
92 elif other.llx_pt is None:
93 self.llx_pt = None
94 return self
96 def copy(self):
97 return bbox_pt(self.llx_pt, self.lly_pt, self.urx_pt, self.ury_pt)
99 def set(self, other):
100 self.llx_pt = other.llx_pt
101 self.lly_pt = other.lly_pt
102 self.urx_pt = other.urx_pt
103 self.ury_pt = other.ury_pt
105 def lowrestuple_pt(self):
106 if self.llx_pt is None:
107 raise ValueError("Cannot return low-res tuple for empty bbox")
108 return (math.floor(self.llx_pt), math.floor(self.lly_pt),
109 math.ceil(self.urx_pt), math.ceil(self.ury_pt))
111 def highrestuple_pt(self):
112 if self.llx_pt is None:
113 raise ValueError("Cannot return high-res tuple for empty bbox")
114 return (self.llx_pt, self.lly_pt, self.urx_pt, self.ury_pt)
116 def intersects(self, other):
117 """check, if two bboxes intersect eachother"""
118 if self.llx_pt is None or other.llx_pt is None:
119 return 0
120 else:
121 return not (self.llx_pt > other.urx_pt or
122 self.lly_pt > other.ury_pt or
123 self.urx_pt < other.llx_pt or
124 self.ury_pt < other.lly_pt)
126 def includepoint_pt(self, x_pt, y_pt):
127 if self.llx_pt is None:
128 self.llx_pt = self.urx_pt = x_pt
129 self.ury_pt = self.ury_pt = y_pt
130 else:
131 self.llx_pt = min(self.llx_pt, x_pt)
132 self.lly_pt = min(self.lly_pt, y_pt)
133 self.urx_pt = max(self.urx_pt, x_pt)
134 self.ury_pt = max(self.ury_pt, y_pt)
136 def transform(self, trafo):
137 """transform bbox in place by trafo"""
138 if self.llx_pt is None:
139 return
140 # we have to transform all four corner points of the bbox
141 llx_pt, lly_pt = trafo.apply_pt(self.llx_pt, self.lly_pt)
142 lrx_pt, lry_pt = trafo.apply_pt(self.urx_pt, self.lly_pt)
143 urx_pt, ury_pt = trafo.apply_pt(self.urx_pt, self.ury_pt)
144 ulx_pt, uly_pt = trafo.apply_pt(self.llx_pt, self.ury_pt)
146 # Now, by sorting, we obtain the lower left and upper right corner
147 # of the new bounding box.
148 self.llx_pt = min(llx_pt, lrx_pt, urx_pt, ulx_pt)
149 self.lly_pt = min(lly_pt, lry_pt, ury_pt, uly_pt)
150 self.urx_pt = max(llx_pt, lrx_pt, urx_pt, ulx_pt)
151 self.ury_pt = max(lly_pt, lry_pt, ury_pt, uly_pt)
153 def transformed(self, trafo):
154 """return bbox transformed by trafo"""
155 if self.llx_pt is None:
156 return empty()
157 # we have to transform all four corner points of the bbox
158 llx_pt, lly_pt = trafo.apply_pt(self.llx_pt, self.lly_pt)
159 lrx_pt, lry_pt = trafo.apply_pt(self.urx_pt, self.lly_pt)
160 urx_pt, ury_pt = trafo.apply_pt(self.urx_pt, self.ury_pt)
161 ulx_pt, uly_pt = trafo.apply_pt(self.llx_pt, self.ury_pt)
163 # Now, by sorting, we obtain the lower left and upper right corner
164 # of the new bounding box.
165 return bbox_pt(min(llx_pt, lrx_pt, urx_pt, ulx_pt), min(lly_pt, lry_pt, ury_pt, uly_pt),
166 max(llx_pt, lrx_pt, urx_pt, ulx_pt), max(lly_pt, lry_pt, ury_pt, uly_pt))
168 def enlarge_pt(self, all_pt=0, bottom_pt=None, left_pt=None, top_pt=None, right_pt=None):
169 """enlarge bbox in place by the given amounts in pts
171 all is used, if bottom, left, top and/or right are not given.
174 if self.llx_pt is None:
175 return
176 if bottom_pt is None:
177 bottom_pt = all_pt
178 if left_pt is None:
179 left_pt = all_pt
180 if top_pt is None:
181 top_pt = all_pt
182 if right_pt is None:
183 right_pt = all_pt
184 self.llx_pt -= left_pt
185 self.lly_pt -= bottom_pt
186 self.urx_pt += right_pt
187 self.ury_pt += top_pt
189 def enlarged_pt(self, all_pt=0, bottom_pt=None, left_pt=None, top_pt=None, right_pt=None):
190 """return bbox enlarged by the given amounts in pts
192 all is used, if bottom, left, top and/or right are not given.
195 if self.llx_pt is None:
196 return empty()
197 if bottom_pt is None:
198 bottom_pt = all_pt
199 if left_pt is None:
200 left_pt = all_pt
201 if top_pt is None:
202 top_pt = all_pt
203 if right_pt is None:
204 right_pt = all_pt
205 return bbox_pt(self.llx_pt-left_pt, self.lly_pt-bottom_pt, self.urx_pt+right_pt, self.ury_pt+top_pt)
207 def enlarge(self, all=0, bottom=None, left=None, top=None, right=None):
208 """enlarge bbox in place
210 all is used, if bottom, left, top and/or right are not given.
213 if self.llx_pt is None:
214 return
215 bottom_pt = left_pt = top_pt = right_pt = unit.topt(all)
216 if bottom is not None:
217 bottom_pt = unit.topt(bottom)
218 if left is not None:
219 left_pt = unit.topt(left)
220 if top is not None:
221 top_pt = unit.topt(top)
222 if right is not None:
223 right_pt = unit.topt(right)
224 self.llx_pt -= left_pt
225 self.lly_pt -= bottom_pt
226 self.urx_pt += right_pt
227 self.ury_pt += top_pt
229 def enlarged(self, all=0, bottom=None, left=None, top=None, right=None):
230 """return bbox enlarged
232 all is used, if bottom, left, top and/or right are not given.
235 if self.llx_pt is None:
236 return empty()
237 bottom_pt = left_pt = top_pt = right_pt = unit.topt(all)
238 if bottom is not None:
239 bottom_pt = unit.topt(bottom)
240 if left is not None:
241 left_pt = unit.topt(left)
242 if top is not None:
243 top_pt = unit.topt(top)
244 if right is not None:
245 right_pt = unit.topt(right)
246 return bbox_pt(self.llx_pt-left_pt, self.lly_pt-bottom_pt, self.urx_pt+right_pt, self.ury_pt+top_pt)
248 def rect(self):
249 """return rectangle corresponding to bbox"""
250 if self.llx_pt is None:
251 raise ValueError("Cannot return path for empty bbox")
252 import path
253 return path.rect_pt(self.llx_pt, self.lly_pt, self.urx_pt-self.llx_pt, self.ury_pt-self.lly_pt)
255 path = rect
257 def height_pt(self):
258 """return height of bbox in pts"""
259 if self.llx_pt is None:
260 raise ValueError("Cannot return heigth of empty bbox")
261 return self.ury_pt-self.lly_pt
263 def width_pt(self):
264 """return width of bbox in pts"""
265 if self.llx_pt is None:
266 raise ValueError("Cannot return width of empty bbox")
267 return self.urx_pt-self.llx_pt
269 def top_pt(self):
270 """return top coordinate of bbox in pts"""
271 if self.llx_pt is None:
272 raise ValueError("Cannot return top coordinate of empty bbox")
273 return self.ury_pt
275 def bottom_pt(self):
276 """return bottom coordinate of bbox in pts"""
277 if self.llx_pt is None:
278 raise ValueError("Cannot return bottom coordinate of empty bbox")
279 return self.lly_pt
281 def left_pt(self):
282 """return left coordinate of bbox in pts"""
283 if self.llx_pt is None:
284 raise ValueError("Cannot return left coordinate of empty bbox")
285 return self.llx_pt
287 def right_pt(self):
288 """return right coordinate of bbox in pts"""
289 if self.llx_pt is None:
290 raise ValueError("Cannot return right coordinate of empty bbox")
291 return self.urx_pt
293 def center_pt(self):
294 """return coordinates of the center of the bbox in pts"""
295 if self.llx_pt is None:
296 raise ValueError("Cannot return center coordinates of empty bbox")
297 return 0.5 * (self.llx_pt+self.urx_pt), 0.5 * (self.lly_pt+self.ury_pt)
299 def height(self):
300 """return height of bbox"""
301 return self.height_pt() * unit.t_pt
303 def width(self):
304 """return width of bbox"""
305 return self.width_pt() * unit.t_pt
307 def top(self):
308 """return top coordinate of bbox"""
309 return self.ury_pt * unit.t_pt
311 def bottom(self):
312 """return bottom coordinate of bbox"""
313 return self.lly_pt * unit.t_pt
315 def left(self):
316 """return left coordinate of bbox"""
317 return self.llx_pt * unit.t_pt
319 def right(self):
320 """return right coordinate of bbox"""
321 return self.urx_pt * unit.t_pt
323 def center(self):
324 """return coordinates of the center of the bbox"""
325 centerx_pt, centery_pt = self.center_pt()
326 return centerx_pt * unit.t_pt, centery_pt * unit.t_pt
329 class bbox(bbox_pt):
331 """class for bounding boxes"""
333 def __init__(self, llx_pt, lly_pt, urx_pt, ury_pt):
334 llx_pt = unit.topt(llx_pt)
335 lly_pt = unit.topt(lly_pt)
336 urx_pt = unit.topt(urx_pt)
337 ury_pt = unit.topt(ury_pt)
338 bbox_pt.__init__(self, llx_pt, lly_pt, urx_pt, ury_pt)
341 class empty(bbox_pt):
343 """empty bounding box, i.e., one containing no point"""
344 def __init__(self):
345 bbox_pt.__init__(self, None, None, None, None)