Commit decf22e1 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Make PSD pixel data saving code be shared between KisImageData and KisLayerRecord

It also fixes a set of loading/saving bugs we had
parent 7a2b8377
......@@ -149,7 +149,7 @@ bool PSDImageData::read(QIODevice *io, KisPaintDeviceSP dev ) {
return true;
}
bool PSDImageData::write(QIODevice *io, KisPaintDeviceSP dev)
bool PSDImageData::write(QIODevice *io, KisPaintDeviceSP dev, bool hasAlpha)
{
// XXX: make the compression settting configurable. For now, always use RLE.
psdwrite(io, (quint16)Compression::RLE);
......@@ -157,93 +157,33 @@ bool PSDImageData::write(QIODevice *io, KisPaintDeviceSP dev)
// now write all the channels in display order
// fill in the channel chooser, in the display order, but store the pixel index as well.
QRect rc(0, 0, m_header->width, m_header->height);
QVector<quint8* > tmp = dev->readPlanarBytes(0, 0, rc.width(), rc.height());
// then reorder the planes to fit the psd model -- alpha first, then display order
QVector<quint8* > planes;
QList<KoChannelInfo*> origChannels = dev->colorSpace()->channels();
quint8* alphaPlane = 0;
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) {
alphaPlane = tmp[channelIndex];
} else {
planes.append(tmp[channelIndex]);
}
}
planes.append(alphaPlane); // alpha is last, in contrast with layers, where it's first.
// now planes are holding pointers to quint8 arrays
tmp.clear();
// Now fix up the cmyk channels, we need to invert them
if (m_header->colormode == CMYK || m_header->colormode == CMYK64) {
for (int i = 0; i < 4; ++i) {
if (m_header->channelDepth == 8) {
for (int j = 0; j < rc.width() * rc.height(); ++j) {
planes[i][j] = 255 - planes[i][j];
}
}
else if (m_header->channelDepth == 16) {
quint16 val;
for (int j = 0; j < rc.width() * rc.height(); ++j) {
val = reinterpret_cast<quint16*>(planes[i])[j];
val = quint16_MAX - ntohs(val);
reinterpret_cast<quint16*>(planes[i])[j] = val;
}
}
}
}
quint64 channelLengthPos = io->pos();
// write zero's for the channel lengths section
for (uint i = 0; i < dev->colorSpace()->channelCount() * rc.height(); ++i) {
psdwrite(io, (quint16)0);
}
// here the actual channel data starts
quint64 channelStartPos = io->pos();
for (int channelInfoIndex = 0; channelInfoIndex < planes.size(); ++channelInfoIndex) {
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);
if (m_header->channelDepth == 8) {
} else if (m_header->channelDepth == 16) {
quint16 *dataPtr = reinterpret_cast<quint16 *>(uncompressed.data());
for (int i = 0; i < rc.width(); i++) {
quint16 val = htons(*dataPtr);
*dataPtr = val;
++dataPtr;
}
} else if (m_header->channelDepth == 32) {
quint32 *dataPtr = reinterpret_cast<quint32 *>(uncompressed.data());
for (int i = 0; i < rc.width(); i++) {
quint32 val = htonl(*dataPtr);
*dataPtr = val;
++dataPtr;
}
}
QByteArray compressed = Compression::compress(uncompressed, Compression::RLE);
const int channelSize = m_header->channelDepth / 8;
const psd_color_mode colorMode = m_header->colormode;
io->seek(channelLengthPos);
psdwrite(io, (quint16)compressed.size());
channelLengthPos +=2;
io->seek(channelStartPos);
QVector<PsdPixelUtils::ChannelWritingInfo> writingInfoList;
if (io->write(compressed) != compressed.size()) {
error = "Could not write image data";
return false;
}
bool writeAlpha = hasAlpha &&
dev->colorSpace()->channelCount() != dev->colorSpace()->colorChannelCount();
channelStartPos += compressed.size();
}
}
const int numChannels =
writeAlpha ?
dev->colorSpace()->channelCount() :
dev->colorSpace()->colorChannelCount();
qDeleteAll(planes);
planes.clear();
for (int i = 0; i < numChannels; i++) {
const int rleOffset = io->pos();
int channelId = writeAlpha && i == numChannels - 1 ? -1 : i;
writingInfoList <<
PsdPixelUtils::ChannelWritingInfo(channelId, -1, rleOffset);
io->seek(io->pos() + rc.height() * sizeof(quint16));
}
PsdPixelUtils::writePixelDataCommon(io, dev, rc,
colorMode, channelSize,
false, false, writingInfoList);
return true;
}
......@@ -38,7 +38,7 @@ public:
virtual ~PSDImageData();
bool read(QIODevice *io, KisPaintDeviceSP dev);
bool write(QIODevice *io, KisPaintDeviceSP dev);
bool write(QIODevice *io, KisPaintDeviceSP dev, bool hasAlpha);
QString error;
......
......@@ -587,36 +587,6 @@ void PSDLayerRecord::write(QIODevice* io,
}
}
void writeChannelDataRLE(QIODevice *io, const quint8 *plane, const int channelSize, const QRect &rc, const qint64 sizeFieldOffset)
{
KisAslWriterUtils::OffsetStreamPusher<quint32> channelBlockSizeExternalTag(io, 0, sizeFieldOffset);
SAFE_WRITE_EX(io, (quint16)Compression::RLE);
// the start of RLE sizes block
const qint64 channelRLESizePos = io->pos();
// write zero's for the channel lengths block
for(int i = 0; i < rc.height(); ++i) {
// XXX: choose size for PSB!
const quint16 fakeRLEBLockSize = 0;
SAFE_WRITE_EX(io, fakeRLEBLockSize);
}
quint32 stride = channelSize * 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);
KisAslWriterUtils::OffsetStreamPusher<quint16> rleExternalTag(io, 0, channelRLESizePos + row * sizeof(quint16));
if (io->write(compressed) != compressed.size()) {
throw KisAslWriterUtils::ASLWriteException("Failed to write image data");
}
}
}
void PSDLayerRecord::writeTransparencyMaskPixelData(QIODevice *io)
{
if (m_onlyTransparencyMask) {
......@@ -626,11 +596,20 @@ void PSDLayerRecord::writeTransparencyMaskPixelData(QIODevice *io)
QByteArray buffer(m_onlyTransparencyMaskRect.width() * m_onlyTransparencyMaskRect.height(), 0);
device->readBytes((quint8*)buffer.data(), m_onlyTransparencyMaskRect);
writeChannelDataRLE(io, (quint8*)buffer.data(), 1, m_onlyTransparencyMaskRect, m_transparencyMaskSizeOffset);
PsdPixelUtils::writeChannelDataRLE(io, (quint8*)buffer.data(), 1, m_onlyTransparencyMaskRect, m_transparencyMaskSizeOffset, -1, true);
}
}
void PSDLayerRecord::writePixelData(QIODevice *io)
{
try {
writePixelDataImpl(io);
} catch (KisAslWriterUtils::ASLWriteException &e) {
throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what()));
}
}
void PSDLayerRecord::writePixelDataImpl(QIODevice *io)
{
dbgFile << "writing pixel data for layer" << layerName << "at" << io->pos();
......@@ -638,104 +617,34 @@ void PSDLayerRecord::writePixelData(QIODevice *io)
const QRect rc(left, top, right - left, bottom - top);
if (rc.isEmpty()) {
try {
dbgFile << "Layer is empty! Writing placeholder information.";
dbgFile << "Layer is empty! Writing placeholder information.";
for (int i = 0; i < nChannels; i++) {
const ChannelInfo *channelInfo = channelInfoRecords[i];
KisAslWriterUtils::OffsetStreamPusher<quint32> channelBlockSizeExternalTag(io, 0, channelInfo->channelInfoPosition);
SAFE_WRITE_EX(io, (quint16)Compression::Uncompressed);
}
writeTransparencyMaskPixelData(io);
} catch (KisAslWriterUtils::ASLWriteException &e) {
throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what()));
for (int i = 0; i < nChannels; i++) {
const ChannelInfo *channelInfo = channelInfoRecords[i];
KisAslWriterUtils::OffsetStreamPusher<quint32> channelBlockSizeExternalTag(io, 0, channelInfo->channelInfoPosition);
SAFE_WRITE_EX(io, (quint16)Compression::Uncompressed);
}
writeTransparencyMaskPixelData(io);
return;
}
// now write all the channels in display order
dbgFile << "layer" << layerName;
QVector<quint8* > tmp = dev->readPlanarBytes(rc.x() - dev->x(), rc.y() - dev->y(), rc.width(), rc.height());
// then reorder the planes to fit the psd model -- alpha first, then display order
QVector<quint8*> planes;
QList<KoChannelInfo*> origChannels = dev->colorSpace()->channels();
foreach(KoChannelInfo *ch, KoChannelInfo::displayOrderSorted(origChannels)) {
int channelIndex = KoChannelInfo::displayPositionToChannelIndex(ch->displayPosition(), origChannels);
quint8 *holder = tmp[channelIndex];
tmp[channelIndex] = 0;
if (ch->channelType() == KoChannelInfo::ALPHA) {
planes.insert(0, holder);
} else {
planes.append(holder);
}
}
// now planes are holding pointers to quint8 arrays
tmp.clear();
try {
// here's where we save the total size of the channel data
for (int channelInfoIndex = 0; channelInfoIndex < nChannels; ++channelInfoIndex) {
const ChannelInfo *channelInfo = channelInfoRecords[channelInfoIndex];
dbgFile << "\tWriting channel" << channelInfoIndex << "psd channel id" << channelInfo->channelId;
// if the bitdepth > 8, place the bytes in the right order
// if cmyk, invert the pixel value
if (m_header.channelDepth == 8) {
if (channelInfo->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<quint16*>(planes[channelInfoIndex])[i];
val = ntohs(val);
if (channelInfo->channelId >= 0 && (m_header.colormode == CMYK || m_header.colormode == CMYK64)) {
val = quint16_MAX - val;
}
reinterpret_cast<quint16*>(planes[channelInfoIndex])[i] = val;
}
}
else if (m_header.channelDepth == 32) {
quint32 val;
for (int i = 0; i < rc.width() * rc.height(); ++i) {
val = reinterpret_cast<quint32*>(planes[channelInfoIndex])[i];
val = ntohl(val);
if (channelInfo->channelId >= 0 && (m_header.colormode == CMYK || m_header.colormode == CMYK64)) {
val = quint16_MAX - val;
}
reinterpret_cast<quint32*>(planes[channelInfoIndex])[i] = val;
}
}
dbgFile << "\t\tchannel start" << ppVar(io->pos());
writeChannelDataRLE(io, planes[channelInfoIndex], m_header.channelDepth / 8, rc, channelInfo->channelInfoPosition);
}
writeTransparencyMaskPixelData(io);
} catch (KisAslWriterUtils::ASLWriteException &e) {
qDeleteAll(planes);
planes.clear();
const int channelSize = m_header.channelDepth / 8;
const psd_color_mode colorMode = m_header.colormode;
throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what()));
QVector<PsdPixelUtils::ChannelWritingInfo> writingInfoList;
foreach (const ChannelInfo *channelInfo, channelInfoRecords) {
writingInfoList <<
PsdPixelUtils::ChannelWritingInfo(channelInfo->channelId,
channelInfo->channelInfoPosition);
}
qDeleteAll(planes);
planes.clear();
PsdPixelUtils::writePixelDataCommon(io, dev, rc, colorMode, channelSize, true, true, writingInfoList);
writeTransparencyMaskPixelData(io);
}
bool PSDLayerRecord::valid()
......
......@@ -158,12 +158,9 @@ public:
private:
void writeTransparencyMaskPixelData(QIODevice *io);
private:
void writePixelDataImpl(QIODevice *io);
bool readRGB(KisPaintDeviceSP dev ,QIODevice *io);
bool readCMYK(KisPaintDeviceSP dev ,QIODevice *io);
bool readLAB(KisPaintDeviceSP dev ,QIODevice *io);
bool readGray(KisPaintDeviceSP dev ,QIODevice *io);
private:
KisPaintDeviceSP m_layerContentDevice;
KisNodeSP m_onlyTransparencyMask;
......
......@@ -30,6 +30,7 @@
#include <netinet/in.h> // htonl
#include <asl/kis_asl_writer_utils.h>
#include <asl/kis_asl_reader_utils.h>
#include "psd_layer_record.h"
......@@ -349,4 +350,176 @@ void readChannels(QIODevice *io,
throw KisAslReaderUtils::ASLParseException(error);
}
}
void writeChannelDataRLE(QIODevice *io, const quint8 *plane, const int channelSize, const QRect &rc, const qint64 sizeFieldOffset, const qint64 rleBlockOffset, const bool writeCompressionType)
{
typedef KisAslWriterUtils::OffsetStreamPusher<quint32> Pusher;
QScopedPointer<Pusher> channelBlockSizeExternalTag;
if (sizeFieldOffset >= 0) {
channelBlockSizeExternalTag.reset(new Pusher(io, 0, sizeFieldOffset));
}
if (writeCompressionType) {
SAFE_WRITE_EX(io, (quint16)Compression::RLE);
}
const bool externalRleBlock = rleBlockOffset >= 0;
// the start of RLE sizes block
const qint64 channelRLESizePos = externalRleBlock ? rleBlockOffset : io->pos();
{
QScopedPointer<KisOffsetKeeper> rleOffsetKeeper;
if (externalRleBlock) {
rleOffsetKeeper.reset(new KisOffsetKeeper(io));
io->seek(rleBlockOffset);
}
// write zero's for the channel lengths block
for(int i = 0; i < rc.height(); ++i) {
// XXX: choose size for PSB!
const quint16 fakeRLEBLockSize = 0;
SAFE_WRITE_EX(io, fakeRLEBLockSize);
}
}
quint32 stride = channelSize * 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);
KisAslWriterUtils::OffsetStreamPusher<quint16> rleExternalTag(io, 0, channelRLESizePos + row * sizeof(quint16));
if (io->write(compressed) != compressed.size()) {
throw KisAslWriterUtils::ASLWriteException("Failed to write image data");
}
}
}
inline void preparePixelForWrite(quint8 *dataPlane,
int numPixels,
int channelSize,
int channelId,
psd_color_mode colorMode)
{
// if the bitdepth > 8, place the bytes in the right order
// if cmyk, invert the pixel value
if (channelSize == 1) {
if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) {
for (int i = 0; i < numPixels; ++i) {
dataPlane[i] = 255 - dataPlane[i];
}
}
}
else if (channelSize == 2) {
quint16 val;
for (int i = 0; i < numPixels; ++i) {
quint16 *pixelPtr = reinterpret_cast<quint16*>(dataPlane) + i;
val = *pixelPtr;
val = ntohs(val);
if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) {
val = quint16_MAX - val;
}
*pixelPtr = val;
}
}
else if (channelSize == 4) {
quint32 val;
for (int i = 0; i < numPixels; ++i) {
quint32 *pixelPtr = reinterpret_cast<quint32*>(dataPlane) + i;
val = *pixelPtr;
val = ntohl(val);
if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) {
val = quint16_MAX - val;
}
*pixelPtr = val;
}
}
}
void writePixelDataCommon(QIODevice *io,
KisPaintDeviceSP dev,
const QRect &rc,
psd_color_mode colorMode,
int channelSize,
bool alphaFirst,
const bool writeCompressionType,
QVector<ChannelWritingInfo> &writingInfoList)
{
// Empty rects must be processed separately on a higher level!
KIS_ASSERT_RECOVER_RETURN(!rc.isEmpty());
QVector<quint8* > tmp = dev->readPlanarBytes(rc.x() - dev->x(), rc.y() - dev->y(), rc.width(), rc.height());
const KoColorSpace *colorSpace = dev->colorSpace();
QVector<quint8*> planes;
{ // prepare 'planes' array
quint8 *alphaPlanePtr = 0;
QList<KoChannelInfo*> origChannels = colorSpace->channels();
foreach(KoChannelInfo *ch, KoChannelInfo::displayOrderSorted(origChannels)) {
int channelIndex = KoChannelInfo::displayPositionToChannelIndex(ch->displayPosition(), origChannels);
quint8 *holder = 0;
qSwap(holder, tmp[channelIndex]);
if (ch->channelType() == KoChannelInfo::ALPHA) {
qSwap(holder, alphaPlanePtr);
} else {
planes.append(holder);
}
}
if (alphaPlanePtr) {
if (alphaFirst) {
planes.insert(0, alphaPlanePtr);
KIS_ASSERT_RECOVER_NOOP(writingInfoList.first().channelId == -1);
} else {
planes.append(alphaPlanePtr);
KIS_ASSERT_RECOVER_NOOP(
(writingInfoList.size() == planes.size() - 1) ||
(writingInfoList.last().channelId == -1));
}
}
// now planes are holding pointers to quint8 arrays
tmp.clear();
}
KIS_ASSERT_RECOVER_RETURN(planes.size() >= writingInfoList.size());
const int numPixels = rc.width() * rc.height();
// write down the planes
try {
for (int i = 0; i < writingInfoList.size(); i++) {
const ChannelWritingInfo &info = writingInfoList[i];
dbgFile << "\tWriting channel" << i << "psd channel id" << info.channelId;
preparePixelForWrite(planes[i], numPixels, channelSize, info.channelId, colorMode);
dbgFile << "\t\tchannel start" << ppVar(io->pos());
writeChannelDataRLE(io, planes[i], channelSize, rc, info.sizeFieldOffset, info.rleBlockOffset, writeCompressionType);
}
} catch (KisAslWriterUtils::ASLWriteException &e) {
qDeleteAll(planes);
planes.clear();
throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what()));
}
qDeleteAll(planes);
planes.clear();
}
}
......@@ -27,10 +27,21 @@
class QIODevice;
struct ChannelInfo;
struct ChannelWritingInfo;
namespace PsdPixelUtils
{
struct ChannelWritingInfo {
ChannelWritingInfo() : channelId(0), sizeFieldOffset(-1), rleBlockOffset(-1) {}
ChannelWritingInfo(qint16 _channelId, int _sizeFieldOffset) : channelId(_channelId), sizeFieldOffset(_sizeFieldOffset), rleBlockOffset(-1) {}
ChannelWritingInfo(qint16 _channelId, int _sizeFieldOffset, int _rleBlockOffset) : channelId(_channelId), sizeFieldOffset(_sizeFieldOffset), rleBlockOffset(_rleBlockOffset) {}
qint16 channelId;
int sizeFieldOffset;
int rleBlockOffset;
};
void readChannels(QIODevice *io,
KisPaintDeviceSP device,
psd_color_mode colorMode,
......@@ -38,6 +49,23 @@ namespace PsdPixelUtils
const QRect &layerRect,
QVector<ChannelInfo*> infoRecords);
void writeChannelDataRLE(QIODevice *io,
const quint8 *plane,
const int channelSize,
const QRect &rc,
const qint64 sizeFieldOffset,
const qint64 rleBlockOffset,
const bool writeCompressionType);
void writePixelDataCommon(QIODevice *io,
KisPaintDeviceSP dev,
const QRect &rc,
psd_color_mode colorMode,
int channelSize,
bool alphaFirst,
const bool writeCompressionType,
QVector<ChannelWritingInfo> &writingInfoList);
}
#endif /* __PSD_PIXEL_UTILS_H */
......@@ -142,11 +142,17 @@ KisImageBuilder_Result PSDSaver::buildFile(const KUrl& uri)
return KisImageBuilder_RESULT_NOT_LOCAL;
}
const bool haveLayers = m_image->rootLayer()->childCount() > 1 ||
checkIfHasTransparency(m_image->rootLayer()->firstChild()->projection());
// HEADER
PSDHeader header;
header.signature = "8BPS";
header.version = 1;
header.nChannels = m_image->colorSpace()->channelCount();
header.nChannels = haveLayers ?
m_image->colorSpace()->channelCount() :
m_image->colorSpace()->colorChannelCount();
header.width = m_image->width();
header.height = m_image->height();
......@@ -240,9 +246,6 @@ KisImageBuilder_Result PSDSaver::buildFile(const KUrl& uri)
// Only save layers and masks if there is more than one layer
dbgFile << "m_image->rootLayer->childCount" << m_image->rootLayer()->childCount() << f.pos();
bool haveLayers = m_image->rootLayer()->childCount() > 1 ||
checkIfHasTransparency(m_image