2 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 // ToDo: This definitely needs to be worked over for endian issues
9 #include "ICOTranslator.h"
11 #include <ByteOrder.h>
20 # define TRACE(x) printf x
29 static const rgba32_color kMagicTransparentColor
= *(rgba32_color
*)&B_TRANSPARENT_MAGIC_RGBA32
;
34 TempAllocator() : fMemory(NULL
) {}
35 ~TempAllocator() { free(fMemory
); }
37 void *Allocate(size_t size
) { return fMemory
= malloc(size
); }
45 ico_header::IsValid() const
48 && (type
== kTypeIcon
|| type
== kTypeCursor
)
54 ico_header::SwapToHost()
56 swap_data(B_UINT16_TYPE
, this, sizeof(ico_header
), B_SWAP_LENDIAN_TO_HOST
);
61 ico_header::SwapFromHost()
63 swap_data(B_UINT16_TYPE
, this, sizeof(ico_header
), B_SWAP_HOST_TO_LENDIAN
);
71 ico_dir_entry::SwapToHost()
73 swap_data(B_UINT16_TYPE
, &planes
, sizeof(uint16
) * 2, B_SWAP_LENDIAN_TO_HOST
);
74 swap_data(B_UINT32_TYPE
, &size
, sizeof(uint32
) * 2, B_SWAP_LENDIAN_TO_HOST
);
79 ico_dir_entry::SwapFromHost()
81 swap_data(B_UINT16_TYPE
, &planes
, sizeof(uint16
) * 2, B_SWAP_HOST_TO_LENDIAN
);
82 swap_data(B_UINT32_TYPE
, &size
, sizeof(uint32
) * 2, B_SWAP_HOST_TO_LENDIAN
);
90 ico_bitmap_header::IsValid() const
92 return size
== sizeof(ico_bitmap_header
) && compression
== 0
93 && (bits_per_pixel
== 1 || bits_per_pixel
== 4 || bits_per_pixel
== 8
94 || bits_per_pixel
== 16 || bits_per_pixel
== 24 || bits_per_pixel
== 32);
99 ico_bitmap_header::SwapToHost()
101 swap_data(B_UINT32_TYPE
, &size
, sizeof(uint32
) * 3, B_SWAP_LENDIAN_TO_HOST
);
102 swap_data(B_UINT16_TYPE
, &planes
, sizeof(uint16
) * 2, B_SWAP_LENDIAN_TO_HOST
);
103 swap_data(B_UINT32_TYPE
, &compression
, sizeof(uint32
) * 6, B_SWAP_LENDIAN_TO_HOST
);
108 ico_bitmap_header::SwapFromHost()
110 swap_data(B_UINT32_TYPE
, &size
, sizeof(uint32
) * 3, B_SWAP_HOST_TO_LENDIAN
);
111 swap_data(B_UINT16_TYPE
, &planes
, sizeof(uint16
) * 2, B_SWAP_HOST_TO_LENDIAN
);
112 swap_data(B_UINT32_TYPE
, &compression
, sizeof(uint32
) * 6, B_SWAP_HOST_TO_LENDIAN
);
119 static inline uint8
*
120 get_data_row(uint8
*data
, int32 dataSize
, int32 rowBytes
, int32 row
)
122 return data
+ dataSize
- (row
+ 1) * rowBytes
;
127 get_bytes_per_row(int32 width
, int32 bitsPerPixel
)
129 return (((bitsPerPixel
* width
+ 7) / 8) + 3) & ~3;
134 set_1_bit_per_pixel(uint8
*line
, int32 x
, int32 value
)
136 int32 mask
= 1 << (7 - (x
& 7));
140 line
[x
/ 8] &= ~mask
;
145 set_4_bits_per_pixel(uint8
*line
, int32 x
, int32 value
)
147 int32 shift
= x
& 1 ? 0 : 4;
148 int32 mask
= ~0L & (0xf0 >> shift
);
151 line
[x
/ 2] |= value
<< shift
;
156 get_1_bit_per_pixel(uint8
*line
, int32 x
)
158 return (line
[x
/ 8] >> (7 - (x
& 7))) & 1;
163 get_4_bits_per_pixel(uint8
*line
, int32 x
)
165 return (line
[x
/ 2] >> (4 * ((x
+ 1) & 1))) & 0xf;
170 get_alpha_value(color_space space
, uint32 value
)
172 // ToDo: support more color spaces
173 if (space
== B_RGBA32
)
181 rgba32_color_to_16_bit_color(rgba32_color
&color
)
183 return ((color
.blue
>> 3) << 11) | ((color
.green
>> 2) << 5) | (color
.red
>> 3);
188 find_rgba32_color(rgba32_color
*palette
, int32 numColors
, rgba32_color
&color
)
190 // ToDo: sorting and binary search?
191 for (int32 i
= 0; i
< numColors
; i
++) {
192 if (palette
[i
] == color
)
200 static inline rgba32_color
201 get_rgba32_color_from_bits(TranslatorBitmap
&bitsHeader
, uint8
*data
, int32 x
, int32 y
)
203 data
+= bitsHeader
.rowBytes
* y
;
205 switch (bitsHeader
.colors
) {
207 return *(rgba32_color
*)(data
+ 4 * x
);
210 // stupid applications like ArtPaint use the alpha channel in B_RGB32 images...
211 rgba32_color color
= *(rgba32_color
*)(data
+ 4 * x
);
212 if (color
.alpha
>= 128)
217 // ToDo: support some more color spaces...
223 fill_palette(TranslatorBitmap
&bitsHeader
, uint8
*data
, rgba32_color
*palette
)
227 for (int32 y
= 0; y
< bitsHeader
.bounds
.IntegerHeight() + 1; y
++) {
228 for (int32 x
= 0; x
< bitsHeader
.bounds
.IntegerWidth() + 1; x
++) {
229 rgba32_color color
= get_rgba32_color_from_bits(bitsHeader
, data
, x
, y
);
231 int32 index
= find_rgba32_color(palette
, numColors
, color
);
233 // add this color if there is space left
234 if (numColors
== 256)
238 // the alpha channel is actually unused
239 palette
[numColors
++] = color
;
248 /** This function is used to determine, if a true alpha channel has to
249 * be used in order to preserve all information.
253 has_true_alpha_channel(color_space space
, uint8
*data
,
254 int32 width
, int32 height
, int32 bytesPerRow
)
256 for (int32 y
= 0; y
< height
; y
++) {
257 for (int32 x
= 0; x
< width
; x
++) {
258 uint8 value
= get_alpha_value(space
, ((uint32
*)data
)[x
]);
259 if (value
!= 0 && value
!= 255)
271 convert_data_to_bits(ico_dir_entry
&entry
, ico_bitmap_header
&header
,
272 const rgba32_color
*palette
, BPositionIO
&source
,
275 uint16 bitsPerPixel
= header
.bits_per_pixel
;
277 // round row bytes to next 4 byte boundary
278 int32 xorRowBytes
= get_bytes_per_row(entry
.width
, header
.bits_per_pixel
);
279 int32 andRowBytes
= 0;
280 if (bitsPerPixel
!= 32)
281 andRowBytes
= get_bytes_per_row(entry
.width
, 1);
282 int32 outRowBytes
= entry
.width
* 4;
285 TempAllocator xorAllocator
, andAllocator
, rowAllocator
;
287 int32 xorDataSize
= xorRowBytes
* entry
.height
;
288 uint8
*xorData
= (uint8
*)xorAllocator
.Allocate(xorDataSize
);
292 int32 andDataSize
= andRowBytes
* entry
.height
;
293 uint8
*andData
= NULL
;
294 if (bitsPerPixel
!= 32) {
295 andData
= (uint8
*)andAllocator
.Allocate(andDataSize
);
300 rgba32_color
*outRowData
= (rgba32_color
*)rowAllocator
.Allocate(outRowBytes
);
301 if (outRowData
== NULL
)
304 ssize_t bytesRead
= source
.Read(xorData
, xorDataSize
);
305 if (bytesRead
!= xorDataSize
)
308 if (bitsPerPixel
!= 32) {
309 bytesRead
= source
.Read(andData
, andDataSize
);
310 if (bytesRead
!= andDataSize
) {
311 // reading the alpha channel failed, so we're ignoring it
312 // (but we're still able to show the image data)
317 for (uint32 row
= 0; row
< entry
.height
; row
++) {
318 for (uint32 x
= 0; x
< entry
.width
; x
++) {
319 uint8
*line
= get_data_row(xorData
, xorDataSize
, xorRowBytes
, row
);
321 if (palette
!= NULL
) {
324 switch (bitsPerPixel
) {
326 index
= get_1_bit_per_pixel(line
, x
);
329 index
= get_4_bits_per_pixel(line
, x
);
337 outRowData
[x
] = palette
[index
];
339 switch (bitsPerPixel
) {
342 uint16 color
= ((uint16
*)line
)[x
];
343 outRowData
[x
].blue
= (color
>> 11) << 3;
344 outRowData
[x
].green
= ((color
>> 5) & 0x3f) << 3;
345 outRowData
[x
].red
= (color
& 0x1f) << 3;
350 outRowData
[x
].blue
= line
[x
* 3 + 0];
351 outRowData
[x
].green
= line
[x
* 3 + 1];
352 outRowData
[x
].red
= line
[x
* 3 + 2];
356 outRowData
[x
] = ((rgba32_color
*)line
)[x
];
361 if (bitsPerPixel
!= 32) {
364 && get_1_bit_per_pixel(get_data_row(andData
, andDataSize
, andRowBytes
, row
), x
))
365 outRowData
[x
] = kMagicTransparentColor
;
367 outRowData
[x
].alpha
= 255;
368 } else if (outRowData
[x
].alpha
== 0)
369 outRowData
[x
] = kMagicTransparentColor
;
372 ssize_t bytesWritten
= target
.Write(outRowData
, outRowBytes
);
373 if (bytesWritten
< B_OK
)
375 if (bytesWritten
!= outRowBytes
)
384 convert_bits_to_data(TranslatorBitmap
&bitsHeader
, uint8
*bitsData
, ico_dir_entry
&entry
,
385 ico_bitmap_header
&header
, rgba32_color
*palette
, BPositionIO
&target
)
387 int32 bitsPerPixel
= header
.bits_per_pixel
;
389 // round row bytes to next 4 byte boundary
390 int32 xorRowBytes
= get_bytes_per_row(entry
.width
, bitsPerPixel
);
391 int32 andRowBytes
= get_bytes_per_row(entry
.width
, 1);
393 TempAllocator xorAllocator
, andAllocator
;
395 uint8
*xorRowData
= (uint8
*)xorAllocator
.Allocate(xorRowBytes
);
396 if (xorRowData
== NULL
)
399 uint8
*andRowData
= (uint8
*)andAllocator
.Allocate(andRowBytes
);
400 if (andRowData
== NULL
)
403 int32 numColors
= 1 << bitsPerPixel
;
405 // write XOR data (the actual image data)
406 // (ICO data is upside down, so we're starting at the last line)
408 for (uint32 row
= entry
.height
; row
-- > 0;) {
409 for (uint32 x
= 0; x
< entry
.width
; x
++) {
410 rgba32_color color
= get_rgba32_color_from_bits(bitsHeader
, bitsData
, x
, row
);
412 if (palette
!= NULL
) {
413 uint8 index
= find_rgba32_color(palette
, numColors
, color
);
415 switch (bitsPerPixel
) {
417 set_1_bit_per_pixel(xorRowData
, x
, index
);
420 set_4_bits_per_pixel(xorRowData
, x
, index
);
424 xorRowData
[x
] = index
;
428 switch (bitsPerPixel
) {
432 uint16
*data
= (uint16
*)xorRowData
;
433 data
[x
] = rgba32_color_to_16_bit_color(color
);
439 xorRowData
[x
* 3 + 0] = color
.blue
;
440 xorRowData
[x
* 3 + 1] = color
.green
;
441 xorRowData
[x
* 3 + 2] = color
.red
;
447 rgba32_color
*data
= (rgba32_color
*)xorRowData
;
455 ssize_t bytesWritten
= target
.Write(xorRowData
, xorRowBytes
);
456 if (bytesWritten
< B_OK
)
458 if (bytesWritten
!= xorRowBytes
)
462 if (bitsPerPixel
== 32) {
463 // the alpha channel has already been written with the image data
467 // write AND data (the transparency bit)
469 for (uint32 row
= entry
.height
; row
-- > 0;) {
470 for (uint32 x
= 0; x
< entry
.width
; x
++) {
471 rgba32_color color
= get_rgba32_color_from_bits(bitsHeader
, bitsData
, x
, row
);
472 bool transparent
= *(uint32
*)&color
== B_TRANSPARENT_MAGIC_RGBA32
|| color
.alpha
== 0;
474 set_1_bit_per_pixel(andRowData
, x
, transparent
? 1 : 0);
477 ssize_t bytesWritten
= target
.Write(andRowData
, andRowBytes
);
478 if (bytesWritten
< B_OK
)
480 if (bytesWritten
!= andRowBytes
)
492 ICO::is_valid_size(int32 size
)
494 return size
== 16 || size
== 32 || size
== 48;
499 ICO::identify(BMessage
*settings
, BPositionIO
&stream
, uint8
&type
, int32
&bitsPerPixel
)
501 // read in the header
504 if (stream
.Read(&header
, sizeof(ico_header
)) != (ssize_t
)sizeof(ico_header
))
511 if (!header
.IsValid())
518 // Add page count to ioExtension
519 settings
->RemoveName(kDocumentCount
);
520 settings
->AddInt32(kDocumentCount
, header
.entry_count
);
522 // Check if a document index has been specified
523 if (settings
->FindInt32(kDocumentIndex
, &iconIndex
) == B_OK
)
528 if (iconIndex
< 0 || iconIndex
>= header
.entry_count
)
529 return B_NO_TRANSLATOR
;
532 TRACE(("iconIndex = %ld, count = %ld\n", iconIndex
, header
.entry_count
));
534 // read in directory entries
536 for (uint32 i
= 0; i
< header
.entry_count
; i
++) {
538 if (stream
.Read(&entry
, sizeof(ico_dir_entry
)) != (ssize_t
)sizeof(ico_dir_entry
))
542 TRACE(("width: %d, height: %d, planes: %d, color_count: %d, bits_per_pixel: %d, size: %ld, offset: %ld\n",
543 entry
.width
, entry
.height
, entry
.planes
, entry
.color_count
, entry
.bits_per_pixel
,
544 entry
.size
, entry
.offset
));
546 ico_bitmap_header bitmapHeader
;
547 if (stream
.ReadAt(entry
.offset
, &bitmapHeader
, sizeof(ico_bitmap_header
)) != (ssize_t
)sizeof(ico_bitmap_header
))
550 bitmapHeader
.SwapToHost();
551 TRACE(("size: %ld, width: %ld, height: %ld, bits_per_pixel: %d, x/y per meter: %ld:%ld, compression: %ld, image_size: %ld, colors used: %ld, important colors: %ld\n",
552 bitmapHeader
.size
, bitmapHeader
.width
, bitmapHeader
.height
, bitmapHeader
.bits_per_pixel
,
553 bitmapHeader
.x_pixels_per_meter
, bitmapHeader
.y_pixels_per_meter
,
554 bitmapHeader
.compression
, bitmapHeader
.image_size
, bitmapHeader
.colors_used
,
555 bitmapHeader
.important_colors
));
557 if (!bitmapHeader
.IsValid())
560 if ((uint32
)iconIndex
== i
)
561 bitsPerPixel
= bitmapHeader
.bits_per_pixel
;
568 /** Converts an ICO image of any type into a B_RGBA32 B_TRANSLATOR_BITMAP.
572 ICO::convert_ico_to_bits(BMessage
*settings
, BPositionIO
&source
, BPositionIO
&target
)
575 if (source
.Read(&header
, sizeof(ico_header
)) != (ssize_t
)sizeof(ico_header
))
582 if (!header
.IsValid())
588 // Check if a document index has been specified
589 if (settings
->FindInt32(kDocumentIndex
, &iconIndex
) == B_OK
)
594 if (iconIndex
< 0 || iconIndex
>= header
.entry_count
)
598 // read in selected entry
601 if (source
.ReadAt(sizeof(ico_header
) + sizeof(ico_dir_entry
) * iconIndex
,
602 &entry
, sizeof(ico_dir_entry
)) != (ssize_t
)sizeof(ico_dir_entry
))
606 source
.Seek(entry
.offset
, SEEK_SET
);
608 ico_bitmap_header bitmapHeader
;
609 if (source
.Read(&bitmapHeader
, sizeof(ico_bitmap_header
)) != (ssize_t
)sizeof(ico_bitmap_header
))
612 bitmapHeader
.SwapToHost();
614 if (!bitmapHeader
.IsValid())
617 if (bitmapHeader
.compression
!= 0)
621 if (bitmapHeader
.bits_per_pixel
<= 8)
622 numColors
= 1L << bitmapHeader
.bits_per_pixel
;
624 // This is a work-around for a broken ICO file writer that publishes
625 // a wrong image height in the ico_dir_entry structure
626 if (entry
.size
!= 0 && 2 * entry
.width
== entry
.height
&& numColors
!= 0
627 && sizeof(rgba32_color
) * numColors
+ entry
.width
* entry
.height
> entry
.size
)
628 entry
.height
= entry
.width
;
630 TranslatorBitmap bitsHeader
;
631 bitsHeader
.magic
= B_TRANSLATOR_BITMAP
;
632 bitsHeader
.bounds
.left
= 0;
633 bitsHeader
.bounds
.top
= 0;
634 bitsHeader
.bounds
.right
= entry
.width
- 1;
635 bitsHeader
.bounds
.bottom
= entry
.height
- 1;
636 bitsHeader
.bounds
.Set(0, 0, entry
.width
- 1, entry
.height
- 1);
637 bitsHeader
.rowBytes
= entry
.width
* 4;
638 bitsHeader
.colors
= B_RGBA32
;
639 bitsHeader
.dataSize
= bitsHeader
.rowBytes
* entry
.height
;
643 rgba32_color palette
[256];
645 if (source
.Read(palette
, numColors
* 4) != numColors
* 4)
648 // clear alpha channel (it's not used in ICO color information)
649 for (int32 i
= 0; i
< numColors
; i
++)
650 palette
[i
].alpha
= 0;
653 // write out Be's Bitmap header
654 swap_data(B_UINT32_TYPE
, &bitsHeader
, sizeof(TranslatorBitmap
), B_SWAP_HOST_TO_BENDIAN
);
655 target
.Write(&bitsHeader
, sizeof(TranslatorBitmap
));
657 return convert_data_to_bits(entry
, bitmapHeader
, numColors
> 0 ? palette
: NULL
, source
, target
);
662 ICO::convert_bits_to_ico(BMessage
*settings
, BPositionIO
&source
,
663 TranslatorBitmap
&bitsHeader
, BPositionIO
&target
)
665 int32 width
= bitsHeader
.bounds
.IntegerWidth() + 1;
666 int32 height
= bitsHeader
.bounds
.IntegerHeight() + 1;
667 if (!is_valid_size(width
) || !is_valid_size(height
))
671 switch (bitsHeader
.colors
) {
689 fprintf(stderr
, "unsupported color space.\n");
693 TempAllocator dataAllocator
;
694 uint8
*bitsData
= (uint8
*)dataAllocator
.Allocate(bitsHeader
.rowBytes
* height
);
695 if (bitsData
== NULL
)
698 ssize_t bytesRead
= source
.Read(bitsData
, bitsHeader
.rowBytes
* height
);
699 if (bytesRead
< B_OK
)
702 rgba32_color palette
[256];
703 if (bitsPerPixel
> 8) {
704 // it's a non-palette mode - but does it have to be?
705 if (bitsHeader
.colors
!= B_RGBA32
706 || !has_true_alpha_channel(bitsHeader
.colors
, bitsData
,
707 width
, height
, bitsHeader
.rowBytes
)) {
708 memset(palette
, 0, sizeof(palette
));
711 int32 colors
= fill_palette(bitsHeader
, bitsData
, palette
);
713 // we fit into a palette mode
723 int32 numColors
= 1 << bitsPerPixel
;
726 header
.type
= B_HOST_TO_LENDIAN_INT16(1);
727 header
.entry_count
= B_HOST_TO_LENDIAN_INT16(1);
730 ssize_t bytesWritten
= target
.Write(&header
, sizeof(ico_header
));
731 if (bytesWritten
< B_OK
)
736 entry
.height
= height
;
738 entry
.bits_per_pixel
= bitsPerPixel
;
739 entry
.color_count
= 0;
740 if (bitsPerPixel
<= 8)
741 entry
.color_count
= numColors
;
743 // When bits_per_pixel == 32, the data already contains the alpha channel
745 int32 xorRowBytes
= get_bytes_per_row(width
, bitsPerPixel
);
746 int32 andRowBytes
= 0;
747 if (bitsPerPixel
!= 32)
748 andRowBytes
= get_bytes_per_row(width
, 1);
750 entry
.size
= sizeof(ico_bitmap_header
) + width
* (xorRowBytes
+ andRowBytes
);
751 if (bitsPerPixel
<= 8)
752 entry
.size
+= numColors
* sizeof(rgba32_color
);
753 entry
.offset
= sizeof(ico_header
) + sizeof(ico_dir_entry
);
756 ico_bitmap_header bitmapHeader
;
757 memset(&bitmapHeader
, 0, sizeof(ico_bitmap_header
));
758 bitmapHeader
.size
= sizeof(ico_bitmap_header
);
759 bitmapHeader
.width
= width
;
760 bitmapHeader
.height
= height
+ (bitsPerPixel
== 32 ? 0 : height
);
761 bitmapHeader
.bits_per_pixel
= bitsPerPixel
;
762 bitmapHeader
.planes
= 1;
763 bitmapHeader
.image_size
= 0;
764 if (bitsPerPixel
<= 8)
765 bitmapHeader
.colors_used
= numColors
;
767 entry
.SwapFromHost();
768 bitmapHeader
.SwapFromHost();
770 bytesWritten
= target
.Write(&entry
, sizeof(ico_dir_entry
));
771 if (bytesWritten
< B_OK
)
774 bytesWritten
= target
.Write(&bitmapHeader
, sizeof(ico_bitmap_header
));
775 if (bytesWritten
< B_OK
)
778 // we'll need them in convert_bits_to_data()
780 bitmapHeader
.SwapToHost();
782 if (bitsPerPixel
<= 8) {
783 bytesWritten
= target
.Write(palette
, numColors
* sizeof(rgba32_color
));
784 if (bytesWritten
< B_OK
)
788 return convert_bits_to_data(bitsHeader
, bitsData
, entry
, bitmapHeader
,
789 bitsPerPixel
<= 8 ? palette
: NULL
, target
);