root/src/add-ons/translators/ico/ICO.cpp
/*
 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

// ToDo: This definitely needs to be worked over for endian issues

#include "ICO.h"
#include "ICOTranslator.h"

#include <ByteOrder.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//#define TRACE_ICO
#ifdef TRACE_ICO
#       define TRACE(x) printf x
#else
#       define TRACE(x) ;
#endif


using namespace ICO;


static const rgba32_color kMagicTransparentColor = *(rgba32_color *)&B_TRANSPARENT_MAGIC_RGBA32;


class TempAllocator {
        public:
                TempAllocator() : fMemory(NULL) {}
                ~TempAllocator() { free(fMemory); }

                void *Allocate(size_t size) { return fMemory = malloc(size); }

        private:
                void    *fMemory;
};


bool 
ico_header::IsValid() const
{
        return reserved == 0
                && (type == kTypeIcon || type == kTypeCursor)
                && entry_count < 32;
}


void
ico_header::SwapToHost()
{
        swap_data(B_UINT16_TYPE, this, sizeof(ico_header), B_SWAP_LENDIAN_TO_HOST);
}


void
ico_header::SwapFromHost()
{
        swap_data(B_UINT16_TYPE, this, sizeof(ico_header), B_SWAP_HOST_TO_LENDIAN);
}


//      #pragma mark -


void
ico_dir_entry::SwapToHost()
{
        swap_data(B_UINT16_TYPE, &planes, sizeof(uint16) * 2, B_SWAP_LENDIAN_TO_HOST);
        swap_data(B_UINT32_TYPE, &size, sizeof(uint32) * 2, B_SWAP_LENDIAN_TO_HOST);
}


void
ico_dir_entry::SwapFromHost()
{
        swap_data(B_UINT16_TYPE, &planes, sizeof(uint16) * 2, B_SWAP_HOST_TO_LENDIAN);
        swap_data(B_UINT32_TYPE, &size, sizeof(uint32) * 2, B_SWAP_HOST_TO_LENDIAN);
}


//      #pragma mark -


bool
ico_bitmap_header::IsValid() const
{
        return size == sizeof(ico_bitmap_header) && compression == 0
                && (bits_per_pixel == 1 || bits_per_pixel == 4 || bits_per_pixel == 8
                        || bits_per_pixel == 16 || bits_per_pixel == 24 || bits_per_pixel == 32);
}


void
ico_bitmap_header::SwapToHost()
{
        swap_data(B_UINT32_TYPE, &size, sizeof(uint32) * 3, B_SWAP_LENDIAN_TO_HOST);
        swap_data(B_UINT16_TYPE, &planes, sizeof(uint16) * 2, B_SWAP_LENDIAN_TO_HOST);
        swap_data(B_UINT32_TYPE, &compression, sizeof(uint32) * 6, B_SWAP_LENDIAN_TO_HOST);
}


void
ico_bitmap_header::SwapFromHost()
{
        swap_data(B_UINT32_TYPE, &size, sizeof(uint32) * 3, B_SWAP_HOST_TO_LENDIAN);
        swap_data(B_UINT16_TYPE, &planes, sizeof(uint16) * 2, B_SWAP_HOST_TO_LENDIAN);
        swap_data(B_UINT32_TYPE, &compression, sizeof(uint32) * 6, B_SWAP_HOST_TO_LENDIAN);
}


//      #pragma mark -


static inline uint8 *
get_data_row(uint8 *data, int32 dataSize, int32 rowBytes, int32 row)
{
        return data + dataSize - (row + 1) * rowBytes;
}


static inline int32
get_bytes_per_row(int32 width, int32 bitsPerPixel)
{
        return (((bitsPerPixel * width + 7) / 8) + 3) & ~3;
}


static inline void
set_1_bit_per_pixel(uint8 *line, int32 x, int32 value)
{
        int32 mask = 1 << (7 - (x & 7));
        if (value)
                line[x / 8] |= mask;
        else
                line[x / 8] &= ~mask;
}


static inline void
set_4_bits_per_pixel(uint8 *line, int32 x, int32 value)
{
        int32 shift = x & 1 ? 0 : 4;
        int32 mask = ~0L & (0xf0 >> shift);

        line[x / 2] &= mask;
        line[x / 2] |= value << shift;
}


static inline int32
get_1_bit_per_pixel(uint8 *line, int32 x)
{
        return (line[x / 8] >> (7 - (x & 7))) & 1;
}


static inline int32
get_4_bits_per_pixel(uint8 *line, int32 x)
{
        return (line[x / 2] >> (4 * ((x + 1) & 1))) & 0xf;
}


static uint8
get_alpha_value(color_space space, uint32 value)
{
        // ToDo: support more color spaces
        if (space == B_RGBA32)
                return value >> 24;

        return 0;
}


static uint16
rgba32_color_to_16_bit_color(rgba32_color &color)
{
        return ((color.blue >> 3) << 11) | ((color.green >> 2) << 5) | (color.red >> 3);
}


static int32
find_rgba32_color(rgba32_color *palette, int32 numColors, rgba32_color &color)
{
        // ToDo: sorting and binary search?
        for (int32 i = 0; i < numColors; i++) {
                if (palette[i] == color)
                        return i;
        }

        return -1;
}


static inline rgba32_color
get_rgba32_color_from_bits(TranslatorBitmap &bitsHeader, uint8 *data, int32 x, int32 y)
{
        data += bitsHeader.rowBytes * y;

        switch (bitsHeader.colors) {
                case B_RGBA32:
                        return *(rgba32_color *)(data + 4 * x);
                case B_RGB32:
                default:
                        // stupid applications like ArtPaint use the alpha channel in B_RGB32 images...
                        rgba32_color color = *(rgba32_color *)(data + 4 * x);
                        if (color.alpha >= 128)
                                color.alpha = 255;
                        else
                                color.alpha = 0;
                        return color;
                // ToDo: support some more color spaces...
        }
}


static int32
fill_palette(TranslatorBitmap &bitsHeader, uint8 *data, rgba32_color *palette)
{
        int32 numColors = 0;

        for (int32 y = 0; y < bitsHeader.bounds.IntegerHeight() + 1; y++) {
                for (int32 x = 0; x < bitsHeader.bounds.IntegerWidth() + 1; x++) {
                        rgba32_color color = get_rgba32_color_from_bits(bitsHeader, data, x, y);

                        int32 index = find_rgba32_color(palette, numColors, color);
                        if (index == -1) {
                                // add this color if there is space left
                                if (numColors == 256)
                                        return -1;

                                color.alpha = 0;
                                        // the alpha channel is actually unused
                                palette[numColors++] = color;
                        }
                }
        }

        return numColors;
}


/**     This function is used to determine, if a true alpha channel has to
 *      be used in order to preserve all information.
 */

