Re: NSImageRGBColorTable crash
Re: NSImageRGBColorTable crash
- Subject: Re: NSImageRGBColorTable crash
- From: Andy Matuschak <email@hidden>
- Date: Fri, 16 Jul 2004 10:13:24 -0400
- Resent-date: Fri, 16 Jul 2004 10:35:07 -0400
- Resent-from: Andy Matuschak <email@hidden>
- Resent-message-id: <4CB9A3D6-D732-11D8-AA5D-000A95AB8E70@andymatuschak.o rg>
- Resent-to: email@hidden
Hello. I've spent the last two days trying to figure out a way around
this bug, and I've found a number of things:
1. The number of colors in the color table must be a power of two.
2. The number of bytes doesn't have to be 768 - it can be any other
power of two * 3.
3. There's an issue with repetition: If you have five colors, it
expects you to be using a palette of eight colors and so it expects you
to have filled the last three palette entries with black. However, if
you have five colors and use a palette of sixteen colors, filling the
last eleven with black, it gets confused. This is most likely why
you're having problems with writing out 256 entries of black.
4. Most importantly, however, after one fixes everything and conforms
to the way GIF expects the table to formatted, it still won't work.
Why? Because Cocoa's implementation is broken. I spent many an hour
wondering why my colors got all screwed up before I tried something
very simple: let's load in a gif, then write it out again using its own
color table.
Turns out Cocoa can't round-trip a gif. The implementation is either
incomplete or broken. A note would've been nice before I spent all that
time. Anyway, I decided to figure out another way to write gifs. I
found libungif, which you can download here:
http://sourceforge.net/projects/libungif. It's an open-source gif
manipulation library that doesn't have any LZW compression for patent
reasons. It's MIT license, so you can use it in pretty much any
project. Just download it, compile and install. I chose to statically
link it so that my users didn't have to download the library, but
that's not really necessary. Anyway, here's the method I wrote to take
an NSImage, quantize it, and get all the data. It's a little messy
after being written at 4:00 AM, but it works:
+ gifDataForImage:anImage
{
id image = [[[NSImage alloc] initWith
Data:[anImage
TIFFRepresentation]] autorelease];
NSSize size = [image size];
[image lockFocus];
// first we find a valid transparent color
id transparentColor = [NSColor colorWithCalibratedRed:0 green:0 blue:0
alpha:1];
BOOL found = NO;
while (!found) // I know this is ugly. It works.
{
found = NO;
int i, j;
for (i = 0; i < size.width; i++)
{
for (j = 0; j < size.height; j++)
{
id converted = [NSReadPixel(NSMakePoint(i, j))
colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
if ((unsigned char)([converted redComponent] * 255) == (unsigned
char)([transparentColor redComponent] * 255) && (unsigned
char)([converted greenComponent] * 255) == (unsigned
char)([transparentColor greenComponent] * 255) && (unsigned
char)([converted blueComponent] * 255) == (unsigned
char)([transparentColor blueComponent] * 255))
{
if ([transparentColor redComponent] < 1)
{
transparentColor = [NSColor
colorWithCalibratedRed:[transparentColor redComponent]+0.1
green:[transparentColor greenComponent] blue:[transparentColor
blueComponent] alpha:1];
}
else if ([transparentColor greenComponent] < 1)
{
transparentColor = [NSColor
colorWithCalibratedRed:[transparentColor redComponent]
green:[transparentColor greenComponent]+0.1 blue:[transparentColor
blueComponent] alpha:1];
}
else if ([transparentColor blueComponent] < 1)
{
transparentColor = [NSColor
colorWithCalibratedRed:[transparentColor redComponent]
green:[transparentColor greenComponent] blue:[transparentColor
blueComponent]+0.1 alpha:1];
}
found = YES;
}
}
}
}
int colorMapSize = 256;
GifByteType *redBuffer = malloc(size.width * size.height);
GifByteType *greenBuffer = malloc(size.width * size.height);
GifByteType *blueBuffer = malloc(size.width * size.height);
EGifSetGifVersion("89a");
int i, j, count = 0;
for (j = size.height - 1; j >= 0; j--)
{
for (i = 0; i < size.width; i++, count++)
{
NSColor * color = [NSReadPixel(NSMakePoint(i, j))
colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
if ([color alphaComponent] < 0.5) { color = transparentColor; }
redBuffer[count] = (GifByteType)([color redComponent] * 255);
greenBuffer[count] = (GifByteType)([color greenComponent] * 255);
blueBuffer[count] = (GifByteType)([color blueComponent] * 255);
}
}
ColorMapObject *colorMap = MakeMapObject(colorMapSize, NULL);
GifByteType *outputBuffer = malloc(size.width * size.height *
sizeof(GifByteType));
QuantizeBuffer(size.width, size.height, &colorMapSize, redBuffer,
greenBuffer, blueBuffer, outputBuffer, colorMap->Colors);
GifFileType *gifFile = EGifOpenFileName("/tmp/dummy.gif", NO);
//find the index of the transparent color in the color map
unsigned transparentIndex, bestDelta = 1000;
for (i = 0; i < colorMapSize; i++)
{
unsigned char transRed = (unsigned char)([transparentColor
redComponent] * 255);
unsigned char transGreen = (unsigned char)([transparentColor
greenComponent] * 255);
unsigned char transBlue = (unsigned char)([transparentColor
blueComponent] * 255);
GifColorType color = colorMap->Colors[i];
if (color.Red == transRed && color.Green == transGreen && color.Blue
== transBlue)
{
transparentIndex = i;
}
else
{
unsigned int tempDelta = (color.Red < transRed ? transRed -
color.Red : color.Red - transRed) +
(color.Green < transGreen ? transGreen - color.Green : color.Green -
transGreen) +
(color.Blue < transBlue ? transBlue - color.Blue : color.Blue -
transBlue);
if (tempDelta < bestDelta)
{
transparentIndex = i;
bestDelta = tempDelta;
}
}
}
EGifPutScreenDesc(gifFile, size.width, size.height, colorMapSize, 0,
colorMap);
unsigned char extension[4] = { 0 };
extension[0] = 0x01; // byte 1 is a flag; 00000001 turns transparency
on.
extension[1] = 0x00; // byte 2 is delay time, presumably for animation.
extension[2] = 0x00; // byte 3 is continued delay time.
extension[3] = transparentIndex; // byte 4 is the index of the
transparent color in the palette.
EGifPutExtension(gifFile, 0xF9, sizeof(extension), extension); // 0xf9
is the transparency extension magic code
EGifPutImageDesc(gifFile, 0, 0, size.width, size.height, 0, NULL);
GifByteType * position = outputBuffer;
for (i = 0; i < size.height; i++)
{
EGifPutLine(gifFile, position, size.width);
position += (int)size.width;
}
EGifCloseFile(gifFile);
[image unlockFocus];
id finalData = [NSData dataWithContentsOfFile:@"/tmp/dummy.gif"];
remove("/tmp/dummy.gif");
return finalData;
}
Hope this helps.
- Andy Matuschak
Open Sword Group
http://www.opensword.org
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.