452 lines
12 KiB
C++
452 lines
12 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
|
27th April 2017).
|
|
|
|
End User License Agreement: www.juce.com/juce-5-licence
|
|
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
|
|
|
Or: You may also use this code under the terms of the GPL v3 (see
|
|
www.gnu.org/licenses).
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
#if (JUCE_MAC || JUCE_IOS) && USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER
|
|
Image juce_loadWithCoreImage (InputStream& input);
|
|
#else
|
|
|
|
//==============================================================================
|
|
class GIFLoader
|
|
{
|
|
public:
|
|
GIFLoader (InputStream& in)
|
|
: input (in),
|
|
dataBlockIsZero (false), fresh (false), finished (false),
|
|
currentBit (0), lastBit (0), lastByteIndex (0),
|
|
codeSize (0), setCodeSize (0), maxCode (0), maxCodeSize (0),
|
|
firstcode (0), oldcode (0), clearCode (0), endCode (0)
|
|
{
|
|
int imageWidth, imageHeight;
|
|
if (! getSizeFromHeader (imageWidth, imageHeight))
|
|
return;
|
|
|
|
uint8 buf [16];
|
|
if (in.read (buf, 3) != 3)
|
|
return;
|
|
|
|
int numColours = 2 << (buf[0] & 7);
|
|
int transparent = -1;
|
|
|
|
if ((buf[0] & 0x80) != 0)
|
|
readPalette (numColours);
|
|
|
|
for (;;)
|
|
{
|
|
if (input.read (buf, 1) != 1 || buf[0] == ';')
|
|
break;
|
|
|
|
if (buf[0] == '!')
|
|
{
|
|
if (readExtension (transparent))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if (buf[0] != ',')
|
|
continue;
|
|
|
|
if (input.read (buf, 9) == 9)
|
|
{
|
|
imageWidth = (int) ByteOrder::littleEndianShort (buf + 4);
|
|
imageHeight = (int) ByteOrder::littleEndianShort (buf + 6);
|
|
|
|
numColours = 2 << (buf[8] & 7);
|
|
|
|
if ((buf[8] & 0x80) != 0)
|
|
if (! readPalette (numColours))
|
|
break;
|
|
|
|
image = Image (transparent >= 0 ? Image::ARGB : Image::RGB,
|
|
imageWidth, imageHeight, transparent >= 0);
|
|
|
|
image.getProperties()->set ("originalImageHadAlpha", transparent >= 0);
|
|
|
|
readImage ((buf[8] & 0x40) != 0, transparent);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
Image image;
|
|
|
|
private:
|
|
InputStream& input;
|
|
uint8 buffer [260];
|
|
PixelARGB palette [256];
|
|
bool dataBlockIsZero, fresh, finished;
|
|
int currentBit, lastBit, lastByteIndex;
|
|
int codeSize, setCodeSize;
|
|
int maxCode, maxCodeSize;
|
|
int firstcode, oldcode;
|
|
int clearCode, endCode;
|
|
enum { maxGifCode = 1 << 12 };
|
|
int table [2] [maxGifCode];
|
|
int stack [2 * maxGifCode];
|
|
int* sp;
|
|
|
|
bool getSizeFromHeader (int& w, int& h)
|
|
{
|
|
char b[6];
|
|
|
|
if (input.read (b, 6) == 6
|
|
&& (strncmp ("GIF87a", b, 6) == 0
|
|
|| strncmp ("GIF89a", b, 6) == 0))
|
|
{
|
|
if (input.read (b, 4) == 4)
|
|
{
|
|
w = (int) ByteOrder::littleEndianShort (b);
|
|
h = (int) ByteOrder::littleEndianShort (b + 2);
|
|
return w > 0 && h > 0;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool readPalette (const int numCols)
|
|
{
|
|
for (int i = 0; i < numCols; ++i)
|
|
{
|
|
uint8 rgb[4];
|
|
input.read (rgb, 3);
|
|
|
|
palette[i].setARGB (0xff, rgb[0], rgb[1], rgb[2]);
|
|
palette[i].premultiply();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int readDataBlock (uint8* const dest)
|
|
{
|
|
uint8 n;
|
|
if (input.read (&n, 1) == 1)
|
|
{
|
|
dataBlockIsZero = (n == 0);
|
|
|
|
if (dataBlockIsZero || (input.read (dest, n) == n))
|
|
return n;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int readExtension (int& transparent)
|
|
{
|
|
uint8 type;
|
|
if (input.read (&type, 1) != 1)
|
|
return false;
|
|
|
|
uint8 b [260];
|
|
int n = 0;
|
|
|
|
if (type == 0xf9)
|
|
{
|
|
n = readDataBlock (b);
|
|
if (n < 0)
|
|
return 1;
|
|
|
|
if ((b[0] & 1) != 0)
|
|
transparent = b[3];
|
|
}
|
|
|
|
do
|
|
{
|
|
n = readDataBlock (b);
|
|
}
|
|
while (n > 0);
|
|
|
|
return n >= 0;
|
|
}
|
|
|
|
void clearTable()
|
|
{
|
|
int i;
|
|
for (i = 0; i < clearCode; ++i)
|
|
{
|
|
table[0][i] = 0;
|
|
table[1][i] = i;
|
|
}
|
|
|
|
for (; i < maxGifCode; ++i)
|
|
{
|
|
table[0][i] = 0;
|
|
table[1][i] = 0;
|
|
}
|
|
}
|
|
|
|
void initialise (const int inputCodeSize)
|
|
{
|
|
setCodeSize = inputCodeSize;
|
|
codeSize = setCodeSize + 1;
|
|
clearCode = 1 << setCodeSize;
|
|
endCode = clearCode + 1;
|
|
maxCodeSize = 2 * clearCode;
|
|
maxCode = clearCode + 2;
|
|
|
|
getCode (0, true);
|
|
|
|
fresh = true;
|
|
clearTable();
|
|
sp = stack;
|
|
}
|
|
|
|
int readLZWByte()
|
|
{
|
|
if (fresh)
|
|
{
|
|
fresh = false;
|
|
|
|
for (;;)
|
|
{
|
|
firstcode = oldcode = getCode (codeSize, false);
|
|
|
|
if (firstcode != clearCode)
|
|
return firstcode;
|
|
}
|
|
}
|
|
|
|
if (sp > stack)
|
|
return *--sp;
|
|
|
|
int code;
|
|
|
|
while ((code = getCode (codeSize, false)) >= 0)
|
|
{
|
|
if (code == clearCode)
|
|
{
|
|
clearTable();
|
|
codeSize = setCodeSize + 1;
|
|
maxCodeSize = 2 * clearCode;
|
|
maxCode = clearCode + 2;
|
|
sp = stack;
|
|
firstcode = oldcode = getCode (codeSize, false);
|
|
return firstcode;
|
|
}
|
|
else if (code == endCode)
|
|
{
|
|
if (dataBlockIsZero)
|
|
return -2;
|
|
|
|
uint8 buf [260];
|
|
int n;
|
|
|
|
while ((n = readDataBlock (buf)) > 0)
|
|
{}
|
|
|
|
if (n != 0)
|
|
return -2;
|
|
}
|
|
|
|
const int incode = code;
|
|
|
|
if (code >= maxCode)
|
|
{
|
|
*sp++ = firstcode;
|
|
code = oldcode;
|
|
}
|
|
|
|
while (code >= clearCode)
|
|
{
|
|
*sp++ = table[1][code];
|
|
if (code == table[0][code])
|
|
return -2;
|
|
|
|
code = table[0][code];
|
|
}
|
|
|
|
*sp++ = firstcode = table[1][code];
|
|
|
|
if ((code = maxCode) < maxGifCode)
|
|
{
|
|
table[0][code] = oldcode;
|
|
table[1][code] = firstcode;
|
|
++maxCode;
|
|
|
|
if (maxCode >= maxCodeSize && maxCodeSize < maxGifCode)
|
|
{
|
|
maxCodeSize <<= 1;
|
|
++codeSize;
|
|
}
|
|
}
|
|
|
|
oldcode = incode;
|
|
|
|
if (sp > stack)
|
|
return *--sp;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
int getCode (const int codeSize_, const bool shouldInitialise)
|
|
{
|
|
if (shouldInitialise)
|
|
{
|
|
currentBit = 0;
|
|
lastBit = 0;
|
|
finished = false;
|
|
return 0;
|
|
}
|
|
|
|
if ((currentBit + codeSize_) >= lastBit)
|
|
{
|
|
if (finished)
|
|
return -1;
|
|
|
|
buffer[0] = buffer [lastByteIndex - 2];
|
|
buffer[1] = buffer [lastByteIndex - 1];
|
|
|
|
const int n = readDataBlock (buffer + 2);
|
|
|
|
if (n == 0)
|
|
finished = true;
|
|
|
|
lastByteIndex = 2 + n;
|
|
currentBit = (currentBit - lastBit) + 16;
|
|
lastBit = (2 + n) * 8 ;
|
|
}
|
|
|
|
int result = 0;
|
|
int i = currentBit;
|
|
|
|
for (int j = 0; j < codeSize_; ++j)
|
|
{
|
|
result |= ((buffer[i >> 3] & (1 << (i & 7))) != 0) << j;
|
|
++i;
|
|
}
|
|
|
|
currentBit += codeSize_;
|
|
return result;
|
|
}
|
|
|
|
bool readImage (const int interlace, const int transparent)
|
|
{
|
|
uint8 c;
|
|
if (input.read (&c, 1) != 1)
|
|
return false;
|
|
|
|
initialise (c);
|
|
|
|
if (transparent >= 0)
|
|
palette [transparent].setARGB (0, 0, 0, 0);
|
|
|
|
int xpos = 0, ypos = 0, yStep = 8, pass = 0;
|
|
|
|
const Image::BitmapData destData (image, Image::BitmapData::writeOnly);
|
|
uint8* p = destData.getPixelPointer (0, 0);
|
|
const bool hasAlpha = image.hasAlphaChannel();
|
|
|
|
for (;;)
|
|
{
|
|
const int index = readLZWByte();
|
|
if (index < 0)
|
|
break;
|
|
|
|
if (hasAlpha)
|
|
((PixelARGB*) p)->set (palette [index]);
|
|
else
|
|
((PixelRGB*) p)->set (palette [index]);
|
|
|
|
p += destData.pixelStride;
|
|
|
|
if (++xpos == destData.width)
|
|
{
|
|
xpos = 0;
|
|
|
|
if (interlace)
|
|
{
|
|
ypos += yStep;
|
|
|
|
while (ypos >= destData.height)
|
|
{
|
|
switch (++pass)
|
|
{
|
|
case 1: ypos = 4; yStep = 8; break;
|
|
case 2: ypos = 2; yStep = 4; break;
|
|
case 3: ypos = 1; yStep = 2; break;
|
|
default: return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (++ypos >= destData.height)
|
|
break;
|
|
}
|
|
|
|
p = destData.getPixelPointer (xpos, ypos);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (GIFLoader)
|
|
};
|
|
|
|
#endif
|
|
|
|
//==============================================================================
|
|
GIFImageFormat::GIFImageFormat() {}
|
|
GIFImageFormat::~GIFImageFormat() {}
|
|
|
|
String GIFImageFormat::getFormatName() { return "GIF"; }
|
|
bool GIFImageFormat::usesFileExtension (const File& f) { return f.hasFileExtension ("gif"); }
|
|
|
|
bool GIFImageFormat::canUnderstand (InputStream& in)
|
|
{
|
|
char header [4];
|
|
|
|
return (in.read (header, sizeof (header)) == sizeof (header))
|
|
&& header[0] == 'G'
|
|
&& header[1] == 'I'
|
|
&& header[2] == 'F';
|
|
}
|
|
|
|
Image GIFImageFormat::decodeImage (InputStream& in)
|
|
{
|
|
#if (JUCE_MAC || JUCE_IOS) && USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER
|
|
return juce_loadWithCoreImage (in);
|
|
#else
|
|
const std::unique_ptr<GIFLoader> loader (new GIFLoader (in));
|
|
return loader->image;
|
|
#endif
|
|
}
|
|
|
|
bool GIFImageFormat::writeImageToStream (const Image& /*sourceImage*/, OutputStream& /*destStream*/)
|
|
{
|
|
jassertfalse; // writing isn't implemented for GIFs!
|
|
return false;
|
|
}
|
|
|
|
} // namespace juce
|