static bool
has_true_alpha_channel(color_space space, uint8 *data,
        int32 width, int32 height, int32 bytesPerRow)
{
        for (int32 y = 0; y < height; y++) {
                for (int32 x = 0; x < width; x++) {
                        uint8 value = get_alpha_value(space, ((uint32 *)data)[x]);
                        if (value != 0 && value != 255)
                                return true;
                }

                data += bytesPerRow;
        }

        return false;
}


static status_t
convert_data_to_bits(ico_dir_entry &entry, ico_bitmap_header &header,
        const rgba32_color *palette, BPositionIO &source,
        BPositionIO &target)
{
        uint16 bitsPerPixel = header.bits_per_pixel;

        // round row bytes to next 4 byte boundary
        int32 xorRowBytes = get_bytes_per_row(entry.width, header.bits_per_pixel);
        int32 andRowBytes = 0;
        if (bitsPerPixel != 32)
                andRowBytes = get_bytes_per_row(entry.width, 1);
        int32 outRowBytes = entry.width * 4;

        // allocate buffers
        TempAllocator xorAllocator, andAllocator, rowAllocator;

        int32 xorDataSize = xorRowBytes * entry.height;
        uint8 *xorData = (uint8 *)xorAllocator.Allocate(xorDataSize);
        if (xorData == NULL)
                return B_NO_MEMORY;

        int32 andDataSize = andRowBytes * entry.height;
        uint8 *andData = NULL;
        if (bitsPerPixel != 32) {
                andData = (uint8 *)andAllocator.Allocate(andDataSize);
                if (andData == NULL)
                        return B_NO_MEMORY;
        }

        rgba32_color *outRowData = (rgba32_color *)rowAllocator.Allocate(outRowBytes);
        if (outRowData == NULL)
                return B_NO_MEMORY;

        ssize_t bytesRead = source.Read(xorData, xorDataSize);
        if (bytesRead != xorDataSize)
                return B_BAD_DATA;

        if (bitsPerPixel != 32) {
                bytesRead = source.Read(andData, andDataSize);
                if (bytesRead != andDataSize) {
                        // reading the alpha channel failed, so we're ignoring it
                        // (but we're still able to show the image data)
                        andData = NULL;
                }
        }

        for (uint32 row = 0; row < entry.height; row++) {
                for (uint32 x = 0; x < entry.width; x++) {
                        uint8 *line = get_data_row(xorData, xorDataSize, xorRowBytes, row);

                        if (palette != NULL) {
                                uint8 index;

                                switch (bitsPerPixel) {
                                        case 1:
                                                index = get_1_bit_per_pixel(line, x);
                                                break;
                                        case 4:
                                                index = get_4_bits_per_pixel(line, x);
                                                break;
                                        case 8:
                                        default:
                                                index = line[x];
                                                break;
                                }

                                outRowData[x] = palette[index];
                        } else {
                                switch (bitsPerPixel) {
                                        case 16:
                                        {
                                                uint16 color = ((uint16 *)line)[x];
                                                outRowData[x].blue = (color >> 11) << 3;
                                                outRowData[x].green = ((color >> 5) & 0x3f) << 3;
                                                outRowData[x].red = (color & 0x1f) << 3;
                                                break;
                                        }

                                        case 24:
                                                outRowData[x].blue = line[x * 3 + 0];
                                                outRowData[x].green = line[x * 3 + 1];
                                                outRowData[x].red = line[x * 3 + 2];
                                                break;

                                        case 32:
                                                outRowData[x] = ((rgba32_color *)line)[x];
                                                break;
                                }
                        }

                        if (bitsPerPixel != 32) {
                                // set alpha channel
                                if (andData != NULL
                                        && get_1_bit_per_pixel(get_data_row(andData, andDataSize, andRowBytes, row), x))
                                        outRowData[x] = kMagicTransparentColor;
                                else
                                        outRowData[x].alpha = 255;
                        } else if (outRowData[x].alpha == 0)
                                outRowData[x] = kMagicTransparentColor;
                }

                ssize_t bytesWritten = target.Write(outRowData, outRowBytes);
                if (bytesWritten < B_OK)
                        return bytesWritten;
                if (bytesWritten != outRowBytes)
                        return B_IO_ERROR;
        }

        return B_OK;
}


