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
28BGradient::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
39BGradient::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
50BGradient::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
61BGradient::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!=
72bool
73BGradient::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
83static bool
84sort_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
95BGradient::BGradient()
96	: BArchivable(),
97	fColorStops(4),
98	fType(TYPE_NONE)
99{
100}
101
102
103BGradient::BGradient(const BGradient& other)
104	: BArchivable(),
105	fColorStops(std::max((int32)4, other.CountColorStops()))
106{
107	*this = other;
108}
109
110
111// constructor
112BGradient::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
178BGradient::~BGradient()
179{
180	MakeEmpty();
181}
182
183
184// Archive
185status_t
186BGradient::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=
258BGradient&
259BGradient::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==
290bool
291BGradient::operator==(const BGradient& other) const
292{
293	return ((other.GetType() == GetType()) && ColorStopsAreEqual(other));
294}
295
296
297// operator!=
298bool
299BGradient::operator!=(const BGradient& other) const
300{
301	return !(*this == other);
302}
303
304
305// ColorStopsAreEqual
306bool
307BGradient::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
329void
330BGradient::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
339int32
340BGradient::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
364bool
365BGradient::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
377bool
378BGradient::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
390bool
391BGradient::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
405bool
406BGradient::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
418bool
419BGradient::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
431int32
432BGradient::CountColorStops() const
433{
434	return fColorStops.CountItems();
435}
436
437
438// ColorStopAt
439BGradient::ColorStop*
440BGradient::ColorStopAt(int32 index) const
441{
442	return (ColorStop*)fColorStops.ItemAt(index);
443}
444
445
446// ColorStopAtFast
447BGradient::ColorStop*
448BGradient::ColorStopAtFast(int32 index) const
449{
450	return (ColorStop*)fColorStops.ItemAtFast(index);
451}
452
453
454// ColorStops
455BGradient::ColorStop*
456BGradient::ColorStops() const
457{
458	if (CountColorStops() > 0) {
459		return (ColorStop*) fColorStops.Items();
460	}
461	return NULL;
462}
463
464
465// SortColorStopsByOffset
466void
467BGradient::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
480void
481BGradient::MakeEmpty()
482{
483	int32 count = CountColorStops();
484	for (int32 i = 0; i < count; i++)
485		delete ColorStopAtFast(i);
486	fColorStops.MakeEmpty();
487}
488
489
490status_t
491BGradient::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
538static BGradient*
539gradient_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
559status_t
560BGradient::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