prevent double call of _cleanup, which harms usefiles (and is a bad idea in general)
[PyX.git] / pyx / bbox.py
blob4e013b2e99f1d6a6602e975fe26c3a02b95317f7
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2006 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import math
24 from . import unit
27 # classes representing bounding boxes
30 class bbox_pt:
32 """class for bounding boxes
34 This variant requires points in the constructor, and is used for internal
35 purposes.
37 A bbox for which llx_pt is None represents an empty bbox, i.e., one containing
38 no points.
39 """
41 def __init__(self, llx_pt, lly_pt, urx_pt, ury_pt):
42 self.llx_pt = llx_pt
43 self.lly_pt = lly_pt
44 self.urx_pt = urx_pt
45 self.ury_pt = ury_pt
47 def __bool__(self):
48 return self.llx_pt is not None
50 def __add__(self, other):
51 """join two bboxes"""
52 if self.llx_pt is not None:
53 if other.llx_pt is not None:
54 return bbox_pt(min(self.llx_pt, other.llx_pt), min(self.lly_pt, other.lly_pt),
55 max(self.urx_pt, other.urx_pt), max(self.ury_pt, other.ury_pt))
56 else:
57 return bbox_pt(self.llx_pt, self.lly_pt, self.urx_pt, self.ury_pt)
58 else:
59 return bbox_pt(other.llx_pt, other.lly_pt, other.urx_pt, other.ury_pt)
61 def __iadd__(self, other):
62 """join two bboxes inplace"""
63 if self.llx_pt is not None:
64 if other.llx_pt is not None:
65 self.llx_pt = min(self.llx_pt, other.llx_pt)
66 self.lly_pt = min(self.lly_pt, other.lly_pt)
67 self.urx_pt = max(self.urx_pt, other.urx_pt)
68 self.ury_pt = max(self.ury_pt, other.ury_pt)
69 else:
70 self.llx_pt = other.llx_pt
71 self.lly_pt = other.lly_pt
72 self.urx_pt = other.urx_pt
73 self.ury_pt = other.ury_pt
74 return self
76 def __mul__(self, other):
77 """return intersection of two bboxes"""
78 if self.llx_pt is not None and other.llx_pt is not None:
79 return bbox_pt(max(self.llx_pt, other.llx_pt), max(self.lly_pt, other.lly_pt),
80 min(self.urx_pt, other.urx_pt), min(self.ury_pt, other.ury_pt))
81 else:
82 return empty()
84 def __imul__(self, other):
85 """intersect two bboxes in place"""
86 if self.llx_pt is not None and other.llx_pt is not None:
87 self.llx_pt = max(self.llx_pt, other.llx_pt)
88 self.lly_pt = max(self.lly_pt, other.lly_pt)
89 self.urx_pt = min(self.urx_pt, other.urx_pt)
90 self.ury_pt = min(self.ury_pt, other.ury_pt)
91 elif other.llx_pt is None:
92 self.llx_pt = None
93 return self
95 def copy(self):
96 return bbox_pt(self.llx_pt, self.lly_pt, self.urx_pt, self.ury_pt)
98 def set(self, other):
99 self.llx_pt = other.llx_pt
100 self.lly_pt = other.lly_pt
101 self.urx_pt = other.urx_pt
102 self.ury_pt = other.ury_pt
104 def lowrestuple_pt(self):
105 if self.llx_pt is None:
106 raise ValueError("Cannot return low-res tuple for empty bbox")
107 return (math.floor(self.llx_pt), math.floor(self.lly_pt),
108 math.ceil(self.urx_pt), math.ceil(self.ury_pt))
110 def highrestuple_pt(self):
111 if self.llx_pt is None:
112 raise ValueError("Cannot return high-res tuple for empty bbox")
113 return (self.llx_pt, self.lly_pt, self.urx_pt, self.ury_pt)
115 def intersects(self, other):
116 """check, if two bboxes intersect eachother"""
117 if self.llx_pt is None or other.llx_pt is None:
118 return 0
119 else:
120 return not (self.llx_pt > other.urx_pt or
121 self.lly_pt > other.ury_pt or
122 self.urx_pt < other.llx_pt or
123 self.ury_pt < other.lly_pt)
125 def includepoint_pt(self, x_pt, y_pt):
126 if self.llx_pt is None:
127 self.llx_pt = self.urx_pt = x_pt
128 self.lly_pt = self.ury_pt = y_pt
129 else:
130 self.llx_pt = min(self.llx_pt, x_pt)
131 self.lly_pt = min(self.lly_pt, y_pt)
132 self.urx_pt = max(self.urx_pt, x_pt)
133 self.ury_pt = max(self.ury_pt, y_pt)
135 def transform(self, trafo):
136 """transform bbox in place by trafo"""
137 if self.llx_pt is None:
138 return
139 # we have to transform all four corner points of the bbox
140 llx_pt, lly_pt = trafo.apply_pt(self.llx_pt, self.lly_pt)
141 lrx_pt, lry_pt = trafo.apply_pt(self.urx_pt, self.lly_pt)
142 urx_pt, ury_pt = trafo.apply_pt(self.urx_pt, self.ury_pt)
143 ulx_pt, uly_pt = trafo.apply_pt(self.llx_pt, self.ury_pt)
145 # Now, by sorting, we obtain the lower left and upper right corner
146 # of the new bounding box.
147 self.llx_pt = min(llx_pt, lrx_pt, urx_pt, ulx_pt)
148 self.lly_pt = min(lly_pt, lry_pt, ury_pt, uly_pt)
149 self.urx_pt = max(llx_pt, lrx_pt, urx_pt, ulx_pt)
150 self.ury_pt = max(lly_pt, lry_pt, ury_pt, uly_pt)
152 def transformed(self, trafo):
153 """return bbox transformed by trafo"""
154 if self.llx_pt is None:
155 return empty()
156 # we have to transform all four corner points of the bbox
157 llx_pt, lly_pt = trafo.apply_pt(self.llx_pt, self.lly_pt)
158 lrx_pt, lry_pt = trafo.apply_pt(self.urx_pt, self.lly_pt)
159 urx_pt, ury_pt = trafo.apply_pt(self.urx_pt, self.ury_pt)
160 ulx_pt, uly_pt = trafo.apply_pt(self.llx_pt, self.ury_pt)
162 # Now, by sorting, we obtain the lower left and upper right corner
163 # of the new bounding box.
164 return bbox_pt(min(llx_pt, lrx_pt, urx_pt, ulx_pt), min(lly_pt, lry_pt, ury_pt, uly_pt),
165 max(llx_pt, lrx_pt, urx_pt, ulx_pt), max(lly_pt, lry_pt, ury_pt, uly_pt))
167 def enlarge_pt(self, all_pt=0, bottom_pt=None, left_pt=None, top_pt=None, right_pt=None):
168 """enlarge bbox in place by the given amounts in pts
170 all is used, if bottom, left, top and/or right are not given.
173 if self.llx_pt is None:
174 return
175 if bottom_pt is None:
176 bottom_pt = all_pt
177 if left_pt is None:
178 left_pt = all_pt
179 if top_pt is None:
180 top_pt = all_pt
181 if right_pt is None:
182 right_pt = all_pt
183 self.llx_pt -= left_pt
184 self.lly_pt -= bottom_pt
185 self.urx_pt += right_pt
186 self.ury_pt += top_pt
188 def enlarged_pt(self, all_pt=0, bottom_pt=None, left_pt=None, top_pt=None, right_pt=None):
189 """return bbox enlarged by the given amounts in pts
191 all is used, if bottom, left, top and/or right are not given.
194 if self.llx_pt is None:
195 return empty()
196 if bottom_pt is None:
197 bottom_pt = all_pt
198 if left_pt is None:
199 left_pt = all_pt
200 if top_pt is None:
201 top_pt = all_pt
202 if right_pt is None:
203 right_pt = all_pt
204 return bbox_pt(self.llx_pt-left_pt, self.lly_pt-bottom_pt, self.urx_pt+right_pt, self.ury_pt+top_pt)
206 def enlarge(self, all=0, bottom=None, left=None, top=None, right=None):
207 """enlarge bbox in place
209 all is used, if bottom, left, top and/or right are not given.
212 if self.llx_pt is None:
213 return
214 bottom_pt = left_pt = top_pt = right_pt = unit.topt(all)
215 if bottom is not None:
216 bottom_pt = unit.topt(bottom)
217 if left is not None:
218 left_pt = unit.topt(left)
219 if top is not None:
220 top_pt = unit.topt(top)
221 if right is not None:
222 right_pt = unit.topt(right)
223 self.llx_pt -= left_pt
224 self.lly_pt -= bottom_pt
225 self.urx_pt += right_pt
226 self.ury_pt += top_pt
228 def enlarged(self, all=0, bottom=None, left=None, top=None, right=None):
229 """return bbox enlarged
231 all is used, if bottom, left, top and/or right are not given.
234 if self.llx_pt is None:
235 return empty()
236 bottom_pt = left_pt = top_pt = right_pt = unit.topt(all)
237 if bottom is not None:
238 bottom_pt = unit.topt(bottom)
239 if left is not None:
240 left_pt = unit.topt(left)
241 if top is not None:
242 top_pt = unit.topt(top)
243 if right is not None:
244 right_pt = unit.topt(right)
245 return bbox_pt(self.llx_pt-left_pt, self.lly_pt-bottom_pt, self.urx_pt+right_pt, self.ury_pt+top_pt)
247 def rect(self):
248 """return rectangle corresponding to bbox"""
249 from . import path
250 if self.llx_pt is None:
251 raise ValueError("Cannot return path for empty bbox")
252 return path.rect_pt(self.llx_pt, self.lly_pt, self.urx_pt-self.llx_pt, self.ury_pt-self.lly_pt)
254 path = rect
256 def height_pt(self):
257 """return height of bbox in pts"""
258 if self.llx_pt is None:
259 raise ValueError("Cannot return heigth of empty bbox")
260 return self.ury_pt-self.lly_pt
262 def width_pt(self):
263 """return width of bbox in pts"""
264 if self.llx_pt is None:
265 raise ValueError("Cannot return width of empty bbox")
266 return self.urx_pt-self.llx_pt
268 def top_pt(self):
269 """return top coordinate of bbox in pts"""
270 if self.llx_pt is None:
271 raise ValueError("Cannot return top coordinate of empty bbox")
272 return self.ury_pt
274 def bottom_pt(self):
275 """return bottom coordinate of bbox in pts"""
276 if self.llx_pt is None:
277 raise ValueError("Cannot return bottom coordinate of empty bbox")
278 return self.lly_pt
280 def left_pt(self):
281 """return left coordinate of bbox in pts"""
282 if self.llx_pt is None:
283 raise ValueError("Cannot return left coordinate of empty bbox")
284 return self.llx_pt
286 def right_pt(self):
287 """return right coordinate of bbox in pts"""
288 if self.llx_pt is None:
289 raise ValueError("Cannot return right coordinate of empty bbox")
290 return self.urx_pt
292 def center_pt(self):
293 """return coordinates of the center of the bbox in pts"""
294 if self.llx_pt is None:
295 raise ValueError("Cannot return center coordinates of empty bbox")
296 return 0.5 * (self.llx_pt+self.urx_pt), 0.5 * (self.lly_pt+self.ury_pt)
298 def height(self):
299 """return height of bbox"""
300 return self.height_pt() * unit.t_pt
302 def width(self):
303 """return width of bbox"""
304 return self.width_pt() * unit.t_pt
306 def top(self):
307 """return top coordinate of bbox"""
308 return self.ury_pt * unit.t_pt
310 def bottom(self):
311 """return bottom coordinate of bbox"""
312 return self.lly_pt * unit.t_pt
314 def left(self):
315 """return left coordinate of bbox"""
316 return self.llx_pt * unit.t_pt
318 def right(self):
319 """return right coordinate of bbox"""
320 return self.urx_pt * unit.t_pt
322 def center(self):
323 """return coordinates of the center of the bbox"""
324 centerx_pt, centery_pt = self.center_pt()
325 return centerx_pt * unit.t_pt, centery_pt * unit.t_pt
328 class bbox(bbox_pt):
330 """class for bounding boxes"""
332 def __init__(self, llx_pt, lly_pt, urx_pt, ury_pt):
333 llx_pt = unit.topt(llx_pt)
334 lly_pt = unit.topt(lly_pt)
335 urx_pt = unit.topt(urx_pt)
336 ury_pt = unit.topt(ury_pt)
337 bbox_pt.__init__(self, llx_pt, lly_pt, urx_pt, ury_pt)
340 class empty(bbox_pt):
342 """empty bounding box, i.e., one containing no point"""
343 def __init__(self):
344 bbox_pt.__init__(self, None, None, None, None)