static status_t
convert_bits_to_data(TranslatorBitmap &bitsHeader, uint8 *bitsData, ico_dir_entry &entry,
        ico_bitmap_header &header, rgba32_color *palette, BPositionIO &target)
{
        int32 bitsPerPixel = header.bits_per_pixel;

        // round row bytes to next 4 byte boundary
        int32 xorRowBytes = get_bytes_per_row(entry.width, bitsPerPixel);
        int32 andRowBytes = get_bytes_per_row(entry.width, 1);

        TempAllocator xorAllocator, andAllocator;

        uint8 *xorRowData = (uint8 *)xorAllocator.Allocate(xorRowBytes);
        if (xorRowData == NULL)
                return B_NO_MEMORY;

        uint8 *andRowData = (uint8 *)andAllocator.Allocate(andRowBytes);
        if (andRowData == NULL)
                return B_NO_MEMORY;

        int32 numColors = 1 << bitsPerPixel;

        // write XOR data (the actual image data)
        // (ICO data is upside down, so we're starting at the last line)

        for (uint32 row = entry.height; row-- > 0;) {
                for (uint32 x = 0; x < entry.width; x++) {
                        rgba32_color color = get_rgba32_color_from_bits(bitsHeader, bitsData, x, row);

                        if (palette != NULL) {
                                uint8 index = find_rgba32_color(palette, numColors, color);

                                switch (bitsPerPixel) {
                                        case 1:
                                                set_1_bit_per_pixel(xorRowData, x, index);
                                                break;
                                        case 4:
                                                set_4_bits_per_pixel(xorRowData, x, index);
                                                break;
                                        case 8:
                                        default:
                                                xorRowData[x] = index;
                                                break;
                                }
                        } else {
                                switch (bitsPerPixel) {
                                        default:
                                        case 16:
                                        {
                                                uint16 *data = (uint16 *)xorRowData;
                                                data[x] = rgba32_color_to_16_bit_color(color); 
                                                break;
                                        }

                                        case 24:
                                        {
                                                xorRowData[x * 3 + 0] = color.blue;
                                                xorRowData[x * 3 + 1] = color.green;
                                                xorRowData[x * 3 + 2] = color.red;
                                                break;
                                        }

                                        case 32:
                                        {
                                                rgba32_color *data = (rgba32_color *)xorRowData;
                                                data[x] = color;
                                                break;
                                        }
                                }
                        }
                }

                ssize_t bytesWritten = target.Write(xorRowData, xorRowBytes);
                if (bytesWritten < B_OK)
                        return bytesWritten;
                if (bytesWritten != xorRowBytes)
                        return B_IO_ERROR;
        }

        if (bitsPerPixel == 32) {
                // the alpha channel has already been written with the image data
                return B_OK;
        }

        // write AND data (the transparency bit)

        for (uint32 row = entry.height; row-- > 0;) {
                for (uint32 x = 0; x < entry.width; x++) {
                        rgba32_color color = get_rgba32_color_from_bits(bitsHeader, bitsData, x, row);
                        bool transparent = *(uint32 *)&color == B_TRANSPARENT_MAGIC_RGBA32 || color.alpha == 0;

                        set_1_bit_per_pixel(andRowData, x, transparent ? 1 : 0);
                }

                ssize_t bytesWritten = target.Write(andRowData, andRowBytes);
                if (bytesWritten < B_OK)
                        return bytesWritten;
                if (bytesWritten != andRowBytes)
                        return B_IO_ERROR;
        }

        return B_OK;
}


