[graphics/krita] /: PSD: refactor compression and fix ZIP decompression

Halla Rempt null at kde.org
Mon Aug 23 14:11:45 BST 2021


Git commit e26c14cc1ca5bdb70a3662f2c37e3a6e786404a5 by Halla Rempt, on behalf of L. E. Segovia.
Committed on 23/08/2021 at 13:11.
Pushed by rempt into branch 'master'.

PSD: refactor compression and fix ZIP decompression

Previously, there were two code paths for handling ZIP compressed blobs.
One used zlib (only for decompression, code from psdparse, working) and
the other employed q(De)Compress. This latter one not only does not
return blobs compressed with Deflate, making its use with layer channel
data impossible, but is also broken - the code prepending the data size
acts on a closed QIODevice, which is not detected by the test suite.

This commit:
- refactors the existing code into a single Compression class,
- making Zlib a required dependency (which already is, through QuaZip
  and libpng),
- fixes the test suite by checking the size of the compressed output.

While at it, add further hardening to the Zlib handling code.

CCMAIL: kimageshop at kde.org

M  +3    -3    CMakeLists.txt
M  +0    -8    libs/psd/CMakeLists.txt
D  +0    -4    libs/psd/config_psd.h.cmake
M  +1    -1    libs/psd/psd_layer_record.cpp
M  +2    -3    libs/psd/psd_layer_record.h
M  +2    -2    libs/psd/psd_layer_section.cpp
M  +15   -119  libs/psd/psd_pixel_utils.cpp
M  +7    -5    libs/psdutils/CMakeLists.txt
M  +5    -5    libs/psdutils/asl/kis_asl_patterns_writer.cpp
M  +3    -3    libs/psdutils/asl/kis_asl_reader.cpp
M  +228  -43   libs/psdutils/compression.cpp
M  +5    -4    libs/psdutils/compression.h
M  +8    -0    libs/psdutils/psd.h
M  +24   -12   libs/psdutils/tests/compression_test.cpp
M  +5    -11   plugins/impex/psd/CMakeLists.txt
D  +0    -4    plugins/impex/psd/config_psd.h.cmake
M  +8    -12   plugins/impex/psd/psd_image_data.cpp

https://invent.kde.org/graphics/krita/commit/e26c14cc1ca5bdb70a3662f2c37e3a6e786404a5

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 248c8896cd..57adfafdc9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -727,12 +727,12 @@ if(KSeExpr_FOUND)
     list (APPEND ANDROID_EXTRA_LIBS ${KSeExpr_LIBRARY} ${KSeExprUI_LIBRARY})
 endif()
 
-find_package(ZLIB)
+find_package(ZLIB REQUIRED)
 set_package_properties(ZLIB PROPERTIES
     DESCRIPTION "Compression library"
     URL "https://www.zlib.net/"
-    TYPE OPTIONAL
-    PURPOSE "Optionally used by the G'Mic and the PSD plugins")
+    TYPE REQUIRED
+    PURPOSE "Required by Krita's PNG and PSD support")
 macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB)
 
 find_package(OpenEXR 3.0 CONFIG QUIET)
