[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