diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.h b/krita/libpsd/asl/kis_offset_keeper.h similarity index 54% copy from krita/plugins/formats/psd/tests/kis_psd_test.h copy to krita/libpsd/asl/kis_offset_keeper.h index bc45a0ccf0f..6b4308736e0 100644 --- a/krita/plugins/formats/psd/tests/kis_psd_test.h +++ b/krita/libpsd/asl/kis_offset_keeper.h @@ -1,32 +1,52 @@ /* - * Copyright (C) 2009 Boudewijn Rempt + * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_PSD_TEST_H_ -#define _KIS_PSD_TEST_H_ +#ifndef __KIS_OFFSET_KEEPER_H +#define __KIS_OFFSET_KEEPER_H -#include -class KisPSDTest : public QObject +#include +#include + +/** + * Restore the offset of the io device on exit from the current + * namespace + */ + +class KisOffsetKeeper { - Q_OBJECT -private Q_SLOTS: - void testFiles(); - void testOpening(); +public: + + KisOffsetKeeper(QIODevice *device) + : m_device(device) + { + m_expectedPos = m_device->pos(); + } + + ~KisOffsetKeeper() { + if (m_device->pos() != m_expectedPos) { + m_device->seek(m_expectedPos); + } + } + +private: + QIODevice *m_device; + qint64 m_expectedPos; }; -#endif +#endif /* __KIS_OFFSET_KEEPER_H */ diff --git a/krita/plugins/formats/psd/psd_layer_record.cpp b/krita/plugins/formats/psd/psd_layer_record.cpp index 89694720b0d..9b225223a3e 100644 --- a/krita/plugins/formats/psd/psd_layer_record.cpp +++ b/krita/plugins/formats/psd/psd_layer_record.cpp @@ -1,1152 +1,1165 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_layer_record.h" #include // htonl #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include "psd_utils.h" #include "psd_header.h" #include "compression.h" #include #include #include +#include + + // Just for pretty debug messages QString channelIdToChannelType(int channelId, psd_color_mode colormode) { switch(channelId) { case -3: return "Real User Supplied Layer Mask (when both a user mask and a vector mask are present"; case -2: return "User Supplied Layer Mask"; case -1: return "Transparency mask"; case 0: switch(colormode) { case Bitmap: case Indexed: return QString("bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return "gray"; case RGB: case RGB48: return "red"; case Lab: case Lab48: return "L"; case CMYK: case CMYK64: return "cyan"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 1: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return "green"; case Lab: case Lab48: return "a"; case CMYK: case CMYK64: return "Magenta"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 2: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return "blue"; case Lab: case Lab48: return "b"; case CMYK: case CMYK64: return "yellow"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; case 3: switch(colormode) { case Bitmap: case Indexed: return QString("WARNING bitmap or indexed: %1").arg(channelId); case Grayscale: case Gray16: return QString("WARNING: %1").arg(channelId); case RGB: case RGB48: return QString("alpha: %1").arg(channelId); case Lab: case Lab48: return QString("alpha: %1").arg(channelId); case CMYK: case CMYK64: return "Key"; case MultiChannel: case DeepMultichannel: return QString("multichannel channel %1").arg(channelId); case DuoTone: case Duotone16: return QString("duotone channel %1").arg(channelId); default: return QString("unknown: %1").arg(channelId); }; default: return QString("unknown: %1").arg(channelId); }; } PSDLayerRecord::PSDLayerRecord(const PSDHeader& header) : top(0) , left(0) , bottom(0) , right(0) , nChannels(0) , opacity(0) , clipping(0) , transparencyProtected(false) , visible(true) , irrelevant(false) , layerName("UNINITIALIZED") , m_header(header) { infoBlocks.m_header = header; } bool PSDLayerRecord::read(QIODevice* io) { dbgFile << "Going to read layer record. Pos:" << io->pos(); if (!psdread(io, &top) || !psdread(io, &left) || !psdread(io, &bottom) || !psdread(io, &right) || !psdread(io, &nChannels)) { error = "could not read layer record"; return false; } dbgFile << "\ttop" << top << "left" << left << "bottom" << bottom << "right" << right << "number of channels" << nChannels; Q_ASSERT(top <= bottom); Q_ASSERT(left <= right); Q_ASSERT(nChannels > 0); switch(m_header.colormode) { case(Bitmap): case(Indexed): case(DuoTone): case(Grayscale): case(MultiChannel): if (nChannels < 1) { error = QString("Not enough channels. Got: %1").arg(nChannels); return false; } break; case(RGB): case(CMYK): case(Lab): default: if (nChannels < 3) { error = QString("Not enough channels. Got: %1").arg(nChannels); return false; } break; }; if (nChannels > MAX_CHANNELS) { error = QString("Too many channels. Got: %1").arg(nChannels); return false; } for (int i = 0; i < nChannels; ++i) { if (io->atEnd()) { error = "Could not read enough data for channels"; return false; } ChannelInfo* info = new ChannelInfo; if (!psdread(io, &info->channelId)) { error = "could not read channel id"; delete info; return false; } bool r; if (m_header.version == 1) { quint32 channelDataLength; r = psdread(io, &channelDataLength); info->channelDataLength = (quint64)channelDataLength; } else { r = psdread(io, &info->channelDataLength); } if (!r) { error = "Could not read length for channel data"; delete info; return false; } dbgFile << "\tchannel" << i << "id" << channelIdToChannelType(info->channelId, m_header.colormode) << "length" << info->channelDataLength << "start" << info->channelDataStart << "offset" << info->channelOffset << "channelInfoPosition" << info->channelInfoPosition; channelInfoRecords << info; } if (!psd_read_blendmode(io, blendModeKey)) { error = QString("Could not read blend mode key. Got: %1").arg(blendModeKey); return false; } dbgFile << "\tBlend mode" << blendModeKey << "pos" << io->pos(); if (!psdread(io, &opacity)) { error = "Could not read opacity"; return false; } dbgFile << "\tOpacity" << opacity << io->pos(); if (!psdread(io, &clipping)) { error = "Could not read clipping"; return false; } dbgFile << "\tclipping" << clipping << io->pos(); quint8 flags; if (!psdread(io, &flags)) { error = "Could not read flags"; return false; } dbgFile << "\tflags" << flags << io->pos(); transparencyProtected = flags & 1 ? true : false; dbgFile << "\ttransparency protected" << transparencyProtected; visible = flags & 2 ? false : true; dbgFile << "\tvisible" << visible; if (flags & 8) { irrelevant = flags & 16 ? true : false; } else { irrelevant = false; } dbgFile << "\tirrelevant" << irrelevant; dbgFile << "\tfiller at " << io->pos(); quint8 filler; if (!psdread(io, &filler) || filler != 0) { error = "Could not read padding"; return false; } dbgFile << "\tGoing to read extra data length" << io->pos(); quint32 extraDataLength; if (!psdread(io, &extraDataLength) || io->bytesAvailable() < extraDataLength) { error = QString("Could not read extra layer data: %1 at pos %2").arg(extraDataLength).arg(io->pos()); return false; } dbgFile << "\tExtra data length" << extraDataLength; if (extraDataLength > 0) { dbgFile << "Going to read extra data field. Bytes available: " << io->bytesAvailable() << "pos" << io->pos(); quint32 layerMaskLength = 1; // invalid... if (!psdread(io, &layerMaskLength) || io->bytesAvailable() < layerMaskLength || !(layerMaskLength == 0 || layerMaskLength == 20 || layerMaskLength == 36)) { error = QString("Could not read layer mask length: %1").arg(layerMaskLength); return false; } memset(&layerMask, 0, sizeof(LayerMaskData)); if (layerMaskLength == 20 || layerMaskLength == 36) { if (!psdread(io, &layerMask.top) || !psdread(io, &layerMask.left) || !psdread(io, &layerMask.bottom) || - !psdread(io, &layerMask.top) || + !psdread(io, &layerMask.right) || !psdread(io, &layerMask.defaultColor) || !psdread(io, &flags)) { error = "could not read mask record"; return false; } } if (layerMaskLength == 20) { quint16 padding; if (!psdread(io, &padding)) { error = "Could not read layer mask padding"; return false; } } if (layerMaskLength == 36 ) { if (!psdread(io, &flags) || !psdread(io, &layerMask.defaultColor) || !psdread(io, &layerMask.top) || !psdread(io, &layerMask.left) || !psdread(io, &layerMask.bottom) || !psdread(io, &layerMask.top)) { error = "could not read 'real' mask record"; return false; } } layerMask.positionedRelativeToLayer = flags & 1 ? true : false; layerMask.disabled = flags & 2 ? true : false; layerMask.invertLayerMaskWhenBlending = flags & 4 ? true : false; dbgFile << "\tRead layer mask/adjustment layer data. Length of block:" << layerMaskLength << "pos" << io->pos(); // layer blending thingies quint32 blendingDataLength; if (!psdread(io, &blendingDataLength) || io->bytesAvailable() < blendingDataLength) { error = "Could not read extra blending data."; return false; } //dbgFile << "blending block data length" << blendingDataLength << ", pos" << io->pos(); blendingRanges.data = io->read(blendingDataLength); if ((quint32)blendingRanges.data.size() != blendingDataLength) { error = QString("Got %1 bytes for the blending range block, needed %2").arg(blendingRanges.data.size(), blendingDataLength); } /* // XXX: reading this block correctly failed, I have more channel ranges than I'd expected. if (!psdread(io, &blendingRanges.blackValues[0]) || !psdread(io, &blendingRanges.blackValues[1]) || !psdread(io, &blendingRanges.whiteValues[0]) || !psdread(io, &blendingRanges.whiteValues[1]) || !psdread(io, &blendingRanges.compositeGrayBlendDestinationRange)) { error = "Could not read blending black/white values"; return false; } for (int i = 0; i < nChannels; ++i) { quint32 src; quint32 dst; if (!psdread(io, &src) || !psdread(io, &dst)) { error = QString("could not read src/dst range for channel %1").arg(i); return false; } dbgFile << "\tread range " << src << "to" << dst << "for channel" << i; blendingRanges.sourceDestinationRanges << QPair(src, dst); } */ dbgFile << "\tGoing to read layer name at" << io->pos(); quint8 layerNameLength; if (!psdread(io, &layerNameLength)) { error = "Could not read layer name length"; return false; } dbgFile << "\tlayer name length unpadded" << layerNameLength << "pos" << io->pos(); layerNameLength = ((layerNameLength + 1 + 3) & ~0x03) - 1; dbgFile << "\tlayer name length padded" << layerNameLength << "pos" << io->pos(); layerName = io->read(layerNameLength); dbgFile << "\tlayer name" << layerName << io->pos(); if (!infoBlocks.read(io)) { error = infoBlocks.error; return false; } if (infoBlocks.keys.contains("luni") && !infoBlocks.unicodeLayerName.isEmpty()) { layerName = infoBlocks.unicodeLayerName; } } return valid(); } bool PSDLayerRecord::write(QIODevice* io, KisNodeSP node) { dbgFile << "writing layer info record" << node->name() << "at" << io->pos(); m_node = node; dbgFile << "saving layer record for " << layerName << "at pos" << io->pos(); dbgFile << "\ttop" << top << "left" << left << "bottom" << bottom << "right" << right << "number of channels" << nChannels; Q_ASSERT(left <= right); Q_ASSERT(top <= bottom); Q_ASSERT(nChannels > 0); psdwrite(io, (quint32)top); psdwrite(io, (quint32)left); psdwrite(io, (quint32)bottom); psdwrite(io, (quint32)right); psdwrite(io, (quint16)nChannels); foreach(ChannelInfo *channel, channelInfoRecords) { psdwrite(io, (quint16)channel->channelId); channel->channelInfoPosition = io->pos(); dbgFile << "ChannelInfo record position:" << channel->channelInfoPosition << "channel id" << channel->channelId; psdwrite(io, (quint32)0); // to be filled in when we know how big each channel block is going to be } // blend mode io->write("8BIM", 4); dbgFile << "blendModeKey" << blendModeKey << "pos" << io->pos(); io->write(blendModeKey.toLatin1()); // opacity psdwrite(io, opacity); // clipping - unused psdwrite(io, clipping); // visibility and protection quint8 flags = 0; if (transparencyProtected) flags |= 1; if (!visible) flags |= 2; psdwrite(io, flags); // padding byte to make the length even psdwrite(io, (quint8)0); // position of the extra data size quint64 extraDataPos = io->pos(); psdwrite(io, (quint32)0); // length of the extra data fields // layer mask data: not implemented for now, so zero psdwrite(io, quint32(0)); // Layer blending ranges: not implemented for now, so zero psdwrite(io, quint32(0)); // layer name: Pascal string, padded to a multiple of 4 bytes. psdwrite_pascalstring(io, layerName, 4); // write luni data block { quint32 len = qMin(layerName.length(), 255); quint32 xdBlockSize = len; if (len % 2) { xdBlockSize = len + 1; } xdBlockSize = (xdBlockSize * 2) + 4; io->write("8BIMluni", 8); psdwrite(io, xdBlockSize); psdwrite(io, len); const ushort *chars = layerName.utf16(); for (uint i = 0; i < len; i++) { psdwrite(io, (quint16)chars[i]); } if (len % 2) { psdwrite(io, (quint16)0); // padding } } // write real length for extra data quint64 eofPos = io->pos(); io->seek(extraDataPos); psdwrite(io, (quint32)(eofPos - extraDataPos - sizeof(quint32))); dbgFile << "ExtraData size" << (eofPos - extraDataPos - sizeof(quint32)) << "extra data pos" << extraDataPos << "eofpos" << eofPos; // retor to eof to continue writing io->seek(eofPos); return true; } bool PSDLayerRecord::writePixelData(QIODevice *io) { dbgFile << "writing pixel data for layer" << layerName << "at" << io->pos(); KisPaintDeviceSP dev = m_node->projection(); // now write all the channels in display order QRect rc = dev->extent(); // yeah... we read the entire layer into a vector of quint8 arrays dbgFile << "layer" << layerName; dbgFile << "\tnode x" << m_node->x() << "paint device x" << dev->x() << "extent x" << rc.x(); dbgFile << "\tnode y" << m_node->y() << "paint device x" << dev->y() << "extent y" << rc.y(); QVector tmp = dev->readPlanarBytes(rc.x() - m_node->x(), rc.y() -m_node->y(), rc.width(), rc.height()); // KisPaintDeviceSP dev2 = new KisPaintDevice(dev->colorSpace()); // dev2->writePlanarBytes(tmp, 0, 0, rc.width(), rc.height()); // dev2->convertToQImage(0).save(layerName + ".png"); // then reorder the planes to fit the psd model -- alpha first, then display order QVector planes; QList origChannels = dev->colorSpace()->channels(); foreach(KoChannelInfo *ch, KoChannelInfo::displayOrderSorted(origChannels)) { int channelIndex = KoChannelInfo::displayPositionToChannelIndex(ch->displayPosition(), origChannels); //qDebug() << ppVar(ch->name()) << ppVar(ch->pos()) << ppVar(ch->displayPosition()) << ppVar(channelIndex); if (ch->channelType() == KoChannelInfo::ALPHA) { planes.insert(0, tmp[channelIndex]); } else { planes.append(tmp[channelIndex]); } } // now planes are holding pointers to quint8 arrays tmp.clear(); // here's where we save the total size of the channel data for (int channelInfoIndex = 0; channelInfoIndex < nChannels; ++channelInfoIndex) { dbgFile << "\tWriting channel" << channelInfoIndex << "psd channel id" << channelInfoRecords[channelInfoIndex]->channelId; // if the bitdepth > 8, place the bytes in the right order // if cmyk, invert the pixel value if (m_header.channelDepth == 8) { if (channelInfoRecords[channelInfoIndex]->channelId >= 0 && (m_header.colormode == CMYK || m_header.colormode == CMYK64)) { for (int i = 0; i < rc.width() * rc.height(); ++i) { planes[channelInfoIndex][i] = 255 - planes[channelInfoIndex][i]; } } } else if (m_header.channelDepth == 16) { quint16 val; for (int i = 0; i < rc.width() * rc.height(); ++i) { val = reinterpret_cast(planes[channelInfoIndex])[i]; val = ntohs(val); if (channelInfoRecords[channelInfoIndex]->channelId >= 0 && (m_header.colormode == CMYK || m_header.colormode == CMYK64)) { val = quint16_MAX - val; } reinterpret_cast(planes[channelInfoIndex])[i] = val; } } quint32 len = 0; // where this block starts, for the total size calculation quint64 startChannelBlockPos = io->pos(); // XXX: make the compression settting configurable. For now, always use RLE. psdwrite(io, (quint16)Compression::RLE); len += sizeof(quint16); // where this block starts, for the total size calculation quint64 channelRLESizePos = io->pos(); // write zero's for the channel lengths section for(int i = 0; i < rc.height(); ++i) { psdwrite(io, (quint16)0); } len += rc.height() * sizeof(quint16); // here the actual channel data starts; that's where we return after writing // the size of the current row quint64 channelStartPos = io->pos(); quint8 *plane = planes[channelInfoIndex]; quint32 stride = (m_header.channelDepth / 8) * rc.width(); for (qint32 row = 0; row < rc.height(); ++row) { QByteArray uncompressed = QByteArray::fromRawData((const char*)plane + row * stride, stride); QByteArray compressed = Compression::compress(uncompressed, Compression::RLE); quint16 size = compressed.size(); io->seek(channelRLESizePos); psdwrite(io, size); channelRLESizePos +=2; io->seek(channelStartPos); if (io->write(compressed) != size) { error = "Could not write image data"; return false; } len += size; // dbgFile << "\t\tUncompressed:" << uncompressed.size() << "compressed" << compressed.size(); // QByteArray control = Compression::uncompress(rc.width(), compressed, Compression::RLE); // Q_ASSERT(qstrcmp(control, uncompressed) == 0); // If the layer's size, and therefore the data, is odd, a pad byte will be inserted // at the end of the row. (weirdly enough, that's not true for the image data) // if ((size & 0x01) != 0) { // psdwrite(io, (quint8)0); // size++; // } channelStartPos += size; } // write the size of the channel image data block in the channel info block quint64 currentPos = io->pos(); io->seek(channelInfoRecords[channelInfoIndex]->channelInfoPosition); Q_ASSERT(len == currentPos - startChannelBlockPos); dbgFile << "\t\ttotal length" << len << "calculated length" << currentPos - startChannelBlockPos << "writing at" << channelInfoRecords[channelInfoIndex]->channelInfoPosition; psdwrite(io, (quint32)(currentPos - startChannelBlockPos)); io->seek(currentPos); } qDeleteAll(planes); planes.clear(); return true; } bool PSDLayerRecord::valid() { // XXX: check validity! return true; } bool PSDLayerRecord::readPixelData(QIODevice *io, KisPaintDeviceSP device) { dbgFile << "Reading pixel data for layer" << layerName << "pos" << io->pos(); switch (m_header.colormode) { case Bitmap: error = "Unsupported color mode: bitmap"; return false; // Not supported; case Indexed: error = "Unsupported color mode: indexed"; return false; // Not supported; case MultiChannel: error = "Unsupported color mode: indexed"; return false; // Not supported case DuoTone: error = "Unsupported color mode: Duotone"; return false; // Not supported case Grayscale: return doGrayscale(device, io); case RGB: return doRGB(device, io); case CMYK: return doCMYK(device, io); case Lab: return doLAB(device, io); case UNKNOWN: default: return false; } return false; } -bool PSDLayerRecord::readMask(QIODevice */*io*/, KisPaintDeviceSP /*dev*/, ChannelInfo *channelInfo) +QRect PSDLayerRecord::channelRect(ChannelInfo *channel) const { + QRect result; + + if (channel->channelId < -1) { + result = QRect(layerMask.left, + layerMask.top, + layerMask.right - layerMask.left, + layerMask.bottom - layerMask.top); + } else { + result = QRect(left, + top, + right - left, + bottom - top); + } + + return result; +} + +bool PSDLayerRecord::readMask(QIODevice *io, KisPaintDeviceSP dev, ChannelInfo *channelInfo) +{ + KisOffsetKeeper keeper(io); + + KIS_ASSERT_RECOVER(channelInfo->channelId < -1) { return false; } + dbgFile << "Going to read" << channelIdToChannelType(channelInfo->channelId, m_header.colormode) << "mask"; -// quint64 oldPosition = io->pos(); -// qint64 width = right - left; - -// if (width <= 0) { -// dbgFile << "Empty Channel"; -// return true; -// } - -// int uncompressedLength = width; - -// if (channel->compressionType == Compression::ZIP -// || channel->compressionType == Compression::ZIPWithPrediction) { - -// error = "Unsupported Compression mode: zip"; -// return false; -// } - -// KisHLineIteratorSP it = dev->createHLineIteratorNG(left, top, width); -// for (int row = top ; row < bottom; row++) -// { -// QMap channelBytes; - -// foreach(ChannelInfo *channelInfo, colorChannelInfoRecords) { -// io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); - -// if (channelInfo->compressionType == Compression::Uncompressed) { -// channelBytes[channelInfo->channelId] = io->read(uncompressedLength); -// channelInfo->channelOffset += uncompressedLength; -// } -// else if (channelInfo->compressionType == Compression::RLE) { -// int rleLength = channelInfo->rleRowLengths[row - top]; -// QByteArray compressedBytes = io->read(rleLength); -// QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); -// channelBytes.insert(channelInfo->channelId, uncompressedBytes); -// channelInfo->channelOffset += rleLength; - -// } -// else { -// error = "Unsupported Compression mode: " + channelInfo->compressionType; -// return false; -// } -// } - -// for (qint64 col = 0; col < width; col++){ - -// if (channelSize == 1) { -// readRGBPixel(channelBytes, col, it->rawData()); -// } - -// else if (channelSize == 2) { -// readRGBPixel(channelBytes, col, it->rawData()); -// } -// else { -// // Unsupported channel sizes for now -// return false; -// } -// /* -// // XXX see implementation Openexr -// else if (channelSize == 4) { -// // NOT IMPLEMENTED! -// } -//*/ -// it->nextPixel(); -// } -// it->nextRow(); -// } -// // go back to the old position, because we've been seeking all over the place -// io->seek(oldPosition); - return true; + QRect maskRect = channelRect(channelInfo); + if (maskRect.isEmpty()) { + dbgFile << "Empty Channel"; + return true; + } + + // the device must be a pixel selection + KIS_ASSERT_RECOVER(dev->pixelSize() == 1) { return false; } + + dev->setDefaultPixel(&layerMask.defaultColor); + + + int uncompressedLength = maskRect.width(); + + if (channelInfo->compressionType == Compression::ZIP || + channelInfo->compressionType == Compression::ZIPWithPrediction) { + + error = "Unsupported Compression mode: zip"; + return false; + } + + KisHLineIteratorSP it = dev->createHLineIteratorNG(maskRect.left(), maskRect.top(), maskRect.width()); + for (int row = maskRect.top(); row <= maskRect.bottom(); row++) + { + QByteArray channelBytes; + + io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); + + if (channelInfo->compressionType == Compression::Uncompressed) { + channelBytes = io->read(uncompressedLength); + channelInfo->channelOffset += uncompressedLength; + } else if (channelInfo->compressionType == Compression::RLE) { + int rleLength = channelInfo->rleRowLengths[row - maskRect.top()]; + QByteArray compressedBytes = io->read(rleLength); + channelBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); + channelInfo->channelOffset += rleLength; + } else { + error = "Unsupported Compression mode: " + channelInfo->compressionType; + return false; + } + + for (int col = 0; col < maskRect.width(); col++){ + *it->rawData() = channelBytes[col]; + it->nextPixel(); + } + + it->nextRow(); + } + + // the position of the io device will be restored by + // KisOffsetKeeper automagically + + return true; } bool PSDLayerRecord::doGrayscale(KisPaintDeviceSP dev, QIODevice *io) { Q_UNUSED(dev); Q_UNUSED(io); return false; } template typename Traits::channels_type convertByteOrder(typename Traits::channels_type value); // default implementation is undefined for every color space should be added manually template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return ntohs(value); } template void readRGBPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; quint16 opacity = KoColorSpaceMathsTraits::unitValue; if (channelBytes.contains(-1)) { opacity = channelBytes[-1].constData()[col]; } Pixel *pixelPtr = reinterpret_cast(dstPtr); channels_type blue = convertByteOrder(reinterpret_cast(channelBytes[2].constData())[col]); channels_type green = convertByteOrder(reinterpret_cast(channelBytes[1].constData())[col]); channels_type red = convertByteOrder(reinterpret_cast(channelBytes[0].constData())[col]); pixelPtr->blue = blue; pixelPtr->green = green; pixelPtr->red = red; pixelPtr->alpha = opacity; } bool PSDLayerRecord::doRGB(KisPaintDeviceSP dev, QIODevice *io) { - quint64 oldPosition = io->pos(); + KisOffsetKeeper keeper(io); + qint64 width = right - left; if (width <= 0) { dbgFile << "Empty layer"; return true; } int channelSize = m_header.channelDepth / 8; int uncompressedLength = width * channelSize; if (channelInfoRecords.first()->compressionType == Compression::ZIP || channelInfoRecords.first()->compressionType == Compression::ZIPWithPrediction) { error = "Unsupported Compression mode: zip"; return false; } KisHLineIteratorSP it = dev->createHLineIteratorNG(left, top, width); for (int row = top ; row < bottom; row++) { QMap channelBytes; foreach(ChannelInfo *channelInfo, channelInfoRecords) { + // user supplied masks are ignored here + if (channelInfo->channelId < -1) continue; + io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); if (channelInfo->compressionType == Compression::Uncompressed) { channelBytes[channelInfo->channelId] = io->read(uncompressedLength); channelInfo->channelOffset += uncompressedLength; } else if (channelInfo->compressionType == Compression::RLE) { int rleLength = channelInfo->rleRowLengths[row - top]; QByteArray compressedBytes = io->read(rleLength); QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); channelBytes.insert(channelInfo->channelId, uncompressedBytes); channelInfo->channelOffset += rleLength; } else { error = "Unsupported Compression mode: " + channelInfo->compressionType; return false; } } for (qint64 col = 0; col < width; col++){ if (channelSize == 1) { readRGBPixel(channelBytes, col, it->rawData()); } else if (channelSize == 2) { readRGBPixel(channelBytes, col, it->rawData()); } else { // Unsupported channel sizes for now return false; } /* // XXX see implementation Openexr else if (channelSize == 4) { // NOT IMPLEMENTED! } */ it->nextPixel(); } it->nextRow(); } - // go back to the old position, because we've been seeking all over the place - io->seek(oldPosition); + return true; } bool PSDLayerRecord::doCMYK(KisPaintDeviceSP dev, QIODevice *io) { dbgFile << "doCMYK for" << layerName << "channels:" << channelInfoRecords.size() << "compression" << channelInfoRecords.first()->compressionType; dbgFile << "top" << top << "bottom" << bottom << "left" << left << "right" << right; - quint64 oldPosition = io->pos(); + + KisOffsetKeeper keeper(io); quint64 width = right - left; int channelSize = m_header.channelDepth / 8; int uncompressedLength = width * channelSize; if (channelInfoRecords.first()->compressionType == Compression::ZIP || channelInfoRecords.first()->compressionType == Compression::ZIPWithPrediction) { dbgFile << "zippedy-do-da!"; // Zip needs to be implemented here. return false; } KisHLineIteratorSP it = dev->createHLineIteratorNG(left, top, width); for (int row = top ; row < bottom; row++) { QMap channelBytes; foreach(ChannelInfo *channelInfo, channelInfoRecords) { io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); if (channelInfo->compressionType == Compression::Uncompressed) { channelBytes[channelInfo->channelId] = io->read(uncompressedLength); channelInfo->channelOffset += uncompressedLength; } else if (channelInfo->compressionType == Compression::RLE) { int rleLength = channelInfo->rleRowLengths[row - top]; QByteArray compressedBytes = io->read(rleLength); QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); channelBytes.insert(channelInfo->channelId, uncompressedBytes); channelInfo->channelOffset += rleLength; } } for (quint64 col = 0; col < width; col++){ if (channelSize == 1) { quint8 opacity = OPACITY_OPAQUE_U8; if (channelBytes.contains(-1)) { opacity = channelBytes[-1].constData()[col]; } quint8 *pixel = new quint8[5]; memset(pixel, 0, 5); dev->colorSpace()->setOpacity(pixel, opacity, 1); memset(pixel, 255 - channelBytes[0].constData()[col], 1); memset(pixel + 1, 255 - channelBytes[1].constData()[col], 1); memset(pixel + 2, 255 - channelBytes[2].constData()[col], 1); memset(pixel + 3, 255 - channelBytes[3].constData()[col], 1); //dbgFile << "C" << pixel[0] << "M" << pixel[1] << "Y" << pixel[2] << "K" << pixel[3] << "A" << pixel[4]; memcpy(it->rawData(), pixel, 5); delete[] pixel; } else if (channelSize == 2) { quint16 opacity = quint16_MAX; if (channelBytes.contains(-1)) { opacity = channelBytes[-1].constData()[col]; } // We don't have a convenient setOpacity function :-( memcpy(it->rawData() + KoCmykTraits::alpha_pos, &opacity, sizeof(quint16)); quint16 C = ntohs(reinterpret_cast(channelBytes[0].constData())[col]); KoCmykTraits::setC(it->rawData(),C); quint16 M = ntohs(reinterpret_cast(channelBytes[1].constData())[col]); KoCmykTraits::setM(it->rawData(),M); quint16 Y = ntohs(reinterpret_cast(channelBytes[2].constData())[col]); KoCmykTraits::setY(it->rawData(),Y); quint16 K = ntohs(reinterpret_cast(channelBytes[3].constData())[col]); KoCmykTraits::setK(it->rawData(),K); } else if (channelSize == 4) { quint32 C = ntohs(reinterpret_cast(channelBytes[0].constData())[col]); KoCmykTraits::setC(it->rawData(),C); quint32 M = ntohs(reinterpret_cast(channelBytes[1].constData())[col]); KoCmykTraits::setM(it->rawData(),M); quint32 Y = ntohs(reinterpret_cast(channelBytes[2].constData())[col]); KoCmykTraits::setY(it->rawData(),Y); quint32 K = ntohs(reinterpret_cast(channelBytes[3].constData())[col]); KoCmykTraits::setK(it->rawData(),K); } else { // Unsupported channel sizes for now return false; } it->nextPixel(); } it->nextRow(); } - // go back to the old position, because we've been seeking all over the place - io->seek(oldPosition); + return true; } bool PSDLayerRecord::doLAB(KisPaintDeviceSP dev, QIODevice *io) -{ quint64 oldPosition = io->pos(); +{ + KisOffsetKeeper keeper(io); quint64 width = right - left; int channelSize = m_header.channelDepth / 8; int uncompressedLength = width * channelSize; if (channelInfoRecords.first()->compressionType == Compression::ZIP || channelInfoRecords.first()->compressionType == Compression::ZIPWithPrediction) { // Zip needs to be implemented here. return false; } KisHLineIteratorSP it = dev->createHLineIteratorNG(left, top, width); for (int row = top ; row < bottom; row++) { QMap channelBytes; foreach(ChannelInfo *channelInfo, channelInfoRecords) { io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); if (channelInfo->compressionType == Compression::Uncompressed) { channelBytes[channelInfo->channelId] = io->read(uncompressedLength); channelInfo->channelOffset += uncompressedLength; } else if (channelInfo->compressionType == Compression::RLE) { int rleLength = channelInfo->rleRowLengths[row - top]; QByteArray compressedBytes = io->read(rleLength); QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); channelBytes.insert(channelInfo->channelId, uncompressedBytes); channelInfo->channelOffset += rleLength; } } for (quint64 col = 0; col < width; col++){ if (channelSize == 1) { quint8 opacity = OPACITY_OPAQUE_U8; if (channelBytes.contains(-1)) { opacity = channelBytes[-1].constData()[col]; } KoLabTraits::setOpacity(it->rawData(), opacity, 1); quint8 L = ntohs(reinterpret_cast(channelBytes[0].constData())[col]); KoLabTraits::setL(it->rawData(),L); quint8 A = ntohs(reinterpret_cast(channelBytes[1].constData())[col]); KoLabTraits::setA(it->rawData(),A); quint8 B = ntohs(reinterpret_cast(channelBytes[2].constData())[col]); KoLabTraits::setB(it->rawData(),B); } else if (channelSize == 2) { quint16 opacity = quint16_MAX; if (channelBytes.contains(-1)) { opacity = channelBytes[-1].constData()[col]; } // We don't have a convenient setOpacity function :-( memcpy(it->rawData() + KoLabU16Traits::alpha_pos, &opacity, sizeof(quint16)); // KoLabTraits::setOpacity(it->rawData(), opacity, 1); quint16 L = ntohs(reinterpret_cast(channelBytes[0].constData())[col]); KoLabTraits::setL(it->rawData(),L); quint16 A = ntohs(reinterpret_cast(channelBytes[1].constData())[col]); KoLabTraits::setA(it->rawData(),A); quint16 B = ntohs(reinterpret_cast(channelBytes[2].constData())[col]); KoLabTraits::setB(it->rawData(),B); } else { // Unsupported channel sizes for now return false; } it->nextPixel(); } it->nextRow(); } - // go back to the old position, because we've been seeking all over the place - io->seek(oldPosition); + return true; } QDebug operator<<(QDebug dbg, const PSDLayerRecord &layer) { #ifndef NODEBUG dbg.nospace() << "valid: " << const_cast(&layer)->valid(); dbg.nospace() << ", name: " << layer.layerName; dbg.nospace() << ", top: " << layer.top; dbg.nospace() << ", left:" << layer.left; dbg.nospace() << ", bottom: " << layer.bottom; dbg.nospace() << ", right: " << layer.right; dbg.nospace() << ", number of channels: " << layer.nChannels; dbg.nospace() << ", blendModeKey: " << layer.blendModeKey; dbg.nospace() << ", opacity: " << layer.opacity; dbg.nospace() << ", clipping: " << layer.clipping; dbg.nospace() << ", transparency protected: " << layer.transparencyProtected; dbg.nospace() << ", visible: " << layer.visible; dbg.nospace() << ", irrelevant: " << layer.irrelevant << "\n"; foreach(const ChannelInfo* channel, layer.channelInfoRecords) { dbg.space() << channel; } #endif return dbg.nospace(); } QDebug operator<<(QDebug dbg, const ChannelInfo &channel) { #ifndef NODEBUG dbg.nospace() << "\tChannel type" << channel.channelId << "size: " << channel.channelDataLength << "compression type" << channel.compressionType << "\n"; #endif return dbg.nospace(); } diff --git a/krita/plugins/formats/psd/psd_layer_record.h b/krita/plugins/formats/psd/psd_layer_record.h index 5a84d882222..63f2a44f0d0 100644 --- a/krita/plugins/formats/psd/psd_layer_record.h +++ b/krita/plugins/formats/psd/psd_layer_record.h @@ -1,166 +1,168 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PSD_LAYER_RECORD_H #define PSD_LAYER_RECORD_H #include #include #include #include #include #include #include #include "psd.h" #include "psd_header.h" #include "compression.h" #include "psd_additional_layer_info_block.h" class QIODevice; enum psd_layer_type { psd_layer_type_normal, psd_layer_type_hidden, psd_layer_type_folder, psd_layer_type_solid_color, psd_layer_type_gradient_fill, psd_layer_type_pattern_fill, psd_layer_type_levels, psd_layer_type_curves, psd_layer_type_brightness_contrast, psd_layer_type_color_balance, psd_layer_type_hue_saturation, psd_layer_type_selective_color, psd_layer_type_threshold, psd_layer_type_invert, psd_layer_type_posterize, psd_layer_type_channel_mixer, psd_layer_type_gradient_map, psd_layer_type_photo_filter, }; struct ChannelInfo { ChannelInfo() : channelId(0) , compressionType(Compression::Unknown) , channelDataStart(0) , channelDataLength(0) , channelOffset(0) , channelInfoPosition(0) {} qint16 channelId; // 0 red, 1 green, 2 blue, -1 transparency, -2 user-supplied layer mask Compression::CompressionType compressionType; quint64 channelDataStart; quint64 channelDataLength; QVector rleRowLengths; int channelOffset; // where the channel data starts int channelInfoPosition; // where the channelinfo record is saved in the file }; class PSDLayerRecord { public: PSDLayerRecord(const PSDHeader &header); ~PSDLayerRecord() { qDeleteAll(channelInfoRecords); } + QRect channelRect(ChannelInfo *channel) const; + bool read(QIODevice* io); bool readPixelData(QIODevice* io, KisPaintDeviceSP device); bool readMask(QIODevice* io, KisPaintDeviceSP dev, ChannelInfo *channel); bool write(QIODevice* io, KisNodeSP node); bool writePixelData(QIODevice* io); bool valid(); QString error; qint32 top; qint32 left; qint32 bottom; qint32 right; quint16 nChannels; QVector channelInfoRecords; QString blendModeKey; quint8 opacity; quint8 clipping; bool transparencyProtected; bool visible; bool irrelevant; struct LayerMaskData { qint32 top; qint32 left; qint32 bottom; qint32 right; quint8 defaultColor; // 0 or 255 bool positionedRelativeToLayer; bool disabled; bool invertLayerMaskWhenBlending; quint8 userMaskDensity; double userMaskFeather; quint8 vectorMaskDensity; double vectorMaskFeather; }; LayerMaskData layerMask; struct LayerBlendingRanges { QByteArray data; quint8 blackValues[2]; quint8 whiteValues[2]; quint32 compositeGrayBlendDestinationRange; QVector > sourceDestinationRanges; }; LayerBlendingRanges blendingRanges; QString layerName; // pascal, not unicode! PsdAdditionalLayerInfoBlock infoBlocks; private: bool doRGB(KisPaintDeviceSP dev ,QIODevice *io); bool doCMYK(KisPaintDeviceSP dev ,QIODevice *io); bool doLAB(KisPaintDeviceSP dev ,QIODevice *io); bool doGrayscale(KisPaintDeviceSP dev ,QIODevice *io); KisNodeSP m_node; const PSDHeader m_header; }; QDebug operator<<(QDebug dbg, const PSDLayerRecord& layer); QDebug operator<<(QDebug dbg, const ChannelInfo& layer); #endif // PSD_LAYER_RECORD_H diff --git a/krita/plugins/formats/psd/psd_layer_section.cpp b/krita/plugins/formats/psd/psd_layer_section.cpp index 863fcf2a378..99f7e11102e 100644 --- a/krita/plugins/formats/psd/psd_layer_section.cpp +++ b/krita/plugins/formats/psd/psd_layer_section.cpp @@ -1,401 +1,393 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_layer_section.h" #include #include #include #include #include #include "psd_header.h" #include "psd_utils.h" #include "compression.h" PSDLayerMaskSection::PSDLayerMaskSection(const PSDHeader& header) : m_header(header) { hasTransparency = false; layerMaskBlockSize = 0; layerInfoSize = 0; nLayers = 0; } PSDLayerMaskSection::~PSDLayerMaskSection() { qDeleteAll(layers); } bool PSDLayerMaskSection::read(QIODevice* io) { dbgFile << "reading layer section. Pos:" << io->pos() << "bytes left:" << io->bytesAvailable(); layerMaskBlockSize = 0; if (m_header.version == 1) { quint32 _layerMaskBlockSize = 0; if (!psdread(io, &_layerMaskBlockSize) || _layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(_layerMaskBlockSize).arg(io->bytesAvailable()); return false; } layerMaskBlockSize = _layerMaskBlockSize; } else if (m_header.version == 2) { if (!psdread(io, &layerMaskBlockSize) || layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(layerMaskBlockSize).arg(io->bytesAvailable()); return false; } } quint64 start = io->pos(); dbgFile << "layer + mask section size" << layerMaskBlockSize; if (layerMaskBlockSize == 0) { dbgFile << "No layer + mask info, so no layers, only a background layer"; return true; } dbgFile << "reading layer info block. Bytes left" << io->bytesAvailable() << "position" << io->pos(); if (m_header.version == 1) { quint32 _layerInfoSize; if (!psdread(io, &_layerInfoSize) || _layerInfoSize > (quint64)io->bytesAvailable()) { error = "Could not read layer section size"; return false; } layerInfoSize = _layerInfoSize; } else if (m_header.version == 2) { if (!psdread(io, &layerInfoSize) || layerInfoSize > (quint64)io->bytesAvailable()) { error = "Could not read layer section size"; return false; } } dbgFile << "Layer info block size" << layerInfoSize; if (layerInfoSize > 0 ) { // rounded to a multiple of 2 if ((layerInfoSize & 0x01) != 0) { layerInfoSize++; } dbgFile << "Layer info block size after rounding" << layerInfoSize; if (!psdread(io, &nLayers) || nLayers == 0) { error = QString("Could not read read number of layers or no layers in image. %1").arg(nLayers); return false; } - if (nLayers < 0) { - hasTransparency = true; // first alpha channel is the alpha channel of the projection. - nLayers = -nLayers; - } - else { - hasTransparency = false; - } - dbgFile << "transparency" << hasTransparency; + hasTransparency = nLayers < 0; // first alpha channel is the alpha channel of the projection. + nLayers = qAbs(nLayers); - dbgFile << "Number of layers" << nLayers << "transparency" << hasTransparency; + dbgFile << "Number of layers:" << nLayers; + dbgFile << "Has separate projection transparency:" << hasTransparency; for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to read layer" << i << "pos" << io->pos(); + dbgFile << "== Enter PSDLayerRecord"; PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header); if (!layerRecord->read(io)) { error = QString("Could not load layer %1: %2").arg(i).arg(layerRecord->error); return false; } + dbgFile << "== Leave PSDLayerRecord"; dbgFile << "Read layer" << i << layerRecord->layerName << "blending mode" << layerRecord->blendModeKey << io->pos() << "Number of channels:" << layerRecord->channelInfoRecords.size(); layers << layerRecord; } } // get the positions for the channels belonging to each layer for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to seek channel positions for layer" << i << "pos" << io->pos(); if (i > layers.size()) { error = QString("Expected layer %1, but only have %2 layers").arg(i).arg(layers.size()); return false; } PSDLayerRecord *layerRecord = layers.at(i); for (int j = 0; j < layerRecord->nChannels; ++j) { // save the current location so we can jump beyond this block later on. quint64 channelStartPos = io->pos(); dbgFile << "\tReading channel image data for channel" << j << "from pos" << io->pos(); - Q_ASSERT(j < layerRecord->channelInfoRecords.size()); - if (j > layerRecord->channelInfoRecords.size()) { - error = QString("Expected channel %1, but only have %2 channels for layer %3") - .arg(j) - .arg(layerRecord->channelInfoRecords.size()) - .arg(i); - return false; - } + KIS_ASSERT_RECOVER(j < layerRecord->channelInfoRecords.size()) { return false; } ChannelInfo* channelInfo = layerRecord->channelInfoRecords.at(j); quint16 compressionType; if (!psdread(io, &compressionType)) { error = "Could not read compression type for channel"; return false; } channelInfo->compressionType = (Compression::CompressionType)compressionType; dbgFile << "\t\tChannel" << j << "has compression type" << compressionType; + QRect channelRect = layerRecord->channelRect(channelInfo); + // read the rle row lengths; if (channelInfo->compressionType == Compression::RLE) { - for(qint64 row = 0; row < (layerRecord->bottom - layerRecord->top); ++row) { + for(qint64 row = 0; row < channelRect.height(); ++row) { //dbgFile << "Reading the RLE bytecount position of row" << row << "at pos" << io->pos(); quint32 byteCount; if (m_header.version == 1) { quint16 _byteCount; if (!psdread(io, &_byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } byteCount = _byteCount; } else { if (!psdread(io, &byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } } ////dbgFile << "rle byte count" << byteCount; channelInfo->rleRowLengths << byteCount; } } // we're beyond all the length bytes, rle bytes and whatever, this is the // location of the real pixel data channelInfo->channelDataStart = io->pos(); dbgFile << "\t\tstart" << channelStartPos << "data start" << channelInfo->channelDataStart << "data length" << channelInfo->channelDataLength << "pos" << io->pos(); // make sure we are at the start of the next channel data block io->seek(channelStartPos + channelInfo->channelDataLength); // this is the length of the actual channel data bytes channelInfo->channelDataLength = channelInfo->channelDataLength - (channelInfo->channelDataStart - channelStartPos); dbgFile << "\t\tchannel record" << j << "for layer" << i << "with id" << channelInfo->channelId << "starting postion" << channelInfo->channelDataStart << "with length" << channelInfo->channelDataLength << "and has compression type" << channelInfo->compressionType; } } quint32 globalMaskBlockLength; if (!psdread(io, &globalMaskBlockLength)) { error = "Could not read global mask info block"; return false; } if (globalMaskBlockLength > 0) { if (!psdread(io, &globalLayerMaskInfo.overlayColorSpace)) { error = "Could not read global mask info overlay colorspace"; return false; } for (int i = 0; i < 4; ++i) { if (!psdread(io, &globalLayerMaskInfo.colorComponents[i])) { error = QString("Could not read mask info visualizaion color component %1").arg(i); return false; } } if (!psdread(io, &globalLayerMaskInfo.opacity)) { error = "Could not read global mask info visualization opacity"; return false; } if (!psdread(io, &globalLayerMaskInfo.kind)) { error = "Could not read global mask info visualization type"; return false; } } /* put us after this section so reading the next section will work even if we mess up */ io->seek(start + layerMaskBlockSize); return valid(); } void flattenLayers(KisNodeSP node, QList &layers) { for (uint i = 0; i < node->childCount(); ++i) { KisNodeSP child = node->at(i); if (child->inherits("KisPaintLayer") || child->inherits("KisShapeLayer")) { layers << child; } if (child->childCount() > 0) { flattenLayers(child, layers); } } dbgFile << layers.size(); } bool PSDLayerMaskSection::write(QIODevice* io, KisNodeSP rootLayer) { dbgFile << "Writing layer layer section"; // Build the whole layer structure QList nodes; flattenLayers(rootLayer, nodes); if (nodes.isEmpty()) { error = "Could not find paint layers to save"; return false; } quint64 layerMaskPos = io->pos(); // length of the layer info and mask information section dbgFile << "Length of layer info and mask info section at" << layerMaskPos; psdwrite(io, (quint32)0); quint64 layerInfoPos = io->pos(); dbgFile << "length of the layer info section, rounded up to a multiple of two, at" << layerInfoPos; psdwrite(io, (quint32)0); // number of layers (negative, because krita always has alpha) dbgFile << "number of layers" << -nodes.size() << "at" << io->pos(); psdwrite(io, (qint16)-nodes.size()); // Layer records section foreach(KisNodeSP node, nodes) { PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header); layers.append(layerRecord); QRect rc = node->projection()->extent(); rc = rc.normalized(); Q_ASSERT(rc.width() >= 0); Q_ASSERT(rc.height() >= 0); // keep to the max of photoshop's capabilities if (rc.width() > 30000) rc.setWidth(30000); if (rc.height() > 30000) rc.setHeight(30000); layerRecord->top = rc.y(); layerRecord->left = rc.x(); layerRecord->bottom = rc.y() + rc.height(); layerRecord->right = rc.x() + rc.width(); layerRecord->nChannels = node->projection()->colorSpace()->colorChannelCount(); // XXX: masks should be saved as channels as well, with id -2 ChannelInfo *info = new ChannelInfo; info->channelId = -1; // For the alpha channel, which we always have in Krita, and should be saved first in layerRecord->channelInfoRecords << info; // the rest is in display order: rgb, cmyk, lab... for (int i = 0; i < layerRecord->nChannels; ++i) { info = new ChannelInfo; info->channelId = i; // 0 for red, 1 = green, etc layerRecord->channelInfoRecords << info; } layerRecord->nChannels++; // to compensate for the alpha channel at the start layerRecord->blendModeKey = composite_op_to_psd_blendmode(node->compositeOpId()); layerRecord->opacity = node->opacity(); layerRecord->clipping = 0; KisPaintLayer *paintLayer = qobject_cast(node.data()); layerRecord->transparencyProtected = (paintLayer && paintLayer->alphaLocked()); layerRecord->visible = node->visible(); layerRecord->layerName = node->name(); if (!layerRecord->write(io, node)) { error = layerRecord->error; return false; } } // Now save the pixel data dbgFile << "start writing layer pixel data" << io->pos(); foreach(PSDLayerRecord *layerRecord, layers) { if (!layerRecord->writePixelData(io)) { error = layerRecord->error; return false; } } // Write the final size of the block dbgFile << "Final io pos after writing layer pixel data" << io->pos(); quint64 eof_pos = io->pos(); io->seek(layerInfoPos); // length of the layer info information section quint32 layerInfoSize = eof_pos - layerInfoPos - sizeof(qint32); dbgFile << "Layer Info Section length" << layerInfoSize << "at" << io->pos(); psdwrite(io, layerInfoSize); io->seek(eof_pos); // Write the global layer mask info -- which is empty psdwrite(io, (quint32)0); // Write the final size of the block dbgFile << "Final io pos after writing layer pixel data" << io->pos(); eof_pos = io->pos(); io->seek(layerInfoPos); // length of the layer and mask info section, rounded up to a multiple of two io->seek(layerMaskPos); quint32 layerMaskSize = eof_pos - layerMaskPos - sizeof(qint32); dbgFile << "Layer and Mask information length" << layerMaskSize << "at" << io->pos(); psdwrite(io, layerMaskSize); io->seek(eof_pos); return true; } bool PSDLayerMaskSection::valid() { if (layerInfoSize > 0) { if (nLayers <= 0) return false; if (nLayers != layers.size()) return false; foreach(PSDLayerRecord* layer, layers) { if (!layer->valid()) { return false; } } } if (!error.isNull()) { return false; } return true; } diff --git a/krita/plugins/formats/psd/psd_loader.cpp b/krita/plugins/formats/psd/psd_loader.cpp index 5580a6a742c..191741370fc 100644 --- a/krita/plugins/formats/psd/psd_loader.cpp +++ b/krita/plugins/formats/psd/psd_loader.cpp @@ -1,281 +1,282 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" PSDLoader::PSDLoader(KisDocument *doc) { m_image = 0; m_doc = doc; m_job = 0; m_stop = false; } PSDLoader::~PSDLoader() { } KisImageBuilder_Result PSDLoader::decode(const KUrl& uri) { // open the file QFile f(uri.toLocalFile()); if (!f.exists()) { return KisImageBuilder_RESULT_NOT_EXIST; } if (!f.open(QIODevice::ReadOnly)) { return KisImageBuilder_RESULT_FAILURE; } dbgFile << "pos:" << f.pos(); PSDHeader header; if (!header.read(&f)) { dbgFile << "failed reading header: " << header.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << header; dbgFile << "Read header. pos:" << f.pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(&f)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read color mode block. pos:" << f.pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(&f)) { dbgFile << "failed image reading resource section: " << resourceSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read image resource section. pos:" << f.pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(&f)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << f.pos(); // Done reading, except possibly for the image data block, which is only relevant if there // are no layers. // Get the right colorspace QPair colorSpaceId = psd_colormode_to_colormodelid(header.colormode, header.channelDepth); if (colorSpaceId.first.isNull()) { dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth; return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Get the icc profile from the image resource section const KoColorProfile* profile = 0; if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) { ICC_PROFILE_1039 *iccProfileData = dynamic_cast(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource); if (iccProfileData ) { profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, iccProfileData->icc); dbgFile << "Loaded ICC profile" << profile->name(); delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE); } } // Create the colorspace const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); if (!cs) { return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Creating the KisImage m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, f.fileName()); Q_CHECK_PTR(m_image); m_image->lock(); // set the correct resolution if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) { RESN_INFO_1005 *resInfo = dynamic_cast(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource); if (resInfo) { m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); // let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it. delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO); } } // Preserve all the annotations foreach(PSDResourceBlock *resourceBlock, resourceSection.resources.values()) { m_image->addAnnotation(resourceBlock); } // Preserve the duotone colormode block for saving back to psd if (header.colormode == DuoTone) { KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock", i18n("Duotone Colormode Block"), colorModeBlock.data); m_image->addAnnotation(annotation); } // Read the projection into our single layer. Since we only read the projection when // we have just one layer, we don't need to later on apply the alpha channel of the // first layer to the projection if the number of layers is negative/ // See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. if (layerSection.nLayers == 0) { dbgFile << "Position" << f.pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'"; KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8); PSDImageData imageData(&header); imageData.read(&f, layer->paintDevice()); m_image->addNode(layer, m_image->rootLayer()); // Only one layer, the background layer, so we're done. m_image->unlock(); return KisImageBuilder_RESULT_OK; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); // read the channels for the various layers for(int i = 0; i < layerSection.nLayers; ++i) { PSDLayerRecord* layerRecord = layerSection.layers.at(i); dbgFile << "Going to read channels for layer" << i << layerRecord->layerName; KisLayerSP newLayer; if (layerRecord->infoBlocks.keys.contains("lsct")) { if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); groupStack.push(groupLayer); newLayer = groupLayer; } else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder || layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = groupStack.pop(); groupLayer->setName(layerRecord->layerName); groupLayer->setVisible(layerRecord->visible); newLayer = groupLayer; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOp(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); if (!layerRecord->readPixelData(&f, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; return KisImageBuilder_RESULT_FAILURE; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } foreach(ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { - if (channelInfo->channelId < 0) { + if (channelInfo->channelId < -1) { qDebug() << ">>>>>>>>>>>>>> transparency mask" << channelInfo->channelId; -// KisTransparencyMaskSP mask = new KisTransparencyMask(); -// mask->initSelection(newLayer); -// if (!layerRecord->readMask(&f, mask->paintDevice(), channelInfo)) { -// dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; -// } -// m_image->addNode(mask, newLayer); + KisTransparencyMaskSP mask = new KisTransparencyMask(); + mask->setName(i18n("Transparency Mask")); + mask->initSelection(newLayer); + if (!layerRecord->readMask(&f, mask->paintDevice(), channelInfo)) { + dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; + } + m_image->addNode(mask, newLayer); } } } m_image->unlock(); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result PSDLoader::buildImage(const KUrl& uri) { if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!KIO::NetAccess::exists(uri, KIO::NetAccess::SourceSide, qApp->activeWindow())) { return KisImageBuilder_RESULT_NOT_EXIST; } // We're not set up to handle asynchronous loading at the moment. KisImageBuilder_Result result = KisImageBuilder_RESULT_FAILURE; QString tmpFile; if (KIO::NetAccess::download(uri, tmpFile, qApp->activeWindow())) { KUrl uriTF; uriTF.setPath( tmpFile ); result = decode(uriTF); KIO::NetAccess::removeTempFile(tmpFile); } return result; } KisImageWSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } #include "psd_loader.moc" diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.cpp b/krita/plugins/formats/psd/tests/kis_psd_test.cpp index 181a6f1dd6b..dbde43c8f6a 100644 --- a/krita/plugins/formats/psd/tests/kis_psd_test.cpp +++ b/krita/plugins/formats/psd/tests/kis_psd_test.cpp @@ -1,58 +1,82 @@ /* * Copyright (C) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_psd_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisPSDTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } void KisPSDTest::testOpening() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); QScopedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); manager.setBatchMode(true); KisImportExportFilter::ConversionStatus status; QString s = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString(), status); qDebug() << s; Q_ASSERT(doc->image()); } +void KisPSDTest::testTransparencyMask() +{ + QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/masks.psd"); + //QFileInfo sourceFileInfo("/home/dmitry/masks_multi.psd"); + + Q_ASSERT(sourceFileInfo.exists()); + + QScopedPointer doc(qobject_cast(KisPart::instance()->createDocument())); + + KisImportExportManager manager(doc.data()); + manager.setBatchMode(true); + + KisImportExportFilter::ConversionStatus status; + QString s = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString(), + status); + // qDebug() << s; + + QVERIFY(doc->image()); + + QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); + + QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single")); +} + QTEST_KDEMAIN(KisPSDTest, GUI) #include "kis_psd_test.moc" diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.h b/krita/plugins/formats/psd/tests/kis_psd_test.h index bc45a0ccf0f..5ce3893e448 100644 --- a/krita/plugins/formats/psd/tests/kis_psd_test.h +++ b/krita/plugins/formats/psd/tests/kis_psd_test.h @@ -1,32 +1,33 @@ /* * Copyright (C) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PSD_TEST_H_ #define _KIS_PSD_TEST_H_ #include class KisPSDTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); void testOpening(); + void testTransparencyMask(); }; #endif