[calligra/calligra/2.9] krita/plugins/formats/psd: [FEATURE] Implement loading for ZIP compressed PSD files
Dmitry Kazakov
dimula73 at gmail.com
Fri Aug 14 08:08:22 UTC 2015
Git commit 02b1733ba001f1b60b1563f005e9a8984ce53bea by Dmitry Kazakov.
Committed on 14/08/2015 at 08:07.
Pushed by dkazakov into branch 'calligra/2.9'.
[FEATURE] Implement loading for ZIP compressed PSD files
Now all types of 8 bit and 16 bit PSD files should load fine, including
multilayered ones. Saving should work as well.
STILL NEEDS TESTING:
We need to test if Photoshop will load 16bit multilayered files saved
in Krita. The point is we still save them differently from what PS is
used to.
CC:kimageshop at kde.org
M +5 -4 krita/plugins/formats/psd/CMakeLists.txt
M +3 -1 krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
M +1 -4 krita/plugins/formats/psd/psd_layer_section.cpp
M +152 -12 krita/plugins/formats/psd/psd_pixel_utils.cpp
M +4 -3 krita/plugins/formats/psd/tests/kis_psd_test.cpp
http://commits.kde.org/calligra/02b1733ba001f1b60b1563f005e9a8984ce53bea
diff --git a/krita/plugins/formats/psd/CMakeLists.txt b/krita/plugins/formats/psd/CMakeLists.txt
index 99b4e14..1ffe2d3 100644
--- a/krita/plugins/formats/psd/CMakeLists.txt
+++ b/krita/plugins/formats/psd/CMakeLists.txt
@@ -4,6 +4,7 @@ macro_optional_find_package(ZLIB)
macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB)
configure_file(config_psd.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_psd.h)
+include_directories(${ZLIB_INCLUDE_DIR})
set(LIB_PSD_SRCS
psd_header.cpp
@@ -25,9 +26,9 @@ set(kritapsdimport_PART_SRCS
kde4_add_plugin(kritapsdimport ${kritapsdimport_PART_SRCS})
if (WIN32)
- target_link_libraries(kritapsdimport kritaui kritapsd ${KDE4_KIO_LIBS} ${WIN32_PLATFORM_NET_LIBS})
+ target_link_libraries(kritapsdimport kritaui kritapsd ${KDE4_KIO_LIBS} ${WIN32_PLATFORM_NET_LIBS} ${ZLIB_LIBRARIES})
else ()
- target_link_libraries(kritapsdimport kritaui kritapsd ${KDE4_KIO_LIBS})
+ target_link_libraries(kritapsdimport kritaui kritapsd ${KDE4_KIO_LIBS} ${ZLIB_LIBRARIES})
endif ()
install(TARGETS kritapsdimport DESTINATION ${PLUGIN_INSTALL_DIR})
@@ -43,9 +44,9 @@ set(kritapsdexport_PART_SRCS
kde4_add_plugin(kritapsdexport ${kritapsdexport_PART_SRCS})
if (WIN32)
- target_link_libraries(kritapsdexport kritaui ${WIN32_PLATFORM_NET_LIBS})
+ target_link_libraries(kritapsdexport kritaui ${WIN32_PLATFORM_NET_LIBS} ${ZLIB_LIBRARIES})
else ()
- target_link_libraries(kritapsdexport kritaui )
+ target_link_libraries(kritapsdexport kritaui ${ZLIB_LIBRARIES})
endif ()
install(TARGETS kritapsdexport DESTINATION ${PLUGIN_INSTALL_DIR})
diff --git a/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp b/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
index c0d4bb7..e52b543 100644
--- a/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
+++ b/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
@@ -95,7 +95,9 @@ void PsdAdditionalLayerInfoBlock::readImpl(QIODevice* io)
}
keys << key;
- if (key == "Lr16" || key == "Lr32") {
+ // TODO: Loading of 32 bit files is not supported yet
+
+ if (key == "Lr16"/* || key == "Lr32"*/) {
if (m_layerInfoBlockHandler) {
int offset = m_header.version > 1 ? 8 : 4;
io->seek(io->pos() - offset);
diff --git a/krita/plugins/formats/psd/psd_layer_section.cpp b/krita/plugins/formats/psd/psd_layer_section.cpp
index b12c79d..5a8eea0 100644
--- a/krita/plugins/formats/psd/psd_layer_section.cpp
+++ b/krita/plugins/formats/psd/psd_layer_section.cpp
@@ -274,11 +274,8 @@ bool PSDLayerMaskSection::readImpl(QIODevice* io)
*
* Here we pass the callback which should be used when such
* additional section is recognized.
- *
- * NOTE: atm, we do not support ZIP compression, which is used in
- * this block, so we just comment it out for now!
*/
- // globalInfoSection.setExtraLayerInfoBlockHandler(boost::bind(&PSDLayerMaskSection::readLayerInfoImpl, this, _1));
+ globalInfoSection.setExtraLayerInfoBlockHandler(boost::bind(&PSDLayerMaskSection::readLayerInfoImpl, this, _1));
globalInfoSection.read(io);
diff --git a/krita/plugins/formats/psd/psd_pixel_utils.cpp b/krita/plugins/formats/psd/psd_pixel_utils.cpp
index a9b4884..60784fe 100644
--- a/krita/plugins/formats/psd/psd_pixel_utils.cpp
+++ b/krita/plugins/formats/psd/psd_pixel_utils.cpp
@@ -37,6 +37,10 @@
#include <asl/kis_offset_keeper.h>
#include "kis_iterator_ng.h"
+#include "config_psd.h"
+#ifdef HAVE_ZLIB
+#include "zlib.h"
+#endif
namespace PsdPixelUtils {
@@ -114,7 +118,7 @@ void readGrayPixel(const QMap<quint16, QByteArray> &channelBytes,
const channels_type unitValue = KoColorSpaceMathsTraits<channels_type>::unitValue;
channels_type opacity = unitValue;
if (channelBytes.contains(-1)) {
- opacity = channelBytes[-1].constData()[col];
+ opacity = convertByteOrder<Traits>(reinterpret_cast<const channels_type *>(channelBytes[-1].constData())[col]);
}
Pixel *pixelPtr = reinterpret_cast<Pixel*>(dstPtr);
@@ -135,7 +139,7 @@ void readRgbPixel(const QMap<quint16, QByteArray> &channelBytes,
const channels_type unitValue = KoColorSpaceMathsTraits<channels_type>::unitValue;
channels_type opacity = unitValue;
if (channelBytes.contains(-1)) {
- opacity = channelBytes[-1].constData()[col];
+ opacity = convertByteOrder<Traits>(reinterpret_cast<const channels_type *>(channelBytes[-1].constData())[col]);
}
Pixel *pixelPtr = reinterpret_cast<Pixel*>(dstPtr);
@@ -160,7 +164,7 @@ void readCmykPixel(const QMap<quint16, QByteArray> &channelBytes,
const channels_type unitValue = KoColorSpaceMathsTraits<channels_type>::unitValue;
channels_type opacity = unitValue;
if (channelBytes.contains(-1)) {
- opacity = channelBytes[-1].constData()[col];
+ opacity = convertByteOrder<Traits>(reinterpret_cast<const channels_type *>(channelBytes[-1].constData())[col]);
}
Pixel *pixelPtr = reinterpret_cast<Pixel*>(dstPtr);
@@ -187,7 +191,7 @@ void readLabPixel(const QMap<quint16, QByteArray> &channelBytes,
const channels_type unitValue = KoColorSpaceMathsTraits<channels_type>::unitValue;
channels_type opacity = unitValue;
if (channelBytes.contains(-1)) {
- opacity = channelBytes[-1].constData()[col];
+ opacity = convertByteOrder<Traits>(reinterpret_cast<const channels_type *>(channelBytes[-1].constData())[col]);
}
Pixel *pixelPtr = reinterpret_cast<Pixel*>(dstPtr);
@@ -254,6 +258,99 @@ void readLabPixelCommon(int channelSize,
}
}
+/**********************************************************************/
+/* Two functions copied from the abandoned PSDParse library (GPL) */
+/* See: http://www.telegraphics.com.au/svn/psdparse/trunk/psd_zip.c */
+/* Created by Patrick in 2007.02.02, libpsd at graphest.com */
+/* Modifications by Toby Thain <toby at telegraphics.com.au> */
+/**********************************************************************/
+
+typedef bool psd_status;
+typedef quint8 psd_uchar;
+typedef int psd_int;
+typedef quint8 Bytef;
+
+psd_status psd_unzip_without_prediction(psd_uchar *src_buf, psd_int src_len,
+ psd_uchar *dst_buf, psd_int dst_len)
+{
+#ifdef HAVE_ZLIB
+ z_stream stream;
+ psd_int state;
+
+ memset(&stream, 0, sizeof(z_stream));
+ stream.data_type = Z_BINARY;
+
+ stream.next_in = (Bytef *)src_buf;
+ stream.avail_in = src_len;
+ stream.next_out = (Bytef *)dst_buf;
+ stream.avail_out = dst_len;
+
+ if(inflateInit(&stream) != Z_OK)
+ return 0;
+
+ do {
+ state = inflate(&stream, Z_PARTIAL_FLUSH);
+ if(state == Z_STREAM_END)
+ break;
+ if(state == Z_DATA_ERROR || state != Z_OK)
+ break;
+ } while (stream.avail_out > 0);
+
+ if (state != Z_STREAM_END && state != Z_OK)
+ return 0;
+
+ return 1;
+
+#endif /* HAVE_ZLIB */
+
+ return 0;
+}
+
+psd_status psd_unzip_with_prediction(psd_uchar *src_buf, psd_int src_len,
+ psd_uchar *dst_buf, psd_int dst_len,
+ psd_int row_size, psd_int color_depth)
+{
+ psd_status status;
+ int len;
+ psd_uchar * buf;
+
+ status = psd_unzip_without_prediction(src_buf, src_len, dst_buf, dst_len);
+ if(!status)
+ return status;
+
+ buf = dst_buf;
+ do {
+ len = row_size;
+ if (color_depth == 16)
+ {
+ while(--len)
+ {
+ buf[2] += buf[0] + ((buf[1] + buf[3]) >> 8);
+ buf[3] += buf[1];
+ buf += 2;
+ }
+ buf += 2;
+ dst_len -= row_size * 2;
+ }
+ else
+ {
+ while(--len)
+ {
+ *(buf + 1) += *buf;
+ buf ++;
+ }
+ buf ++;
+ dst_len -= row_size;
+ }
+ } while(dst_len > 0);
+
+ return 1;
+}
+
+/**********************************************************************/
+/* End of third party block */
+/**********************************************************************/
+
QMap<quint16, QByteArray> fetchChannelsBytes(QIODevice *io, QVector<ChannelInfo*> channelInfoRecords,
int row, int width, int channelSize)
{
@@ -304,19 +401,62 @@ void readCommon(KisPaintDeviceSP dev,
return;
}
- KisHLineIteratorSP it = dev->createHLineIteratorNG(layerRect.left(), layerRect.top(), layerRect.width());
+ if (infoRecords.first()->compressionType == Compression::ZIP ||
+ infoRecords.first()->compressionType == Compression::ZIPWithPrediction) {
+
+ const int numPixels = channelSize * layerRect.width() * layerRect.height();
- for (int i = 0 ; i < layerRect.height(); i++) {
QMap<quint16, QByteArray> channelBytes;
- channelBytes = fetchChannelsBytes(io, infoRecords,
- i, layerRect.width(), channelSize);
+ foreach (ChannelInfo *info, infoRecords) {
+ io->seek(info->channelDataStart);
+ QByteArray compressedBytes = io->read(info->channelDataLength);
+ QByteArray uncompressedBytes(numPixels, 0);
+
+ bool status = false;
+ if (infoRecords.first()->compressionType == Compression::ZIP) {
+ status = psd_unzip_without_prediction((quint8*)compressedBytes.data(), compressedBytes.size(),
+ (quint8*)uncompressedBytes.data(), uncompressedBytes.size());
+ } else {
+ status = psd_unzip_with_prediction((quint8*)compressedBytes.data(), compressedBytes.size(),
+ (quint8*)uncompressedBytes.data(), uncompressedBytes.size(),
+ layerRect.width(), channelSize * 8);
+ }
- for (qint64 col = 0; col < layerRect.width(); col++){
- pixelFunc(channelSize, channelBytes, col, it->rawData());
- it->nextPixel();
+ if (!status) {
+ QString error = QString("Failed to unzip channel data: id = %1, compression = %2").arg(info->channelId).arg(info->compressionType);
+ dbgFile << "ERROR:" << error;
+ dbgFile << " " << ppVar(info->channelId);
+ dbgFile << " " << ppVar(info->channelDataStart);
+ dbgFile << " " << ppVar(info->channelDataLength);
+ dbgFile << " " << ppVar(info->compressionType);
+ throw KisAslReaderUtils::ASLParseException(error);
+ }
+
+ channelBytes.insert(info->channelId, uncompressedBytes);
+ }
+
+ KisSequentialIterator it(dev, layerRect);
+ int col = 0;
+ do {
+ pixelFunc(channelSize, channelBytes, col, it.rawData());
+ col++;
+ } while(it.nextPixel());
+
+ } else {
+ KisHLineIteratorSP it = dev->createHLineIteratorNG(layerRect.left(), layerRect.top(), layerRect.width());
+ for (int i = 0 ; i < layerRect.height(); i++) {
+ QMap<quint16, QByteArray> channelBytes;
+
+ channelBytes = fetchChannelsBytes(io, infoRecords,
+ i, layerRect.width(), channelSize);
+
+ for (qint64 col = 0; col < layerRect.width(); col++){
+ pixelFunc(channelSize, channelBytes, col, it->rawData());
+ it->nextPixel();
+ }
+ it->nextRow();
}
- it->nextRow();
}
}
diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.cpp b/krita/plugins/formats/psd/tests/kis_psd_test.cpp
index 58e0717..b0914ee 100644
--- a/krita/plugins/formats/psd/tests/kis_psd_test.cpp
+++ b/krita/plugins/formats/psd/tests/kis_psd_test.cpp
@@ -249,7 +249,8 @@ void KisPSDTest::testOpeningFromOpenCanvas()
void KisPSDTest::testOpeningAllFormats()
{
- QDir dirSources(QString(FILES_DATA_DIR) + QDir::separator() + "format_set/");
+ QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files");
+ QDir dirSources(path);
foreach(QFileInfo sourceFileInfo, dirSources.entryInfoList()) {
Q_ASSERT(sourceFileInfo.exists());
@@ -258,8 +259,8 @@ void KisPSDTest::testOpeningAllFormats()
continue;
}
- if (sourceFileInfo.fileName() != "sl_cmyk_8b.psd") {
- continue;
+ if (sourceFileInfo.fileName() != "ml_cmyk_16b.psd") {
+ //continue;
}
//qDebug() << "Opening" << ppVar(sourceFileInfo.fileName());
More information about the kimageshop
mailing list