xref: /haiku/src/libs/icon/IconUtils.cpp (revision 56eb8e78cc702792e3b032e3f5f45da9e5dbea9e)
1 /*
2  * Copyright 2006-2007, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  *		Ingo Weinhold <bonefish@cs.tu-berlin.de>
8  */
9 
10 
11 #include "IconUtils.h"
12 
13 #include <new>
14 #include <fs_attr.h>
15 #include <stdio.h>
16 #include <string.h>
17 
18 #include <Bitmap.h>
19 #include <Node.h>
20 #include <TypeConstants.h>
21 
22 #include "AutoDeleter.h"
23 #include "Icon.h"
24 #include "IconRenderer.h"
25 #include "FlatIconImporter.h"
26 
27 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
28 #	define B_MINI_ICON_TYPE		'MICN'
29 #	define B_LARGE_ICON_TYPE	'ICON'
30 #endif
31 
32 using namespace BPrivate::Icon;
33 using std::nothrow;
34 
35 
36 // GetIcon
37 status_t
38 BIconUtils::GetIcon(BNode* node,
39 					const char* vectorIconAttrName,
40 					const char* smallIconAttrName,
41 					const char* largeIconAttrName,
42 					icon_size size,
43 					BBitmap* result)
44 {
45 	if (!result || result->InitCheck())
46 		return B_BAD_VALUE;
47 
48 	status_t ret = B_ERROR;
49 
50 	switch (result->ColorSpace()) {
51 		case B_RGBA32:
52 		case B_RGB32:
53 			// prefer vector icon
54 			ret = GetVectorIcon(node, vectorIconAttrName, result);
55 			if (ret < B_OK) {
56 				// try to fallback to B_CMAP8 icons
57 				// (converting to B_RGBA32 is handled)
58 
59 				// override size
60 				if (result->Bounds().IntegerWidth() + 1 >= 32)
61 					size = B_LARGE_ICON;
62 				else
63 					size = B_MINI_ICON;
64 
65 				ret = GetCMAP8Icon(node,
66 								   smallIconAttrName,
67 								   largeIconAttrName,
68 								   size, result);
69 			}
70 			break;
71 
72 		case B_CMAP8:
73 			// prefer old B_CMAP8 icons
74 			ret = GetCMAP8Icon(node,
75 							   smallIconAttrName,
76 							   largeIconAttrName,
77 							   size, result);
78 			if (ret < B_OK) {
79 				// try to fallback to vector icon
80 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
81 				BBitmap temp(result->Bounds(),
82 							 B_BITMAP_NO_SERVER_LINK, B_RGBA32);
83 #else
84 				BBitmap temp(result->Bounds(), B_RGBA32);
85 #endif
86 				ret = temp.InitCheck();
87 				if (ret < B_OK)
88 					break;
89 				ret = GetVectorIcon(node, vectorIconAttrName, &temp);
90 				if (ret < B_OK)
91 					break;
92 				uint32 width = temp.Bounds().IntegerWidth() + 1;
93 				uint32 height = temp.Bounds().IntegerHeight() + 1;
94 				uint32 bytesPerRow = temp.BytesPerRow();
95 				ret = ConvertToCMAP8((uint8*)temp.Bits(),
96 									 width, height, bytesPerRow, result);
97 			}
98 			break;
99 		default:
100 			printf("BIconUtils::GetIcon() - unsupported colorspace\n");
101 			break;
102 	}
103 
104 	return ret;
105 }
106 
107 // #pragma mark -
108 
109 // GetVectorIcon
110 status_t
111 BIconUtils::GetVectorIcon(BNode* node, const char* attrName,
112 						  BBitmap* result)
113 {
114 	if (!node || node->InitCheck() < B_OK || !attrName)
115 		return B_BAD_VALUE;
116 
117 #if TIME_VECTOR_ICONS
118 bigtime_t startTime = system_time();
119 #endif
120 
121 	// get the attribute info and check type and size of the attr contents
122 	attr_info attrInfo;
123 	status_t ret = node->GetAttrInfo(attrName, &attrInfo);
124 	if (ret < B_OK)
125 		return ret;
126 
127 	type_code attrType = B_VECTOR_ICON_TYPE;
128 
129 	if (attrInfo.type != attrType)
130 		return B_BAD_TYPE;
131 
132 	// chicken out on unrealisticly large attributes
133 	if (attrInfo.size > 16 * 1024)
134 		return B_BAD_VALUE;
135 
136 	uint8 buffer[attrInfo.size];
137 	ssize_t read = node->ReadAttr(attrName, attrType, 0, buffer, attrInfo.size);
138 	if (read != attrInfo.size)
139 		return B_ERROR;
140 
141 #if TIME_VECTOR_ICONS
142 bigtime_t importTime = system_time();
143 #endif
144 
145 	ret = GetVectorIcon(buffer, attrInfo.size, result);
146 	if (ret < B_OK)
147 		return ret;
148 
149 #if TIME_VECTOR_ICONS
150 bigtime_t finishTime = system_time();
151 printf("read: %lld, import: %lld\n", importTime - startTime, finishTime - importTime);
152 #endif
153 
154 	return B_OK;
155 }
156 
157 // GetVectorIcon
158 status_t
159 BIconUtils::GetVectorIcon(const uint8* buffer, size_t size,
160 						  BBitmap* result)
161 {
162 	if (!result)
163 		return B_BAD_VALUE;
164 
165 	status_t ret = result->InitCheck();
166 	if (ret < B_OK)
167 		return ret;
168 
169 	BBitmap* temp = result;
170 	ObjectDeleter<BBitmap> deleter;
171 
172 	if (result->ColorSpace() != B_RGBA32 && result->ColorSpace() != B_RGB32) {
173 		temp = new (nothrow) BBitmap(result->Bounds(),
174 			B_BITMAP_NO_SERVER_LINK, B_RGBA32);
175 		deleter.SetTo(temp);
176 		if (!temp || temp->InitCheck() != B_OK)
177 			return B_NO_MEMORY;
178 	}
179 
180 	Icon icon;
181 	ret = icon.InitCheck();
182 	if (ret < B_OK)
183 		return ret;
184 
185 	FlatIconImporter importer;
186 	ret = importer.Import(&icon, const_cast<uint8*>(buffer), size);
187 	if (ret < B_OK)
188 		return ret;
189 
190 	IconRenderer renderer(temp);
191 	renderer.SetIcon(&icon);
192 	renderer.SetScale((temp->Bounds().Width() + 1.0) / 64.0);
193 	renderer.Render();
194 
195 	if (temp != result) {
196 		uint8* src = (uint8*)temp->Bits();
197 		uint32 width = temp->Bounds().IntegerWidth() + 1;
198 		uint32 height = temp->Bounds().IntegerHeight() + 1;
199 		uint32 srcBPR = temp->BytesPerRow();
200 		ret = ConvertToCMAP8(src, width, height, srcBPR, result);
201 	}
202 
203 	// TODO: would be nice to get rid of this
204 	// (B_RGBA32_PREMULTIPLIED or better yet, new blending_mode)
205 	// NOTE: probably not necessary only because
206 	// transparent colors are "black" in all existing icons
207 	// lighter transparent colors should be too dark if
208 	// app_server uses correct blending
209 //	renderer.Demultiply();
210 
211 	return ret;
212 }
213 
214 // #pragma mark -
215 
216 status_t
217 BIconUtils::GetCMAP8Icon(BNode* node,
218 						 const char* smallIconAttrName,
219 						 const char* largeIconAttrName,
220 						 icon_size size,
221 						 BBitmap* icon)
222 {
223 	// check parameters and initialization
224 	if (!icon || icon->InitCheck() != B_OK
225 		|| !node || node->InitCheck() != B_OK
226 		|| !smallIconAttrName || !largeIconAttrName)
227 		return B_BAD_VALUE;
228 
229 	status_t ret = B_OK;
230 
231 	// NOTE: this might be changed if other icon
232 	// sizes are supported in B_CMAP8 attributes,
233 	// but this is currently not the case, so we
234 	// relax the requirement to pass an icon
235 	// of just the right size
236 	if (size < B_LARGE_ICON)
237 		size = B_MINI_ICON;
238 	else
239 		size = B_LARGE_ICON;
240 
241 	// set some icon size related variables
242 	const char *attribute = NULL;
243 	BRect bounds;
244 	uint32 attrType = 0;
245 	size_t attrSize = 0;
246 	switch (size) {
247 		case B_MINI_ICON:
248 			attribute = smallIconAttrName;
249 			bounds.Set(0, 0, 15, 15);
250 			attrType = B_MINI_ICON_TYPE;
251 			attrSize = 16 * 16;
252 			break;
253 		case B_LARGE_ICON:
254 			attribute = largeIconAttrName;
255 			bounds.Set(0, 0, 31, 31);
256 			attrType = B_LARGE_ICON_TYPE;
257 			attrSize = 32 * 32;
258 			break;
259 		default:
260 			// can not happen, see above
261 			ret = B_BAD_VALUE;
262 			break;
263 	}
264 
265 	// get the attribute info and check type and size of the attr contents
266 	attr_info attrInfo;
267 	if (ret == B_OK)
268 		ret = node->GetAttrInfo(attribute, &attrInfo);
269 	if (ret == B_OK && attrInfo.type != attrType)
270 		ret = B_BAD_TYPE;
271 	if (ret == B_OK && attrInfo.size != attrSize)
272 		ret = B_BAD_DATA;
273 
274 	// check parameters
275 	// currently, scaling B_CMAP8 icons is not supported
276 	if (icon->ColorSpace() == B_CMAP8 && icon->Bounds() != bounds)
277 		return B_BAD_VALUE;
278 
279 	// read the attribute
280 	if (ret == B_OK) {
281 		bool tempBuffer = (icon->ColorSpace() != B_CMAP8
282 						   || icon->Bounds() != bounds);
283 		uint8* buffer = NULL;
284 		ssize_t read;
285 		if (tempBuffer) {
286 			// other color space or bitmap size than stored in attribute
287 			buffer = new(nothrow) uint8[attrSize];
288 			if (!buffer) {
289 				ret = B_NO_MEMORY;
290 			} else {
291 				read = node->ReadAttr(attribute, attrType, 0, buffer,
292 									  attrSize);
293 			}
294 		} else {
295 			read = node->ReadAttr(attribute, attrType, 0, icon->Bits(),
296 								  attrSize);
297 		}
298 		if (ret == B_OK) {
299 			if (read < 0)
300 				ret = read;
301 			else if (read != (ssize_t)attrSize)
302 				ret = B_ERROR;
303 		}
304 		if (tempBuffer) {
305 			// other color space than stored in attribute
306 			if (ret == B_OK) {
307 				ret = ConvertFromCMAP8(buffer,
308 									   (uint32)size, (uint32)size,
309 									   (uint32)size, icon);
310 			}
311 			delete[] buffer;
312 		}
313 	}
314 	return ret;
315 }
316 
317 // #pragma mark -
318 
319 // ConvertFromCMAP8
320 status_t
321 BIconUtils::ConvertFromCMAP8(BBitmap* source, BBitmap* result)
322 {
323 	if (!source)
324 		return B_BAD_VALUE;
325 
326 	status_t ret = source->InitCheck();
327 	if (ret < B_OK)
328 		return ret;
329 
330 	if (source->ColorSpace() != B_CMAP8)
331 		return B_BAD_VALUE;
332 
333 	uint8* src = (uint8*)source->Bits();
334 	uint32 srcBPR = source->BytesPerRow();
335 	uint32 width = source->Bounds().IntegerWidth() + 1;
336 	uint32 height = source->Bounds().IntegerHeight() + 1;
337 
338 	return ConvertFromCMAP8(src, width, height, srcBPR, result);
339 }
340 
341 // scale_bilinear
342 static void
343 scale_bilinear(uint8* bits, int32 srcWidth, int32 srcHeight,
344 			   int32 dstWidth, int32 dstHeight, uint32 bpr)
345 {
346 	// first pass: scale bottom to top
347 
348 	uint8* dst = bits + (dstHeight - 1) * bpr;
349 		// offset to bottom left pixel in target size
350 	for (int32 x = 0; x < srcWidth; x++) {
351 		uint8* d = dst;
352 		for (int32 y = dstHeight - 1; y >= 0; y--) {
353 			int32 lineF = y * 256 * (srcHeight - 1) / (dstHeight - 1);
354 			int32 lineI = lineF >> 8;
355 			uint8 weight = (uint8)(lineF & 0xff);
356 			uint8* s1 = bits + lineI * bpr + 4 * x;
357 			if (weight == 0) {
358 				d[0] = s1[0];
359 				d[1] = s1[1];
360 				d[2] = s1[2];
361 				d[3] = s1[3];
362 			} else {
363 				uint8* s2 = s1 + bpr;
364 
365 				d[0] = (((s2[0] - s1[0]) * weight) + (s1[0] << 8)) >> 8;
366 				d[1] = (((s2[1] - s1[1]) * weight) + (s1[1] << 8)) >> 8;
367 				d[2] = (((s2[2] - s1[2]) * weight) + (s1[2] << 8)) >> 8;
368 				d[3] = (((s2[3] - s1[3]) * weight) + (s1[3] << 8)) >> 8;
369 			}
370 
371 			d -= bpr;
372 		}
373 		dst += 4;
374 	}
375 
376 	// second pass: scale right to left
377 
378 	dst = bits + (dstWidth - 1) * 4;
379 		// offset to top left pixel in target size
380 	for (int32 y = 0; y < dstWidth; y++) {
381 		uint8* d = dst;
382 		for (int32 x = dstWidth - 1; x >= 0; x--) {
383 			int32 columnF = x * 256 * (srcWidth - 1) / (dstWidth - 1);
384 			int32 columnI = columnF >> 8;
385 			uint8 weight = (uint8)(columnF & 0xff);
386 			uint8* s1 = bits + y * bpr + 4 * columnI;
387 			if (weight == 0) {
388 				d[0] = s1[0];
389 				d[1] = s1[1];
390 				d[2] = s1[2];
391 				d[3] = s1[3];
392 			} else {
393 				uint8* s2 = s1 + 4;
394 
395 				d[0] = (((s2[0] - s1[0]) * weight) + (s1[0] << 8)) >> 8;
396 				d[1] = (((s2[1] - s1[1]) * weight) + (s1[1] << 8)) >> 8;
397 				d[2] = (((s2[2] - s1[2]) * weight) + (s1[2] << 8)) >> 8;
398 				d[3] = (((s2[3] - s1[3]) * weight) + (s1[3] << 8)) >> 8;
399 			}
400 
401 			d -= 4;
402 		}
403 		dst += bpr;
404 	}
405 }
406 
407 
408 // ConvertFromCMAP8
409 status_t
410 BIconUtils::ConvertFromCMAP8(const uint8* src,
411 							 uint32 width, uint32 height, uint32 srcBPR,
412 							 BBitmap* result)
413 {
414 	if (!src || !result || srcBPR == 0)
415 		return B_BAD_VALUE;
416 
417 	status_t ret = result->InitCheck();
418 	if (ret < B_OK)
419 		return ret;
420 
421 	uint32 dstWidth = result->Bounds().IntegerWidth() + 1;
422 	uint32 dstHeight = result->Bounds().IntegerHeight() + 1;
423 
424 	if (dstWidth < width || dstHeight < height) {
425 		// TODO: down scaling
426 		return B_ERROR;
427 	}
428 
429 //#if __HAIKU__
430 //
431 //	return result->ImportBits(src, height * srcBPR, srcBPR, 0, B_CMAP8);
432 //
433 //#else
434 
435 	if (result->ColorSpace() != B_RGBA32 && result->ColorSpace() != B_RGB32) {
436 		// TODO: support other color spaces
437 		return B_BAD_VALUE;
438 	}
439 
440 	uint8* dst = (uint8*)result->Bits();
441 	uint32 dstBPR = result->BytesPerRow();
442 
443 	const rgb_color* colorMap = system_colors()->color_list;
444 
445 	for (uint32 y = 0; y < height; y++) {
446 		uint32* d = (uint32*)dst;
447 		const uint8* s = src;
448 		for (uint32 x = 0; x < width; x++) {
449 			const rgb_color c = colorMap[*s];
450 			uint8 alpha = 255;
451 			if (*s == B_TRANSPARENT_MAGIC_CMAP8)
452 				alpha = 0;
453 			*d = (alpha << 24) | (c.red << 16) | (c.green << 8) | (c.blue);
454 			s++;
455 			d++;
456 		}
457 		src += srcBPR;
458 		dst += dstBPR;
459 	}
460 
461 	if (dstWidth > width || dstHeight > height) {
462 		// up scaling
463 		scale_bilinear((uint8*)result->Bits(), width, height,
464 					   dstWidth, dstHeight, dstBPR);
465 	}
466 
467 	return B_OK;
468 
469 //#endif // __HAIKU__
470 }
471 
472 // ConvertToCMAP8
473 status_t
474 BIconUtils::ConvertToCMAP8(const uint8* src,
475 						   uint32 width, uint32 height, uint32 srcBPR,
476 						   BBitmap* result)
477 {
478 	if (!src || !result || srcBPR == 0)
479 		return B_BAD_VALUE;
480 
481 	status_t ret = result->InitCheck();
482 	if (ret < B_OK)
483 		return ret;
484 
485 	if (result->ColorSpace() != B_CMAP8)
486 		return B_BAD_VALUE;
487 
488 	uint32 dstWidth = result->Bounds().IntegerWidth() + 1;
489 	uint32 dstHeight = result->Bounds().IntegerHeight() + 1;
490 
491 	if (dstWidth < width || dstHeight < height) {
492 		// TODO: down scaling
493 		return B_ERROR;
494 	} else if (dstWidth > width || dstHeight > height) {
495 		// TODO: up scaling
496 		// (currently copies bitmap into result at left-top)
497 memset(result->Bits(), 255, result->BitsLength());
498 	}
499 
500 //#if __HAIKU__
501 //
502 //	return result->ImportBits(src, height * srcBPR, srcBPR, 0, B_RGBA32);
503 //
504 //#else
505 
506 	uint8* dst = (uint8*)result->Bits();
507 	uint32 dstBPR = result->BytesPerRow();
508 
509 	const color_map* colorMap = system_colors();
510 	if (!colorMap)
511 		return B_NO_INIT;
512 	uint16 index;
513 
514 	for (uint32 y = 0; y < height; y++) {
515 		uint8* d = dst;
516 		const uint8* s = src;
517 		for (uint32 x = 0; x < width; x++) {
518 			if (s[3] < 128) {
519 				*d = B_TRANSPARENT_MAGIC_CMAP8;
520 			} else {
521 				index = ((s[2] & 0xf8) << 7) | ((s[1] & 0xf8) << 2)
522 						| (s[0] >> 3);
523 				*d = colorMap->index_map[index];
524 			}
525 			s += 4;
526 			d += 1;
527 		}
528 		src += srcBPR;
529 		dst += dstBPR;
530 	}
531 
532 	return B_OK;
533 
534 //#endif // __HAIKU__
535 }
536 
537 // #pragma mark - forbidden
538 
539 BIconUtils::BIconUtils() {}
540 BIconUtils::~BIconUtils() {}
541 BIconUtils::BIconUtils(const BIconUtils&) {}
542 BIconUtils& BIconUtils::operator=(const BIconUtils&) { return *this; }
543 
544