[graphics/krita] /: tiff: support YCbCr + JPEG imports

L. E. Segovia null at kde.org
Tue Jun 28 18:04:36 BST 2022


Git commit dd4d2b4b793a42b2c7406dfc63fc108ea8905ec5 by L. E. Segovia.
Committed on 28/06/2022 at 16:01.
Pushed by lsegovia into branch 'master'.

tiff: support YCbCr + JPEG imports

This commit enables reading JPEG-compressed TIFFs that use the YCbCr
colour space. This is not possible to do with the standard libtiff API
because of Very Bad Decisions :tm: that were taken in the past, both
during the 6.0 standardization [1] and its implementation [2].

To do so, I've implemented a custom made strip decoder that, when the
combination of COMPRESSION_JPEG and PHOTOMETRIC_YCBCR is detected,
redirects to a (also custom made) buffer stream that performs automatic
interleaving and upsampling of the image planes. This data is shaped
into the form specified in the TIFF 6.0 Specification [3], then fed
to the existing YCbCr readers. The planes are decoded using
libjpeg-turbo (to avoid the mindbending API of libjpeg) into three
separate planes without upsampling.

There's a catch, however. Unlike libjpeg-turbo, libjpeg provides a
direct API to prime the decoder with the coefficient tables that are
provided in TIFFTAG_JPEGTABLES, and which libtiff uses as part of
TIFFjpeg_read_header. To remediate this, I've patched libtiff-turbo to
add a tjReadCoefficientTables API, that takes a reference to the tables
and forwards it to the decoder.

BUG: 454116

CCMAIL: kimageshop at kde.org

[1]: DRAFT TIFF Technical Note #2, 17-Mar-95.
<http://libtiff.maptools.org/TIFFTechNote2.html>

[2]: 2022.06.11 07:23 "Re: [Tiff] How to read JPEG-compressed YCbCr
TIFFs", by Joris Van Damme. <https://www.asmail.be/msg0054881320.html>

[3]: "Ordering of Component Samples". In: TIFF Revision 6.0 Final. June
3, 1992: Adobe Developers Association, pp. 93-94.

M  +4    -4    3rdparty/ext_jpeg/CMakeLists.txt
M  +14   -0    CMakeLists.txt
A  +4    -0    config-jpeg.h.cmake
M  +1    -1    plugins/impex/tiff/CMakeLists.txt
M  +138  -2    plugins/impex/tiff/kis_buffer_stream.cc
M  +57   -1    plugins/impex/tiff/kis_buffer_stream.h
M  +177  -6    plugins/impex/tiff/kis_tiff_import.cc
M  +1    -0    plugins/impex/tiff/kis_tiff_import.h
D  +-    --    plugins/impex/tiff/tests/data/quad-jpeg.tif.png
A  +-    --    plugins/impex/tiff/tests/data/results/quad-jpeg.tif.png
R  +-    --    plugins/impex/tiff/tests/data/sources/quad-jpeg.tif [from: plugins/impex/tiff/tests/data/quad-jpeg.tif - 100% similarity]

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

diff --git a/3rdparty/ext_jpeg/CMakeLists.txt b/3rdparty/ext_jpeg/CMakeLists.txt
index 834b5529dd..cd39e1f1b0 100644
--- a/3rdparty/ext_jpeg/CMakeLists.txt
+++ b/3rdparty/ext_jpeg/CMakeLists.txt
@@ -2,8 +2,8 @@ SET(PREFIX_ext_jpeg "${EXTPREFIX}" )
 if (ANDROID)
 ExternalProject_Add( ext_jpeg
     DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
-    URL https://downloads.sourceforge.net/project/libjpeg-turbo/2.1.3/libjpeg-turbo-2.1.3.tar.gz
-    URL_HASH SHA256=467b310903832b033fe56cd37720d1b73a6a3bd0171dbf6ff0b620385f4f76d0
+    URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/ba22c0f76d88f596e157b6c546c599e21ebbaf64.tar.gz
+    URL_HASH SHA256=a70aef6318f0eb71e04065fb396906efa16338561b12e5ee0c5cd3fcfdf356ca
 
     CMAKE_ARGS -DANDROID_ARM_MODE=arm -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_jpeg} -DENABLE_SHARED=ON -DWITH_SIMD=OFF -DENABLE_STATIC=OFF _DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE}
 
