libs-gui/Source/NSBitmapImageRep+ICNS.m
Fred Kiefer bf8446462b * Source/NSBitmapImageRep+ICNS.m: Redefine icns_size_t to int, as
this needs to be a 32 bit integer and on 64 bit systems long
  uses 64 bits.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@28501 72102866-910b-0410-8b05-ffd578937521
2009-08-21 13:57:16 +00:00

600 lines
16 KiB
Objective-C

/*
NSBitmapImageRep+ICNS.m
Methods for loading .icns images.
Copyright (C) 2008 Free Software Foundation, Inc.
Written by: Gregory Casamento
Date: 2008-08-12
Author: Fred Kiefer <fredkiefer@gmx.de>
Date: September 2008
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "NSBitmapImageRep+ICNS.h"
#include <Foundation/NSByteOrder.h>
#include <Foundation/NSData.h>
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>
#include "AppKit/NSGraphics.h"
#define ICNS_HEADER "icns"
#if HAVE_LIBICNS
#include <icns.h>
#else /* !HAVE_LIBICNS */
/*
The following code is a drop in replacement for libicns. It may be used
when the library is not available or unsuited due to its licence (Currently
GPL 2). This code was mostly build based on the documentation found at
http://icns.sourceforge.net/apidocs.html. It also includes icns decoding
ideas based on code in mySTEP.
Only limited formats are implemented and some errors still exist.
*/
typedef unsigned char icns_byte_t;
// must be a 32 bit integer
typedef unsigned int icns_size_t;
typedef struct _icns_type_t {
char c[4];
} icns_type_t;
typedef struct _icns_element_t {
icns_type_t elementType;
icns_size_t elementSize;
} icns_element_t;
typedef struct _icns_icon_info_t {
unsigned int iconHeight;
unsigned int iconWidth;
unsigned int iconDepth;
unsigned int iconChannels;
} icns_icon_info_t;
typedef struct _icns_image_t {
unsigned int imageWidth;
unsigned int imageHeight;
unsigned int imageChannels;
unsigned int imagePixelDepth;
unsigned int imageDataSize;
icns_byte_t *imageData;
} icns_image_t;
typedef struct _icns_family_t {
icns_type_t resourceType;
icns_size_t resourceSize;
icns_element_t elements[1];
} icns_family_t;
#define ICNS_HEADER_SIZE 8
/*
// ics# 0x69637323
static icns_type_t ICNS_16x16_1BIT_DATA = {{'i', 'c', 's', '#'}};
// ich# 0x69636823
static icns_type_t ICNS_48x48_1BIT_DATA = {{'i', 'c', 'h', '#'}};
*/
// is32 0x69733332
static icns_type_t ICNS_16x16_32BIT_DATA = {{'i', 's', '3', '2'}};
// il32 0x696c3332
static icns_type_t ICNS_32x32_32BIT_DATA = {{'i', 'l', '3', '2'}};
// ih32 0x69683332
static icns_type_t ICNS_48x48_32BIT_DATA = {{'i', 'h', '3', '2'}};
// it32 0x69743332
static icns_type_t ICNS_128X128_32BIT_DATA = {{'i', 't', '3', '2'}};
// s8mk 0x73386d6b
static icns_type_t ICNS_16x16_8BIT_MASK = {{'s', '8', 'm', 'k'}};
// l8mk 0x6c386d6b
static icns_type_t ICNS_32x32_8BIT_MASK = {{'l', '8', 'm', 'k'}};
// h8mk 0x68386d6b
static icns_type_t ICNS_48x48_8BIT_MASK = {{'h', '8', 'm', 'k'}};
// t8mk 0x74386d6b
static icns_type_t ICNS_128X128_8BIT_MASK = {{'t', '8', 'm', 'k'}};
static icns_type_t ICNS_FAMILY_TYPE = {{'i','c','n','s'}};
static icns_type_t ICNS_NULL_TYPE = {{0 , 0 , 0 , 0 }};
#define ICNS_STATUS_OK 0
static int icns_types_equal(icns_type_t type1, icns_type_t type2)
{
return (strncmp((char*)&type1.c, (char*)&type2.c, 4) == 0);
}
static icns_type_t icns_get_mask_type_for_icon_type(icns_type_t type)
{
if (icns_types_equal(type, ICNS_16x16_32BIT_DATA))
{
return ICNS_16x16_8BIT_MASK;
}
else if (icns_types_equal(type, ICNS_32x32_32BIT_DATA))
{
return ICNS_32x32_8BIT_MASK;
}
else if (icns_types_equal(type, ICNS_48x48_32BIT_DATA))
{
return ICNS_48x48_8BIT_MASK;
}
else if (icns_types_equal(type, ICNS_128X128_32BIT_DATA))
{
return ICNS_128X128_8BIT_MASK;
}
else
{
return ICNS_NULL_TYPE;
}
}
static icns_icon_info_t icns_get_image_info_for_type(icns_type_t type)
{
icns_icon_info_t info;
if (icns_types_equal(type, ICNS_16x16_32BIT_DATA))
{
info.iconHeight = 16;
info.iconWidth = 16;
info.iconDepth = 8;
info.iconChannels = 4;
}
else if (icns_types_equal(type, ICNS_32x32_32BIT_DATA))
{
info.iconHeight = 32;
info.iconWidth = 32;
info.iconDepth = 8;
info.iconChannels = 4;
}
else if (icns_types_equal(type, ICNS_48x48_32BIT_DATA))
{
info.iconHeight = 48;
info.iconWidth = 48;
info.iconDepth = 8;
info.iconChannels = 4;
}
else if (icns_types_equal(type, ICNS_128X128_32BIT_DATA))
{
info.iconHeight = 128;
info.iconWidth = 128;
info.iconDepth = 8;
info.iconChannels = 4;
}
else
{
info.iconHeight = 0;
info.iconWidth = 0;
info.iconDepth = 0;
info.iconChannels = 0;
}
return info;
}
static int icns_get_element_from_family(icns_family_t *iconFamily,
icns_type_t iconType,
icns_element_t **iconElement)
{
icns_byte_t *bytes = (icns_byte_t *)iconFamily->elements;
unsigned long size = iconFamily->resourceSize;
icns_element_t *element;
icns_byte_t *data;
data = bytes;
element = (icns_element_t *)data;
while ((bytes + size > data) && element->elementSize)
{
if (icns_types_equal(element->elementType, iconType))
{
*iconElement = element;
return ICNS_STATUS_OK;
}
data += element->elementSize;
element = (icns_element_t *)data;
}
return 1;
}
static int icns_import_family_data(int size, icns_byte_t *bytes,
icns_family_t **iconFamily)
{
icns_element_t *element = NULL;
icns_family_t *family;
unsigned long el_size;
icns_byte_t *data;
icns_byte_t *end;
data = bytes;
family = (icns_family_t *)data;
while ((bytes + size > data) && family->resourceSize)
{
if (icns_types_equal(family->resourceType, ICNS_FAMILY_TYPE))
{
element = (icns_element_t *)family;
break;
}
el_size = NSSwapBigIntToHost(family->resourceSize);
data += el_size;
family = (icns_family_t *)data;
}
if (element == NULL)
{
return 1;
}
el_size = NSSwapBigIntToHost(element->elementSize);
family = malloc(el_size);
if (!family)
{
return 1;
}
strncpy((char*)&family->resourceType.c, (char*)&element->elementType.c, 4);
family->resourceSize = el_size;
memcpy((char*)(family->elements),
(char*)element + ICNS_HEADER_SIZE,
el_size - ICNS_HEADER_SIZE);
data = (icns_byte_t *)family->elements;
end = data + el_size - ICNS_HEADER_SIZE;
element = family->elements;
while ((data < end) && element->elementSize)
{
el_size = NSSwapBigIntToHost(element->elementSize);
element->elementSize = el_size;
data += el_size;
element = (icns_element_t *)data;
}
*iconFamily = family;
return ICNS_STATUS_OK;
}
static int icns_init_image(unsigned int iconWidth,
unsigned int iconHeight,
unsigned int iconChannels,
unsigned int iconPixelDepth,
icns_image_t *imageOut)
{
imageOut->imageWidth = iconWidth;
imageOut->imageHeight = iconHeight;
imageOut->imageChannels = iconChannels;
imageOut->imagePixelDepth = iconPixelDepth;
imageOut->imageDataSize = (iconHeight * iconWidth
* iconPixelDepth * iconChannels) / 8;
imageOut->imageData = malloc(imageOut->imageDataSize);
if (!imageOut->imageData)
return 1;
else
return ICNS_STATUS_OK;
}
static int icns_init_image_for_type(icns_type_t iconType,
icns_image_t *imageOut)
{
icns_icon_info_t info;
info = icns_get_image_info_for_type(iconType);
return icns_init_image(info.iconWidth, info.iconHeight, info.iconChannels,
info.iconDepth, imageOut);
}
static int icns_free_image(icns_image_t *imageIn)
{
free(imageIn->imageData);
imageIn->imageData = NULL;
return ICNS_STATUS_OK;
}
static int icns_get_image32_with_mask_from_family(icns_family_t *iconFamily,
icns_type_t type,
icns_image_t *iconImage)
{
icns_element_t *element;
unsigned int samplesPerPixel = 4;
icns_byte_t *b;
icns_byte_t *end;
int j;
int res;
icns_type_t mask_type;
unsigned int imageDataSize;
if (icns_types_equal(type, ICNS_NULL_TYPE))
return 1;
res = icns_get_element_from_family(iconFamily, type, &element);
if (res != ICNS_STATUS_OK)
return res;
res = icns_init_image_for_type(type, iconImage);
if (res != ICNS_STATUS_OK)
return res;
b = (icns_byte_t *)element + ICNS_HEADER_SIZE;
end = b + element->elementSize - ICNS_HEADER_SIZE;
// Safety check
if (end > (icns_byte_t *)iconFamily->elements + iconFamily->resourceSize)
{
icns_free_image(iconImage);
return 1;
}
imageDataSize = iconImage->imageDataSize;
if ((element->elementSize - ICNS_HEADER_SIZE) <
3 * iconImage->imageHeight * iconImage->imageWidth)
{
unsigned int plane;
// Run length encoded planar data
for (plane = 0; plane < 3; plane++)
{
unsigned int offset;
offset = 0;
while ((offset < iconImage->imageHeight * iconImage->imageWidth)
&& (b < end))
{
icns_byte_t bv = *b++;
int runLen;
unsigned int index = samplesPerPixel * offset + plane;
if (bv & 0x80)
{
// Compressed run
icns_byte_t val = *b++;
runLen = bv - 125;
for (j = 0; (j < runLen) && (index < imageDataSize); j++)
{
iconImage->imageData[index] = val;
index += samplesPerPixel;
}
}
else
{
// Uncompressed run
int j;
runLen = bv + 1;
for (j = 0; (j < runLen) && (index < imageDataSize); j++)
{
iconImage->imageData[index] = *b++;
index += samplesPerPixel;
}
}
offset += runLen;
}
}
}
else
{
for (j = 0; j < iconImage->imageHeight * iconImage->imageWidth; j++)
{
iconImage->imageData[samplesPerPixel * j + 0] = *b++;
iconImage->imageData[samplesPerPixel * j + 1] = *b++;
iconImage->imageData[samplesPerPixel * j + 2] = *b++;
}
}
// Fill in the mask
mask_type = icns_get_mask_type_for_icon_type(type);
res = icns_get_element_from_family(iconFamily, mask_type, &element);
if (res == ICNS_STATUS_OK)
{
b = (icns_byte_t *)element + ICNS_HEADER_SIZE;
end = b + element->elementSize - ICNS_HEADER_SIZE;
// Safety check
if (end > (icns_byte_t *)iconFamily->elements + iconFamily->resourceSize)
{
icns_free_image(iconImage);
return 1;
}
for (j = 0; j < iconImage->imageHeight * iconImage->imageWidth; j++)
{
iconImage->imageData[samplesPerPixel * j + 3] = *b++;
}
}
else
{
for (j = 0; j < iconImage->imageHeight * iconImage->imageWidth; j++)
{
iconImage->imageData[samplesPerPixel * j + 3] = 255;
}
}
return ICNS_STATUS_OK;
}
#endif /* !HAVE_LIBICNS */
// Define the pixel
typedef struct pixel_t
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} pixel_t;
@implementation NSBitmapImageRep (ICNS)
+ (BOOL) _bitmapIsICNS: (NSData *)imageData
{
char header[5];
/*
* If the data is 0, return immediately.
*/
if ([imageData length] < 8)
{
return NO;
}
/*
* Check the beginning of the data for
* the string "icns".
*/
[imageData getBytes: header length: 4];
if(strncmp(header, ICNS_HEADER, 4) == 0)
{
return YES;
}
return NO;
}
- (id) _initBitmapFromICNS: (NSData *)imageData
{
int error = 0;
int size = [imageData length];
icns_byte_t *bytes = (icns_byte_t *)[imageData bytes];
icns_family_t *iconFamily = NULL;
unsigned long dataOffset = 0;
icns_byte_t *data = NULL;
icns_type_t typeStr = ICNS_NULL_TYPE;
unsigned int iconWidth = 0, iconHeight = 0;
icns_image_t iconImage;
int sPP = 4;
unsigned char *rgbBuffer = NULL; /* image converted to rgb */
unsigned int rgbBufferPos = 0;
unsigned int rgbBufferSize = 0;
int i = 0, j = 0;
int imageChannels = 0;
error = icns_import_family_data(size, bytes, &iconFamily);
if(error != ICNS_STATUS_OK)
{
NSLog(@"Error reading ICNS data.");
RELEASE(self);
return nil;
}
// skip the header...
dataOffset = sizeof(icns_type_t) + sizeof(icns_size_t);
data = (icns_byte_t *)iconFamily;
// read each icon...
while (((dataOffset + 8) < iconFamily->resourceSize))
{
icns_element_t element;
memcpy(&element, (data + dataOffset), 8);
//
// Temporarily limit to 48 until we can find a way to
// utilize the other representations in the icns file.
//
if (icns_types_equal(element.elementType, ICNS_48x48_32BIT_DATA)
|| (icns_types_equal(typeStr, ICNS_NULL_TYPE)
&& (icns_types_equal(element.elementType, ICNS_32x32_32BIT_DATA)
|| icns_types_equal(element.elementType, ICNS_128X128_32BIT_DATA))))
{
memcpy(&typeStr, &(element.elementType), 4);
}
// next...
dataOffset += element.elementSize;
}
// extract the image...
memset(&iconImage, 0, sizeof(icns_image_t));
error = icns_get_image32_with_mask_from_family(iconFamily,
typeStr,
&iconImage);
if (error)
{
NSLog(@"Error while extracting image from ICNS data.");
RELEASE(self);
free(iconFamily);
return nil;
}
iconWidth = iconImage.imageWidth;
iconHeight = iconImage.imageHeight;
// allocate the buffer...
rgbBufferSize = iconHeight * (iconWidth * sizeof(unsigned char) * sPP);
rgbBuffer = NSZoneMalloc([self zone], rgbBufferSize);
if (rgbBuffer == NULL)
{
NSLog(@"Couldn't allocate memory for image data from ICNS.");
RELEASE(self);
icns_free_image(&iconImage);
free(iconFamily);
return nil;
}
imageChannels = iconImage.imageChannels;
rgbBufferPos = 0;
for (i = 0; i < iconHeight; i++)
{
for (j = 0; j < iconWidth; j++)
{
pixel_t *src_rgb_pixel;
src_rgb_pixel = (pixel_t *)&(iconImage.imageData[i*iconWidth*imageChannels+j*imageChannels]);
rgbBuffer[rgbBufferPos++] = src_rgb_pixel->r;
rgbBuffer[rgbBufferPos++] = src_rgb_pixel->g;
rgbBuffer[rgbBufferPos++] = src_rgb_pixel->b;
rgbBuffer[rgbBufferPos++] = src_rgb_pixel->a;
}
}
icns_free_image(&iconImage);
free(iconFamily);
/* initialize self */
[self initWithBitmapDataPlanes: &rgbBuffer
pixelsWide: iconWidth
pixelsHigh: iconHeight
bitsPerSample: 8
samplesPerPixel: sPP
hasAlpha: YES
isPlanar: NO
colorSpaceName: NSCalibratedRGBColorSpace
// FIXME: Not sure whether this format is pre-multiplied
bitmapFormat: NSAlphaNonpremultipliedBitmapFormat
bytesPerRow: iconWidth * sPP
bitsPerPixel: 8 * sPP];
_imageData = [[NSData alloc] initWithBytesNoCopy: rgbBuffer
length: rgbBufferSize];
return self;
}
@end