#if defined(_MSC_VER) && (_MSC_VER >= 1400)
#define _CRT_SECURE_NO_DEPRECATE 1
#endif
#include "psd.h"
#include "trasterimage.h"
#include "trop.h"
#include "tpixelutils.h"
/*
The entire content of this file is ridden with LEAKS. A bug has been filed,
will hopefully
be dealt with ASAP.
L'intero contenuto del file e' pieno di LEAK. Ho inserito la cosa in Bugilla e
non ho potuto
fare altro sotto rilascio. Non solo, il codice dei distruttori e' SBAGLIATO -
l'ho esplicitamente
disabilitato, tanto non era chiamato cmq.
E' da rifare usando SMART POINTERS (boost::scoped_ptr<> o tcg::unique_ptr<>, o
std::unique_ptr<> se facciamo l'upgrade del compilatore).
Da osservare che anche il campo FILE in TPSDReader leaka se viene sganciata
un'eccezione...
*/
#define LEVEL_NAME_INDEX_SEP "@"
//----forward declarations
std::string buildErrorString(int error);
void readChannel(FILE *f, TPSDLayerInfo *li, TPSDChannelInfo *chan,
int channels, TPSDHeaderInfo *h);
void readLongData(FILE *f, struct dictentry *parent, TPSDLayerInfo *li);
void readByteData(FILE *f, struct dictentry *parent, TPSDLayerInfo *li);
void readKey(FILE *f, struct dictentry *parent, TPSDLayerInfo *li);
void readLayer16(FILE *f, struct dictentry *parent, TPSDLayerInfo *li);
//----end forward declarations
static char swapByte(unsigned char src) {
unsigned char out = 0;
for (int i = 0; i < 8; ++i) {
out = out << 1;
out |= (src & 1);
src = src >> 1;
}
return out;
}
TPSDReader::TPSDReader(const TFilePath &path)
: m_shrinkX(1), m_shrinkY(1), m_region(TRect()) {
m_layerId = 0;
QString name = path.getName().c_str();
name.append(path.getDottedType().c_str());
int sepPos = name.indexOf("#");
int dotPos = name.indexOf(".", sepPos);
name.remove(sepPos, dotPos - sepPos);
m_path = path.getParentDir() + TFilePath(name.toStdString());
// m_path = path;
QMutexLocker sl(&m_mutex);
openFile();
if (!doInfo()) {
fclose(m_file);
throw TImageException(m_path, "Do PSD INFO ERROR");
}
fclose(m_file);
}
TPSDReader::~TPSDReader() {
/*for(int i=0; i<m_headerInfo.layersCount;i++)
free(m_headerInfo.linfo + i);*/
// NO - L'istruzione sopra e' SBAGLIATA, si tratta di uno pseudo-array
// allocato in blocco
// con una SINGOLA malloc() - quindi deve dovrebbe essere deallocato con una
// SINGOLA free().
// Se fino ad ora funzionava e' perche' questa funzione NON VENIVA MAI
// CHIAMATA -
// LEAKAVA TUTTO.
// Non solo, ma il CONTENUTO delle singole strutture - tra cui altri array -
// non viene
// deallocato. Non ho idea se sia garantito che venga deallocato prima di
// arrivare qui,
// ma NON E' AFFATTO SICURO.
}
int TPSDReader::openFile() {
m_file = fopen(m_path, "rb");
if (!m_file) throw TImageException(m_path, buildErrorString(2));
return 0;
}
bool TPSDReader::doInfo() {
// Read Header Block
if (!doHeaderInfo()) return false;
// Read Color Mode Data Block
if (!doColorModeData()) return false;
// Read Image Resources Block
if (!doImageResources()) return false;
// Read Layer Info Block (Layers Number and merged alpha)
if (!doLayerAndMaskInfo()) return false;
// Read Layers and Mask Information Block
// if(!doLayersInfo()) return false;
m_headerInfo.layerDataPos = ftell(m_file);
if (m_headerInfo.layersCount == 0) // tento con extra data
{
fseek(m_file, m_headerInfo.layerDataPos, SEEK_SET);
skipBlock(m_file); // skip global layer mask info
psdByte currentPos = ftell(m_file);
psdByte len = m_headerInfo.lmistart + m_headerInfo.lmilen -
currentPos; // 4 = bytes skipped by global layer mask info
doExtraData(NULL, len);
}
return true;
}
// Read Header Block
bool TPSDReader::doHeaderInfo() {
fread(m_headerInfo.sig, 1, 4, m_file);
m_headerInfo.version = read2UBytes(m_file);
read4Bytes(m_file);
read2Bytes(m_file); // reserved[6];
m_headerInfo.channels = read2UBytes(m_file);
m_headerInfo.rows = read4Bytes(m_file);
m_headerInfo.cols = read4Bytes(m_file);
m_headerInfo.depth = read2UBytes(m_file);
m_headerInfo.mode = read2UBytes(m_file);
if (!feof(m_file) && !memcmp(m_headerInfo.sig, "8BPS", 4)) {
if (m_headerInfo.version == 1) {
if (m_headerInfo.channels <= 0 || m_headerInfo.channels > 64 ||
m_headerInfo.rows <= 0 || m_headerInfo.cols <= 0 ||
m_headerInfo.depth < 0 || m_headerInfo.depth > 32 ||
m_headerInfo.mode < 0) {
throw TImageException(m_path, "Reading PSD Header Info error");
return false;
}
} else {
throw TImageException(m_path, "PSD Version not supported");
return false;
}
} else {
throw TImageException(m_path, "Cannot read Header");
return false;
}
return true;
}
// Read Color Mode Data Block
bool TPSDReader::doColorModeData() {
m_headerInfo.colormodepos = ftell(m_file);
skipBlock(m_file); // skip "color mode data"
return true;
}
// Read Image Resources Block
bool TPSDReader::doImageResources() {
// skipBlock(m_file); //skip "image resources"
long len = read4Bytes(m_file); // lunghezza del blocco Image resources
while (len > 0) {
char type[4], name[0x100];
int id, namelen;
long size;
fread(type, 1, 4, m_file);
id = read2Bytes(m_file);
namelen = fgetc(m_file);
fread(name, 1, NEXT2(1 + namelen) - 1, m_file);
name[namelen] = 0;
size = read4Bytes(m_file);
if (id == 1005) // ResolutionInfo
{
psdByte savepos = ftell(m_file);
long hres, vres;
double hresd, vresd;
hresd = FIXDPI(hres = read4Bytes(m_file));
read2Bytes(m_file);
read2Bytes(m_file);
vresd = FIXDPI(vres = read4Bytes(m_file));
m_headerInfo.vres = vresd;
m_headerInfo.hres = hresd;
fseek(m_file, savepos, SEEK_SET);
}
len -= 4 + 2 + NEXT2(1 + namelen) + 4 + NEXT2(size);
fseek(m_file, NEXT2(size), SEEK_CUR); // skip resource block data
}
if (len != 0) return false;
return true;
}
// Read Layer Info Block (Layers Number and merged alpha)
bool TPSDReader::doLayerAndMaskInfo() {
psdByte layerlen;
m_headerInfo.layersCount = 0;
m_headerInfo.lmilen = read4Bytes(m_file);
m_headerInfo.lmistart = ftell(m_file);
if (m_headerInfo.lmilen) {
// process layer info section
layerlen = read4Bytes(m_file);
m_headerInfo.linfoBlockEmpty = false;
m_headerInfo.mergedalpha = 0;
if (layerlen) {
doLayersInfo();
} else {
// WARNING: layer info section empty
}
} else {
// WARNING: layer & mask info section empty
}
return true;
}
// Read Layers Information Block
// It is called by doLayerAndMaskInfo()
bool TPSDReader::doLayersInfo() {
m_headerInfo.layersCount = read2Bytes(m_file);
m_headerInfo.linfoBlockEmpty = false;
m_headerInfo.mergedalpha = m_headerInfo.layersCount < 0;
if (m_headerInfo.mergedalpha > 0) {
m_headerInfo.layersCount = -m_headerInfo.layersCount;
}
if (!m_headerInfo.linfoBlockEmpty) {
m_headerInfo.linfo = (TPSDLayerInfo *)mymalloc(
m_headerInfo.layersCount * sizeof(struct TPSDLayerInfo));
int i = 0;
for (i = 0; i < m_headerInfo.layersCount; i++) {
readLayerInfo(i);
}
}
return true;
}
bool TPSDReader::readLayerInfo(int i) {
psdByte chlen, extralen, extrastart;
int j, chid, namelen;
TPSDLayerInfo *li = m_headerInfo.linfo + i;
// process layer record
li->top = read4Bytes(m_file);
li->left = read4Bytes(m_file);
li->bottom = read4Bytes(m_file);
li->right = read4Bytes(m_file);
li->channels = read2UBytes(m_file);
if (li->bottom < li->top || li->right < li->left ||
li->channels > 64) // sanity ck
{
// qualcosa è andato storto, skippo il livello
fseek(m_file, 6 * li->channels + 12, SEEK_CUR);
skipBlock(m_file); // skip "layer info: extra data";
} else {
li->chan = (TPSDChannelInfo *)mymalloc(li->channels *
sizeof(struct TPSDChannelInfo));
li->chindex = (int *)mymalloc((li->channels + 2) * sizeof(int));
li->chindex += 2; //
for (j = -2; j < li->channels; ++j) li->chindex[j] = -1;
// fetch info on each of the layer's channels
for (j = 0; j < li->channels; ++j) {
chid = li->chan[j].id = read2Bytes(m_file);
chlen = li->chan[j].length = read4Bytes(m_file);
if (chid >= -2 && chid < li->channels)
li->chindex[chid] = j;
else {
// WARNING: unexpected channel id
}
}
fread(li->blend.sig, 1, 4, m_file);
fread(li->blend.key, 1, 4, m_file);
li->blend.opacity = fgetc(m_file);
li->blend.clipping = fgetc(m_file);
li->blend.flags = fgetc(m_file);
fgetc(m_file); // padding
extralen = read4Bytes(m_file);
extrastart = ftell(m_file);
// layer mask data
if ((li->mask.size = read4Bytes(m_file))) {
li->mask.top = read4Bytes(m_file);
li->mask.left = read4Bytes(m_file);
li->mask.bottom = read4Bytes(m_file);
li->mask.right = read4Bytes(m_file);
li->mask.default_colour = fgetc(m_file);
li->mask.flags = fgetc(m_file);
fseek(m_file, li->mask.size - 18, SEEK_CUR); // skip remainder
li->mask.rows = li->mask.bottom - li->mask.top;
li->mask.cols = li->mask.right - li->mask.left;
} else {
// no layer mask data
}
skipBlock(m_file); // skip "layer blending ranges";
// layer name
li->nameno = (char *)malloc(16);
sprintf(li->nameno, "layer%d", i + 1);
namelen = fgetc(m_file);
li->name = (char *)mymalloc(NEXT4(namelen + 1));
fread(li->name, 1, NEXT4(namelen + 1) - 1, m_file);
li->name[namelen] = 0;
if (namelen) {
if (li->name[0] == '.') li->name[0] = '_';
}
// process layer's 'additional info'
li->additionalpos = ftell(m_file);
li->additionallen = extrastart + extralen - li->additionalpos;
doExtraData(li, li->additionallen);
// leave file positioned at end of layer's data
fseek(m_file, extrastart + extralen, SEEK_SET);
}
return true;
}
void TPSDReader::doImage(TRasterP &rasP, int layerId) {
m_layerId = layerId;
int layerIndex = getLayerInfoIndexById(layerId);
TPSDLayerInfo *li = getLayerInfo(layerIndex);
psdByte imageDataEnd;
// retrieve start data pos
psdByte startPos = ftell(m_file);
if (m_headerInfo.layersCount > 0) {
struct TPSDLayerInfo *lilast =
&m_headerInfo.linfo[m_headerInfo.layersCount - 1];
startPos = lilast->additionalpos + lilast->additionallen;
}
if (layerIndex > 0) {
for (int j = 0; j < layerIndex; j++) {
struct TPSDLayerInfo *liprev = &m_headerInfo.linfo[j];
for (int ch = 0; ch < liprev->channels; ch++) {
startPos += liprev->chan[ch].length;
}
}
}
fseek(m_file, startPos, SEEK_SET);
long pixw = li ? li->right - li->left : m_headerInfo.cols;
long pixh = li ? li->bottom - li->top : m_headerInfo.rows;
int channels = li ? li->channels : m_headerInfo.channels;
if (li == NULL)
fseek(m_file, m_headerInfo.lmistart + m_headerInfo.lmilen, SEEK_SET);
psdPixel rows = pixh;
psdPixel cols = pixw;
int ch = 0;
psdByte **rowpos;
rowpos = (psdByte **)mymalloc(channels * sizeof(psdByte *));
for (ch = 0; ch < channels; ++ch) {
psdPixel chrows =
li && !m_headerInfo.linfoBlockEmpty && li->chan[ch].id == -2
? li->mask.rows
: rows;
rowpos[ch] = (psdByte *)mymalloc((chrows + 1) * sizeof(psdByte));
}
int tnzchannels = 0;
int depth = m_headerInfo.depth;
switch (m_headerInfo.mode) {
// default: // multichannel, cmyk, lab etc
// split = 1;
case ModeBitmap:
case ModeGrayScale:
case ModeGray16:
case ModeDuotone:
case ModeDuotone16:
tnzchannels = 1;
// check if there is an alpha channel, or if merged data has alpha
if (li ? li->chindex[-1] != -1 : channels > 1 && m_headerInfo.mergedalpha) {
tnzchannels = 2;
}
break;
case ModeIndexedColor:
tnzchannels = 1;
break;
case ModeRGBColor:
case ModeRGB48:
tnzchannels = 3;
if (li ? li->chindex[-1] != -1 : channels > 3 && m_headerInfo.mergedalpha) {
tnzchannels = 4;
}
break;
default:
tnzchannels = channels;
// assert(0);
break;
}
if (!li || m_headerInfo.linfoBlockEmpty) { // merged channel
TPSDChannelInfo *mergedChans =
(TPSDChannelInfo *)mymalloc(channels * sizeof(struct TPSDChannelInfo));
readChannel(m_file, NULL, mergedChans, channels, &m_headerInfo);
imageDataEnd = ftell(m_file);
readImageData(rasP, NULL, mergedChans, tnzchannels, rows, cols);
free(mergedChans);
} else {
for (ch = 0; ch < channels; ++ch) {
readChannel(m_file, li, li->chan + ch, 1, &m_headerInfo);
}
imageDataEnd = ftell(m_file);
readImageData(rasP, li, li->chan, tnzchannels, rows, cols);
}
fseek(m_file, imageDataEnd, SEEK_SET);
for (ch = 0; ch < channels; ++ch) free(rowpos[ch]);
free(rowpos);
}
void TPSDReader::load(TRasterImageP &img, int layerId) {
QMutexLocker sl(&m_mutex);
TPSDLayerInfo *li = NULL;
int layerIndex = 0;
if (layerId > 0) {
layerIndex = getLayerInfoIndexById(layerId);
li = getLayerInfo(layerIndex);
}
if (layerId < 0) throw TImageException(m_path, "Layer ID not exists");
if (m_headerInfo.mode == 4 || m_headerInfo.depth == 32) {
img = TRasterImageP();
return;
}
try {
TRasterP rasP;
openFile();
doImage(rasP, layerId);
fclose(m_file);
/*
// do savebox
long sbx0 = li ? li->left : 0;
long sby0 = li ? m_headerInfo.rows-li->bottom : 0;
long sbx1 = li ? li->right - 1 : m_headerInfo.cols - 1;
long sby1 = li ? m_headerInfo.rows - li->top - 1 : m_headerInfo.rows - 1;
TRect layerSaveBox;
layerSaveBox = TRect(sbx0,sby0,sbx1,sby1);
TRect imageRect = TRect(0,0,m_headerInfo.cols-1,m_headerInfo.rows-1);
// E' possibile che il layer sia in parte o tutto al di fuori della'immagine
// in questo caso considero solo la parte visibile, cioè che rientra
nell'immagine.
// Se è tutta fuori restutuisco TRasterImageP()
layerSaveBox *= imageRect;
if(layerSaveBox== TRect()) {
img = TRasterImageP();
return;
} */
if (!rasP) {
img = TRasterImageP();
return;
} // Happens if layer image has 0 rows and (or?)
// cols (dont ask me why, but I've seen it)
TRect layerSaveBox = m_layersSavebox[layerId];
TRect savebox(layerSaveBox);
TDimension imgSize(rasP->getLx(), rasP->getLy());
assert(TRect(imgSize).contains(savebox));
if (TRasterGR8P ras = rasP) {
TPixelGR8 bgColor;
ras->fillOutside(savebox, bgColor);
img = TRasterImageP(ras);
} else if (TRaster32P ras = rasP) {
TPixel32 bgColor(0, 0, 0, 0);
if (savebox != TRect())
ras->fillOutside(savebox, bgColor);
else
ras->fill(bgColor);
img = TRasterImageP(ras);
} else if ((TRaster64P)rasP) {
TRaster32P raux(rasP->getLx(), rasP->getLy());
TRop::convert(raux, rasP);
TPixel32 bgColor(0, 0, 0, 0);
raux->fillOutside(savebox, bgColor);
img = TRasterImageP(raux);
} else {
throw TImageException(m_path, "Invalid Raster");
}
img->setDpi(m_headerInfo.hres, m_headerInfo.vres);
img->setSavebox(savebox);
} catch (...) {
}
}
int TPSDReader::getLayerInfoIndexById(int layerId) {
int layerIndex = -1;
for (int i = 0; i < m_headerInfo.layersCount; i++) {
TPSDLayerInfo *litemp = m_headerInfo.linfo + i;
if (litemp->layerId == layerId) {
layerIndex = i;
break;
}
}
if (layerIndex < 0 && layerId != 0)
throw TImageException(m_path, "Layer ID not exists");
return layerIndex;
}
TPSDLayerInfo *TPSDReader::getLayerInfo(int index) {
if (index < 0 || index >= m_headerInfo.layersCount) return NULL;
return m_headerInfo.linfo + index;
}
TPSDHeaderInfo TPSDReader::getPSDHeaderInfo() { return m_headerInfo; }
void TPSDReader::readImageData(TRasterP &rasP, TPSDLayerInfo *li,
TPSDChannelInfo *chan, int chancount,
psdPixel rows, psdPixel cols) {
int channels = li ? li->channels : m_headerInfo.channels;
short depth = m_headerInfo.depth;
psdByte savepos = ftell(m_file);
if (rows == 0 || cols == 0) return;
psdPixel j;
unsigned char *inrows[4], *rledata;
int ch, map[4];
rledata = (unsigned char *)mymalloc(chan->rowbytes * 2);
for (ch = 0; ch < chancount; ++ch) {
inrows[ch] = (unsigned char *)mymalloc(chan->rowbytes);
map[ch] = li && chancount > 1 ? li->chindex[ch] : ch;
}
// find the alpha channel, if needed
if (li && (chancount == 2 || chancount == 4)) { // grey+alpha
if (li->chindex[-1] == -1) {
// WARNING no alpha found?;
} else
map[chancount - 1] = li->chindex[-1];
}
// region dimensions with shrink
// x0 e x1 non tengono conto dello shrink.
int x0 = 0;
int x1 = m_headerInfo.cols - 1;
int y0 = 0;
int y1 = m_headerInfo.rows - 1;
if (!m_region.isEmpty()) {
x0 = m_region.getP00().x;
// se x0 è fuori dalle dimensioni dell'immagine ritorna un'immagine vuota
if (x0 >= m_headerInfo.cols) {
free(rledata);
return;
}
x1 = x0 + m_region.getLx() - 1;
// controllo che x1 rimanga all'interno dell'immagine
if (x1 >= m_headerInfo.cols) x1 = m_headerInfo.cols - 1;
y0 = m_region.getP00().y;
// se y0 è fuori dalle dimensioni dell'immagine ritorna un'immagine vuota
if (y0 >= m_headerInfo.rows) {
free(rledata);
return;
}
y1 = y0 + m_region.getLy() - 1;
// controllo che y1 rimanga all'interno dell'immagine
if (y1 >= m_headerInfo.rows) y1 = m_headerInfo.rows - 1;
}
if (m_shrinkX > x1 - x0) m_shrinkX = x1 - x0;
if (m_shrinkY > y1 - y0) m_shrinkY = y1 - y0;
assert(m_shrinkX > 0 && m_shrinkY > 0);
if (m_shrinkX > 1) {
x1 -= (x1 - x0) % m_shrinkX;
}
if (m_shrinkY > 1) {
y1 -= (y1 - y0) % m_shrinkY;
}
assert(x0 <= x1 && y0 <= y1);
TDimension imgSize((x1 - x0) / m_shrinkX + 1, (y1 - y0) / m_shrinkY + 1);
if (depth == 1 && chancount == 1) {
rasP = TRasterGR8P(imgSize);
} else if (depth == 8 && chancount > 1) {
rasP = TRaster32P(imgSize);
} else if (m_headerInfo.depth == 8 && chancount == 1) {
rasP = TRasterGR8P(imgSize);
} else if (m_headerInfo.depth == 16 && chancount == 1 &&
m_headerInfo.mergedalpha) {
rasP = TRasterGR8P(imgSize);
} else if (m_headerInfo.depth == 16) {
rasP = TRaster64P(imgSize);
}
// do savebox
// calcolo la savebox in coordinate dell'immagine
long sbx0 = li ? li->left - x0 : 0;
long sby0 = li ? m_headerInfo.rows - li->bottom - y0 : 0;
long sbx1 = li ? li->right - 1 - x0 : x1 - x0;
long sby1 = li ? m_headerInfo.rows - li->top - 1 - y0 : y1 - y0;
TRect layerSaveBox;
layerSaveBox = TRect(sbx0, sby0, sbx1, sby1);
TRect imageRect;
if (!m_region.isEmpty())
imageRect = TRect(0, 0, m_region.getLx() - 1, m_region.getLy() - 1);
else
imageRect = TRect(0, 0, m_headerInfo.cols - 1, m_headerInfo.rows - 1);
// E' possibile che il layer sia in parte o tutto al di fuori della'immagine
// in questo caso considero solo la parte visibile, cioè che rientra
// nell'immagine.
// Se è tutta fuori restutuisco TRasterImageP()
layerSaveBox *= imageRect;
if (layerSaveBox == TRect() || layerSaveBox.isEmpty()) {
free(rledata);
return;
}
// Estraggo da rasP solo il rettangolo che si interseca con il livello
// corrente
// stando attento a prendere i pixel giusti.
int firstXPixIndexOfLayer = layerSaveBox.getP00().x - 1 + m_shrinkX -
(abs(layerSaveBox.getP00().x - 1) % m_shrinkX);
int lrx0 = firstXPixIndexOfLayer / m_shrinkX;
int firstLineIndexOfLayer = layerSaveBox.getP00().y - 1 + m_shrinkY -
(abs(layerSaveBox.getP00().y - 1) % m_shrinkY);
int lry0 = firstLineIndexOfLayer / m_shrinkY;
int lrx1 =
(layerSaveBox.getP11().x - abs(layerSaveBox.getP11().x % m_shrinkX)) /
m_shrinkX;
int lry1 =
(layerSaveBox.getP11().y - abs(layerSaveBox.getP11().y % m_shrinkY)) /
m_shrinkY;
TRect layerSaveBox2 = TRect(lrx0, lry0, lrx1, lry1);
if (layerSaveBox2.isEmpty()) return;
assert(TRect(imgSize).contains(layerSaveBox2));
if (li)
m_layersSavebox[li->layerId] = layerSaveBox2;
else
m_layersSavebox[0] = layerSaveBox2;
TRasterP smallRas = rasP->extract(layerSaveBox2);
assert(smallRas);
if (!smallRas) return;
// Trovo l'indice di colonna del primo pixel del livello che deve essere letto
// L'indice è riferito al livello.
int colOffset = firstXPixIndexOfLayer - layerSaveBox.getP00().x;
assert(colOffset >= 0);
// Trovo l'indice della prima riga del livello che deve essere letta
// L'indice è riferito al livello.
// Nota che nel file photoshop le righe sono memorizzate dall'ultima alla
// prima.
int rowOffset = abs(sby1) % m_shrinkY;
int rowCount = rowOffset;
// if(m_shrinkY==3) rowCount--;
for (j = 0; j < smallRas->getLy(); j++) {
for (ch = 0; ch < chancount; ++ch) {
/* get row data */
if (map[ch] < 0 || map[ch] > chancount) {
// warn("bad map[%d]=%d, skipping a channel", i, map[i]);
memset(inrows[ch], 0, chan->rowbytes); // zero out the row
} else
readrow(m_file, chan + map[ch], rowCount, inrows[ch], rledata);
}
// se la riga corrente non rientra nell'immagine salto la copia
if (sby1 - rowCount < 0 || sby1 - rowCount > m_headerInfo.rows - 1) {
rowCount += m_shrinkY;
continue;
}
if (depth == 1 && chancount == 1) {
if (!(layerSaveBox.getP00().x - sbx0 >= 0 &&
layerSaveBox.getP00().x - sbx0 + smallRas->getLx() / 8 - 1 <
chan->rowbytes))
throw TImageException(
m_path, "Unable to read image with this depth and channels values");
smallRas->lock();
unsigned char *rawdata =
(unsigned char *)smallRas->getRawData(0, smallRas->getLy() - j - 1);
TPixelGR8 *pix = (TPixelGR8 *)rawdata;
int colCount = colOffset;
for (int k = 0; k < smallRas->getLx(); k += 8) {
char value = ~inrows[0][layerSaveBox.getP00().x - sbx0 + colCount];
pix[k].setValue(value);
pix[k + 1].setValue(value);
pix[k + 2].setValue(value);
pix[k + 3].setValue(value);
pix[k + 4].setValue(value);
pix[k + 5].setValue(value);
pix[k + 6].setValue(value);
pix[k + 7].setValue(value);
colCount += m_shrinkX;
}
smallRas->unlock();
} else if (depth == 8 && chancount > 1) {
if (!(layerSaveBox.getP00().x - sbx0 >= 0 &&
layerSaveBox.getP00().x - sbx0 + smallRas->getLx() - 1 <
chan->rowbytes))
throw TImageException(
m_path, "Unable to read image with this depth and channels values");
smallRas->lock();
unsigned char *rawdata =
(unsigned char *)smallRas->getRawData(0, smallRas->getLy() - j - 1);
TPixel32 *pix = (TPixel32 *)rawdata;
int colCount = colOffset;
for (int k = 0; k < smallRas->getLx(); k++) {
if (chancount >= 3) {
pix[k].r = inrows[0][layerSaveBox.getP00().x - sbx0 + colCount];
pix[k].g = inrows[1][layerSaveBox.getP00().x - sbx0 + colCount];
pix[k].b = inrows[2][layerSaveBox.getP00().x - sbx0 + colCount];
if (chancount == 4) // RGB + alpha
pix[k].m = inrows[3][layerSaveBox.getP00().x - sbx0 + colCount];
else
pix[k].m = 255;
} else if (chancount <= 2) // gray + alpha
{
pix[k].r = inrows[0][layerSaveBox.getP00().x - sbx0 + colCount];
pix[k].g = inrows[0][layerSaveBox.getP00().x - sbx0 + colCount];
pix[k].b = inrows[0][layerSaveBox.getP00().x - sbx0 + colCount];
if (chancount == 2)
pix[k].m = inrows[1][layerSaveBox.getP00().x - sbx0 + colCount];
else
pix[k].m = 255;
}
colCount += m_shrinkX;
}
smallRas->unlock();
} else if (m_headerInfo.depth == 8 && chancount == 1) {
if (!(layerSaveBox.getP00().x - sbx0 >= 0 &&
layerSaveBox.getP00().x - sbx0 + smallRas->getLx() - 1 <
chan->rowbytes))
throw TImageException(
m_path, "Unable to read image with this depth and channels values");
smallRas->lock();
unsigned char *rawdata =
(unsigned char *)smallRas->getRawData(0, smallRas->getLy() - j - 1);
TPixelGR8 *pix = (TPixelGR8 *)rawdata;
int colCount = colOffset;
for (int k = 0; k < smallRas->getLx(); k++) {
pix[k].setValue(inrows[0][layerSaveBox.getP00().x - sbx0 + colCount]);
colCount += m_shrinkX;
}
smallRas->unlock();
} else if (m_headerInfo.depth == 16 && chancount == 1 &&
m_headerInfo.mergedalpha) // mergedChannels
{
if (!(layerSaveBox.getP00().x - sbx0 >= 0 &&
layerSaveBox.getP00().x - sbx0 + smallRas->getLx() - 1 <
chan->rowbytes))
throw TImageException(
m_path, "Unable to read image with this depth and channels values");
smallRas->lock();
unsigned char *rawdata =
(unsigned char *)smallRas->getRawData(0, smallRas->getLy() - j - 1);
TPixelGR8 *pix = (TPixelGR8 *)rawdata;
int colCount = colOffset;
for (int k = 0; k < smallRas->getLx(); k++) {
pix[k].setValue(inrows[0][layerSaveBox.getP00().x - sbx0 + colCount]);
colCount += m_shrinkX;
}
smallRas->unlock();
} else if (m_headerInfo.depth == 16) {
if (!(layerSaveBox.getP00().x - sbx0 >= 0 &&
layerSaveBox.getP00().x - sbx0 + smallRas->getLx() - 1 <
chan->rowbytes))
throw TImageException(
m_path, "Unable to read image with this depth and channels values");
smallRas->lock();
unsigned short *rawdata =
(unsigned short *)smallRas->getRawData(0, smallRas->getLy() - j - 1);
TPixel64 *pix = (TPixel64 *)rawdata;
int colCount = colOffset;
for (int k = 0; k < smallRas->getLx(); k++) {
if (chancount >= 3) {
pix[k].r = swapShort(
((psdUint16 *)
inrows[0])[layerSaveBox.getP00().x - sbx0 + colCount]);
pix[k].g = swapShort(
((psdUint16 *)
inrows[1])[layerSaveBox.getP00().x - sbx0 + colCount]);
pix[k].b = swapShort(
((psdUint16 *)
inrows[2])[layerSaveBox.getP00().x - sbx0 + colCount]);
} else if (chancount <= 2) {
pix[k].r = swapShort(
((psdUint16 *)
inrows[0])[layerSaveBox.getP00().x - sbx0 + colCount]);
pix[k].g = swapShort(
((psdUint16 *)
inrows[0])[layerSaveBox.getP00().x - sbx0 + colCount]);
pix[k].b = swapShort(
((psdUint16 *)
inrows[0])[layerSaveBox.getP00().x - sbx0 + colCount]);
if (chancount == 2)
pix[k].m = swapShort(
((psdUint16 *)
inrows[1])[layerSaveBox.getP00().x - sbx0 + colCount]);
}
if (chancount == 4) {
pix[k].m = swapShort(
((psdUint16 *)
inrows[3])[layerSaveBox.getP00().x - sbx0 + colCount]);
} else
pix[k].m = 0xffff;
colCount += m_shrinkX;
}
smallRas->unlock();
} else {
throw TImageException(
m_path, "Unable to read image with this depth and channels values");
}
rowCount += m_shrinkY;
}
fseek(m_file, savepos, SEEK_SET); // restoring filepos
free(rledata);
for (ch = 0; ch < chancount; ++ch) free(inrows[ch]);
}
void TPSDReader::doExtraData(TPSDLayerInfo *li, psdByte length) {
static struct dictentry extradict[] = {
// v4.0
{0, "levl", "LEVELS", "Levels", NULL /*adj_levels*/},
{0, "curv", "CURVES", "Curves", NULL /*adj_curves*/},
{0, "brit", "BRIGHTNESSCONTRAST", "Brightness/contrast", NULL},
{0, "blnc", "COLORBALANCE", "Color balance", NULL},
{0, "hue ", "HUESATURATION4", "Old Hue/saturation, Photoshop 4.0",
NULL /*adj_huesat4*/},
{0, "hue2", "HUESATURATION5", "New Hue/saturation, Photoshop 5.0",
NULL /*adj_huesat5*/},
{0, "selc", "SELECTIVECOLOR", "Selective color", NULL /*adj_selcol*/},
{0, "thrs", "THRESHOLD", "Threshold", NULL},
{0, "nvrt", "INVERT", "Invert", NULL},
{0, "post", "POSTERIZE", "Posterize", NULL},
// v5.0
{0, "lrFX", "EFFECT", "Effects layer", NULL /*ed_layereffects*/},
{0, "tySh", "TYPETOOL5", "Type tool (5.0)", NULL /*ed_typetool*/},
{0, "luni", "-UNICODENAME", "Unicode layer name",
NULL /*ed_unicodename*/},
{0, "lyid", "-LAYERID", "Layer ID",
readLongData}, // '-' prefix means keep tag value on one line
// v6.0
{0, "lfx2", "OBJECTEFFECT", "Object based effects layer",
NULL /*ed_objecteffects*/},
{0, "Patt", "PATTERN", "Pattern", NULL},
{0, "Pat2", "PATTERNCS", "Pattern (CS)", NULL},
{0, "Anno", "ANNOTATION", "Annotation", NULL /*ed_annotation*/},
{0, "clbl", "-BLENDCLIPPING", "Blend clipping", readByteData},
{0, "infx", "-BLENDINTERIOR", "Blend interior", readByteData},
{0, "knko", "-KNOCKOUT", "Knockout", readByteData},
{0, "lspf", "-PROTECTED", "Protected", readLongData},
{0, "lclr", "SHEETCOLOR", "Sheet color", NULL},
{0, "fxrp", "-REFERENCEPOINT", "Reference point",
NULL /*ed_referencepoint*/},
{0, "grdm", "GRADIENT", "Gradient", NULL},
{0, "lsct", "-SECTION", "Section divider", readLongData}, // CS doc
{0, "SoCo", "SOLIDCOLORSHEET", "Solid color sheet",
NULL /*ed_versdesc*/}, // CS doc
{0, "PtFl", "PATTERNFILL", "Pattern fill",
NULL /*ed_versdesc*/}, // CS doc
{0, "GdFl", "GRADIENTFILL", "Gradient fill",
NULL /*ed_versdesc*/}, // CS doc
{0, "vmsk", "VECTORMASK", "Vector mask", NULL}, // CS doc
{0, "TySh", "TYPETOOL6", "Type tool (6.0)",
NULL /*ed_typetool*/}, // CS doc
{0, "ffxi", "-FOREIGNEFFECTID", "Foreign effect ID",
readLongData}, // CS doc (this is probably a key too, who knows)
{0, "lnsr", "-LAYERNAMESOURCE", "Layer name source",
readKey}, // CS doc (who knew this was a signature? docs fail again -
// and what do the values mean?)
{0, "shpa", "PATTERNDATA", "Pattern data", NULL}, // CS doc
{0, "shmd", "METADATASETTING", "Metadata setting",
NULL /*ed_metadata*/}, // CS doc
{0, "brst", "BLENDINGRESTRICTIONS", "Channel blending restrictions",
NULL}, // CS doc
// v7.0
{0, "lyvr", "-LAYERVERSION", "Layer version", readLongData}, // CS doc
{0, "tsly", "-TRANSPARENCYSHAPES", "Transparency shapes layer",
readByteData}, // CS doc
{0, "lmgm", "-LAYERMASKASGLOBALMASK", "Layer mask as global mask",
readByteData}, // CS doc
{0, "vmgm", "-VECTORMASKASGLOBALMASK", "Vector mask as global mask",
readByteData}, // CS doc
// CS
{0, "mixr", "CHANNELMIXER", "Channel mixer", NULL}, // CS doc
{0, "phfl", "PHOTOFILTER", "Photo Filter", NULL}, // CS doc
{0, "Lr16", "LAYER16", "Layer 16", readLayer16},
{0, NULL, NULL, NULL, NULL}};
while (length >= 12) {
psdByte block = sigkeyblock(m_file, extradict, li);
if (!block) {
// warn("bad signature in layer's extra data, skipping the rest");
break;
}
length -= block;
}
}
struct dictentry *TPSDReader::findbykey(FILE *f, struct dictentry *parent,
char *key, TPSDLayerInfo *li) {
struct dictentry *d;
for (d = parent; d->key; ++d)
if (!memcmp(key, d->key, 4)) {
// char *tagname = d->tag + (d->tag[0] == '-');
// fprintf(stderr, "matched tag %s\n", d->tag);
if (d->func) {
psdByte savepos = ftell(f);
// int oneline = d->tag[0] == '-';
// char *tagname = d->tag + oneline;
if (memcmp(key, "Lr16", 4) == 0) {
doLayersInfo();
} else
d->func(f, d, li); // parse contents of this datum
fseek(f, savepos, SEEK_SET);
} else {
// there is no function to parse this block.
// because tag is empty in this case, we only need to consider
// parent's one-line-ness.
}
return d;
}
return NULL;
}
int TPSDReader::sigkeyblock(FILE *f, struct dictentry *dict,
TPSDLayerInfo *li) {
char sig[4], key[4];
long len;
struct dictentry *d;
fread(sig, 1, 4, f);
fread(key, 1, 4, f);
len = read4Bytes(f);
if (!memcmp(sig, "8BIM", 4)) {
if (dict && (d = findbykey(f, dict, key, li)) && !d->func) {
// there is no function to parse this block
// UNQUIET(" (data: %s)\n", d->desc);
}
fseek(f, len, SEEK_CUR);
return len + 12; // return number of bytes consumed
}
return 0; // bad signature
}
//---------------------------- Utility functions
std::string buildErrorString(int error) {
std::string message = "";
switch (error) {
case 0:
message = "NO Error Found";
break;
case 1:
message = "Reading File Error";
break;
case 2:
message = "Opening File Error";
break;
default:
message = "Unknown Error";
}
return message;
}
void readChannel(FILE *f, TPSDLayerInfo *li,
TPSDChannelInfo *chan, // channel info array
int channels, TPSDHeaderInfo *h) {
int comp, ch;
psdByte pos, chpos, rb;
unsigned char *zipdata;
psdPixel count, last, j;
chpos = ftell(f);
if (li) {
// If this is a layer mask, the pixel size is a special case
if (chan->id == -2) {
chan->rows = li->mask.rows;
chan->cols = li->mask.cols;
} else {
// channel has dimensions of the layer
chan->rows = li->bottom - li->top;
chan->cols = li->right - li->left;
}
} else {
// merged image, has dimensions of PSD
chan->rows = h->rows;
chan->cols = h->cols;
}
// Compute image row bytes
rb = ((long)chan->cols * h->depth + 7) / 8;
// Read compression type
comp = read2UBytes(f);
// Prepare compressed data for later access:
pos = chpos + 2;
// skip rle counts, leave pos pointing to first compressed image row
if (comp == RLECOMP) pos += (channels * chan->rows) << h->version;
for (ch = 0; ch < channels; ++ch) {
if (!li) chan[ch].id = ch;
chan[ch].rowbytes = rb;
chan[ch].comptype = comp;
chan[ch].rows = chan->rows;
chan[ch].cols = chan->cols;
chan[ch].filepos = pos;
if (!chan->rows) continue;
// For RLE, we read the row count array and compute file positions.
// For ZIP, read and decompress whole channel.
switch (comp) {
case RAWDATA:
pos += chan->rowbytes * chan->rows;
break;
case RLECOMP:
/* accumulate RLE counts, to make array of row start positions */
chan[ch].rowpos =
(psdByte *)mymalloc((chan[ch].rows + 1) * sizeof(psdByte));
last = chan[ch].rowbytes;
for (j = 0; j < chan[ch].rows && !feof(f); ++j) {
count = h->version == 1 ? read2UBytes(f) : (unsigned long)read4Bytes(f);
if (count > 2 * chan[ch].rowbytes) // this would be impossible
count = last; // make a guess, to help recover
last = count;
chan[ch].rowpos[j] = pos;
pos += count;
}
if (j < chan[ch].rows) {
// fatal error couldn't read RLE counts
}
chan[ch].rowpos[j] = pos; /* = end of last row */
break;
case ZIPWITHOUTPREDICTION:
case ZIPWITHPREDICTION:
if (li) {
pos += chan->length - 2;
zipdata = (unsigned char *)mymalloc(chan->length);
count = fread(zipdata, 1, chan->length - 2, f);
// if(count < chan->length - 2)
// alwayswarn("ZIP data short: wanted %d bytes, got %d",
// chan->length, count);
chan->unzipdata =
(unsigned char *)mymalloc(chan->rows * chan->rowbytes);
if (comp == ZIPWITHOUTPREDICTION)
psdUnzipWithoutPrediction(zipdata, count, chan->unzipdata,
chan->rows * chan->rowbytes);
else
psdUnzipWithPrediction(zipdata, count, chan->unzipdata,
chan->rows * chan->rowbytes, chan->cols,
h->depth);
free(zipdata);
} else {
// WARNING cannot process ZIP compression outside layer
}
break;
default: {
// BAD COMPRESSION TYPE
}
if (li) fseek(f, chan->length - 2, SEEK_CUR);
break;
}
}
// if(li && pos != chpos + chan->length)
// alwayswarn("# channel data is %ld bytes, but length = %ld\n",
// pos - chpos, chan->length);
// set at the end of channel's data
fseek(f, pos, SEEK_SET);
}
void readLongData(FILE *f, struct dictentry *parent, TPSDLayerInfo *li) {
unsigned long id = read4Bytes(f);
if (strcmp(parent->key, "lyid") == 0)
li->layerId = id;
else if (strcmp(parent->key, "lspf") == 0)
li->protect = id;
else if (strcmp(parent->key, "lsct") == 0)
li->section = id;
else if (strcmp(parent->key, "ffxi") == 0)
li->foreignEffectID = id;
else if (strcmp(parent->key, "lyvr") == 0)
li->layerVersion = id;
}
void readByteData(FILE *f, struct dictentry *parent, TPSDLayerInfo *li) {
int id = fgetc(f);
if (strcmp(parent->key, "clbl") == 0)
li->blendClipping = id;
else if (strcmp(parent->key, "infx") == 0)
li->blendInterior = id;
else if (strcmp(parent->key, "knko") == 0)
li->knockout = id;
else if (strcmp(parent->key, "tsly") == 0)
li->transparencyShapes = id;
else if (strcmp(parent->key, "lmgm") == 0)
li->layerMaskAsGlobalMask = id;
else if (strcmp(parent->key, "vmgm") == 0)
li->vectorMaskAsGlobalMask = id;
}
void readKey(FILE *f, struct dictentry *parent, TPSDLayerInfo *li) {
static char key[5];
if (fread(key, 1, 4, f) == 4)
key[4] = 0;
else
key[0] = 0; // or return NULL?
if (strcmp(parent->key, "lnsr") == 0) li->layerNameSource = key;
}
void readLayer16(FILE *f, struct dictentry *parent, TPSDLayerInfo *li) {
// struct psd_header h2 = *psd_header; // a kind of 'nested' set of layers;
// don't alter main PSD header
// overwrite main PSD header, mainly because of the 'merged alpha' flag
// I *think* they mean us to respect the one in Lr16 because in my test data,
// the main layer count is zero, so cannot convey this information.
// dolayerinfo(f, psd_header);
// processlayers(f, psd_header);
}
//---------------------------- END Utility functions
// TPSD PARSER
TPSDParser::TPSDParser(const TFilePath &path) {
m_path = path;
QString name = path.getName().c_str();
name.append(path.getDottedType().c_str());
int sepPos = name.indexOf("#");
int dotPos = name.indexOf(".", sepPos);
name.remove(sepPos, dotPos - sepPos);
TFilePath psdpath = m_path.getParentDir() + TFilePath(name.toStdString());
m_psdreader = new TPSDReader(psdpath);
doLevels();
}
TPSDParser::~TPSDParser() { delete m_psdreader; }
void TPSDParser::doLevels() {
QString path = m_path.getName().c_str();
QStringList list = path.split("#");
m_levels.clear();
if (list.size() > 1) {
TPSDHeaderInfo psdheader = m_psdreader->getPSDHeaderInfo();
if (list.contains("frames") && list.at(0) != "frames") {
int firstLayerId = 0;
Level level;
for (int i = 0; i < psdheader.layersCount; i++) {
TPSDLayerInfo *li = m_psdreader->getLayerInfo(i);
long width = li->right - li->left;
long height = li->bottom - li->top;
if (width > 0 && height > 0) {
assert(li->layerId >= 0);
if (i == 0) firstLayerId = li->layerId;
level.addFrame(li->layerId);
}
}
// non ha importanza quale layerId assegno, l'importante è che esista
level.setLayerId(0);
if (psdheader.layersCount == 0)
level.addFrame(firstLayerId); // succede nel caso in cui la psd non ha
// blocco layerInfo
m_levels.push_back(level);
} else if (list.size() == 3) {
if (list.at(2) == "group") {
int folderTagOpen = 0;
int scavenge = 0;
for (int i = 0; i < psdheader.layersCount; i++) {
TPSDLayerInfo *li = m_psdreader->getLayerInfo(i);
long width = li->right - li->left;
long height = li->bottom - li->top;
if (width > 0 && height > 0 && folderTagOpen == 0) {
assert(li->layerId >= 0);
Level level(li->name, li->layerId);
level.addFrame(li->layerId);
m_levels.push_back(level);
scavenge = 0;
} else {
if (width != 0 && height != 0) {
m_levels[m_levels.size() - 1 - (scavenge - folderTagOpen)]
.addFrame(li->layerId);
} else {
if (strcmp(li->name, "</Layer group>") == 0 ||
strcmp(li->name, "</Layer set>") == 0) {
assert(li->layerId >= 0);
Level level(li->name, li->layerId, true);
m_levels.push_back(level);
folderTagOpen++;
scavenge = folderTagOpen;
} else if (li->section > 0 &&
li->section <= 3) // vedi specifiche psd
{
assert(li->layerId >= 0);
m_levels[m_levels.size() - 1 - (scavenge - folderTagOpen)]
.setName(li->name);
m_levels[m_levels.size() - 1 - (scavenge - folderTagOpen)]
.setLayerId(li->layerId);
folderTagOpen--;
if (folderTagOpen > 0)
m_levels[m_levels.size() - 1 - (scavenge - folderTagOpen)]
.addFrame(li->layerId, true);
}
}
}
}
if (psdheader.layersCount ==
0) // succede nel caso in cui la psd non ha blocco layerInfo
{
Level level;
level.setLayerId(0);
level.addFrame(0);
m_levels.push_back(level);
}
} else
throw TImageException(m_path, "PSD code name error");
} else if (list.size() == 2) // each psd layer is a tlevel
{
TPSDHeaderInfo psdheader = m_psdreader->getPSDHeaderInfo();
for (int i = 0; i < psdheader.layersCount; i++) {
TPSDLayerInfo *li = m_psdreader->getLayerInfo(i);
long width = li->right - li->left;
long height = li->bottom - li->top;
if (width > 0 && height > 0) {
assert(li->layerId >= 0);
Level level(li->name, li->layerId);
level.addFrame(li->layerId);
m_levels.push_back(level);
}
}
if (psdheader.layersCount ==
0) // succede nel caso in cui la psd non ha blocco layerInfo
{
Level level;
level.setLayerId(0);
level.addFrame(0);
m_levels.push_back(level);
}
} else
throw TImageException(m_path, "PSD code name error");
} else // list.size()==1. float
{
Level level;
level.setName(m_path.getName());
level.addFrame(0);
m_levels.push_back(level);
}
}
void TPSDParser::load(TRasterImageP &rasP, int layerId) {
m_psdreader->load(rasP, layerId);
}
int TPSDParser::getLevelIndexById(int layerId) {
int layerIndex = -1;
for (int i = 0; i < (int)m_levels.size(); i++) {
if (m_levels[i].getLayerId() == layerId) {
layerIndex = i;
break;
}
}
if (layerId == 0 && layerIndex < 0) layerIndex = 0;
if (layerIndex < 0 && layerId != 0)
throw TImageException(m_path, "Layer ID not exists");
return layerIndex;
}
int TPSDParser::getLevelIdByName(std::string levelName) {
int pos = levelName.find_last_of(LEVEL_NAME_INDEX_SEP);
int counter = 0;
if (pos != std::string::npos) {
counter = atoi(levelName.substr(pos + 1).c_str());
levelName = levelName.substr(0, pos);
}
int lyid = -1;
int levelNameCount = 0;
for (int i = 0; i < (int)m_levels.size(); i++) {
if (m_levels[i].getName() == levelName) {
lyid = m_levels[i].getLayerId();
if (counter == levelNameCount) break;
levelNameCount++;
}
}
if (lyid == 0 && lyid < 0) lyid = 0;
if (lyid < 0 && lyid != 0)
throw TImageException(m_path, "Layer ID not exists");
return lyid;
}
int TPSDParser::getFramesCount(int levelId) {
int levelIndex = getLevelIndexById(levelId);
assert(levelIndex >= 0 && levelIndex < (int)m_levels.size());
return m_levels[levelIndex].getFrameCount();
}
std::string TPSDParser::getLevelName(int levelId) {
int levelIndex = getLevelIndexById(levelId);
assert(levelIndex >= 0 && levelIndex < (int)m_levels.size());
return m_levels[levelIndex].getName();
}
std::string TPSDParser::getLevelNameWithCounter(int levelId) {
std::string levelName = getLevelName(levelId);
int count = 0;
for (int i = 0; i < (int)m_levels.size(); i++) {
if (m_levels[i].getName() == levelName) {
if (m_levels[i].getLayerId() == levelId) {
break;
}
count++;
}
}
if (count > 0) {
levelName.append(LEVEL_NAME_INDEX_SEP);
std::string c = QString::number(count).toStdString();
levelName.append(c);
}
return levelName;
}