//      #pragma mark -


bool
ICO::is_valid_size(int32 size)
{
        return size == 16 || size == 32 || size == 48;
}


status_t
ICO::identify(BMessage *settings, BPositionIO &stream, uint8 &type, int32 &bitsPerPixel)
{
        // read in the header

        ico_header header;
        if (stream.Read(&header, sizeof(ico_header)) != (ssize_t)sizeof(ico_header))
                return B_BAD_VALUE;

        header.SwapToHost();

        // check header

        if (!header.IsValid())
                return B_BAD_VALUE;

        int32 iconIndex = 0;
        type = header.type;

        if (settings) {
                // Add page count to ioExtension
                settings->RemoveName(kDocumentCount);
                settings->AddInt32(kDocumentCount, header.entry_count);

                // Check if a document index has been specified 
                if (settings->FindInt32(kDocumentIndex, &iconIndex) == B_OK)
                        iconIndex--;
                else
                        iconIndex = 0;

                if (iconIndex < 0 || iconIndex >= header.entry_count)
                        return B_NO_TRANSLATOR;
        }

        TRACE(("iconIndex = %ld, count = %ld\n", iconIndex, header.entry_count));

        // read in directory entries

        for (uint32 i = 0; i < header.entry_count; i++) {
                ico_dir_entry entry;
                if (stream.Read(&entry, sizeof(ico_dir_entry)) != (ssize_t)sizeof(ico_dir_entry))
                        return B_BAD_VALUE;

                entry.SwapToHost();
                TRACE(("width: %d, height: %d, planes: %d, color_count: %d, bits_per_pixel: %d, size: %ld, offset: %ld\n",
                        entry.width, entry.height, entry.planes, entry.color_count, entry.bits_per_pixel,
                        entry.size, entry.offset));

                ico_bitmap_header bitmapHeader;
                if (stream.ReadAt(entry.offset, &bitmapHeader, sizeof(ico_bitmap_header)) != (ssize_t)sizeof(ico_bitmap_header))
                        return B_BAD_VALUE;

                bitmapHeader.SwapToHost();
                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",
                        bitmapHeader.size, bitmapHeader.width, bitmapHeader.height, bitmapHeader.bits_per_pixel,
                        bitmapHeader.x_pixels_per_meter, bitmapHeader.y_pixels_per_meter,
                        bitmapHeader.compression, bitmapHeader.image_size, bitmapHeader.colors_used,
                        bitmapHeader.important_colors));

                if (!bitmapHeader.IsValid())
                        return B_BAD_VALUE;

                if ((uint32)iconIndex == i)
                        bitsPerPixel = bitmapHeader.bits_per_pixel;
        }

        return B_OK;
}