@@ -12,8 +12,8 @@ ExternalProject_Add( ext_jpeg
 else()
 ExternalProject_Add( ext_jpeg
     DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
-    URL https://downloads.sourceforge.net/project/libjpeg-turbo/2.1.3/libjpeg-turbo-2.1.3.tar.gz
-    URL_HASH SHA256=467b310903832b033fe56cd37720d1b73a6a3bd0171dbf6ff0b620385f4f76d0
+    URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/ba22c0f76d88f596e157b6c546c599e21ebbaf64.tar.gz
+    URL_HASH SHA256=a70aef6318f0eb71e04065fb396906efa16338561b12e5ee0c5cd3fcfdf356ca
   
     INSTALL_DIR ${PREFIX_ext_jpeg}
     CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_jpeg} -DWITH_SIMD=OFF _DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0f8a4dc73a..8c9ec5a9c2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -855,6 +855,20 @@ if (JPEG_FOUND)
     macro_bool_to_01(JPEG_FOUND HAVE_JPEG)
 endif()
 
+find_package(libjpeg-turbo 2.1.3)
+set_package_properties(libjpeg-turbo PROPERTIES
+    DESCRIPTION "libjpeg-turbo is a JPEG image codec that uses SIMD instructions (MMX, SSE2, AVX2, Neon, AltiVec) to accelerate baseline JPEG compression and decompression on x86, x86-64, Arm, and PowerPC systems, as well as progressive JPEG compression on x86 and x86-64 systems."
+    URL "https://www.libjpeg-turbo.org"
+    TYPE OPTIONAL
+    PURPOSE "Required by the Krita JPEG and TIFF filters")
+if(libjpeg-turbo_FOUND)
+    get_target_property(JPEG_TURBO_LIBRARY libjpeg-turbo::turbojpeg "LOCATION")
+    set(JPEG_TURBO_LIBRARIES libjpeg-turbo::turbojpeg)
+    list(APPEND ANDROID_EXTRA_LIBS ${JPEG_TURBO_LIBRARY})
+endif()
+macro_bool_to_01(libjpeg-turbo_FOUND HAVE_JPEG_TURBO)
+configure_file(config-jpeg.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-jpeg.h )
+
 find_package(GIF)
 set_package_properties(GIF PROPERTIES
     DESCRIPTION "Library for loading and saving gif files."
diff --git a/config-jpeg.h.cmake b/config-jpeg.h.cmake
new file mode 100644
index 0000000000..0ebf435f7f
--- /dev/null
+++ b/config-jpeg.h.cmake
@@ -0,0 +1,4 @@
+/* config-jpeg.h.  Generated by cmake from config-jpeg.h.cmake */
+
+/* Define if libjpeg has the turbo-jpeg API */
+#cmakedefine HAVE_JPEG_TURBO 1
diff --git a/plugins/impex/tiff/CMakeLists.txt b/plugins/impex/tiff/CMakeLists.txt
index 9fa8b50266..bc392c3750 100644
--- a/plugins/impex/tiff/CMakeLists.txt
+++ b/plugins/impex/tiff/CMakeLists.txt
@@ -23,7 +23,7 @@ set(kritatiffimport_SOURCES
 
 kis_add_library(kritatiffimport MODULE ${kritatiffimport_SOURCES})
 
-target_link_libraries(kritatiffimport kritaui kritaimpex ${KRITATIFFPSD_LIBRARY} ${TIFF_LIBRARIES} LibExiv2::LibExiv2 kritametadata)
+target_link_libraries(kritatiffimport kritaui kritaimpex ${KRITATIFFPSD_LIBRARY} ${TIFF_LIBRARIES} LibExiv2::LibExiv2 kritametadata ${JPEG_TURBO_LIBRARIES})
 
 install(TARGETS kritatiffimport  DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
 
diff --git a/plugins/impex/tiff/kis_buffer_stream.cc b/plugins/impex/tiff/kis_buffer_stream.cc
index ad69653ccc..5a8129cfc7 100644
--- a/plugins/impex/tiff/kis_buffer_stream.cc
+++ b/plugins/impex/tiff/kis_buffer_stream.cc
@@ -26,15 +26,41 @@ KisBufferStreamContigBase::KisBufferStreamContigBase(uint8_t *src,
 void KisBufferStreamContigBase::restart()
 {
     m_srcIt = m_src;
+    m_lineOffset = 0;
+    m_lineNumber = 0;
     m_posinc = 8;
 }
 
 void KisBufferStreamContigBase::moveToLine(tsize_t lineNumber)
 {
-    m_srcIt = m_src + lineNumber * m_lineSize;
+    KIS_ASSERT(lineNumber >= 0);
+    moveToPos(0, lineNumber);
+}
+
+void KisBufferStreamContigBase::moveToPos(tsize_t x, tsize_t y)
+{
+    KIS_ASSERT(x >= 0 && y >= 0);
+    m_lineNumber = y;
+    m_lineOffset = (x * m_depth) / 8;
+    m_srcIt = m_src + y * m_lineSize + m_lineOffset;
     m_posinc = 8;
 }
 
+tsize_t KisBufferStreamContigBase::x() const
+{
+    return (m_lineOffset * 8) / m_depth;
+}
+
+tsize_t KisBufferStreamContigBase::y() const
+{
+    return m_lineNumber;
+}
+
+tsize_t KisBufferStreamContigBase::width() const
+{
+    return (m_lineSize * 8) / m_depth;
+}
+
 uint32_t KisBufferStreamContigBelow16::nextValue()
 {
     uint16_t remain = m_depth;
@@ -47,9 +73,14 @@ uint32_t KisBufferStreamContigBelow16::nextValue()
         value = (value << toread) | (((*m_srcIt) >> (m_posinc)) & ((1 << toread) - 1));
         if (m_posinc == 0) {
             m_srcIt++;
+            m_lineOffset++; // we consumed a byte
             m_posinc = 8;
         }
     }
+    if (m_lineOffset >= m_lineSize) {
+        m_lineNumber += 1;
+        m_lineOffset = 0;
+    }
     return value;
 }
 
@@ -65,9 +96,14 @@ uint32_t KisBufferStreamContigBelow32::nextValue()
         value = (value) | ((((*m_srcIt) >> (m_posinc)) & ((1 << toread) - 1U)) << (m_depth - 8U - remain));
         if (m_posinc == 0) {
             m_srcIt++;
+            m_lineOffset++; // we consumed a byte
             m_posinc = 8U;
         }
     }
+    if (m_lineOffset >= m_lineSize) {
+        m_lineNumber += 1;
+        m_lineOffset = 0;
+    }
     return value;
 }
 
@@ -86,9 +122,14 @@ uint32_t KisBufferStreamContigAbove32::nextValue()
         }
         if (m_posinc == 0) {
             m_srcIt++;
+            m_lineOffset++; // we consumed a byte
             m_posinc = 8U;
         }
     }
