1 /*
2 * Copyright 2006-2007, 2023, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Stephan Aßmus <superstippi@gmx.de>
7 * Zardshard
8 */
9
10
11 #include "PerspectiveTransformer.h"
12
13 #include <new>
14 #include <stdio.h>
15
16 #include <agg_basics.h>
17 #include <agg_bounding_rect.h>
18 #include <Message.h>
19
20 #include "Shape.h"
21
22
23 _USING_ICON_NAMESPACE
24 using std::nothrow;
25
26
PerspectiveTransformer(VertexSource & source,Shape * shape)27 PerspectiveTransformer::PerspectiveTransformer(VertexSource& source, Shape* shape)
28 : Transformer("Perspective"),
29 PathTransformer(source),
30 Perspective(source, *this),
31 fShape(shape)
32 #ifdef ICON_O_MATIC
33 , fInverted(false)
34 #endif
35 {
36 #ifdef ICON_O_MATIC
37 if (fShape != NULL) {
38 fShape->AcquireReference();
39 fShape->AddObserver(this);
40 ObjectChanged(fShape); // finish initialization
41 }
42 #endif
43 }
44
45
PerspectiveTransformer(VertexSource & source,Shape * shape,BMessage * archive)46 PerspectiveTransformer::PerspectiveTransformer(
47 VertexSource& source, Shape* shape, BMessage* archive)
48 : Transformer(archive),
49 PathTransformer(source),
50 Perspective(source, *this),
51 fShape(shape)
52 #ifdef ICON_O_MATIC
53 , fInverted(false)
54 #endif
55 {
56 double matrix[9];
57 for (int i = 0; i < 9; i++) {
58 if (archive->FindDouble("matrix", i, &matrix[i]) != B_OK)
59 matrix[i] = 0;
60 }
61 load_from(matrix);
62
63 #ifdef ICON_O_MATIC
64 if (fShape != NULL) {
65 fShape->AcquireReference();
66 fShape->AddObserver(this);
67 ObjectChanged(fShape); // finish initialization
68 }
69 #endif
70 }
71
72
PerspectiveTransformer(const PerspectiveTransformer & other)73 PerspectiveTransformer::PerspectiveTransformer(const PerspectiveTransformer& other)
74 #ifdef ICON_O_MATIC
75 : Transformer(other.Name()),
76 #else
77 : Transformer(""),
78 #endif
79 PathTransformer(*other.fSource),
80 Perspective(*fSource, *this),
81 fShape(other.fShape)
82 #ifdef ICON_O_MATIC
83 , fInverted(other.fInverted),
84 fFromBox(other.fFromBox),
85 fToLeftTop(other.fToLeftTop),
86 fToRightTop(other.fToRightTop),
87 fToLeftBottom(other.fToLeftBottom),
88 fToRightBottom(other.fToRightBottom),
89 fValid(other.fValid)
90 #endif
91 {
92 double matrix[9];
93 other.store_to(matrix);
94 load_from(matrix);
95
96 #ifdef ICON_O_MATIC
97 if (fShape != NULL) {
98 fShape->AcquireReference();
99 fShape->AddObserver(this);
100 }
101 #endif
102 }
103
104
~PerspectiveTransformer()105 PerspectiveTransformer::~PerspectiveTransformer()
106 {
107 #ifdef ICON_O_MATIC
108 if (fShape != NULL) {
109 fShape->RemoveObserver(this);
110 fShape->ReleaseReference();
111 }
112 #endif
113 }
114
115
116 // #pragma mark -
117
118
119 Transformer*
Clone() const120 PerspectiveTransformer::Clone() const
121 {
122 return new (nothrow) PerspectiveTransformer(*this);
123 }
124
125
126 // #pragma mark -
127
128
129 void
rewind(unsigned path_id)130 PerspectiveTransformer::rewind(unsigned path_id)
131 {
132 Perspective::rewind(path_id);
133 }
134
135
136 unsigned
vertex(double * x,double * y)137 PerspectiveTransformer::vertex(double* x, double* y)
138 {
139 #ifdef ICON_O_MATIC
140 if (fValid)
141 return Perspective::vertex(x, y);
142 else
143 return agg::path_cmd_stop;
144 #else
145 return Perspective::vertex(x, y);
146 #endif
147 }
148
149
150 void
SetSource(VertexSource & source)151 PerspectiveTransformer::SetSource(VertexSource& source)
152 {
153 PathTransformer::SetSource(source);
154 Perspective::attach(source);
155
156 #ifdef ICON_O_MATIC
157 ObjectChanged(fShape);
158 #endif
159 }
160
161
162 double
ApproximationScale() const163 PerspectiveTransformer::ApproximationScale() const
164 {
165 return fSource->ApproximationScale() * scale();
166 }
167
168
169 // #pragma mark -
170
171
172 void
Invert()173 PerspectiveTransformer::Invert()
174 {
175 #ifdef ICON_O_MATIC
176 fInverted = !fInverted;
177
178 // TODO: degenerate matrices may not be adequately handled
179 bool degenerate = !invert();
180 fValid = fValid && !degenerate;
181 #else
182 invert();
183 #endif
184 }
185
186
187 // #pragma mark -
188
189
190 #ifdef ICON_O_MATIC
191
192 status_t
Archive(BMessage * into,bool deep) const193 PerspectiveTransformer::Archive(BMessage* into, bool deep) const
194 {
195 status_t ret = Transformer::Archive(into, deep);
196
197 into->what = archive_code;
198
199 double matrix[9];
200 store_to(matrix);
201
202 for (int i = 0; i < 9; i++) {
203 if (ret == B_OK)
204 ret = into->AddDouble("matrix", matrix[i]);
205 }
206
207 return ret;
208 }
209
210
211 // #prama mark -
212
213
214 void
ObjectChanged(const Observable * object)215 PerspectiveTransformer::ObjectChanged(const Observable* object)
216 {
217 if (fInverted) {
218 printf("calculating the validity or bounding box of an inverted "
219 "perspective transformer is currently unsupported.");
220 return;
221 }
222
223 uint32 pathID[1];
224 pathID[0] = 0;
225 double left, top, right, bottom;
226 agg::bounding_rect(*fSource, pathID, 0, 1, &left, &top, &right, &bottom);
227 BRect newFromBox = BRect(left, top, right, bottom);
228
229 // Stop if nothing we care about has changed
230 // TODO: Can this be done earlier? It would be nice to avoid having to
231 // recalculate the bounding box before realizing nothing needs to be done.
232 if (fFromBox == newFromBox)
233 return;
234
235 fFromBox = newFromBox;
236
237 _CheckValidity();
238
239 double x = fFromBox.left; double y = fFromBox.top;
240 Transform(&x, &y);
241 fToLeftTop = BPoint(x, y);
242
243 x = fFromBox.right; y = fFromBox.top;
244 Transform(&x, &y);
245 fToRightTop = BPoint(x, y);
246
247 x = fFromBox.left; y = fFromBox.bottom;
248 Transform(&x, &y);
249 fToLeftBottom = BPoint(x, y);
250
251 x = fFromBox.right; y = fFromBox.bottom;
252 Transform(&x, &y);
253 fToRightBottom = BPoint(x, y);
254 }
255
256
257 // #pragma mark -
258
259
260 void
TransformTo(BPoint leftTop,BPoint rightTop,BPoint leftBottom,BPoint rightBottom)261 PerspectiveTransformer::TransformTo(
262 BPoint leftTop, BPoint rightTop, BPoint leftBottom, BPoint rightBottom)
263 {
264 fToLeftTop = leftTop;
265 fToRightTop = rightTop;
266 fToLeftBottom = leftBottom;
267 fToRightBottom = rightBottom;
268
269 double quad[8] = {
270 fToLeftTop.x, fToLeftTop.y,
271 fToRightTop.x, fToRightTop.y,
272 fToRightBottom.x, fToRightBottom.y,
273 fToLeftBottom.x, fToLeftBottom.y
274 };
275
276 if (!fInverted) {
277 rect_to_quad(
278 fFromBox.left, fFromBox.top,
279 fFromBox.right, fFromBox.bottom, quad);
280 } else {
281 quad_to_rect(quad,
282 fFromBox.left, fFromBox.top,
283 fFromBox.right, fFromBox.bottom);
284 }
285
286 _CheckValidity();
287 Notify();
288 }
289
290
291 // #pragma mark -
292
293
294 void
_CheckValidity()295 PerspectiveTransformer::_CheckValidity()
296 {
297 // Checks that none of the points are too close to the camera. These tend to
298 // lead to very big numbers or a divide by zero error. Also checks that all
299 // points are on the same side of the camera. Transformations with points on
300 // different sides of the camera look weird and tend to cause crashes.
301
302 fValid = true;
303 double w;
304 bool positive;
305
306 if (!fInverted) {
307 w = fFromBox.left * w0 + fFromBox.top * w1 + w2;
308 fValid &= (fabs(w) > 0.00001);
309 positive = w > 0;
310
311 w = fFromBox.right * w0 + fFromBox.top * w1 + w2;
312 fValid &= (fabs(w) > 0.00001);
313 fValid &= (w>0)==positive;
314
315 w = fFromBox.left * w0 + fFromBox.bottom * w1 + w2;
316 fValid &= (fabs(w) > 0.00001);
317 fValid &= (w>0)==positive;
318
319 w = fFromBox.right * w0 + fFromBox.bottom * w1 + w2;
320 fValid &= (fabs(w) > 0.00001);
321 fValid &= (w>0)==positive;
322 } else {
323 w = fToLeftTop.x * w0 + fToLeftTop.y * w1 + w2;
324 fValid &= (fabs(w) > 0.00001);
325 positive = w > 0;
326
327 w = fToRightTop.x * w0 + fToRightTop.y * w1 + w2;
328 fValid &= (fabs(w) > 0.00001);
329 fValid &= (w>0)==positive;
330
331 w = fToLeftBottom.x * w0 + fToLeftBottom.y * w1 + w2;
332 fValid &= (fabs(w) > 0.00001);
333 fValid &= (w>0)==positive;
334
335 w = fToRightBottom.x * w0 + fToRightBottom.y * w1 + w2;
336 fValid &= (fabs(w) > 0.00001);
337 fValid &= (w>0)==positive;
338 }
339 }
340
341 #endif // ICON_O_MATIC
342