diff --git a/libs/psd/CMakeLists.txt b/libs/psd/CMakeLists.txt
index 3f9ee30be0..63ca3a9b09 100644
--- a/libs/psd/CMakeLists.txt
+++ b/libs/psd/CMakeLists.txt
@@ -1,5 +1,3 @@
-configure_file(config_psd.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_psd.h)
-
 set(kritapsd_SRCS
     psd_additional_layer_info_block.cpp
     psd_header.cpp
@@ -13,16 +11,10 @@ set(kritapsd_SRCS
 add_library(kritapsd SHARED ${kritapsd_SRCS})
 generate_export_header(kritapsd)
 
-target_include_directories(kritapsd
-    PUBLIC
-        ${ZLIB_INCLUDE_DIR}
-)
-
 target_link_libraries(kritapsd
     PUBLIC
         kritaimage
         kritapsdutils
-        ${ZLIB_LIBRARIES}
 )
 
 set_target_properties(kritapsd PROPERTIES
diff --git a/libs/psd/config_psd.h.cmake b/libs/psd/config_psd.h.cmake
deleted file mode 100644
index 484e4eab94..0000000000
--- a/libs/psd/config_psd.h.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-
-/* Defines if your system has the Zlib library */
-#cmakedefine HAVE_ZLIB 1
-
diff --git a/libs/psd/psd_layer_record.cpp b/libs/psd/psd_layer_record.cpp
index 5cfe3da50d..f75e572d41 100644
--- a/libs/psd/psd_layer_record.cpp
+++ b/libs/psd/psd_layer_record.cpp
@@ -649,7 +649,7 @@ void PSDLayerRecord::writePixelDataImpl(QIODevice &io)
         for (int i = 0; i < nChannels; i++) {
             const ChannelInfo *channelInfo = channelInfoRecords[i];
             KisAslWriterUtils::OffsetStreamPusher<quint32, byteOrder> channelBlockSizeExternalTag(io, 0, channelInfo->channelInfoPosition);
-            SAFE_WRITE_EX(byteOrder, io, (quint16)Compression::Uncompressed);
+            SAFE_WRITE_EX(byteOrder, io, static_cast<quint16>(psd_compression_type::Uncompressed));
         }
 
         writeTransparencyMaskPixelData<byteOrder>(io);
diff --git a/libs/psd/psd_layer_record.h b/libs/psd/psd_layer_record.h
index ff31716424..b42ce581f8 100644
--- a/libs/psd/psd_layer_record.h
+++ b/libs/psd/psd_layer_record.h
@@ -14,7 +14,6 @@
 #include <QString>
 #include <QVector>
 
-#include <compression.h>
 #include <kis_node.h>
 #include <kis_paint_device.h>
 #include <kis_types.h>
@@ -49,7 +48,7 @@ enum psd_layer_type {
 struct KRITAPSD_EXPORT ChannelInfo {
     ChannelInfo()
         : channelId(0)
-        , compressionType(Compression::Unknown)
+        , compressionType(psd_compression_type::Unknown)
         , channelDataStart(0)
         , channelDataLength(0)
         , channelOffset(0)
@@ -58,7 +57,7 @@ struct KRITAPSD_EXPORT ChannelInfo {
     }
 
     qint16 channelId; // 0 red, 1 green, 2 blue, -1 transparency, -2 user-supplied layer mask
-    Compression::CompressionType compressionType;
+    psd_compression_type compressionType;
     quint64 channelDataStart;
     quint64 channelDataLength;
     QVector<quint32> rleRowLengths;
diff --git a/libs/psd/psd_layer_section.cpp b/libs/psd/psd_layer_section.cpp
index c094dd09be..635e313c62 100644
--- a/libs/psd/psd_layer_section.cpp
+++ b/libs/psd/psd_layer_section.cpp
@@ -139,13 +139,13 @@ bool PSDLayerMaskSection::readLayerInfoImpl(QIODevice &io)
                     error = "Could not read compression type for channel";
                     return false;
                 }
-                channelInfo->compressionType = (Compression::CompressionType)compressionType;
+                channelInfo->compressionType = static_cast<psd_compression_type>(compressionType);
                 dbgFile << "\t\tChannel" << j << "has compression type" << compressionType;
 
                 QRect channelRect = layerRecord->channelRect(channelInfo);
 
                 // read the rle row lengths;
-                if (channelInfo->compressionType == Compression::RLE) {
+                if (channelInfo->compressionType == psd_compression_type::RLE) {
                     for (qint64 row = 0; row < channelRect.height(); ++row) {
                         // dbgFile << "Reading the RLE bytecount position of row" << row << "at pos" << io.pos();
 
diff --git a/libs/psd/psd_pixel_utils.cpp b/libs/psd/psd_pixel_utils.cpp
index 41c78d2700..bb8b9a2cc6 100644
--- a/libs/psd/psd_pixel_utils.cpp
+++ b/libs/psd/psd_pixel_utils.cpp
@@ -9,28 +9,22 @@
 
 #include <QIODevice>
 #include <QMap>
+#include <QtEndian>
 #include <QtGlobal>
 
 #include <KoColorSpace.h>
 #include <KoColorSpaceMaths.h>
 #include <KoColorSpaceTraits.h>
 #include <colorspaces/KoAlphaColorSpace.h>
+#include <kis_global.h>
+#include <kis_iterator_ng.h>
 
-#include <QtEndian>
-
-#include "kis_global.h"
 #include <asl/kis_asl_reader_utils.h>
 #include <asl/kis_asl_writer_utils.h>
-
-#include "kis_iterator_ng.h"
-#include "psd.h"
-#include "psd_layer_record.h"
 #include <asl/kis_offset_keeper.h>
-
-#include "config_psd.h"
-#ifdef HAVE_ZLIB
-#include "zlib.h"
-#endif
+#include <compression.h>
+#include <psd.h>
+#include <psd_layer_record.h>
 
 namespace PsdPixelUtils
 {
@@ -304,91 +298,6 @@ void readAlphaMaskPixelCommon(int channelSize, const QMap<quint16, QByteArray> &
     }
 }
 
-/**********************************************************************/
-/* 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_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, bool processMasks)
 {
     const int uncompressedLength = width * channelSize;
@@ -402,10 +311,10 @@ QMap<quint16, QByteArray> fetchChannelsBytes(QIODevice &io, QVector<ChannelInfo
 
         io.seek(channelInfo->channelDataStart + channelInfo->channelOffset);
 
-        if (channelInfo->compressionType == Compression::Uncompressed) {
+        if (channelInfo->compressionType == psd_compression_type::Uncompressed) {
             channelBytes[channelInfo->channelId] = io.read(uncompressedLength);
             channelInfo->channelOffset += uncompressedLength;
-        } else if (channelInfo->compressionType == Compression::RLE) {
+        } else if (channelInfo->compressionType == psd_compression_type::RLE) {
             int rleLength = channelInfo->rleRowLengths[row];
             QByteArray compressedBytes = io.read(rleLength);
             QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType);
@@ -438,7 +347,7 @@ void readCommon(KisPaintDeviceSP dev,
         return;
     }
 
-    if (infoRecords.first()->compressionType == Compression::ZIP || infoRecords.first()->compressionType == Compression::ZIPWithPrediction) {
+    if (infoRecords.first()->compressionType == psd_compression_type::ZIP || infoRecords.first()->compressionType == psd_compression_type::ZIPWithPrediction) {
         const int numPixels = channelSize * layerRect.width() * layerRect.height();
 
         QMap<quint16, QByteArray> channelBytes;
@@ -446,24 +355,11 @@ void readCommon(KisPaintDeviceSP dev,
         Q_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);
-            }
+            QByteArray uncompressedBytes;
+
+            uncompressedBytes = Compression::uncompress(numPixels, compressedBytes, infoRecords.first()->compressionType, layerRect.width(), channelSize * 8);
 
-            if (!status) {
+            if (uncompressedBytes.size() != numPixels) {
                 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);
@@ -585,7 +481,7 @@ void writeChannelDataRLEImpl(QIODevice &io,
     }
 
     if (writeCompressionType) {
-        SAFE_WRITE_EX(byteOrder, io, (quint16)Compression::RLE);
+        SAFE_WRITE_EX(byteOrder, io, static_cast<quint16>(psd_compression_type::RLE));
     }
 
     const bool externalRleBlock = rleBlockOffset >= 0;
@@ -612,7 +508,7 @@ void writeChannelDataRLEImpl(QIODevice &io,
     const int 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);
+        QByteArray compressed = Compression::compress(uncompressed, psd_compression_type::RLE);
 
         KisAslWriterUtils::OffsetStreamPusher<quint16, byteOrder> rleExternalTag(io, 0, channelRLESizePos + row * static_cast<qint64>(sizeof(quint16)));
 
diff --git a/libs/psdutils/CMakeLists.txt b/libs/psdutils/CMakeLists.txt
index 56f6164b42..9c022508ba 100644
--- a/libs/psdutils/CMakeLists.txt
+++ b/libs/psdutils/CMakeLists.txt
@@ -1,6 +1,3 @@
-include_directories( ${CMAKE_BINARY_DIR}/libs/psdutils  #For kispsd_include.h
-)
-
 set(kritapsdutils_LIB_SRCS
     psd.cpp
     compression.cpp
@@ -17,10 +14,15 @@ set(kritapsdutils_LIB_SRCS
 add_library(kritapsdutils SHARED ${kritapsdutils_LIB_SRCS} )
 generate_export_header(kritapsdutils BASE_NAME kritapsdutils)
 
+target_include_directories(kritapsdutils
+    PUBLIC
+        ${ZLIB_INCLUDE_DIR}
+)
+
 if (WIN32)
-    target_link_libraries(kritapsdutils kritapigment kritaglobal KF5::I18n  ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${WIN32_PLATFORM_NET_LIBS})
+    target_link_libraries(kritapsdutils kritapigment kritaglobal KF5::I18n  ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${WIN32_PLATFORM_NET_LIBS} ${ZLIB_LIBRARIES})
 else (WIN32)
-    target_link_libraries(kritapsdutils kritapigment kritaglobal KF5::I18n  ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})
+    target_link_libraries(kritapsdutils kritapigment kritaglobal KF5::I18n  ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${ZLIB_LIBRARIES})
 endif (WIN32)
 
 set_target_properties(kritapsdutils PROPERTIES
diff --git a/libs/psdutils/asl/kis_asl_patterns_writer.cpp b/libs/psdutils/asl/kis_asl_patterns_writer.cpp
index d11a0db24d..47026eec98 100644
--- a/libs/psdutils/asl/kis_asl_patterns_writer.cpp
+++ b/libs/psdutils/asl/kis_asl_patterns_writer.cpp
@@ -8,14 +8,14 @@
 #include "kis_asl_patterns_writer.h"
 
 #include <functional>
+
+#include <compression.h>
+#include <kis_debug.h>
 #include <resources/KoPattern.h>
 
-#include "compression.h"
 #include "kis_asl_callback_object_catcher.h"
-#include "kis_asl_xml_parser.h"
-#include "kis_debug.h"
-
 #include "kis_asl_writer_utils.h"
+#include "kis_asl_xml_parser.h"
 
 KisAslPatternsWriter::KisAslPatternsWriter(const QDomDocument &doc, QIODevice &device, psd_byte_order byteOrder)
     : m_doc(doc)
@@ -65,7 +65,7 @@ void sliceQImage(const QImage &image, QVector<QVector<QByteArray>> *dstPlanes, b
                 dstPtr += dstStep;
             }
 
-            compressedRows[i].append(Compression::compress(uncompressedRows[i].last(), Compression::RLE));
+            compressedRows[i].append(Compression::compress(uncompressedRows[i].last(), psd_compression_type::RLE));
             if (compressedRows[i].last().isEmpty()) {
                 throw KisAslWriterUtils::ASLWriteException("Failed to compress pattern plane");
             }
diff --git a/libs/psdutils/asl/kis_asl_reader.cpp b/libs/psdutils/asl/kis_asl_reader.cpp
index fcc6bfd297..0ddf91bab2 100644
--- a/libs/psdutils/asl/kis_asl_reader.cpp
+++ b/libs/psdutils/asl/kis_asl_reader.cpp
@@ -311,10 +311,10 @@ QImage readVirtualArrayList(QIODevice &device, int numPlanes)
 
         const int dataLength = planeRect.width() * planeRect.height() * channelSize;
 
-        if (useCompression == Compression::Uncompressed) {
+        if (useCompression == psd_compression_type::Uncompressed) {
             dataPlanes[i] = device.read(dataLength);
 
-        } else if (useCompression == Compression::RLE) {
+        } else if (useCompression == psd_compression_type::RLE) {
             const int numRows = planeRect.height();
 
             QVector<quint16> rowSizes;
@@ -335,7 +335,7 @@ QImage readVirtualArrayList(QIODevice &device, int numPlanes)
                     throw ASLParseException("VAList: failed to read compressed data!");
                 }
 
-                QByteArray uncompressedData = Compression::uncompress(planeRect.width() * channelSize, compressedData, Compression::RLE);
+                QByteArray uncompressedData = Compression::uncompress(planeRect.width() * channelSize, compressedData, psd_compression_type::RLE);
 
                 if (uncompressedData.size() != planeRect.width()) {
                     throw ASLParseException("VAList: failed to decompress data!");
diff --git a/libs/psdutils/compression.cpp b/libs/psdutils/compression.cpp
index f2203f0603..a4e225eda1 100644
--- a/libs/psdutils/compression.cpp
+++ b/libs/psdutils/compression.cpp
@@ -1,24 +1,31 @@
 /*
+ *  SPDX-FileCopyrightText: 2004-2007 Graphest Software <libpsd at graphest.com>
  *  SPDX-FileCopyrightText: 2007 John Marshall
  *  SPDX-FileCopyrightText: 2010 Boudewijn Rempt <boud at valdyas.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
+
 #include "compression.h"
 
-#include "kis_debug.h"
-#include "psd_utils.h"
 #include <QBuffer>
 #include <QtEndian>
+#include <zlib.h>
+
+#include <kis_debug.h>
+#include <psd_utils.h>
 
+namespace KisRLE
+{
 // from gimp's psd-save.c
-static quint32 pack_pb_line(const QByteArray &src, QByteArray &dst)
+int compress(const QByteArray &src, QByteArray &dst)
 {
-    quint32 length = src.size();
+    int length = src.size();
     dst.resize(length * 2);
     dst.fill(0, length * 2);
 
-    quint32 remaining = length;
+    int remaining = length;
     quint8 i, j;
     quint32 dest_ptr = 0;
     const char *start = src.constData();
@@ -32,14 +39,13 @@ static quint32 pack_pb_line(const QByteArray &src, QByteArray &dst)
 
         if (i > 1) /* Match found */
         {
-            dst[dest_ptr++] = -(i - 1);
+            dst[dest_ptr++] = static_cast<char>(-(i - 1));
             dst[dest_ptr++] = *start;
 
             start += i;
             remaining -= i;
             length += 2;
-        } else /* Look for characters different from the previous */
-        {
+        } else { /* Look for characters different from the previous */
             i = 0;
             while ((i < 128) && (remaining - (i + 1) > 0) && (start[i] != start[(i + 1)] || remaining - (i + 2) <= 0 || start[i] != start[(i + 2)]))
                 i++;
@@ -53,7 +59,7 @@ static quint32 pack_pb_line(const QByteArray &src, QByteArray &dst)
 
             if (i > 0) /* Some distinct ones found */
             {
-                dst[dest_ptr++] = i - 1;
+                dst[dest_ptr++] = static_cast<char>(i - 1U);
                 for (j = 0; j < i; j++) {
                     dst[dest_ptr++] = start[j];
                 }
@@ -67,18 +73,32 @@ static quint32 pack_pb_line(const QByteArray &src, QByteArray &dst)
     return length;
 }
 
+QByteArray compress(const QByteArray &data)
+{
+    QByteArray output;
+    const int result = KisRLE::compress(data, output);
+    if (result <= 0)
+        return QByteArray();
+    else
+        return output;
+}
+
 // from gimp's psd-util.c
-quint32 decode_packbits(const char *src, char *dst, quint16 packed_len, quint32 unpacked_len)
+int decompress(const QByteArray &input, QByteArray &output, int unpacked_len)
 {
+    output.resize(unpacked_len);
+
     /*
      *  Decode a PackBits chunk.
      */
     qint32 n;
+    const char *src = input.data();
+    char *dst = output.data();
     char dat;
-    qint32 unpack_left = unpacked_len;
-    qint32 pack_left = packed_len;
+    int unpack_left = unpacked_len;
+    int pack_left = input.size();
     qint32 error_code = 0;
-    qint32 return_val = 0;
+    int return_val = 0;
 
     while (unpack_left > 0 && pack_left > 0) {
         n = *src;
@@ -163,36 +183,205 @@ quint32 decode_packbits(const char *src, char *dst, quint16 packed_len, quint32
     return return_val;
 }
 
-QByteArray Compression::uncompress(quint32 unpacked_len, QByteArray bytes, Compression::CompressionType compressionType)
+QByteArray decompress(const QByteArray &data, int expected_length)
 {
-    if (unpacked_len > 30000)
+    QByteArray output(expected_length, '\0');
+    const int result = KisRLE::decompress(data, output, expected_length);
+    if (result != 0)
         return QByteArray();
+    else
+        return output;
+}
+} // namespace KisRLE
+
+namespace KisZip
+{
+// Based on the reverse of psd_unzip_without_prediction
+int compress(const char *input, int unpacked_len, char *dst, int maxout)
+{
+    z_stream stream{};
+    int state;
+
+    stream.data_type = Z_BINARY;
+    stream.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(input));
+    stream.avail_in = static_cast<uInt>(unpacked_len);
+    stream.next_out = reinterpret_cast<Bytef *>(dst);
+    stream.avail_out = static_cast<uInt>(maxout);
+
+    dbgFile << "Expected unpacked length:" << unpacked_len << ", maxout:" << maxout;
+
+    if (deflateInit(&stream, -1) != Z_OK) {
+        dbgFile << "Failed deflate initialization";
+        return 0;
+    }
+
+    int flush = Z_PARTIAL_FLUSH;
+
+    do {
+        state = deflate(&stream, flush);
+        if (state == Z_STREAM_END) {
+            dbgFile << "Finished deflating";
+            flush = Z_FINISH;
+        } else if (state != Z_OK) {
+            dbgFile << "Error deflating" << state << stream.msg;
+            break;
+        }
+    } while (stream.avail_in > 0);
+
+    if (state != Z_OK || stream.avail_in > 0) {
+        dbgFile << "Failed deflating" << state << stream.msg;
+        return 0;
+    }
+
+    dbgFile << "Success, deflated size:" << stream.total_out;
+
+    return static_cast<int>(stream.total_out);
+}
+
+QByteArray compress(const QByteArray &data)
+{
+    QByteArray output(data.length() * 4, '\0');
+    const int result = KisZip::compress(data.constData(), data.size(), output.data(), output.size());
+    output.resize(result);
+    return output;
+}
+
+/**********************************************************************/
+/* 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>             */
+/* Refactored by L. E. Segovia <amy at amyspark.me>, 2021.06.30          */
+/**********************************************************************/
+int psd_unzip_without_prediction(const char *src, int packed_len, char *dst, int unpacked_len)
+{
+    z_stream stream{};
+    int state;
+
+    stream.data_type = Z_BINARY;
+    stream.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(src));
+    stream.avail_in = static_cast<uInt>(packed_len);
+    stream.next_out = reinterpret_cast<Bytef *>(dst);
+    stream.avail_out = static_cast<uInt>(unpacked_len);
+
+    if (inflateInit(&stream) != Z_OK)
+        return 0;
+
+    int flush = Z_PARTIAL_FLUSH;
+
+    do {
+        state = inflate(&stream, flush);
+        if (state == Z_STREAM_END) {
+            dbgFile << "Finished inflating";
+            break;
+        } else if (state == Z_DATA_ERROR) {
+            dbgFile << "Error inflating" << state << stream.msg;
+            if (inflateSync(&stream) != Z_OK)
+                return 0;
+            continue;
+        }
+    } while (stream.avail_out > 0);
+
+    if ((state != Z_STREAM_END && state != Z_OK) || stream.avail_out > 0) {
+        dbgFile << "Failed inflating" << state << stream.msg;
+        return 0;
+    }
+
+    return static_cast<int>(stream.total_out);
+}
+
+QByteArray psd_unzip_with_prediction(const QByteArray &src, int dst_len, int row_size, int color_depth)
+{
+    int len;
+
+    QByteArray dst_buf = Compression::uncompress(dst_len, src, psd_compression_type::ZIP);
+    if (dst_buf.size() == 0)
+        return dst_buf;
+
+    char *buf = dst_buf.data();
+    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 dst_buf;
+}
+
+/**********************************************************************/
+/* End of third party block                                           */
+/**********************************************************************/
+
+QByteArray psd_zip_with_prediction(const QByteArray &src, int row_size, int color_depth)
+{
+    QByteArray tempbuf(src);
+
+    int len;
+    int dst_len = src.size();
+
+    char *buf = tempbuf.data();
+    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 Compression::compress(tempbuf, psd_compression_type::ZIP);
+}
+
+QByteArray decompress(const QByteArray &data, int expected_length)
+{
+    QByteArray output(expected_length, '\0');
+    const int result = psd_unzip_without_prediction(data.constData(), data.size(), output.data(), expected_length);
+    if (result == 0)
+        return QByteArray();
+    else
+        return output;
+}
+} // namespace KisZip
+
+QByteArray Compression::uncompress(int unpacked_len, QByteArray bytes, psd_compression_type compressionType, int row_size, int color_depth)
+{
     if (bytes.size() < 1)
         return QByteArray();
 
     switch (compressionType) {
     case Uncompressed:
         return bytes;
-    case RLE: {
-        QByteArray ba;
-        ba.resize(static_cast<int>(unpacked_len));
-        decode_packbits(bytes.constData(), ba.data(), static_cast<quint16>(bytes.length()), unpacked_len);
-        return ba;
-    }
+    case RLE:
+        return KisRLE::decompress(bytes, unpacked_len);
     case ZIP:
-    case ZIPWithPrediction: {
-        // prepend the expected length of the pixels in big-endian
-        // format to the byte array as qUncompress expects...
-
-        QByteArray b;
-        QBuffer buf(&b);
-        quint32 val = qFromBigEndian(unpacked_len);
-        buf.write((char *)&val, 4);
-        b.append(bytes);
-
-        // and let's hope that this is sufficient...
-        return qUncompress(b);
-    }
+        return KisZip::decompress(bytes, unpacked_len);
+    case ZIPWithPrediction:
+        return KisZip::psd_unzip_with_prediction(bytes, unpacked_len, row_size, color_depth);
     default:
         qFatal("Cannot uncompress layer data: invalid compression type");
     }
@@ -200,7 +389,7 @@ QByteArray Compression::uncompress(quint32 unpacked_len, QByteArray bytes, Compr
     return QByteArray();
 }
 
-QByteArray Compression::compress(QByteArray bytes, Compression::CompressionType compressionType)
+QByteArray Compression::compress(QByteArray bytes, psd_compression_type compressionType, int row_size, int color_depth)
 {
     if (bytes.size() < 1)
         return QByteArray();
@@ -208,16 +397,12 @@ QByteArray Compression::compress(QByteArray bytes, Compression::CompressionType
     switch (compressionType) {
     case Uncompressed:
         return bytes;
-    case RLE: {
-        QByteArray dst;
-        int packed_len = pack_pb_line(bytes, dst);
-        Q_ASSERT(packed_len == dst.size());
-        Q_UNUSED(packed_len);
-        return dst;
-    }
+    case RLE:
+        return KisRLE::compress(bytes);
     case ZIP:
+        return KisZip::compress(bytes);
     case ZIPWithPrediction:
-        return qCompress(bytes);
+        return KisZip::psd_zip_with_prediction(bytes, row_size, color_depth);
     default:
         qFatal("Cannot compress layer data: invalid compression type");
     }
diff --git a/libs/psdutils/compression.h b/libs/psdutils/compression.h
index 10f0a05c59..57bce4a651 100644
--- a/libs/psdutils/compression.h
+++ b/libs/psdutils/compression.h
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2010 Boudewijn Rempt <boud at valdyas.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -7,15 +8,15 @@
 #define COMPRESSION_H
 
 #include "kritapsdutils_export.h"
+
 #include <QByteArray>
+#include <psd.h>
 
 class KRITAPSDUTILS_EXPORT Compression
 {
 public:
-    enum CompressionType { Uncompressed = 0, RLE, ZIP, ZIPWithPrediction, Unknown };
-
-    static QByteArray uncompress(quint32 unpacked_len, QByteArray bytes, CompressionType compressionType);
-    static QByteArray compress(QByteArray bytes, CompressionType compressionType);
+    static QByteArray uncompress(int unpacked_len, QByteArray bytes, psd_compression_type compressionType, int row_size = 0, int color_depth = 0);
+    static QByteArray compress(QByteArray bytes, psd_compression_type compressionType, int row_size = 0, int color_depth = 0);
 };
 
 #endif // PSD_COMPRESSION_H
diff --git a/libs/psdutils/psd.h b/libs/psdutils/psd.h
index b254347fc2..f48582140b 100644
--- a/libs/psdutils/psd.h
+++ b/libs/psdutils/psd.h
@@ -36,6 +36,14 @@ enum class psd_byte_order : std::uint_least8_t {
     psdInvalidByteOrder = 255,
 };
 
+enum psd_compression_type : std::uint16_t {
+    Uncompressed = 0,
+    RLE,
+    ZIP,
+    ZIPWithPrediction,
+    Unknown = 255,
+};
+
 /**
  * Image color/depth modes
  */
diff --git a/libs/psdutils/tests/compression_test.cpp b/libs/psdutils/tests/compression_test.cpp
index deab21ede5..9d12afa2b9 100644
--- a/libs/psdutils/tests/compression_test.cpp
+++ b/libs/psdutils/tests/compression_test.cpp
@@ -20,10 +20,12 @@
 void CompressionTest::testCompressionRLE()
 {
     QByteArray ba("Twee eeee aaaaa asdasda47892347981    wwwwwwwwwwwwWWWWWWWWWW");
-    QByteArray compressed = Compression::compress(ba, Compression::RLE);
+    QByteArray compressed = Compression::compress(ba, psd_compression_type::RLE);
+    QVERIFY(compressed.size() > 0);
     dbgKrita << compressed.size() << "uncompressed" << ba.size();
 
-    QByteArray uncompressed = Compression::uncompress(ba.size(), compressed, Compression::RLE);
+    QByteArray uncompressed = Compression::uncompress(ba.size(), compressed, psd_compression_type::RLE);
+    QVERIFY(uncompressed.size() > 0);
     QVERIFY(qstrcmp(ba, uncompressed) == 0);
 
     ba.clear();
@@ -31,19 +33,23 @@ void CompressionTest::testCompressionRLE()
     for (int i = 0; i < 500; ++i) {
         ds << rand();
     }
-    compressed = Compression::compress(ba, Compression::RLE);
+    compressed = Compression::compress(ba, psd_compression_type::RLE);
     dbgKrita << compressed.size() << "uncompressed" << ba.size();
-    uncompressed = Compression::uncompress(ba.size(), compressed, Compression::RLE);
+    QVERIFY(compressed.size() > 0);
+    uncompressed = Compression::uncompress(ba.size(), compressed, psd_compression_type::RLE);
+    QVERIFY(uncompressed.size() > 0);
     QVERIFY(qstrcmp(ba, uncompressed) == 0);
 }
 
 void CompressionTest::testCompressionZIP()
 {
     QByteArray ba("Twee eeee aaaaa asdasda47892347981    wwwwwwwwwwwwWWWWWWWWWW");
-    QByteArray compressed = Compression::compress(ba, Compression::ZIP);
+    QByteArray compressed = Compression::compress(ba, psd_compression_type::ZIP);
+    QVERIFY(compressed.size() > 0);
     dbgKrita << compressed.size() << "uncompressed" << ba.size();
 
-    QByteArray uncompressed = Compression::uncompress(ba.size(), compressed, Compression::ZIP);
+    QByteArray uncompressed = Compression::uncompress(ba.size(), compressed, psd_compression_type::ZIP);
+    QVERIFY(uncompressed.size() > 0);
     QVERIFY(qstrcmp(ba, uncompressed) == 0);
 
     ba.clear();
@@ -51,19 +57,23 @@ void CompressionTest::testCompressionZIP()
     for (int i = 0; i < 500; ++i) {
         ds << rand();
     }
-    compressed = Compression::compress(ba, Compression::ZIP);
+    compressed = Compression::compress(ba, psd_compression_type::ZIP);
+    QVERIFY(compressed.size() > 0);
     dbgKrita << compressed.size() << "uncompressed" << ba.size();
-    uncompressed = Compression::uncompress(ba.size(), compressed, Compression::ZIP);
+    uncompressed = Compression::uncompress(ba.size(), compressed, psd_compression_type::ZIP);
+    QVERIFY(uncompressed.size() > 0);
     QVERIFY(qstrcmp(ba, uncompressed) == 0);
 }
 
 void CompressionTest::testCompressionUncompressed()
 {
     QByteArray ba("Twee eeee aaaaa asdasda47892347981    wwwwwwwwwwwwWWWWWWWWWW");
-    QByteArray compressed = Compression::compress(ba, Compression::Uncompressed);
+    QByteArray compressed = Compression::compress(ba, psd_compression_type::Uncompressed);
+    QVERIFY(compressed.size() > 0);
     dbgKrita << compressed.size() << "uncompressed" << ba.size();
 
-    QByteArray uncompressed = Compression::uncompress(ba.size(), compressed, Compression::Uncompressed);
+    QByteArray uncompressed = Compression::uncompress(ba.size(), compressed, psd_compression_type::Uncompressed);
+    QVERIFY(uncompressed.size() > 0);
     QVERIFY(qstrcmp(ba, uncompressed) == 0);
 
     ba.clear();
@@ -71,9 +81,11 @@ void CompressionTest::testCompressionUncompressed()
     for (int i = 0; i < 500; ++i) {
         ds << rand();
     }
-    compressed = Compression::compress(ba, Compression::Uncompressed);
+    compressed = Compression::compress(ba, psd_compression_type::Uncompressed);
     dbgKrita << compressed.size() << "uncompressed" << ba.size();
-    uncompressed = Compression::uncompress(ba.size(), compressed, Compression::Uncompressed);
+    QVERIFY(compressed.size() > 0);
+    uncompressed = Compression::uncompress(ba.size(), compressed, psd_compression_type::Uncompressed);
+    QVERIFY(uncompressed.size() > 0);
     QVERIFY(qstrcmp(ba, uncompressed) == 0);
 }
 
diff --git a/plugins/impex/psd/CMakeLists.txt b/plugins/impex/psd/CMakeLists.txt
index e99401b434..0516c2dd39 100644
--- a/plugins/impex/psd/CMakeLists.txt
+++ b/plugins/impex/psd/CMakeLists.txt
@@ -2,17 +2,11 @@ if (NOT MSVC AND NOT APPLE)
     add_subdirectory(tests)
 endif()
 
-configure_file(config_psd.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_psd.h)
-
 include_directories(
     ${CMAKE_BINARY_DIR}/libs/psd
     ${CMAKE_SOURCE_DIR}/libs/psd
 )  #For kispsd_include.h
 
-include_directories(SYSTEM
-    ${ZLIB_INCLUDE_DIR}
-)
-
 set(LIB_PSD_SRCS 
     psd_colormode_block.cpp
     psd_image_data.cpp
@@ -31,7 +25,7 @@ set(kritapsdimport_SOURCES
 
 add_library(kritapsdimport MODULE ${kritapsdimport_SOURCES})
 
-target_link_libraries(kritapsdimport kritaglobal kritaui kritapsd KF5::I18n ${ZLIB_LIBRARIES})
+target_link_libraries(kritapsdimport kritaglobal kritaui kritapsd KF5::I18n)
 
 install(TARGETS kritapsdimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
 
@@ -48,10 +42,10 @@ set(kritapsdexport_SOURCES
 add_library(kritapsdexport MODULE ${kritapsdexport_SOURCES})
 
 if (MSVC)
-    target_link_libraries(kritapsdexport kritaui kritapsd kritaimpex ${WIN32_PLATFORM_NET_LIBS} ${ZLIB_LIBRARIES})
-else ()	
-    target_link_libraries(kritapsdexport kritaui kritapsd kritaimpex ${ZLIB_LIBRARIES})
-endif ()	
+    target_link_libraries(kritapsdexport kritaui kritapsd kritaimpex kritastore ${WIN32_PLATFORM_NET_LIBS})
+else ()
+    target_link_libraries(kritapsdexport kritaui kritapsd kritaimpex)
+endif ()
 
 install(TARGETS kritapsdexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
 
diff --git a/plugins/impex/psd/config_psd.h.cmake b/plugins/impex/psd/config_psd.h.cmake
deleted file mode 100644
index 484e4eab94..0000000000
--- a/plugins/impex/psd/config_psd.h.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-
-/* Defines if your system has the Zlib library */
-#cmakedefine HAVE_ZLIB 1
-
diff --git a/plugins/impex/psd/psd_image_data.cpp b/plugins/impex/psd/psd_image_data.cpp
index 957bffdfdd..39e5a5f3a9 100644
--- a/plugins/impex/psd/psd_image_data.cpp
+++ b/plugins/impex/psd/psd_image_data.cpp
@@ -18,17 +18,13 @@
 #include <KoColorSpace.h>
 #include <KoColorSpaceMaths.h>
 #include <KoColorSpaceTraits.h>
-
-#include "psd_utils.h"
-#include "compression.h"
-
-#include "kis_iterator_ng.h"
-#include "kis_paint_device.h"
+#include <kis_iterator_ng.h>
+#include <kis_paint_device.h>
 
 #include <asl/kis_asl_reader_utils.h>
-
-#include "psd_pixel_utils.h"
-
+#include <compression.h>
+#include <psd_pixel_utils.h>
+#include <psd_utils.h>
 
 PSDImageData::PSDImageData(PSDHeader *header)
 {
@@ -56,7 +52,7 @@ bool PSDImageData::read(QIODevice &io, KisPaintDeviceSP dev)
             m_channelOffsets << 0;
             ChannelInfo channelInfo;
             channelInfo.channelId = channel;
-            channelInfo.compressionType = Compression::Uncompressed;
+            channelInfo.compressionType = psd_compression_type::Uncompressed;
             channelInfo.channelDataStart = start;
             channelInfo.channelDataLength = quint64(m_header->width) * m_header->height * m_channelSize;
             start += channelInfo.channelDataLength;
@@ -84,7 +80,7 @@ bool PSDImageData::read(QIODevice &io, KisPaintDeviceSP dev)
             ChannelInfo channelInfo;
             channelInfo.channelId = channel;
             channelInfo.channelDataStart = start;
-            channelInfo.compressionType = Compression::RLE;
+            channelInfo.compressionType = psd_compression_type::RLE;
             for (quint32 row = 0; row < m_header->height; row++ ) {
                 if (m_header->version == 1) {
                     quint16 rlelength16; // use temporary variable to not cast pointers and not rely on endianness
@@ -140,7 +136,7 @@ bool PSDImageData::read(QIODevice &io, KisPaintDeviceSP dev)
 bool PSDImageData::write(QIODevice &io, KisPaintDeviceSP dev, bool hasAlpha)
 {
     // XXX: make the compression setting configurable. For now, always use RLE.
-    psdwrite(io, (quint16)Compression::RLE);
+    psdwrite(io, static_cast<quint16>(psd_compression_type::RLE));
 
     // now write all the channels in display order
     // fill in the channel chooser, in the display order, but store the pixel index as well.



More information about the kimageshop mailing list