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