/**     Converts an ICO image of any type into a B_RGBA32 B_TRANSLATOR_BITMAP.
 */

status_t
ICO::convert_ico_to_bits(BMessage *settings, BPositionIO &source, BPositionIO &target)
{
        ico_header header;
        if (source.Read(&header, sizeof(ico_header)) != (ssize_t)sizeof(ico_header))
                return B_BAD_VALUE;

        header.SwapToHost();

        // check header

        if (!header.IsValid())
                return B_BAD_VALUE;

        int32 iconIndex = 0;

        if (settings) {
                // Check if a document index has been specified 
                if (settings->FindInt32(kDocumentIndex, &iconIndex) == B_OK)
                        iconIndex--;
                else
                        iconIndex = 0;

                if (iconIndex < 0 || iconIndex >= header.entry_count)
                        return B_BAD_VALUE;
        }

        // read in selected entry

        ico_dir_entry entry;
        if (source.ReadAt(sizeof(ico_header) + sizeof(ico_dir_entry) * iconIndex,
                        &entry, sizeof(ico_dir_entry)) != (ssize_t)sizeof(ico_dir_entry))
                return B_BAD_VALUE;

        entry.SwapToHost();
        source.Seek(entry.offset, SEEK_SET);

        ico_bitmap_header bitmapHeader;
        if (source.Read(&bitmapHeader, sizeof(ico_bitmap_header)) != (ssize_t)sizeof(ico_bitmap_header))
                return B_BAD_VALUE;

        bitmapHeader.SwapToHost();

        if (!bitmapHeader.IsValid())
                return B_BAD_VALUE;

        if (bitmapHeader.compression != 0)
                return EOPNOTSUPP;

        int32 numColors = 0;
        if (bitmapHeader.bits_per_pixel <= 8)
                numColors = 1L << bitmapHeader.bits_per_pixel;

        // This is a work-around for a broken ICO file writer that publishes
        // a wrong image height in the ico_dir_entry structure
        if (entry.size != 0 && 2 * entry.width == entry.height && numColors != 0
                && sizeof(rgba32_color) * numColors + entry.width * entry.height > entry.size)
                entry.height = entry.width;

        TranslatorBitmap bitsHeader;
        bitsHeader.magic = B_TRANSLATOR_BITMAP;
        bitsHeader.bounds.left = 0;
        bitsHeader.bounds.top = 0;
        bitsHeader.bounds.right = entry.width - 1;
        bitsHeader.bounds.bottom = entry.height - 1;
        bitsHeader.bounds.Set(0, 0, entry.width - 1, entry.height - 1);
        bitsHeader.rowBytes = entry.width * 4;
        bitsHeader.colors = B_RGBA32;
        bitsHeader.dataSize = bitsHeader.rowBytes * entry.height;

        // read in palette

        rgba32_color palette[256];
        if (numColors > 0) {
                if (source.Read(palette, numColors * 4) != numColors * 4)
                        return B_BAD_VALUE;

                // clear alpha channel (it's not used in ICO color information)
                for (int32 i = 0; i < numColors; i++)
                        palette[i].alpha = 0;
        }

        // write out Be's Bitmap header
        swap_data(B_UINT32_TYPE, &bitsHeader, sizeof(TranslatorBitmap), B_SWAP_HOST_TO_BENDIAN);
        target.Write(&bitsHeader, sizeof(TranslatorBitmap));

        return convert_data_to_bits(entry, bitmapHeader, numColors > 0 ? palette : NULL, source, target);
}


