root/src/add-ons/translators/icns/ICNSLoader.cpp
/*
 * Copyright 2012, Gerasim Troeglazov, 3dEyes@gmail.com. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include "ICNSLoader.h"
#include "BaseTranslator.h"


static int compareTypes(const void *a, const void *b)
{
        icns_type_t **typeItemA = (icns_type_t**)a;
        icns_type_t **typeItemB = (icns_type_t**)b;

        icns_icon_info_t imageInfoA = icns_get_image_info_for_type(**typeItemA);
        icns_icon_info_t imageInfoB = icns_get_image_info_for_type(**typeItemB);

        return imageInfoB.iconWidth - imageInfoA.iconWidth;
}


icns_type_t
ICNSFormat(float width, float height, color_space colors)
{
        int imageWidth = (int)ceil(width);
        int imageHeight = (int)ceil(height);

        if (imageWidth != imageHeight)
                return ICNS_NULL_TYPE;

        //other colors depth not supported now
        if (colors != B_RGB32 && colors != B_RGBA32)
                return ICNS_NULL_TYPE;

        switch (imageWidth) {
                case 16:
                        return ICNS_16x16_32BIT_DATA;
                case 32:
                        return ICNS_32x32_32BIT_DATA;
                case 48:
                        return ICNS_48x48_32BIT_DATA;
                case 128:
                        return ICNS_128X128_32BIT_DATA;
                case 256:
                        return ICNS_256x256_32BIT_ARGB_DATA;
                case 512:
                        return ICNS_512x512_32BIT_ARGB_DATA;
                case 1024:
                        return ICNS_1024x1024_32BIT_ARGB_DATA;
        }
        return ICNS_NULL_TYPE;
}


ICNSLoader::ICNSLoader(BPositionIO *stream)
{
        fLoaded = false;
        fIconsCount = 0;

        stream->Seek(0, SEEK_END);
        fStreamSize = stream->Position();
        stream->Seek(0, SEEK_SET);

        if (fStreamSize <= 0)
                return;

        uint8* icnsDataBuffer = new uint8[fStreamSize];
        size_t readedBytes = stream->Read(icnsDataBuffer,fStreamSize);

        fIconFamily = NULL;
        int status = icns_import_family_data(readedBytes, icnsDataBuffer,
                &fIconFamily);

        if (status != 0) {
                delete[] icnsDataBuffer;
                return;
        }

        icns_byte_t *dataPtr = (icns_byte_t*)fIconFamily;
        off_t dataOffset = sizeof(icns_type_t) + sizeof(icns_size_t);

        while ((dataOffset+8) < fIconFamily->resourceSize) {
                icns_element_t   iconElement;
                icns_size_t      iconDataSize;

                memcpy(&iconElement, (dataPtr + dataOffset), 8);
                iconDataSize = iconElement.elementSize - 8;

                if (IS_SPUPPORTED_TYPE(iconElement.elementType)) {
                                icns_type_t* newTypeItem = new icns_type_t;
                                *newTypeItem = iconElement.elementType;
                                fFormatList.AddItem(newTypeItem);
                                fIconsCount++;
                }
                dataOffset += iconElement.elementSize;
        }

        fFormatList.SortItems(compareTypes);

        delete[] icnsDataBuffer;

        fLoaded = true;
}


ICNSLoader::~ICNSLoader()
{
        if (fIconFamily != NULL)
                free(fIconFamily);

        icns_type_t* item;
        for (int32 i = 0; (item = (icns_type_t*)fFormatList.ItemAt(i)) != NULL; i++)
                delete item;
        fFormatList.MakeEmpty();
}


bool
ICNSLoader::IsLoaded(void)
{
        return fLoaded;
}


int
ICNSLoader::IconsCount(void)
{
        return fIconsCount;
}


int
ICNSLoader::GetIcon(BPositionIO *target, int index)
{
        if (index < 1 || index > fIconsCount || !fLoaded)
                return B_NO_TRANSLATOR;

        icns_image_t iconImage;
        memset(&iconImage, 0, sizeof(icns_image_t));

        icns_type_t typeItem = *((icns_type_t*)fFormatList.ItemAt(index - 1));
        int status = icns_get_image32_with_mask_from_family(fIconFamily,
                typeItem, &iconImage);

        if (status != 0)
                return B_NO_TRANSLATOR;

        TranslatorBitmap bitsHeader;
        bitsHeader.magic = B_TRANSLATOR_BITMAP;
        bitsHeader.bounds.left = 0;
        bitsHeader.bounds.top = 0;
        bitsHeader.bounds.right = iconImage.imageWidth - 1;
        bitsHeader.bounds.bottom = iconImage.imageHeight - 1;
        bitsHeader.rowBytes = sizeof(uint32) * iconImage.imageWidth;
        bitsHeader.colors = B_RGBA32;
        bitsHeader.dataSize = bitsHeader.rowBytes * iconImage.imageHeight;
        if (swap_data(B_UINT32_TYPE, &bitsHeader,
                sizeof(TranslatorBitmap), B_SWAP_HOST_TO_BENDIAN) != B_OK) {
                icns_free_image(&iconImage);
                return B_NO_TRANSLATOR;
        }
        target->Write(&bitsHeader, sizeof(TranslatorBitmap));

        uint8 *rowBuff = new uint8[iconImage.imageWidth * sizeof(uint32)];
        for (uint32 i = 0; i < iconImage.imageHeight; i++) {
                uint8 *rowData = iconImage.imageData
                        + (i * iconImage.imageWidth * sizeof(uint32));
                uint8 *rowBuffPtr = rowBuff;
                for (uint32 j=0; j < iconImage.imageWidth; j++) {
                        rowBuffPtr[0] = rowData[2];
                        rowBuffPtr[1] = rowData[1];
                        rowBuffPtr[2] = rowData[0];
                        rowBuffPtr[3] = rowData[3];
                        rowBuffPtr += sizeof(uint32);
                        rowData += sizeof(uint32);
                }
                target->Write(rowBuff, iconImage.imageWidth * sizeof(uint32));
        }
        delete[] rowBuff;
        icns_free_image(&iconImage);

        return B_OK;
}


ICNSSaver::ICNSSaver(BPositionIO *stream, uint32 rowBytes, icns_type_t type)
{
        fCreated = false;

        icns_icon_info_t imageTypeInfo = icns_get_image_info_for_type(type);
        int iconWidth = imageTypeInfo.iconWidth;
        int iconHeight = imageTypeInfo.iconHeight;
        int bpp = 32;

        uint8 *bits = new uint8[iconWidth * iconHeight * sizeof(uint32)];

        uint8 *rowPtr = bits;
        for (int i = 0; i < iconHeight; i++) {
                stream->Read(rowPtr, rowBytes);
                uint8 *bytePtr = rowPtr;
                for (int j=0; j < iconWidth; j++) {
                        uint8 temp = bytePtr[0];
                        bytePtr[0] = bytePtr[2];
                        bytePtr[2] = temp;
                        bytePtr += sizeof(uint32);
                }
                rowPtr += iconWidth * sizeof(uint32);
        }

        icns_create_family(&fIconFamily);

        icns_image_t icnsImage;
        icnsImage.imageWidth = iconWidth;
        icnsImage.imageHeight = iconHeight;
        icnsImage.imageChannels = 4;
        icnsImage.imagePixelDepth = 8;
        icnsImage.imageDataSize = iconWidth * iconHeight * 4;
        icnsImage.imageData = bits;

        icns_icon_info_t iconInfo;
        iconInfo.isImage = 1;
        iconInfo.iconWidth = icnsImage.imageWidth;
        iconInfo.iconHeight = icnsImage.imageHeight;
        iconInfo.iconBitDepth = bpp;
        iconInfo.iconChannels = (bpp == 32 ? 4 : 1);
        iconInfo.iconPixelDepth = bpp / iconInfo.iconChannels;

        icns_type_t iconType = icns_get_type_from_image_info(iconInfo);

        if (iconType == ICNS_NULL_TYPE) {
                delete[] bits;
                free(fIconFamily);
                fIconFamily = NULL;
                return;
        }

        icns_element_t *iconElement = NULL;
        int icnsErr = icns_new_element_from_image(&icnsImage, iconType,
                &iconElement);

        if (iconElement != NULL) {
                if (icnsErr == ICNS_STATUS_OK) {
                        icns_set_element_in_family(&fIconFamily, iconElement);
                        fCreated = true;
                }
                free(iconElement);
        }

        if (iconType != ICNS_1024x1024_32BIT_ARGB_DATA
                && iconType != ICNS_512x512_32BIT_ARGB_DATA
                && iconType != ICNS_256x256_32BIT_ARGB_DATA) {
                icns_type_t maskType =
                        icns_get_mask_type_for_icon_type(iconType);

                icns_image_t icnsMask;
                icns_init_image_for_type(maskType, &icnsMask);

                uint32 iconDataOffset = 0;
                uint32 maskDataOffset = 0;

                while (iconDataOffset < icnsImage.imageDataSize
                        && maskDataOffset < icnsMask.imageDataSize) {
                        icnsMask.imageData[maskDataOffset] =
                                icnsImage.imageData[iconDataOffset + 3];
                        iconDataOffset += 4;
                        maskDataOffset += 1;
                }

                icns_element_t *maskElement = NULL;
                icnsErr = icns_new_element_from_mask(&icnsMask, maskType,
                        &maskElement);

                if (maskElement != NULL) {
                        if (icnsErr == ICNS_STATUS_OK) {
                                icns_set_element_in_family(&fIconFamily,
                                        maskElement);
                        } else
                                fCreated = false;
                        free(maskElement);
                }
                icns_free_image(&icnsMask);
        }

        if (!fCreated) {
                free(fIconFamily);
                fIconFamily = NULL;
        }

        delete[] bits;
}


ICNSSaver::~ICNSSaver()
{
        if (fIconFamily != NULL)
                free(fIconFamily);
}


int
ICNSSaver::SaveData(BPositionIO *target)
{
        icns_size_t dataSize;
        icns_byte_t *dataPtrOut;
        icns_export_family_data(fIconFamily, &dataSize, &dataPtrOut);
        if (dataSize != 0 && dataPtrOut != NULL) {
                if (target->Write(dataPtrOut, dataSize) == dataSize)
                        return B_OK;
        }
        return B_ERROR;
}


bool
ICNSSaver::IsCreated(void)
{
        return fCreated;
}