xref: /haiku/src/kits/interface/Gradient.cpp (revision 3af8011358bd4c624a0979336d48dabb466171ed)
1 /*
2  * Copyright 2006-2009, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  *		Artur Wyszynski <harakash@gmail.com>
8  */
9 
10 #include "Gradient.h"
11 
12 #include <algorithm>
13 #include <math.h>
14 #include <stdio.h>
15 
16 #include <DataIO.h>
17 #include <Message.h>
18 
19 #include <AutoDeleter.h>
20 #include <GradientLinear.h>
21 #include <GradientRadial.h>
22 #include <GradientRadialFocus.h>
23 #include <GradientDiamond.h>
24 #include <GradientConic.h>
25 
26 
27 // constructor
28 BGradient::ColorStop::ColorStop(const rgb_color c, float o)
29 {
30 	color.red = c.red;
31 	color.green = c.green;
32 	color.blue = c.blue;
33 	color.alpha = c.alpha;
34 	offset = o;
35 }
36 
37 
38 // constructor
39 BGradient::ColorStop::ColorStop(uint8 r, uint8 g, uint8 b, uint8 a, float o)
40 {
41 	color.red = r;
42 	color.green = g;
43 	color.blue = b;
44 	color.alpha = a;
45 	offset = o;
46 }
47 
48 
49 // constructor
50 BGradient::ColorStop::ColorStop(const ColorStop& other)
51 {
52 	color.red = other.color.red;
53 	color.green = other.color.green;
54 	color.blue = other.color.blue;
55 	color.alpha = other.color.alpha;
56 	offset = other.offset;
57 }
58 
59 
60 // constructor
61 BGradient::ColorStop::ColorStop()
62 {
63 	color.red = 0;
64 	color.green = 0;
65 	color.blue = 0;
66 	color.alpha = 255;
67 	offset = 0;
68 }
69 
70 
71 // operator!=
72 bool
73 BGradient::ColorStop::operator!=(const ColorStop& other) const
74 {
75 	return color.red != other.color.red ||
76 	color.green != other.color.green ||
77 	color.blue != other.color.blue ||
78 	color.alpha != other.color.alpha ||
79 	offset != other.offset;
80 }
81 
82 
83 static bool
84 sort_color_stops_by_offset(const BGradient::ColorStop* left,
85 	const BGradient::ColorStop* right)
86 {
87 	return left->offset < right->offset;
88 }
89 
90 
91 // #pragma mark -
92 
93 
94 // constructor
95 BGradient::BGradient()
96 	: BArchivable(),
97 	fColorStops(4),
98 	fType(TYPE_NONE)
99 {
100 }
101 
102 
103 BGradient::BGradient(const BGradient& other)
104 	: BArchivable(),
105 	fColorStops(std::max((int32)4, other.CountColorStops()))
106 {
107 	*this = other;
108 }
109 
110 
111 // constructor
112 BGradient::BGradient(BMessage* archive)
113 	: BArchivable(archive),
114 	fColorStops(4),
115 	fType(TYPE_NONE)
116 {
117 	if (!archive)
118 		return;
119 
120 	// color stops
121 	ColorStop stop;
122 	for (int32 i = 0; archive->FindFloat("offset", i, &stop.offset) >= B_OK; i++) {
123 		if (archive->FindInt32("color", i, (int32*)&stop.color) >= B_OK)
124 			AddColorStop(stop, i);
125 		else
126 			break;
127 	}
128 	if (archive->FindInt32("type", (int32*)&fType) < B_OK)
129 		fType = TYPE_LINEAR;
130 
131 	// linear
132 	if (archive->FindFloat("linear_x1", (float*)&fData.linear.x1) < B_OK)
133 		fData.linear.x1 = 0.0f;
134 	if (archive->FindFloat("linear_y1", (float*)&fData.linear.y1) < B_OK)
135 		fData.linear.y1 = 0.0f;
136 	if (archive->FindFloat("linear_x2", (float*)&fData.linear.x2) < B_OK)
137 		fData.linear.x2 = 0.0f;
138 	if (archive->FindFloat("linear_y2", (float*)&fData.linear.y2) < B_OK)
139 		fData.linear.y2 = 0.0f;
140 
141 	// radial
142 	if (archive->FindFloat("radial_cx", (float*)&fData.radial.cx) < B_OK)
143 		fData.radial.cx = 0.0f;
144 	if (archive->FindFloat("radial_cy", (float*)&fData.radial.cy) < B_OK)
145 		fData.radial.cy = 0.0f;
146 	if (archive->FindFloat("radial_radius", (float*)&fData.radial.radius) < B_OK)
147 		fData.radial.radius = 0.0f;
148 
149 	// radial focus
150 	if (archive->FindFloat("radial_f_cx", (float*)&fData.radial_focus.cx) < B_OK)
151 		fData.radial_focus.cx = 0.0f;
152 	if (archive->FindFloat("radial_f_cy", (float*)&fData.radial_focus.cy) < B_OK)
153 		fData.radial_focus.cy = 0.0f;
154 	if (archive->FindFloat("radial_f_fx", (float*)&fData.radial_focus.fx) < B_OK)
155 		fData.radial_focus.fx = 0.0f;
156 	if (archive->FindFloat("radial_f_fy", (float*)&fData.radial_focus.fy) < B_OK)
157 		fData.radial_focus.fy = 0.0f;
158 	if (archive->FindFloat("radial_f_radius", (float*)&fData.radial_focus.radius) < B_OK)
159 		fData.radial_focus.radius = 0.0f;
160 
161 	// diamond
162 	if (archive->FindFloat("diamond_cx", (float*)&fData.diamond.cx) < B_OK)
163 		fData.diamond.cx = 0.0f;
164 	if (archive->FindFloat("diamond_cy", (float*)&fData.diamond.cy) < B_OK)
165 		fData.diamond.cy = 0.0f;
166 
167 	// conic
168 	if (archive->FindFloat("conic_cx", (float*)&fData.conic.cx) < B_OK)
169 		fData.conic.cx = 0.0f;
170 	if (archive->FindFloat("conic_cy", (float*)&fData.conic.cy) < B_OK)
171 		fData.conic.cy = 0.0f;
172 	if (archive->FindFloat("conic_angle", (float*)&fData.conic.angle) < B_OK)
173 		fData.conic.angle = 0.0f;
174 }
175 
176 
177 // destructor
178 BGradient::~BGradient()
179 {
180 	MakeEmpty();
181 }
182 
183 
184 // Archive
185 status_t
186 BGradient::Archive(BMessage* into, bool deep) const
187 {
188 	status_t ret = BArchivable::Archive(into, deep);
189 
190 	// color steps
191 	if (ret >= B_OK) {
192 		for (int32 i = 0; ColorStop* stop = ColorStopAt(i); i++) {
193 			ret = into->AddInt32("color", (const uint32&)stop->color);
194 			if (ret < B_OK)
195 				break;
196 			ret = into->AddFloat("offset", stop->offset);
197 			if (ret < B_OK)
198 				break;
199 		}
200 	}
201 	// gradient type
202 	if (ret >= B_OK)
203 		ret = into->AddInt32("type", (int32)fType);
204 
205 	// linear
206 	if (ret >= B_OK)
207 		ret = into->AddFloat("linear_x1", (float)fData.linear.x1);
208 	if (ret >= B_OK)
209 		ret = into->AddFloat("linear_y1", (float)fData.linear.y1);
210 	if (ret >= B_OK)
211 		ret = into->AddFloat("linear_x2", (float)fData.linear.x2);
212 	if (ret >= B_OK)
213 		ret = into->AddFloat("linear_y2", (float)fData.linear.y2);
214 
215 	// radial
216 	if (ret >= B_OK)
217 		ret = into->AddFloat("radial_cx", (float)fData.radial.cx);
218 	if (ret >= B_OK)
219 		ret = into->AddFloat("radial_cy", (float)fData.radial.cy);
220 	if (ret >= B_OK)
221 		ret = into->AddFloat("radial_radius", (float)fData.radial.radius);
222 
223 	// radial focus
224 	if (ret >= B_OK)
225 		ret = into->AddFloat("radial_f_cx", (float)fData.radial_focus.cx);
226 	if (ret >= B_OK)
227 		ret = into->AddFloat("radial_f_cy", (float)fData.radial_focus.cy);
228 	if (ret >= B_OK)
229 		ret = into->AddFloat("radial_f_fx", (float)fData.radial_focus.fx);
230 	if (ret >= B_OK)
231 		ret = into->AddFloat("radial_f_fy", (float)fData.radial_focus.fy);
232 	if (ret >= B_OK)
233 		ret = into->AddFloat("radial_f_radius", (float)fData.radial_focus.radius);
234 
235 	// diamond
236 	if (ret >= B_OK)
237 		ret = into->AddFloat("diamond_cx", (float)fData.diamond.cx);
238 	if (ret >= B_OK)
239 		ret = into->AddFloat("diamond_cy", (float)fData.diamond.cy);
240 
241 	// conic
242 	if (ret >= B_OK)
243 		ret = into->AddFloat("conic_cx", (float)fData.conic.cx);
244 	if (ret >= B_OK)
245 		ret = into->AddFloat("conic_cy", (float)fData.conic.cy);
246 	if (ret >= B_OK)
247 		ret = into->AddFloat("conic_angle", (float)fData.conic.angle);
248 
249 	// finish off
250 	if (ret >= B_OK)
251 		ret = into->AddString("class", "BGradient");
252 
253 	return ret;
254 }
255 
256 
257 // operator=
258 BGradient&
259 BGradient::operator=(const BGradient& other)
260 {
261 	if (&other == this)
262 		return *this;
263 
264 	SetColorStops(other);
265 	fType = other.fType;
266 	switch (fType) {
267 		case TYPE_LINEAR:
268 			fData.linear = other.fData.linear;
269 			break;
270 		case TYPE_RADIAL:
271 			fData.radial = other.fData.radial;
272 			break;
273 		case TYPE_RADIAL_FOCUS:
274 			fData.radial_focus = other.fData.radial_focus;
275 			break;
276 		case TYPE_DIAMOND:
277 			fData.diamond = other.fData.diamond;
278 			break;
279 		case TYPE_CONIC:
280 			fData.conic = other.fData.conic;
281 			break;
282 		case TYPE_NONE:
283 			break;
284 	}
285 	return *this;
286 }
287 
288 
289 // operator==
290 bool
291 BGradient::operator==(const BGradient& other) const
292 {
293 	return ((other.GetType() == GetType()) && ColorStopsAreEqual(other));
294 }
295 
296 
297 // operator!=
298 bool
299 BGradient::operator!=(const BGradient& other) const
300 {
301 	return !(*this == other);
302 }
303 
304 
305 // ColorStopsAreEqual
306 bool
307 BGradient::ColorStopsAreEqual(const BGradient& other) const
308 {
309 	int32 count = CountColorStops();
310 	if (count == other.CountColorStops() &&
311 		fType == other.fType) {
312 
313 		bool equal = true;
314 		for (int32 i = 0; i < count; i++) {
315 			ColorStop* ourStop = ColorStopAtFast(i);
316 			ColorStop* otherStop = other.ColorStopAtFast(i);
317 			if (*ourStop != *otherStop) {
318 				equal = false;
319 				break;
320 			}
321 		}
322 		return equal;
323 	}
324 	return false;
325 }
326 
327 
328 // SetColorStops
329 void
330 BGradient::SetColorStops(const BGradient& other)
331 {
332 	MakeEmpty();
333 	for (int32 i = 0; ColorStop* stop = other.ColorStopAt(i); i++)
334 		AddColorStop(*stop, i);
335 }
336 
337 
338 // AddColor
339 int32
340 BGradient::AddColor(const rgb_color& color, float offset)
341 {
342 	// Out of bounds stops would crash the app_server
343 	if (offset < 0.f || offset > 255.f)
344 		return -1;
345 
346 	// find the correct index (sorted by offset)
347 	ColorStop* stop = new ColorStop(color, offset);
348 	int32 index = 0;
349 	int32 count = CountColorStops();
350 	for (; index < count; index++) {
351 		ColorStop* s = ColorStopAtFast(index);
352 		if (s->offset > stop->offset)
353 			break;
354 	}
355 	if (!fColorStops.AddItem((void*)stop, index)) {
356 		delete stop;
357 		return -1;
358 	}
359 	return index;
360 }
361 
362 
363 // AddColorStop
364 bool
365 BGradient::AddColorStop(const ColorStop& colorStop, int32 index)
366 {
367 	ColorStop* stop = new ColorStop(colorStop);
368 	if (!fColorStops.AddItem((void*)stop, index)) {
369 		delete stop;
370 		return false;
371 	}
372 	return true;
373 }
374 
375 
376 // RemoveColor
377 bool
378 BGradient::RemoveColor(int32 index)
379 {
380 	ColorStop* stop = (ColorStop*)fColorStops.RemoveItem(index);
381 	if (!stop) {
382 		return false;
383 	}
384 	delete stop;
385 	return true;
386 }
387 
388 
389 // SetColorStop
390 bool
391 BGradient::SetColorStop(int32 index, const ColorStop& color)
392 {
393 	if (ColorStop* stop = ColorStopAt(index)) {
394 		if (*stop != color) {
395 			stop->color = color.color;
396 			stop->offset = color.offset;
397 			return true;
398 		}
399 	}
400 	return false;
401 }
402 
403 
404 // SetColor
405 bool
406 BGradient::SetColor(int32 index, const rgb_color& color)
407 {
408 	ColorStop* stop = ColorStopAt(index);
409 	if (stop && stop->color != color) {
410 		stop->color = color;
411 		return true;
412 	}
413 	return false;
414 }
415 
416 
417 // SetOffset
418 bool
419 BGradient::SetOffset(int32 index, float offset)
420 {
421 	ColorStop* stop = ColorStopAt(index);
422 	if (stop && stop->offset != offset) {
423 		stop->offset = offset;
424 		return true;
425 	}
426 	return false;
427 }
428 
429 
430 // CountColorStops
431 int32
432 BGradient::CountColorStops() const
433 {
434 	return fColorStops.CountItems();
435 }
436 
437 
438 // ColorStopAt
439 BGradient::ColorStop*
440 BGradient::ColorStopAt(int32 index) const
441 {
442 	return (ColorStop*)fColorStops.ItemAt(index);
443 }
444 
445 
446 // ColorStopAtFast
447 BGradient::ColorStop*
448 BGradient::ColorStopAtFast(int32 index) const
449 {
450 	return (ColorStop*)fColorStops.ItemAtFast(index);
451 }
452 
453 
454 // ColorStops
455 BGradient::ColorStop*
456 BGradient::ColorStops() const
457 {
458 	if (CountColorStops() > 0) {
459 		return (ColorStop*) fColorStops.Items();
460 	}
461 	return NULL;
462 }
463 
464 
465 // SortColorStopsByOffset
466 void
467 BGradient::SortColorStopsByOffset()
468 {
469 	// Use stable sort: stops with the same offset will retain their original
470 	// order. This can be used to have sharp color changes in the gradient.
471 	// BList.SortItems() uses qsort(), which isn't stable, and sometimes swaps
472 	// such stops.
473 	const BGradient::ColorStop** first = (const BGradient::ColorStop**)fColorStops.Items();
474 	const BGradient::ColorStop** last = first + fColorStops.CountItems();
475 	std::stable_sort(first, last, sort_color_stops_by_offset);
476 }
477 
478 
479 // MakeEmpty
480 void
481 BGradient::MakeEmpty()
482 {
483 	int32 count = CountColorStops();
484 	for (int32 i = 0; i < count; i++)
485 		delete ColorStopAtFast(i);
486 	fColorStops.MakeEmpty();
487 }
488 
489 
490 status_t
491 BGradient::Flatten(BDataIO* stream) const
492 {
493 	int32 stopCount = CountColorStops();
494 	stream->Write(&fType, sizeof(Type));
495 	stream->Write(&stopCount, sizeof(int32));
496 	if (stopCount > 0) {
497 		for (int i = 0; i < stopCount; i++) {
498 			stream->Write(ColorStopAtFast(i),
499 				sizeof(ColorStop));
500 		}
501 	}
502 
503 	switch (fType) {
504 		case TYPE_LINEAR:
505 			stream->Write(&fData.linear.x1, sizeof(float));
506 			stream->Write(&fData.linear.y1, sizeof(float));
507 			stream->Write(&fData.linear.x2, sizeof(float));
508 			stream->Write(&fData.linear.y2, sizeof(float));
509 			break;
510 		case TYPE_RADIAL:
511 			stream->Write(&fData.radial.cx, sizeof(float));
512 			stream->Write(&fData.radial.cy, sizeof(float));
513 			stream->Write(&fData.radial.radius, sizeof(float));
514 			break;
515 		case TYPE_RADIAL_FOCUS:
516 			stream->Write(&fData.radial_focus.cx, sizeof(float));
517 			stream->Write(&fData.radial_focus.cy, sizeof(float));
518 			stream->Write(&fData.radial_focus.fx, sizeof(float));
519 			stream->Write(&fData.radial_focus.fy, sizeof(float));
520 			stream->Write(&fData.radial_focus.radius, sizeof(float));
521 			break;
522 		case TYPE_DIAMOND:
523 			stream->Write(&fData.diamond.cx, sizeof(float));
524 			stream->Write(&fData.diamond.cy, sizeof(float));
525 			break;
526 		case TYPE_CONIC:
527 			stream->Write(&fData.conic.cx, sizeof(float));
528 			stream->Write(&fData.conic.cy, sizeof(float));
529 			stream->Write(&fData.conic.angle, sizeof(float));
530 			break;
531 		case TYPE_NONE:
532 			break;
533 	}
534 	return B_OK;
535 }
536 
537 
538 static BGradient*
539 gradient_for_type(BGradient::Type type)
540 {
541 	switch (type) {
542 		case BGradient::TYPE_LINEAR:
543 			return new (std::nothrow) BGradientLinear();
544 		case BGradient::TYPE_RADIAL:
545 			return new (std::nothrow) BGradientRadial();
546 		case BGradient::TYPE_RADIAL_FOCUS:
547 			return new (std::nothrow) BGradientRadialFocus();
548 		case BGradient::TYPE_DIAMOND:
549 			return new (std::nothrow) BGradientDiamond();
550 		case BGradient::TYPE_CONIC:
551 			return new (std::nothrow) BGradientConic();
552 		case BGradient::TYPE_NONE:
553 			return new (std::nothrow) BGradient();
554 	}
555 	return NULL;
556 }
557 
558 
559 status_t
560 BGradient::Unflatten(BGradient *&output, BDataIO* stream)
561 {
562 	output = NULL;
563 	Type gradientType;
564 	int32 colorsCount;
565 	stream->Read(&gradientType, sizeof(Type));
566 	status_t status = stream->Read(&colorsCount, sizeof(int32));
567 	if (status < B_OK)
568 		return status;
569 
570 	ObjectDeleter<BGradient> gradient(gradient_for_type(gradientType));
571 	if (!gradient.IsSet())
572 		return B_NO_MEMORY;
573 
574 	if (colorsCount > 0) {
575 		ColorStop stop;
576 		for (int i = 0; i < colorsCount; i++) {
577 			if ((status = stream->Read(&stop, sizeof(ColorStop))) < B_OK)
578 				return status;
579 			if (!gradient->AddColorStop(stop, i))
580 				return B_NO_MEMORY;
581 		}
582 	}
583 
584 	switch (gradientType) {
585 		case TYPE_LINEAR:
586 			stream->Read(&gradient->fData.linear.x1, sizeof(float));
587 			stream->Read(&gradient->fData.linear.y1, sizeof(float));
588 			stream->Read(&gradient->fData.linear.x2, sizeof(float));
589 			if ((status = stream->Read(&gradient->fData.linear.y2, sizeof(float))) < B_OK)
590 				return status;
591 			break;
592 		case TYPE_RADIAL:
593 			stream->Read(&gradient->fData.radial.cx, sizeof(float));
594 			stream->Read(&gradient->fData.radial.cy, sizeof(float));
595 			if ((stream->Read(&gradient->fData.radial.radius, sizeof(float))) < B_OK)
596 				return status;
597 			break;
598 		case TYPE_RADIAL_FOCUS:
599 			stream->Read(&gradient->fData.radial_focus.cx, sizeof(float));
600 			stream->Read(&gradient->fData.radial_focus.cy, sizeof(float));
601 			stream->Read(&gradient->fData.radial_focus.fx, sizeof(float));
602 			stream->Read(&gradient->fData.radial_focus.fy, sizeof(float));
603 			if ((stream->Read(&gradient->fData.radial_focus.radius, sizeof(float))) < B_OK)
604 				return status;
605 			break;
606 		case TYPE_DIAMOND:
607 			stream->Read(&gradient->fData.diamond.cx, sizeof(float));
608 			if ((stream->Read(&gradient->fData.diamond.cy, sizeof(float))) < B_OK)
609 				return status;
610 			break;
611 		case TYPE_CONIC:
612 			stream->Read(&gradient->fData.conic.cx, sizeof(float));
613 			stream->Read(&gradient->fData.conic.cy, sizeof(float));
614 			if ((stream->Read(&gradient->fData.conic.angle, sizeof(float))) < B_OK)
615 				return status;
616 			break;
617 		case TYPE_NONE:
618 			break;
619 	}
620 
621 	output = gradient.Detach();
622 	return B_OK;
623 }
624