xref: /haiku/src/libs/icon/transformer/PerspectiveTransformer.cpp (revision 0822ec27a9fde7fac91a39214286c5d0d0eeb154)
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