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