+    if (m_lineOffset >= m_lineSize) {
+        m_lineNumber += 1;
+        m_lineOffset = 0;
+    }
     return value;
 }
 
@@ -141,8 +182,103 @@ void KisBufferStreamSeparate::restart()
 }
 
 void KisBufferStreamSeparate::moveToLine(tsize_t lineNumber)
+{
+    KIS_ASSERT(lineNumber >= 0);
+    moveToPos(0, lineNumber);
+}
+
+void KisBufferStreamSeparate::moveToPos(tsize_t x, tsize_t y)
 {
     for (const auto &stream : streams) {
-        stream->moveToLine(lineNumber);
+        stream->moveToPos(x, y);
     }
 }
+
+tsize_t KisBufferStreamSeparate::x() const
+{
+    return streams[m_current_sample]->x();
+}
+
+tsize_t KisBufferStreamSeparate::y() const
+{
+    return streams[m_current_sample]->y();
+}
+
+tsize_t KisBufferStreamSeparate::width() const
+{
+    return streams[m_current_sample]->width();
+}
+
+KisBufferStreamInterleaveUpsample::KisBufferStreamInterleaveUpsample(
+    uint8_t **srcs,
+    uint16_t nb_samples,
+    uint16_t depth,
+    tsize_t *lineSize,
+    uint16_t hsubsample,
+    uint16_t vsubsample)
+    : KisBufferStreamSeparate(srcs, nb_samples, depth, lineSize)
+    , m_hsubsample(hsubsample)
+    , m_vsubsample(vsubsample)
+{
+}
+
+uint32_t KisBufferStreamInterleaveUpsample::nextValue()
+{
+    uint32_t value = streams[m_currentPlane]->nextValue();
+    if (m_currentPlane == 0) {
+        m_current_sample++;
+        if (m_current_sample % m_hsubsample == 0) {
+            if (m_current_sample >= m_hsubsample * m_vsubsample) {
+                // Fix up the position of the luminance plane
+                // If it's already 0, the cursor has already looped (correctly)
+                // to the next line
+                if (streams[m_currentPlane]->x() != 0) {
+                    streams[m_currentPlane]->moveToPos(
+                        streams[m_currentPlane]->x(),
+                        streams[m_currentPlane]->y() - m_vsubsample + 1);
+                }
+                // Move to Cb/Cr
+                m_currentPlane += 1;
+                m_current_sample = 0;
+            } else {
+                // Go to next line
+                // If the position is already 0, we need to correct the row
+                // AND column
+                if (streams[m_currentPlane]->x() != 0) {
+                    streams[m_currentPlane]->moveToPos(
+                        streams[m_currentPlane]->x() - m_hsubsample,
+                        streams[m_currentPlane]->y() + 1);
+                } else {
+                    streams[m_currentPlane]->moveToPos(
+                        streams[m_currentPlane]->width() - m_hsubsample,
+                        streams[m_currentPlane]->y());
+                }
+            }
+        }
+    } else if (m_currentPlane < m_nb_samples - 1) {
+        m_currentPlane += 1;
+    } else {
+        m_currentPlane = 0;
+    }
+    return value;
+}
+
+void KisBufferStreamInterleaveUpsample::moveToPos(tsize_t x, tsize_t y)
+{
+    // Needs to subsample
+    for (uint16_t i = 0; i < m_nb_samples; i++) {
+        const tsize_t realX = i == 0 ? x : x / m_hsubsample;
+        const tsize_t realY = i == 0 ? y : y / m_vsubsample;
+        streams.at(i)->moveToPos(realX, realY);
+    }
+}
+
+tsize_t KisBufferStreamInterleaveUpsample::x() const
+{
+    return streams[0]->x();
+}
+
+tsize_t KisBufferStreamInterleaveUpsample::y() const
+{
+    return streams[0]->y();
+}
diff --git a/plugins/impex/tiff/kis_buffer_stream.h b/plugins/impex/tiff/kis_buffer_stream.h
index 706b85de31..2b0575ff93 100644
--- a/plugins/impex/tiff/kis_buffer_stream.h
+++ b/plugins/impex/tiff/kis_buffer_stream.h
@@ -23,7 +23,11 @@ public:
     virtual uint32_t nextValue() = 0;
     virtual void restart() = 0;
     virtual void moveToLine(tsize_t lineNumber) = 0;
