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