status_t
ICO::convert_bits_to_ico(BMessage *settings, BPositionIO &source,
        TranslatorBitmap &bitsHeader, BPositionIO &target)
{
        int32 width = bitsHeader.bounds.IntegerWidth() + 1;
        int32 height = bitsHeader.bounds.IntegerHeight() + 1;
        if (!is_valid_size(width) || !is_valid_size(height))
                return B_BAD_VALUE;

        int32 bitsPerPixel;
        switch (bitsHeader.colors) {
                case B_RGBA32:
                        bitsPerPixel = 32;
                        break;
                case B_RGB32:
                        bitsPerPixel = 24;
                        break;
                case B_RGB16:
                        bitsPerPixel = 16;
                        break;
                case B_CMAP8:
                case B_GRAY8:
                        bitsPerPixel = 8;
                        break;
                case B_GRAY1:
                        bitsPerPixel = 1;
                        break;
                default:
                        fprintf(stderr, "unsupported color space.\n");
                        return B_BAD_VALUE;
        }

        TempAllocator dataAllocator;
        uint8 *bitsData = (uint8 *)dataAllocator.Allocate(bitsHeader.rowBytes * height);
        if (bitsData == NULL)
                return B_NO_MEMORY;
                
        ssize_t bytesRead = source.Read(bitsData, bitsHeader.rowBytes * height);
        if (bytesRead < B_OK)
                return bytesRead;

        rgba32_color palette[256];
        if (bitsPerPixel > 8) {
                // it's a non-palette mode - but does it have to be?
                if (bitsHeader.colors != B_RGBA32
                        || !has_true_alpha_channel(bitsHeader.colors, bitsData,
                                        width, height, bitsHeader.rowBytes)) {
                        memset(palette, 0, sizeof(palette));

                        // count colors
                        int32 colors = fill_palette(bitsHeader, bitsData, palette);
                        if (colors != -1) {
                                // we fit into a palette mode
                                if (colors > 16)
                                        bitsPerPixel = 8;
                                else if (colors > 2)
                                        bitsPerPixel = 4;
                                else
                                        bitsPerPixel = 1;
                        }
                }
        }
        int32 numColors = 1 << bitsPerPixel;

        ico_header header;
        header.type = B_HOST_TO_LENDIAN_INT16(1);
        header.entry_count = B_HOST_TO_LENDIAN_INT16(1);
        header.reserved = 0;
        
        ssize_t bytesWritten = target.Write(&header, sizeof(ico_header));
        if (bytesWritten < B_OK)
                return bytesWritten;

        ico_dir_entry entry;
        entry.width = width;
        entry.height = height;
        entry.planes = 1;
        entry.bits_per_pixel = bitsPerPixel;
        entry.color_count = 0;
        if (bitsPerPixel <= 8)
                entry.color_count = numColors;

        // When bits_per_pixel == 32, the data already contains the alpha channel

        int32 xorRowBytes = get_bytes_per_row(width, bitsPerPixel);
        int32 andRowBytes = 0;
        if (bitsPerPixel != 32)
                andRowBytes = get_bytes_per_row(width, 1);

        entry.size = sizeof(ico_bitmap_header) + width * (xorRowBytes + andRowBytes);
        if (bitsPerPixel <= 8)
                entry.size += numColors * sizeof(rgba32_color);
        entry.offset = sizeof(ico_header) + sizeof(ico_dir_entry);
        entry.reserved = 0;

        ico_bitmap_header bitmapHeader;
        memset(&bitmapHeader, 0, sizeof(ico_bitmap_header));
        bitmapHeader.size = sizeof(ico_bitmap_header);
        bitmapHeader.width = width;
        bitmapHeader.height = height + (bitsPerPixel == 32 ? 0 : height);
        bitmapHeader.bits_per_pixel = bitsPerPixel;
        bitmapHeader.planes = 1;
        bitmapHeader.image_size = 0;
        if (bitsPerPixel <= 8)
                bitmapHeader.colors_used = numColors;

        entry.SwapFromHost();
        bitmapHeader.SwapFromHost();

        bytesWritten = target.Write(&entry, sizeof(ico_dir_entry));
        if (bytesWritten < B_OK)
                return bytesWritten;

        bytesWritten = target.Write(&bitmapHeader, sizeof(ico_bitmap_header));
        if (bytesWritten < B_OK)
                return bytesWritten;

        // we'll need them in convert_bits_to_data()
        entry.SwapToHost();
        bitmapHeader.SwapToHost();

        if (bitsPerPixel <= 8) {
                bytesWritten = target.Write(palette, numColors * sizeof(rgba32_color));
                if (bytesWritten < B_OK)
                        return bytesWritten;
        }

        return convert_bits_to_data(bitsHeader, bitsData, entry, bitmapHeader,
                bitsPerPixel <= 8 ? palette : NULL, target);
}