-    virtual ~KisBufferStreamBase() {}
+    virtual void moveToPos(tsize_t x, tsize_t y) = 0;
+    virtual tsize_t x() const = 0;
+    virtual tsize_t y() const = 0;
+    virtual tsize_t width() const = 0;
+
 protected:
     uint16_t m_depth;
 };
@@ -39,11 +43,21 @@ public:
 
     void moveToLine(tsize_t lineNumber) override;
 
+    void moveToPos(tsize_t x, tsize_t y) override;
+
+    tsize_t x() const override;
+
+    tsize_t y() const override;
+
+    tsize_t width() const override;
+
 protected:
     uint8_t *const m_src;
     uint8_t *m_srcIt;
     uint16_t m_posinc = 0;
     const tsize_t m_lineSize;
+    tsize_t m_lineNumber = 0;
+    tsize_t m_lineOffset = 0;
 };
 
 class KisBufferStreamContigBelow16 : public KisBufferStreamContigBase
@@ -98,10 +112,52 @@ public:
 
     void moveToLine(tsize_t lineNumber) override;
 
+    void moveToPos(tsize_t x, tsize_t y) override;
+
+    tsize_t x() const override;
+
+    tsize_t y() const override;
+
+    tsize_t width() const override;
+
 protected:
     QVector<QSharedPointer<KisBufferStreamBase>> streams;
     uint16_t m_current_sample = 0;
     uint16_t m_nb_samples;
 };
 
