1/*
2 * Copyright 2006-2013, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus, superstippi@gmx.de
7 *		Ingo Weinhold, ingo_weinhold@gmx.de
8 */
9
10
11#include <Icon.h>
12
13#include <string.h>
14
15#include <new>
16
17#include <Bitmap.h>
18
19#include <AutoDeleter.h>
20
21
22namespace BPrivate {
23
24
25BIcon::BIcon()
26	:
27	fEnabledBitmaps(8, true),
28	fDisabledBitmaps(8, true)
29{
30}
31
32
33BIcon::~BIcon()
34{
35}
36
37
38status_t
39BIcon::SetTo(const BBitmap* bitmap, uint32 flags)
40{
41	if (!bitmap->IsValid())
42		return B_BAD_VALUE;
43
44	DeleteBitmaps();
45
46	// check the color space
47	bool hasAlpha = false;
48	bool canUseForMakeBitmaps = false;
49
50	switch (bitmap->ColorSpace()) {
51		case B_RGBA32:
52		case B_RGBA32_BIG:
53			hasAlpha = true;
54			// fall through
55		case B_RGB32:
56		case B_RGB32_BIG:
57			canUseForMakeBitmaps = true;
58			break;
59
60		case B_UVLA32:
61		case B_LABA32:
62		case B_HSIA32:
63		case B_HSVA32:
64		case B_HLSA32:
65		case B_CMYA32:
66		case B_RGBA15:
67		case B_RGBA15_BIG:
68		case B_CMAP8:
69			hasAlpha = true;
70			break;
71
72		default:
73			break;
74	}
75
76	BBitmap* trimmedBitmap = NULL;
77
78	// trim the bitmap, if requested and the bitmap actually has alpha
79	status_t error;
80	if ((flags & (B_TRIM_ICON_BITMAP | B_TRIM_ICON_BITMAP_KEEP_ASPECT)) != 0
81		&& hasAlpha) {
82		if (bitmap->ColorSpace() == B_RGBA32) {
83			error = _TrimBitmap(bitmap,
84				(flags & B_TRIM_ICON_BITMAP_KEEP_ASPECT) != 0, trimmedBitmap);
85		} else {
86			BBitmap* rgb32Bitmap = _ConvertToRGB32(bitmap, true);
87			if (rgb32Bitmap != NULL) {
88				error = _TrimBitmap(rgb32Bitmap,
89					(flags & B_TRIM_ICON_BITMAP_KEEP_ASPECT) != 0,
90					trimmedBitmap);
91				delete rgb32Bitmap;
92			} else
93				error = B_NO_MEMORY;
94		}
95
96		if (error != B_OK)
97			return error;
98
99		bitmap = trimmedBitmap;
100		canUseForMakeBitmaps = true;
101	}
102
103	// create the bitmaps
104	if (canUseForMakeBitmaps) {
105		error = _MakeBitmaps(bitmap, flags);
106	} else {
107		if (BBitmap* rgb32Bitmap = _ConvertToRGB32(bitmap, true)) {
108			error = _MakeBitmaps(rgb32Bitmap, flags);
109			delete rgb32Bitmap;
110		} else
111			error = B_NO_MEMORY;
112	}
113
114	delete trimmedBitmap;
115
116	return error;
117}
118
119
120bool
121BIcon::SetBitmap(BBitmap* bitmap, uint32 which)
122{
123	BitmapList& list = (which & B_DISABLED_ICON_BITMAP) == 0
124		? fEnabledBitmaps : fDisabledBitmaps;
125	which &= ~uint32(B_DISABLED_ICON_BITMAP);
126
127	int32 count = list.CountItems();
128	if ((int32)which < count) {
129		list.ReplaceItem(which, bitmap);
130		return true;
131	}
132
133	while (list.CountItems() < (int32)which) {
134		if (!list.AddItem((BBitmap*)NULL))
135			return false;
136	}
137
138	return list.AddItem(bitmap);
139}
140
141
142BBitmap*
143BIcon::Bitmap(uint32 which) const
144{
145	const BitmapList& list = (which & B_DISABLED_ICON_BITMAP) == 0
146		? fEnabledBitmaps : fDisabledBitmaps;
147	return list.ItemAt(which & ~uint32(B_DISABLED_ICON_BITMAP));
148}
149
150
151BBitmap*
152BIcon::CreateBitmap(const BRect& bounds, color_space colorSpace, uint32 which)
153{
154	BBitmap* bitmap = new(std::nothrow) BBitmap(bounds, colorSpace);
155	if (bitmap == NULL || !bitmap->IsValid() || !SetBitmap(bitmap, which)) {
156		delete bitmap;
157		return NULL;
158	}
159
160	return bitmap;
161}
162
163
164status_t
165BIcon::SetExternalBitmap(const BBitmap* bitmap, uint32 which, uint32 flags)
166{
167	BBitmap* ourBitmap = NULL;
168	if (bitmap != NULL) {
169		if (!bitmap->IsValid())
170			return B_BAD_VALUE;
171
172		if ((flags & B_KEEP_ICON_BITMAP) != 0) {
173			ourBitmap = const_cast<BBitmap*>(bitmap);
174		} else {
175			ourBitmap = _ConvertToRGB32(bitmap);
176			if (ourBitmap == NULL)
177				return B_NO_MEMORY;
178		}
179	}
180
181	if (!SetBitmap(ourBitmap, which)) {
182		if (ourBitmap != bitmap)
183			delete ourBitmap;
184		return B_NO_MEMORY;
185	}
186
187	return B_OK;
188}
189
190
191BBitmap*
192BIcon::CopyBitmap(const BBitmap& bitmapToClone, uint32 which)
193{
194	BBitmap* bitmap = new(std::nothrow) BBitmap(bitmapToClone);
195	if (bitmap == NULL || !bitmap->IsValid() || !SetBitmap(bitmap, which)) {
196		delete bitmap;
197		return NULL;
198	}
199
200	return bitmap;
201}
202
203
204void
205BIcon::DeleteBitmaps()
206{
207	fEnabledBitmaps.MakeEmpty(true);
208	fDisabledBitmaps.MakeEmpty(true);
209}
210
211
212/*static*/ status_t
213BIcon::UpdateIcon(const BBitmap* bitmap, uint32 flags, BIcon*& _icon)
214{
215	if (bitmap == NULL) {
216		delete _icon;
217		_icon = NULL;
218		return B_OK;
219	}
220
221	BIcon* icon = new(std::nothrow) BIcon;
222	if (icon == NULL)
223		return B_NO_MEMORY;
224
225	status_t error = icon->SetTo(bitmap, flags);
226	if (error != B_OK) {
227		delete icon;
228		return error;
229	}
230
231	_icon = icon;
232	return B_OK;
233}
234
235
236/*static*/ status_t
237BIcon::SetIconBitmap(const BBitmap* bitmap, uint32 which, uint32 flags,
238	BIcon*& _icon)
239{
240	bool newIcon = false;
241	if (_icon == NULL) {
242		if (bitmap == NULL)
243			return B_OK;
244
245		_icon = new(std::nothrow) BIcon;
246		if (_icon == NULL)
247			return B_NO_MEMORY;
248		newIcon = true;
249	}
250
251	status_t error = _icon->SetExternalBitmap(bitmap, which, flags);
252	if (error != B_OK) {
253		if (newIcon) {
254			delete _icon;
255			_icon = NULL;
256		}
257		return error;
258	}
259
260	return B_OK;
261}
262
263
264/*static*/ BBitmap*
265BIcon::_ConvertToRGB32(const BBitmap* bitmap, bool noAppServerLink)
266{
267	BBitmap* rgb32Bitmap = new(std::nothrow) BBitmap(bitmap->Bounds(),
268		noAppServerLink ? B_BITMAP_NO_SERVER_LINK : 0, B_RGBA32);
269	if (rgb32Bitmap == NULL)
270		return NULL;
271
272	if (!rgb32Bitmap->IsValid() || rgb32Bitmap->ImportBits(bitmap)!= B_OK) {
273		delete rgb32Bitmap;
274		return NULL;
275	}
276
277	return rgb32Bitmap;
278}
279
280
281/*static*/ status_t
282BIcon::_TrimBitmap(const BBitmap* bitmap, bool keepAspect,
283	BBitmap*& _trimmedBitmap)
284{
285	if (bitmap == NULL || !bitmap->IsValid()
286		|| bitmap->ColorSpace() != B_RGBA32) {
287		return B_BAD_VALUE;
288	}
289
290	uint8* bits = (uint8*)bitmap->Bits();
291	uint32 bpr = bitmap->BytesPerRow();
292	uint32 width = bitmap->Bounds().IntegerWidth() + 1;
293	uint32 height = bitmap->Bounds().IntegerHeight() + 1;
294	BRect trimmed(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN);
295
296	for (uint32 y = 0; y < height; y++) {
297		uint8* b = bits + 3;
298		bool rowHasAlpha = false;
299		for (uint32 x = 0; x < width; x++) {
300			if (*b) {
301				rowHasAlpha = true;
302				if (x < trimmed.left)
303					trimmed.left = x;
304				if (x > trimmed.right)
305					trimmed.right = x;
306			}
307			b += 4;
308		}
309		if (rowHasAlpha) {
310			if (y < trimmed.top)
311				trimmed.top = y;
312			if (y > trimmed.bottom)
313				trimmed.bottom = y;
314		}
315		bits += bpr;
316	}
317
318	if (!trimmed.IsValid())
319		return B_BAD_VALUE;
320
321	if (keepAspect) {
322		float minInset = trimmed.left;
323		minInset = min_c(minInset, trimmed.top);
324		minInset = min_c(minInset, bitmap->Bounds().right - trimmed.right);
325		minInset = min_c(minInset, bitmap->Bounds().bottom - trimmed.bottom);
326		trimmed = bitmap->Bounds().InsetByCopy(minInset, minInset);
327	}
328	trimmed = trimmed & bitmap->Bounds();
329
330	BBitmap* trimmedBitmap = new(std::nothrow) BBitmap(
331		trimmed.OffsetToCopy(B_ORIGIN), B_BITMAP_NO_SERVER_LINK, B_RGBA32);
332	if (trimmedBitmap == NULL)
333		return B_NO_MEMORY;
334
335	bits = (uint8*)bitmap->Bits();
336	bits += 4 * (int32)trimmed.left + bpr * (int32)trimmed.top;
337	uint8* dst = (uint8*)trimmedBitmap->Bits();
338	uint32 trimmedWidth = trimmedBitmap->Bounds().IntegerWidth() + 1;
339	uint32 trimmedHeight = trimmedBitmap->Bounds().IntegerHeight() + 1;
340	uint32 trimmedBPR = trimmedBitmap->BytesPerRow();
341	for (uint32 y = 0; y < trimmedHeight; y++) {
342		memcpy(dst, bits, trimmedWidth * 4);
343		dst += trimmedBPR;
344		bits += bpr;
345	}
346
347	_trimmedBitmap = trimmedBitmap;
348	return B_OK;
349}
350
351
352status_t
353BIcon::_MakeBitmaps(const BBitmap* bitmap, uint32 flags)
354{
355	// make our own versions of the bitmap
356	BRect b(bitmap->Bounds());
357
358	color_space format = bitmap->ColorSpace();
359	BBitmap* normalBitmap = CreateBitmap(b, format, B_INACTIVE_ICON_BITMAP);
360	if (normalBitmap == NULL)
361		return B_NO_MEMORY;
362
363	BBitmap* disabledBitmap = NULL;
364	if ((flags & B_CREATE_DISABLED_ICON_BITMAPS) != 0) {
365		disabledBitmap = CreateBitmap(b, format,
366			B_INACTIVE_ICON_BITMAP | B_DISABLED_ICON_BITMAP);
367		if (disabledBitmap == NULL)
368			return B_NO_MEMORY;
369	}
370
371	BBitmap* clickedBitmap = NULL;
372	if ((flags & (B_CREATE_ACTIVE_ICON_BITMAP
373			| B_CREATE_PARTIALLY_ACTIVE_ICON_BITMAP)) != 0) {
374		clickedBitmap = CreateBitmap(b, format, B_ACTIVE_ICON_BITMAP);
375		if (clickedBitmap == NULL)
376			return B_NO_MEMORY;
377	}
378
379	BBitmap* disabledClickedBitmap = NULL;
380	if (disabledBitmap != NULL && clickedBitmap != NULL) {
381		disabledClickedBitmap = CreateBitmap(b, format,
382			B_ACTIVE_ICON_BITMAP | B_DISABLED_ICON_BITMAP);
383		if (disabledClickedBitmap == NULL)
384			return B_NO_MEMORY;
385	}
386
387	// copy bitmaps from file bitmap
388	uint8* nBits = normalBitmap != NULL ? (uint8*)normalBitmap->Bits() : NULL;
389	uint8* dBits = disabledBitmap != NULL
390		? (uint8*)disabledBitmap->Bits() : NULL;
391	uint8* cBits = clickedBitmap != NULL ? (uint8*)clickedBitmap->Bits() : NULL;
392	uint8* dcBits = disabledClickedBitmap != NULL
393		? (uint8*)disabledClickedBitmap->Bits() : NULL;
394	uint8* fBits = (uint8*)bitmap->Bits();
395	int32 nbpr = normalBitmap->BytesPerRow();
396	int32 fbpr = bitmap->BytesPerRow();
397	int32 pixels = b.IntegerWidth() + 1;
398	int32 lines = b.IntegerHeight() + 1;
399	if (format == B_RGB32) {
400		// nontransparent version
401
402		// iterate over color components
403		for (int32 y = 0; y < lines; y++) {
404			for (int32 x = 0; x < pixels; x++) {
405				int32 nOffset = 4 * x;
406				int32 fOffset = 4 * x;
407				nBits[nOffset + 0] = fBits[fOffset + 0];
408				nBits[nOffset + 1] = fBits[fOffset + 1];
409				nBits[nOffset + 2] = fBits[fOffset + 2];
410				nBits[nOffset + 3] = 255;
411
412				// clicked bits are darker (lame method...)
413				if (cBits != NULL) {
414					cBits[nOffset + 0] = uint8((float)nBits[nOffset + 0] * 0.8);
415					cBits[nOffset + 1] = uint8((float)nBits[nOffset + 1] * 0.8);
416					cBits[nOffset + 2] = uint8((float)nBits[nOffset + 2] * 0.8);
417					cBits[nOffset + 3] = 255;
418				}
419
420				// disabled bits have less contrast (lame method...)
421				if (dBits != NULL) {
422					uint8 grey = 216;
423					float dist = (nBits[nOffset + 0] - grey) * 0.4;
424					dBits[nOffset + 0] = (uint8)(grey + dist);
425					dist = (nBits[nOffset + 1] - grey) * 0.4;
426					dBits[nOffset + 1] = (uint8)(grey + dist);
427					dist = (nBits[nOffset + 2] - grey) * 0.4;
428					dBits[nOffset + 2] = (uint8)(grey + dist);
429					dBits[nOffset + 3] = 255;
430				}
431
432				// disabled bits have less contrast (lame method...)
433				if (dcBits != NULL) {
434					uint8 grey = 188;
435					float dist = (nBits[nOffset + 0] - grey) * 0.4;
436					dcBits[nOffset + 0] = (uint8)(grey + dist);
437					dist = (nBits[nOffset + 1] - grey) * 0.4;
438					dcBits[nOffset + 1] = (uint8)(grey + dist);
439					dist = (nBits[nOffset + 2] - grey) * 0.4;
440					dcBits[nOffset + 2] = (uint8)(grey + dist);
441					dcBits[nOffset + 3] = 255;
442				}
443			}
444			fBits += fbpr;
445			nBits += nbpr;
446			if (cBits != NULL)
447				cBits += nbpr;
448			if (dBits != NULL)
449				dBits += nbpr;
450			if (dcBits != NULL)
451				dcBits += nbpr;
452		}
453	} else if (format == B_RGB32_BIG) {
454		// nontransparent version
455
456		// iterate over color components
457		for (int32 y = 0; y < lines; y++) {
458			for (int32 x = 0; x < pixels; x++) {
459				int32 nOffset = 4 * x;
460				int32 fOffset = 4 * x;
461				nBits[nOffset + 3] = fBits[fOffset + 3];
462				nBits[nOffset + 2] = fBits[fOffset + 2];
463				nBits[nOffset + 1] = fBits[fOffset + 1];
464				nBits[nOffset + 0] = 255;
465
466				// clicked bits are darker (lame method...)
467				if (cBits != NULL) {
468					cBits[nOffset + 3] = uint8((float)nBits[nOffset + 3] * 0.8);
469					cBits[nOffset + 2] = uint8((float)nBits[nOffset + 2] * 0.8);
470					cBits[nOffset + 1] = uint8((float)nBits[nOffset + 1] * 0.8);
471					cBits[nOffset + 0] = 255;
472				}
473
474				// disabled bits have less contrast (lame method...)
475				if (dBits != NULL) {
476					uint8 grey = 216;
477					float dist = (nBits[nOffset + 3] - grey) * 0.4;
478					dBits[nOffset + 3] = (uint8)(grey + dist);
479					dist = (nBits[nOffset + 2] - grey) * 0.4;
480					dBits[nOffset + 2] = (uint8)(grey + dist);
481					dist = (nBits[nOffset + 1] - grey) * 0.4;
482					dBits[nOffset + 1] = (uint8)(grey + dist);
483					dBits[nOffset + 0] = 255;
484				}
485
486				// disabled bits have less contrast (lame method...)
487				if (dcBits != NULL) {
488					uint8 grey = 188;
489					float dist = (nBits[nOffset + 3] - grey) * 0.4;
490					dcBits[nOffset + 3] = (uint8)(grey + dist);
491					dist = (nBits[nOffset + 2] - grey) * 0.4;
492					dcBits[nOffset + 2] = (uint8)(grey + dist);
493					dist = (nBits[nOffset + 1] - grey) * 0.4;
494					dcBits[nOffset + 1] = (uint8)(grey + dist);
495					dcBits[nOffset + 0] = 255;
496				}
497			}
498			fBits += fbpr;
499			nBits += nbpr;
500			if (cBits != NULL)
501				cBits += nbpr;
502			if (dBits != NULL)
503				dBits += nbpr;
504			if (dcBits != NULL)
505				dcBits += nbpr;
506		}
507	} else if (format == B_RGBA32) {
508		// transparent version
509
510		// iterate over color components
511		for (int32 y = 0; y < lines; y++) {
512			for (int32 x = 0; x < pixels; x++) {
513				int32 nOffset = 4 * x;
514				int32 fOffset = 4 * x;
515				nBits[nOffset + 0] = fBits[fOffset + 0];
516				nBits[nOffset + 1] = fBits[fOffset + 1];
517				nBits[nOffset + 2] = fBits[fOffset + 2];
518				nBits[nOffset + 3] = fBits[fOffset + 3];
519
520				// clicked bits are darker (lame method...)
521				if (cBits != NULL) {
522					cBits[nOffset + 0] = (uint8)(nBits[nOffset + 0] * 0.8);
523					cBits[nOffset + 1] = (uint8)(nBits[nOffset + 1] * 0.8);
524					cBits[nOffset + 2] = (uint8)(nBits[nOffset + 2] * 0.8);
525					cBits[nOffset + 3] = fBits[fOffset + 3];
526				}
527
528				// disabled bits have less opacity
529				if (dBits != NULL) {
530					uint8 grey = ((uint16)nBits[nOffset + 0] * 10
531					    + nBits[nOffset + 1] * 60
532						+ nBits[nOffset + 2] * 30) / 100;
533					float dist = (nBits[nOffset + 0] - grey) * 0.3;
534					dBits[nOffset + 0] = (uint8)(grey + dist);
535					dist = (nBits[nOffset + 1] - grey) * 0.3;
536					dBits[nOffset + 1] = (uint8)(grey + dist);
537					dist = (nBits[nOffset + 2] - grey) * 0.3;
538					dBits[nOffset + 2] = (uint8)(grey + dist);
539					dBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.3);
540				}
541
542				// disabled bits have less contrast (lame method...)
543				if (dcBits != NULL) {
544					dcBits[nOffset + 0] = (uint8)(dBits[nOffset + 0] * 0.8);
545					dcBits[nOffset + 1] = (uint8)(dBits[nOffset + 1] * 0.8);
546					dcBits[nOffset + 2] = (uint8)(dBits[nOffset + 2] * 0.8);
547					dcBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.3);
548				}
549			}
550			fBits += fbpr;
551			nBits += nbpr;
552			if (cBits != NULL)
553				cBits += nbpr;
554			if (dBits != NULL)
555				dBits += nbpr;
556			if (dcBits != NULL)
557				dcBits += nbpr;
558		}
559	} else if (format == B_RGBA32_BIG) {
560		// transparent version
561
562		// iterate over color components
563		for (int32 y = 0; y < lines; y++) {
564			for (int32 x = 0; x < pixels; x++) {
565				int32 nOffset = 4 * x;
566				int32 fOffset = 4 * x;
567				nBits[nOffset + 3] = fBits[fOffset + 3];
568				nBits[nOffset + 2] = fBits[fOffset + 2];
569				nBits[nOffset + 1] = fBits[fOffset + 1];
570				nBits[nOffset + 0] = fBits[fOffset + 0];
571
572				// clicked bits are darker (lame method...)
573				if (cBits != NULL) {
574					cBits[nOffset + 3] = (uint8)(nBits[nOffset + 3] * 0.8);
575					cBits[nOffset + 2] = (uint8)(nBits[nOffset + 2] * 0.8);
576					cBits[nOffset + 1] = (uint8)(nBits[nOffset + 1] * 0.8);
577					cBits[nOffset + 0] = fBits[fOffset + 0];
578				}
579
580				// disabled bits have less opacity
581				if (dBits != NULL) {
582					uint8 grey = ((uint16)nBits[nOffset + 3] * 10
583					    + nBits[nOffset + 2] * 60
584						+ nBits[nOffset + 1] * 30) / 100;
585					float dist = (nBits[nOffset + 3] - grey) * 0.3;
586					dBits[nOffset + 3] = (uint8)(grey + dist);
587					dist = (nBits[nOffset + 2] - grey) * 0.3;
588					dBits[nOffset + 2] = (uint8)(grey + dist);
589					dist = (nBits[nOffset + 1] - grey) * 0.3;
590					dBits[nOffset + 1] = (uint8)(grey + dist);
591					dBits[nOffset + 0] = (uint8)(fBits[fOffset + 0] * 0.3);
592				}
593
594				// disabled bits have less contrast (lame method...)
595				if (dcBits != NULL) {
596					dcBits[nOffset + 3] = (uint8)(dBits[nOffset + 3] * 0.8);
597					dcBits[nOffset + 2] = (uint8)(dBits[nOffset + 2] * 0.8);
598					dcBits[nOffset + 1] = (uint8)(dBits[nOffset + 1] * 0.8);
599					dcBits[nOffset + 0] = (uint8)(fBits[fOffset + 0] * 0.3);
600				}
601			}
602			fBits += fbpr;
603			nBits += nbpr;
604			if (cBits != NULL)
605				cBits += nbpr;
606			if (dBits != NULL)
607				dBits += nbpr;
608			if (dcBits != NULL)
609				dcBits += nbpr;
610		}
611	} else {
612		// unsupported format
613		return B_BAD_VALUE;
614	}
615
616	// make the partially-on bitmaps a copy of the on bitmaps
617	if ((flags & B_CREATE_PARTIALLY_ACTIVE_ICON_BITMAP) != 0) {
618		if (CopyBitmap(clickedBitmap, B_PARTIALLY_ACTIVATE_ICON_BITMAP) == NULL)
619			return B_NO_MEMORY;
620		if ((flags & B_CREATE_DISABLED_ICON_BITMAPS) != 0) {
621			if (CopyBitmap(disabledClickedBitmap,
622					B_PARTIALLY_ACTIVATE_ICON_BITMAP | B_DISABLED_ICON_BITMAP)
623					== NULL) {
624				return B_NO_MEMORY;
625			}
626		}
627	}
628
629	return B_OK;
630}
631
632
633}	// namespace BPrivate
634