2020-01-03 20:05:16 +01:00
|
|
|
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
|
|
|
// This file is part of the "Irrlicht Engine".
|
|
|
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
|
|
|
|
|
|
#include "CImageLoaderPSD.h"
|
|
|
|
|
|
|
|
#ifdef _IRR_COMPILE_WITH_PSD_LOADER_
|
|
|
|
|
|
|
|
#include "IReadFile.h"
|
|
|
|
#include "os.h"
|
|
|
|
#include "CImage.h"
|
|
|
|
#include "irrString.h"
|
|
|
|
|
|
|
|
|
|
|
|
namespace irr
|
|
|
|
{
|
|
|
|
namespace video
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
//! constructor
|
|
|
|
CImageLoaderPSD::CImageLoaderPSD()
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
setDebugName("CImageLoaderPSD");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//! returns true if the file maybe is able to be loaded by this class
|
|
|
|
//! based on the file extension (e.g. ".tga")
|
|
|
|
bool CImageLoaderPSD::isALoadableFileExtension(const io::path& filename) const
|
|
|
|
{
|
|
|
|
return core::hasFileExtension ( filename, "psd" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//! returns true if the file maybe is able to be loaded by this class
|
|
|
|
bool CImageLoaderPSD::isALoadableFileFormat(io::IReadFile* file) const
|
|
|
|
{
|
|
|
|
if (!file)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
u8 type[3];
|
|
|
|
file->read(&type, sizeof(u8)*3);
|
|
|
|
return (type[2]==2); // we currently only handle tgas of type 2.
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//! creates a surface from the file
|
|
|
|
IImage* CImageLoaderPSD::loadImage(io::IReadFile* file) const
|
|
|
|
{
|
|
|
|
u32* imageData = 0;
|
|
|
|
|
|
|
|
PsdHeader header;
|
|
|
|
file->read(&header, sizeof(PsdHeader));
|
|
|
|
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
|
|
header.version = os::Byteswap::byteswap(header.version);
|
|
|
|
header.channels = os::Byteswap::byteswap(header.channels);
|
|
|
|
header.height = os::Byteswap::byteswap(header.height);
|
|
|
|
header.width = os::Byteswap::byteswap(header.width);
|
|
|
|
header.depth = os::Byteswap::byteswap(header.depth);
|
|
|
|
header.mode = os::Byteswap::byteswap(header.mode);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (header.signature[0] != '8' ||
|
|
|
|
header.signature[1] != 'B' ||
|
|
|
|
header.signature[2] != 'P' ||
|
|
|
|
header.signature[3] != 'S')
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (header.version != 1)
|
|
|
|
{
|
|
|
|
os::Printer::log("Unsupported PSD file version", file->getFileName(), ELL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (header.mode != 3 || header.depth != 8)
|
|
|
|
{
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Unsupported PSD color mode or depth", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip color mode data
|
|
|
|
|
|
|
|
u32 l;
|
|
|
|
file->read(&l, sizeof(u32));
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
|
|
l = os::Byteswap::byteswap(l);
|
|
|
|
#endif
|
|
|
|
if (!file->seek(l, true))
|
|
|
|
{
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Error seeking file pos to image resources", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip image resources
|
|
|
|
|
|
|
|
file->read(&l, sizeof(u32));
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
|
|
l = os::Byteswap::byteswap(l);
|
|
|
|
#endif
|
|
|
|
if (!file->seek(l, true))
|
|
|
|
{
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Error seeking file pos to layer and mask", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip layer & mask
|
|
|
|
|
|
|
|
file->read(&l, sizeof(u32));
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
|
|
l = os::Byteswap::byteswap(l);
|
|
|
|
#endif
|
|
|
|
if (!file->seek(l, true))
|
|
|
|
{
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Error seeking file pos to image data section", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read image data
|
|
|
|
|
|
|
|
u16 compressionType;
|
|
|
|
file->read(&compressionType, sizeof(u16));
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
|
|
compressionType = os::Byteswap::byteswap(compressionType);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (compressionType != 1 && compressionType != 0)
|
|
|
|
{
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Unsupported psd compression mode", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create image data block
|
|
|
|
|
|
|
|
imageData = new u32[header.width * header.height];
|
|
|
|
|
|
|
|
bool res = false;
|
|
|
|
|
|
|
|
if (compressionType == 0)
|
|
|
|
res = readRawImageData(file, header, imageData); // RAW image data
|
|
|
|
else
|
|
|
|
res = readRLEImageData(file, header, imageData); // RLE compressed data
|
|
|
|
|
|
|
|
video::IImage* image = 0;
|
|
|
|
|
|
|
|
if (res)
|
|
|
|
{
|
|
|
|
// create surface
|
|
|
|
image = new CImage(ECF_A8R8G8B8,
|
|
|
|
core::dimension2d<u32>(header.width, header.height), imageData);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!image)
|
|
|
|
delete [] imageData;
|
|
|
|
imageData = 0;
|
|
|
|
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CImageLoaderPSD::readRawImageData(io::IReadFile* file, const PsdHeader& header, u32* imageData) const
|
|
|
|
{
|
|
|
|
u8* tmpData = new u8[header.width * header.height];
|
|
|
|
|
|
|
|
for (s32 channel=0; channel<header.channels && channel < 3; ++channel)
|
|
|
|
{
|
|
|
|
if (!file->read(tmpData, sizeof(c8) * header.width * header.height))
|
|
|
|
{
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Error reading color channel", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
s16 shift = getShiftFromChannel((c8)channel, header);
|
|
|
|
if (shift != -1)
|
|
|
|
{
|
|
|
|
u32 mask = 0xff << shift;
|
|
|
|
|
|
|
|
for (u32 x=0; x<header.width; ++x)
|
|
|
|
{
|
|
|
|
for (u32 y=0; y<header.height; ++y)
|
|
|
|
{
|
|
|
|
s32 index = x + y*header.width;
|
|
|
|
imageData[index] = ~(~imageData[index] | mask);
|
|
|
|
imageData[index] |= tmpData[index] << shift;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
delete [] tmpData;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CImageLoaderPSD::readRLEImageData(io::IReadFile* file, const PsdHeader& header, u32* imageData) const
|
|
|
|
{
|
|
|
|
/* If the compression code is 1, the image data
|
|
|
|
starts with the byte counts for all the scan lines in the channel
|
|
|
|
(LayerBottom LayerTop), with each count stored as a two
|
|
|
|
byte value. The RLE compressed data follows, with each scan line
|
|
|
|
compressed separately. The RLE compression is the same compres-sion
|
|
|
|
algorithm used by the Macintosh ROM routine PackBits, and
|
|
|
|
the TIFF standard.
|
|
|
|
If the Layer's Size, and therefore the data, is odd, a pad byte will
|
|
|
|
be inserted at the end of the row.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
A pseudo code fragment to unpack might look like this:
|
|
|
|
|
|
|
|
Loop until you get the number of unpacked bytes you are expecting:
|
|
|
|
Read the next source byte into n.
|
|
|
|
If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
|
|
|
|
Else if n is between -127 and -1 inclusive, copy the next byte -n+1
|
|
|
|
times.
|
|
|
|
Else if n is -128, noop.
|
|
|
|
Endloop
|
|
|
|
|
|
|
|
In the inverse routine, it is best to encode a 2-byte repeat run as a replicate run
|
|
|
|
except when preceded and followed by a literal run. In that case, it is best to merge
|
|
|
|
the three runs into one literal run. Always encode 3-byte repeats as replicate runs.
|
|
|
|
That is the essence of the algorithm. Here are some additional rules:
|
|
|
|
- Pack each row separately. Do not compress across row boundaries.
|
|
|
|
- The number of uncompressed bytes per row is defined to be (ImageWidth + 7)
|
|
|
|
/ 8. If the uncompressed bitmap is required to have an even number of bytes per
|
|
|
|
row, decompress into word-aligned buffers.
|
|
|
|
- If a run is larger than 128 bytes, encode the remainder of the run as one or more
|
|
|
|
additional replicate runs.
|
|
|
|
When PackBits data is decompressed, the result should be interpreted as per com-pression
|
|
|
|
type 1 (no compression).
|
|
|
|
*/
|
|
|
|
|
|
|
|
u8* tmpData = new u8[header.width * header.height];
|
|
|
|
u16 *rleCount= new u16 [header.height * header.channels];
|
|
|
|
|
|
|
|
s32 size=0;
|
|
|
|
|
|
|
|
for (u32 y=0; y<header.height * header.channels; ++y)
|
|
|
|
{
|
|
|
|
if (!file->read(&rleCount[y], sizeof(u16)))
|
|
|
|
{
|
|
|
|
delete [] tmpData;
|
|
|
|
delete [] rleCount;
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Error reading rle rows", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
|
|
rleCount[y] = os::Byteswap::byteswap(rleCount[y]);
|
|
|
|
#endif
|
|
|
|
size += rleCount[y];
|
|
|
|
}
|
|
|
|
|
|
|
|
s8 *buf = new s8[size];
|
|
|
|
if (!file->read(buf, size))
|
|
|
|
{
|
|
|
|
delete [] rleCount;
|
|
|
|
delete [] buf;
|
|
|
|
delete [] tmpData;
|
2022-09-22 23:55:03 +02:00
|
|
|
os::Printer::log("Error reading rle rows", file->getFileName(), ELL_ERROR);
|
2020-01-03 20:05:16 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
u16 *rcount=rleCount;
|
|
|
|
|
|
|
|
s8 rh;
|
|
|
|
u16 bytesRead;
|
|
|
|
u8 *dest;
|
|
|
|
s8 *pBuf = buf;
|
|
|
|
|
|
|
|
// decompress packbit rle
|
|
|
|
|
|
|
|
for (s32 channel=0; channel<header.channels; channel++)
|
|
|
|
{
|
|
|
|
for (u32 y=0; y<header.height; ++y, ++rcount)
|
|
|
|
{
|
|
|
|
bytesRead=0;
|
|
|
|
dest = &tmpData[y*header.width];
|
|
|
|
|
|
|
|
while (bytesRead < *rcount)
|
|
|
|
{
|
|
|
|
rh = *pBuf++;
|
|
|
|
++bytesRead;
|
|
|
|
|
|
|
|
if (rh >= 0)
|
|
|
|
{
|
|
|
|
++rh;
|
|
|
|
|
|
|
|
while (rh--)
|
|
|
|
{
|
|
|
|
*dest = *pBuf++;
|
|
|
|
++bytesRead;
|
|
|
|
++dest;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (rh > -128)
|
|
|
|
{
|
|
|
|
rh = -rh +1;
|
|
|
|
|
|
|
|
while (rh--)
|
|
|
|
{
|
|
|
|
*dest = *pBuf;
|
|
|
|
++dest;
|
|
|
|
}
|
|
|
|
|
|
|
|
++pBuf;
|
|
|
|
++bytesRead;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s16 shift = getShiftFromChannel((c8)channel, header);
|
|
|
|
|
|
|
|
if (shift != -1)
|
|
|
|
{
|
|
|
|
u32 mask = 0xff << shift;
|
|
|
|
|
|
|
|
for (u32 x=0; x<header.width; ++x)
|
|
|
|
for (u32 y=0; y<header.height; ++y)
|
|
|
|
{
|
|
|
|
s32 index = x + y*header.width;
|
|
|
|
imageData[index] = ~(~imageData[index] | mask);
|
|
|
|
imageData[index] |= tmpData[index] << shift;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delete [] rleCount;
|
|
|
|
delete [] buf;
|
|
|
|
delete [] tmpData;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s16 CImageLoaderPSD::getShiftFromChannel(c8 channelNr, const PsdHeader& header) const
|
|
|
|
{
|
|
|
|
switch(channelNr)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
return 16; // red
|
|
|
|
case 1:
|
|
|
|
return 8; // green
|
|
|
|
case 2:
|
|
|
|
return 0; // blue
|
|
|
|
case 3:
|
|
|
|
return header.channels == 4 ? 24 : -1; // ?
|
|
|
|
case 4:
|
|
|
|
return 24; // alpha
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//! creates a loader which is able to load tgas
|
|
|
|
IImageLoader* createImageLoaderPSD()
|
|
|
|
{
|
|
|
|
return new CImageLoaderPSD();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // end namespace video
|
|
|
|
} // end namespace irr
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|