+class KisBufferStreamInterleaveUpsample : public KisBufferStreamSeparate
+{
+public:
+    KisBufferStreamInterleaveUpsample(uint8_t **srcs,
+                                      uint16_t nb_samples,
+                                      uint16_t depth,
+                                      tsize_t *lineSize,
+                                      uint16_t hsubsample,
+                                      uint16_t vsubsample);
+
+    uint32_t nextValue() override;
+
+    void moveToPos(tsize_t x, tsize_t y) override;
+
+    tsize_t x() const override;
+
+    tsize_t y() const override;
+
+    tsize_t width() const override
+    {
+        return streams[0]->width();
+    }
+
+    void restart() override
+    {
+        KisBufferStreamSeparate::restart();
+        m_currentPlane = 0;
+    }
+
+protected:
+    uint16_t m_hsubsample, m_vsubsample;
+    uint16_t m_currentPlane = 0;
+};
+
 #endif
diff --git a/plugins/impex/tiff/kis_tiff_import.cc b/plugins/impex/tiff/kis_tiff_import.cc
index d0c56112be..4e819d497e 100644
--- a/plugins/impex/tiff/kis_tiff_import.cc
+++ b/plugins/impex/tiff/kis_tiff_import.cc
@@ -39,6 +39,11 @@
 #include "kis_tiff_psd_resource_record.h"
 #endif
 
+#ifdef HAVE_JPEG_TURBO
+#include <turbojpeg.h>
+#endif
+
+#include "kis_buffer_stream.h"
 #include "kis_tiff_logger.h"
 #include "kis_tiff_reader.h"
 #include "kis_tiff_ycbcr_reader.h"
@@ -782,8 +787,7 @@ KisTIFFImport::readImageFromTiff(KisDocument *m_doc,
                               &vsubsampling);
         lineSizeCoeffs[1] = hsubsampling;
         lineSizeCoeffs[2] = hsubsampling;
-        uint16_t position = 0;
-        TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position);
+        dbgFile << "Subsampling" << 4 << hsubsampling << vsubsampling;
         if (dstDepth == 8) {
             tiffReader = new KisTIFFYCbCrReader<uint8_t>(
                 layer->paintDevice(),
@@ -947,6 +951,9 @@ KisTIFFImport::readImageFromTiff(KisDocument *m_doc,
         return ImportExportCodes::FileFormatIncorrect;
     }
 
+    uint32_t compression = COMPRESSION_NONE;
+    TIFFGetFieldDefaulted(image, TIFFTAG_COMPRESSION, &compression, COMPRESSION_NONE);
+
     if (TIFFIsTiled(image)) {
         dbgFile << "tiled image";
         uint32_t tileWidth = 0;
@@ -1025,7 +1032,66 @@ KisTIFFImport::readImageFromTiff(KisDocument *m_doc,
             qMin(rowsPerStrip,
                  height); // when TIFFNumberOfStrips(image) == 1 it might happen
                           // that rowsPerStrip is incorrectly set
-        if (planarconfig == PLANARCONFIG_CONTIG) {
+
+#ifdef HAVE_JPEG_TURBO
+        uint32_t hasSplitTables = 0;
+        uint8_t *tables = nullptr;
+        uint32_t sz = 0;
+        QVector<unsigned char> jpegBuf;
+
+        auto handle = [&]() -> std::unique_ptr<void, decltype(&tjDestroy)> {
+            if (planarconfig == PLANARCONFIG_CONTIG
+                && color_type == PHOTOMETRIC_YCBCR
+                && compression == COMPRESSION_JPEG) {
+                return {tjInitDecompress(), &tjDestroy};
+            } else {
+                return {nullptr, &tjDestroy};
+            }
+        }();
+
+        if (color_type == PHOTOMETRIC_YCBCR && compression == COMPRESSION_JPEG
+            && hsubsampling != 1 && vsubsampling != 1) {
+            jpegBuf.resize(stripsize);
+            dbgFile << "Setting up libjpeg-turbo for handling subsampled JPEG "
+                       "strips...";
+            if (!TIFFGetFieldDefaulted(image,
+                                       TIFFTAG_JPEGTABLESMODE,
+                                       &hasSplitTables)) {
+                errFile << "Error when detecting the JPEG coefficient "
+                           "table mode";
+                return ImportExportCodes::FileFormatIncorrect;
+            }
+            if (hasSplitTables) {
+                if (!TIFFGetField(image, TIFFTAG_JPEGTABLES, &sz, &tables)) {
+                    errFile
+                        << "Unable to retrieve the JPEG abbreviated datastream";
+                    return ImportExportCodes::FileFormatIncorrect;
+                }
+            }
+
+            {
+                int width = 0;
+                int height = 0;
+
+                if (hasSplitTables
+                    && tjDecompressHeader(handle.get(),
+                                          tables,
+                                          sz,
+                                          &width,
+                                          &height)
+                        != 0) {
+                    errFile << tjGetErrorStr2(handle.get());
+                    m_doc->setErrorMessage(i18nc("TIFF errors", "This TIFF file is compressed with JPEG, but libjpeg-turbo could not load its coefficient quantization and/or Huffman coding tables. Please upgrade your version of libjpeg-turbo and try again."));
+                    return ImportExportCodes::FileFormatIncorrect;
+                }
+            }
+        }
+#endif
+
+        if (planarconfig == PLANARCONFIG_CONTIG
+            && !(color_type == PHOTOMETRIC_YCBCR
+                 && compression == COMPRESSION_JPEG && hsubsampling != 1
+                 && vsubsampling != 1)) {
             buf.reset(_TIFFmalloc(stripsize));
             if (depth < 16) {
                 tiffstream = new KisBufferStreamContigBelow16(
@@ -1043,6 +1109,61 @@ KisTIFFImport::readImageFromTiff(KisDocument *m_doc,
                     depth,
                     stripsize / rowsPerStrip);
             }
+        } else if (planarconfig == PLANARCONFIG_CONTIG
+                   && color_type == PHOTOMETRIC_YCBCR
+                   && compression == COMPRESSION_JPEG) {
+#ifdef HAVE_JPEG_TURBO
+            ps_buf.resize(nbchannels);
+            TIFFReadRawStrip(image, 0, jpegBuf.data(), stripsize);
+
+            int width = basicInfo.width;
+            int height = rowsPerStrip;
+            int jpegSubsamp = TJ_444;
+            int jpegColorspace = TJCS_YCbCr;
+
+            if (tjDecompressHeader3(handle.get(),
+                                    jpegBuf.data(),
+                                    stripsize,
+                                    &width,
+                                    &height,
+                                    &jpegSubsamp,
+                                    &jpegColorspace)
+                != 0) {
+                errFile << tjGetErrorStr2(handle.get());
+                return ImportExportCodes::FileFormatIncorrect;
+            }
+
+            QVector<tsize_t> lineSizes(nbchannels);
+            for (uint32_t i = 0; i < nbchannels; i++) {
+                const unsigned long uncompressedStripsize =
+                    tjPlaneSizeYUV(i, width, 0, height, jpegColorspace);
+                KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(
+                    uncompressedStripsize != (unsigned long)-1,
+                    ImportExportCodes::FileFormatIncorrect);
+                dbgFile << QString("Uncompressed strip size (plane %1): %2")
+                               .arg(i)
+                               .arg(uncompressedStripsize);
+                tsize_t scanLineSize = uncompressedStripsize / rowsPerStrip;
+                dbgFile << QString("scan line size (plane %1): %2")
+                               .arg(i)
+                               .arg(scanLineSize);
+                ps_buf[i] = _TIFFmalloc(uncompressedStripsize);
+                lineSizes[i] = scanLineSize;
+            }
+            tiffstream = new KisBufferStreamInterleaveUpsample(
+                reinterpret_cast<uint8_t **>(ps_buf.data()),
+                nbchannels,
+                depth,
+                lineSizes.data(),
+                hsubsampling,
+                vsubsampling);
+#else
+            m_doc->setErrorMessage(
+                i18nc("TIFF",
+                      "Subsampled YCbCr TIFF files compressed with JPEG cannot "
+                      "be loaded."));
+            return ImportExportCodes::FileFormatIncorrect;
+#endif
         } else {
             ps_buf.resize(nbchannels);
             tsize_t scanLineSize = stripsize / rowsPerStrip;
@@ -1067,12 +1188,57 @@ KisTIFFImport::readImageFromTiff(KisDocument *m_doc,
         dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image)
                 << " rowsPerStrip =" << rowsPerStrip
                 << " stripsize =" << stripsize;
+
         for (uint32_t strip = 0; y < height; strip++) {
+#ifdef HAVE_JPEG_TURBO
+            if (planarconfig == PLANARCONFIG_CONTIG
+                && !(color_type == PHOTOMETRIC_YCBCR
+                     && compression == COMPRESSION_JPEG && hsubsampling != 1
+                     && vsubsampling != 1)) {
+#else
             if (planarconfig == PLANARCONFIG_CONTIG) {
+#endif
                 TIFFReadEncodedStrip(image,
                                      TIFFComputeStrip(image, y, 0),
                                      buf.get(),
                                      (tsize_t)-1);
+#ifdef HAVE_JPEG_TURBO
+            } else if (planarconfig == PLANARCONFIG_CONTIG
+                       && (color_type == PHOTOMETRIC_YCBCR
+                           && compression == COMPRESSION_JPEG)) {
+                TIFFReadRawStrip(image, strip, jpegBuf.data(), stripsize);
+
+                int width = basicInfo.width;
+                int height = rowsPerStrip;
+                int jpegSubsamp = TJ_444;
+                int jpegColorspace = TJCS_YCbCr;
+
+                if (tjDecompressHeader3(handle.get(),
+                                        jpegBuf.data(),
+                                        stripsize,
+                                        &width,
+                                        &height,
+                                        &jpegSubsamp,
+                                        &jpegColorspace)
+                    != 0) {
+                    errFile << tjGetErrorStr2(handle.get());
+                    return ImportExportCodes::FileFormatIncorrect;
+                }
+
+                if (tjDecompressToYUVPlanes(
+                        handle.get(),
+                        jpegBuf.data(),
+                        stripsize,
+                        reinterpret_cast<unsigned char **>(ps_buf.data()),
+                        width,
+                        nullptr,
+                        height,
+                        0)
+                    != 0) {
+                    errFile << tjGetErrorStr2(handle.get());
+                    return ImportExportCodes::FileFormatIncorrect;
+                }
+#endif
             } else {
                 for (uint16_t i = 0; i < nbchannels; i++) {
                     TIFFReadEncodedStrip(image,
@@ -1301,7 +1467,10 @@ KisImportExportErrorCode KisTIFFImport::readTIFFDirectory(KisDocument *m_doc,
                 "Chemical proof");
         } else if (basicInfo.colorSpaceIdTag.first == LABAColorModelID.id()) {
             profile = KoColorSpaceRegistry::instance()->profileByName(
-                "Lab identity build-in");
+                "Lab identity built-in");
+        } else if (basicInfo.colorSpaceIdTag.first == YCbCrAColorModelID.id()) {
+            profile = KoColorSpaceRegistry::instance()->profileByName(
+                "ITU-R BT.709-6 YCbCr ICC V4 profile");
         }
         if (!profile) {
             dbgFile << "No suitable default profile found.";
@@ -1429,7 +1598,10 @@ KisImportExportErrorCode KisTIFFImport::readTIFFDirectory(KisDocument *m_doc,
     return readImageFromTiff(m_doc, image, basicInfo);
 }
 
-KisImportExportErrorCode KisTIFFImport::convert(KisDocument *document, QIODevice */*io*/,  KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode
+KisTIFFImport::convert(KisDocument *document,
+                       QIODevice * /*io*/,
+                       KisPropertiesConfigurationSP /*configuration*/)
 {
     dbgFile << "Start decoding TIFF File";
 
@@ -1558,4 +1730,3 @@ KisImportExportErrorCode KisTIFFImport::convert(KisDocument *document, QIODevice
 }
 
 #include <kis_tiff_import.moc>
-
diff --git a/plugins/impex/tiff/kis_tiff_import.h b/plugins/impex/tiff/kis_tiff_import.h
index 993f3af3af..0b09d7e9c3 100644
--- a/plugins/impex/tiff/kis_tiff_import.h
+++ b/plugins/impex/tiff/kis_tiff_import.h
@@ -12,6 +12,7 @@
 
 #include <KisImportExportFilter.h>
 #include <config-tiff.h>
+#include <config-jpeg.h>
 #include <kis_types.h>
 
 class QBuffer;
diff --git a/plugins/impex/tiff/tests/data/quad-jpeg.tif.png b/plugins/impex/tiff/tests/data/quad-jpeg.tif.png
deleted file mode 100644
index 36985d7435..0000000000
Binary files a/plugins/impex/tiff/tests/data/quad-jpeg.tif.png and /dev/null differ
diff --git a/plugins/impex/tiff/tests/data/results/quad-jpeg.tif.png b/plugins/impex/tiff/tests/data/results/quad-jpeg.tif.png
new file mode 100644
index 0000000000..a6f592cad8
Binary files /dev/null and b/plugins/impex/tiff/tests/data/results/quad-jpeg.tif.png differ
diff --git a/plugins/impex/tiff/tests/data/quad-jpeg.tif b/plugins/impex/tiff/tests/data/sources/quad-jpeg.tif
similarity index 100%
rename from plugins/impex/tiff/tests/data/quad-jpeg.tif
rename to plugins/impex/tiff/tests/data/sources/quad-jpeg.tif



More information about the kimageshop mailing list