[GTK][WPE] Support JPEG 2000 images
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Jan 2019 10:02:33 +0000 (10:02 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Jan 2019 10:02:33 +0000 (10:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=186272

Reviewed by Žan Doberšek.

.:

Add USE_OPENJPEG build option.

* Source/cmake/OptionsGTK.cmake:
* Source/cmake/OptionsWPE.cmake:

Source/WebCore:

Add JPEG2000ImageDecoder to support JPEG2000 images using OpenJPEG. For now only SRGB and SYCC color spaces are
supported.

* platform/ImageDecoders.cmake:
* platform/MIMETypeRegistry.cpp:
(WebCore::MIMETypeRegistry::supportedImageMIMETypes):
* platform/image-decoders/ScalableImageDecoder.cpp:
(WebCore::ScalableImageDecoder::create):
* platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.cpp: Added.
(WebCore::syccToRGB):
(WebCore::sycc444ToRGB):
(WebCore::sycc422ToRGB):
(WebCore::sycc420ToRGB):
(WebCore::JPEG2000ImageDecoder::JPEG2000ImageDecoder):
(WebCore::JPEG2000ImageDecoder::frameBufferAtIndex):
(WebCore::JPEG2000ImageDecoder::decode):
* platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.h: Added.

Tools:

Add OpenJPEG to jhbuild since 2.2.0 version is required and it's not available in debian stable.

* gtk/jhbuild.modules:
* wpe/jhbuild.modules:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@240428 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
ChangeLog
Source/WebCore/ChangeLog
Source/WebCore/platform/ImageDecoders.cmake
Source/WebCore/platform/MIMETypeRegistry.cpp
Source/WebCore/platform/image-decoders/ScalableImageDecoder.cpp
Source/WebCore/platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.cpp [new file with mode: 0644]
Source/WebCore/platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.h [new file with mode: 0644]
Source/cmake/OptionsGTK.cmake
Source/cmake/OptionsWPE.cmake
Tools/ChangeLog
Tools/gtk/jhbuild.modules
Tools/wpe/jhbuild.modules

index 506fd35..568276d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2019-01-24  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK][WPE] Support JPEG 2000 images
+        https://bugs.webkit.org/show_bug.cgi?id=186272
+
+        Reviewed by Žan Doberšek.
+
+        Add USE_OPENJPEG build option.
+
+        * Source/cmake/OptionsGTK.cmake:
+        * Source/cmake/OptionsWPE.cmake:
+
 2019-01-18  Jer Noble  <jer.noble@apple.com>
 
         SDK_VARIANT build destinations should be separate from non-SDK_VARIANT builds
index 8ea5505..676ce88 100644 (file)
@@ -1,3 +1,28 @@
+2019-01-24  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK][WPE] Support JPEG 2000 images
+        https://bugs.webkit.org/show_bug.cgi?id=186272
+
+        Reviewed by Žan Doberšek.
+
+        Add JPEG2000ImageDecoder to support JPEG2000 images using OpenJPEG. For now only SRGB and SYCC color spaces are
+        supported.
+
+        * platform/ImageDecoders.cmake:
+        * platform/MIMETypeRegistry.cpp:
+        (WebCore::MIMETypeRegistry::supportedImageMIMETypes):
+        * platform/image-decoders/ScalableImageDecoder.cpp:
+        (WebCore::ScalableImageDecoder::create):
+        * platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.cpp: Added.
+        (WebCore::syccToRGB):
+        (WebCore::sycc444ToRGB):
+        (WebCore::sycc422ToRGB):
+        (WebCore::sycc420ToRGB):
+        (WebCore::JPEG2000ImageDecoder::JPEG2000ImageDecoder):
+        (WebCore::JPEG2000ImageDecoder::frameBufferAtIndex):
+        (WebCore::JPEG2000ImageDecoder::decode):
+        * platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.h: Added.
+
 2019-01-23  Simon Fraser  <simon.fraser@apple.com>
 
         Change some RenderLayerCompositor functions to use references
index 58d3305..292aa88 100644 (file)
@@ -4,6 +4,7 @@ list(APPEND WebCore_INCLUDE_DIRECTORIES
     "${WEBCORE_DIR}/platform/image-decoders/gif"
     "${WEBCORE_DIR}/platform/image-decoders/ico"
     "${WEBCORE_DIR}/platform/image-decoders/jpeg"
+    "${WEBCORE_DIR}/platform/image-decoders/jpeg2000"
     "${WEBCORE_DIR}/platform/image-decoders/png"
     "${WEBCORE_DIR}/platform/image-decoders/webp"
 )
@@ -22,6 +23,8 @@ list(APPEND WebCore_SOURCES
 
     platform/image-decoders/jpeg/JPEGImageDecoder.cpp
 
+    platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.cpp
+
     platform/image-decoders/png/PNGImageDecoder.cpp
 
     platform/image-decoders/webp/WEBPImageDecoder.cpp
@@ -36,6 +39,15 @@ list(APPEND WebCore_LIBRARIES
     ${PNG_LIBRARIES}
 )
 
+if (OpenJPEG_FOUND)
+    list(APPEND WebCore_SYSTEM_INCLUDE_DIRECTORIES
+        ${OPENJPEG_INCLUDE_DIRS}
+    )
+    list(APPEND WebCore_LIBRARIES
+      ${OPENJPEG_LIBRARIES}
+    )
+endif ()
+
 if (WEBP_FOUND)
     list(APPEND WebCore_SYSTEM_INCLUDE_DIRECTORIES
         ${WEBP_INCLUDE_DIRS}
index fdef56f..f9eabb5 100644 (file)
@@ -125,6 +125,10 @@ const HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::supportedImag
         "image/vnd.microsoft.icon"_s, // ico
         "image/x-icon"_s, // ico
         "image/x-xbitmap"_s, // xbm
+#if USE(OPENJPEG)
+        "image/jp2"_s,
+        "image/jpeg2000"_s,
+#endif
 #if USE(WEBP)
         "image/webp"_s,
 #endif
index e24de4d..00810e5 100644 (file)
@@ -29,6 +29,9 @@
 #include "JPEGImageDecoder.h"
 #include "PNGImageDecoder.h"
 #include "SharedBuffer.h"
+#if USE(OPENJPEG)
+#include "JPEG2000ImageDecoder.h"
+#endif
 #if USE(WEBP)
 #include "WEBPImageDecoder.h"
 #endif
@@ -73,6 +76,19 @@ bool matchesJPEGSignature(char* contents)
     return !memcmp(contents, "\xFF\xD8\xFF", 3);
 }
 
+#if USE(OPENJPEG)
+bool matchesJP2Signature(char* contents)
+{
+    return !memcmp(contents, "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A", 12)
+        || !memcmp(contents, "\x0D\x0A\x87\x0A", 4);
+}
+
+bool matchesJ2KSignature(char* contents)
+{
+    return !memcmp(contents, "\xFF\x4F\xFF\x51", 4);
+}
+#endif
+
 #if USE(WEBP)
 bool matchesWebPSignature(char* contents)
 {
@@ -117,6 +133,14 @@ RefPtr<ScalableImageDecoder> ScalableImageDecoder::create(SharedBuffer& data, Al
     if (matchesJPEGSignature(contents))
         return JPEGImageDecoder::create(alphaOption, gammaAndColorProfileOption);
 
+#if USE(OPENJPEG)
+    if (matchesJP2Signature(contents))
+        return JPEG2000ImageDecoder::create(JPEG2000ImageDecoder::Format::JP2, alphaOption, gammaAndColorProfileOption);
+
+    if (matchesJ2KSignature(contents))
+        return JPEG2000ImageDecoder::create(JPEG2000ImageDecoder::Format::J2K, alphaOption, gammaAndColorProfileOption);
+#endif
+
 #if USE(WEBP)
     if (matchesWebPSignature(contents))
         return WEBPImageDecoder::create(alphaOption, gammaAndColorProfileOption);
diff --git a/Source/WebCore/platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.cpp b/Source/WebCore/platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.cpp
new file mode 100644 (file)
index 0000000..35047e1
--- /dev/null
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "JPEG2000ImageDecoder.h"
+
+#if USE(OPENJPEG)
+
+#include <openjpeg.h>
+
+namespace WebCore {
+
+// SYCC to RGB conversion code from libopenjpeg (BSD), adapted to WebKit coding style.
+// --------------------------------------------------------
+// Matrix for sYCC, Amendment 1 to IEC 61966-2-1
+//
+// Y :   0.299   0.587    0.114   :R
+// Cb:  -0.1687 -0.3312   0.5     :G
+// Cr:   0.5    -0.4187  -0.0812  :B
+//
+// Inverse:
+//
+// R: 1        -3.68213e-05    1.40199      :Y
+// G: 1.00003  -0.344125      -0.714128     :Cb - 2^(prec - 1)
+// B: 0.999823  1.77204       -8.04142e-06  :Cr - 2^(prec - 1)
+//
+// -----------------------------------------------------------*/
+static void syccToRGB(int offset, int upb, int y, int cb, int cr, int* r, int* g, int* b)
+{
+    cb -= offset;
+    cr -= offset;
+
+    *r = static_cast<int>(clampTo<float>(y - 0.0000368 * cb + 1.40199 * cr + 0.5, 0, upb));
+    *g = static_cast<int>(clampTo<float>(1.0003 * y - 0.344125 * cb - 0.7141128 * cr + 0.5, 0, upb));
+    *b = static_cast<int>(clampTo<float>(0.999823 * y + 1.77204 * cb - 0.000008 * cr + 0.5, 0, upb));
+}
+
+static void sycc444ToRGB(opj_image_t* img)
+{
+    Checked<int> upb = static_cast<int>(img->comps[0].prec);
+    int offset = 1 << (upb.unsafeGet() - 1);
+    upb = (1 << upb.unsafeGet()) - 1;
+
+    Checked<size_t> maxw = static_cast<size_t>(img->comps[0].w);
+    Checked<size_t> maxh = static_cast<size_t>(img->comps[0].h);
+    size_t max = (maxw * maxh).unsafeGet();
+
+    const int* y = img->comps[0].data;
+    const int* cb = img->comps[1].data;
+    const int* cr = img->comps[2].data;
+
+    auto* d0 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* d1 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* d2 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* r = d0;
+    auto* g = d1;
+    auto* b = d2;
+
+    if (!r || !g || !b) {
+        opj_image_data_free(r);
+        opj_image_data_free(g);
+        opj_image_data_free(b);
+        return;
+    }
+
+    for (size_t i = 0; i < max; ++i) {
+        syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+        ++y;
+        ++cb;
+        ++cr;
+        ++r;
+        ++g;
+        ++b;
+    }
+
+    opj_image_data_free(img->comps[0].data);
+    img->comps[0].data = d0;
+    opj_image_data_free(img->comps[1].data);
+    img->comps[1].data = d1;
+    opj_image_data_free(img->comps[2].data);
+    img->comps[2].data = d2;
+    img->color_space = OPJ_CLRSPC_SRGB;
+}
+
+static void sycc422ToRGB(opj_image_t* img)
+{
+    Checked<int> upb = static_cast<int>(img->comps[0].prec);
+    int offset = 1 << (upb.unsafeGet() - 1);
+    upb = (1 << upb.unsafeGet()) - 1;
+
+    Checked<size_t> maxw = static_cast<size_t>(img->comps[0].w);
+    Checked<size_t> maxh = static_cast<size_t>(img->comps[0].h);
+    size_t max = (maxw * maxh).unsafeGet();
+
+    const int* y = img->comps[0].data;
+    const int* cb = img->comps[1].data;
+    const int* cr = img->comps[2].data;
+
+    auto* d0 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* d1 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* d2 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* r = d0;
+    auto* g = d1;
+    auto* b = d2;
+
+    if (!r || !g || !b) {
+        opj_image_data_free(r);
+        opj_image_data_free(g);
+        opj_image_data_free(b);
+        return;
+    }
+
+    // if img->x0 is odd, then first column shall use Cb/Cr = 0.
+    size_t offx = img->x0 & 1U;
+    size_t loopmaxw = maxw.unsafeGet() - offx;
+
+    for (size_t i = 0; i < maxh.unsafeGet(); ++i) {
+        size_t j;
+
+        if (offx > 0) {
+            syccToRGB(offset, upb.unsafeGet(), *y, 0, 0, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+        }
+
+        for (j = 0; j < (loopmaxw & ~static_cast<size_t>(1U)); j += 2U) {
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+            ++cb;
+            ++cr;
+        }
+        if (j < loopmaxw) {
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+            ++cb;
+            ++cr;
+        }
+    }
+
+    opj_image_data_free(img->comps[0].data);
+    img->comps[0].data = d0;
+    opj_image_data_free(img->comps[1].data);
+    img->comps[1].data = d1;
+    opj_image_data_free(img->comps[2].data);
+    img->comps[2].data = d2;
+
+    img->comps[1].w = img->comps[2].w = img->comps[0].w;
+    img->comps[1].h = img->comps[2].h = img->comps[0].h;
+    img->comps[1].dx = img->comps[2].dx = img->comps[0].dx;
+    img->comps[1].dy = img->comps[2].dy = img->comps[0].dy;
+    img->color_space = OPJ_CLRSPC_SRGB;
+}
+
+static void sycc420ToRGB(opj_image_t* img)
+{
+    Checked<int> upb = static_cast<int>(img->comps[0].prec);
+    int offset = 1 << (upb.unsafeGet() - 1);
+    upb = (1 << upb.unsafeGet()) - 1;
+
+    Checked<size_t> maxw = static_cast<size_t>(img->comps[0].w);
+    Checked<size_t> maxh = static_cast<size_t>(img->comps[0].h);
+    size_t max = (maxw * maxh).unsafeGet();
+
+    const int* y = img->comps[0].data;
+    const int* cb = img->comps[1].data;
+    const int* cr = img->comps[2].data;
+
+    auto* d0 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* d1 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* d2 = static_cast<int*>(opj_image_data_alloc(sizeof(int) * max));
+    auto* r = d0;
+    auto* g = d1;
+    auto* b = d2;
+
+    if (!r || !g || !b) {
+        opj_image_data_free(r);
+        opj_image_data_free(g);
+        opj_image_data_free(b);
+        return;
+    }
+
+    // if img->x0 is odd, then first column shall use Cb/Cr = 0.
+    size_t offx = img->x0 & 1U;
+    size_t loopmaxw = maxw.unsafeGet() - offx;
+    // if img->y0 is odd, then first line shall use Cb/Cr = 0.
+    size_t offy = img->y0 & 1U;
+    size_t loopmaxh = maxh.unsafeGet() - offy;
+
+    if (offy > 0) {
+        size_t j;
+        for (j = 0; j < maxw.unsafeGet(); ++j) {
+            syccToRGB(offset, upb.unsafeGet(), *y, 0, 0, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+        }
+    }
+
+    size_t i;
+    for (i = 0; i < (loopmaxh & ~static_cast<size_t>(1U)); i += 2U) {
+        const int* ny = y + maxw.unsafeGet();
+        int* nr = r + maxw.unsafeGet();
+        int* ng = g + maxw.unsafeGet();
+        int* nb = b + maxw.unsafeGet();
+
+        if (offx > 0) {
+            syccToRGB(offset, upb.unsafeGet(), *y, 0, 0, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+            syccToRGB(offset, upb.unsafeGet(), *ny, *cb, *cr, nr, ng, nb);
+            ++ny;
+            ++nr;
+            ++ng;
+            ++nb;
+        }
+
+        size_t j;
+        for (j = 0; j < (loopmaxw & ~static_cast<size_t>(1U)); j += 2U) {
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+
+            syccToRGB(offset, upb.unsafeGet(), *ny, *cb, *cr, nr, ng, nb);
+            ++ny;
+            ++nr;
+            ++ng;
+            ++nb;
+            syccToRGB(offset, upb.unsafeGet(), *ny, *cb, *cr, nr, ng, nb);
+            ++ny;
+            ++nr;
+            ++ng;
+            ++nb;
+            ++cb;
+            ++cr;
+        }
+        if (j < loopmaxw) {
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+
+            syccToRGB(offset, upb.unsafeGet(), *ny, *cb, *cr, nr, ng, nb);
+            ++ny;
+            ++nr;
+            ++ng;
+            ++nb;
+            ++cb;
+            ++cr;
+        }
+        y += maxw.unsafeGet();
+        r += maxw.unsafeGet();
+        g += maxw.unsafeGet();
+        b += maxw.unsafeGet();
+    }
+    if (i < loopmaxh) {
+        size_t j;
+        for (j = 0; j < (maxw.unsafeGet() & ~static_cast<size_t>(1U)); j += 2U) {
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+
+            ++y;
+            ++r;
+            ++g;
+            ++b;
+            ++cb;
+            ++cr;
+        }
+        if (j < maxw.unsafeGet())
+            syccToRGB(offset, upb.unsafeGet(), *y, *cb, *cr, r, g, b);
+    }
+
+    opj_image_data_free(img->comps[0].data);
+    img->comps[0].data = d0;
+    opj_image_data_free(img->comps[1].data);
+    img->comps[1].data = d1;
+    opj_image_data_free(img->comps[2].data);
+    img->comps[2].data = d2;
+
+    img->comps[1].w = img->comps[2].w = img->comps[0].w;
+    img->comps[1].h = img->comps[2].h = img->comps[0].h;
+    img->comps[1].dx = img->comps[2].dx = img->comps[0].dx;
+    img->comps[1].dy = img->comps[2].dy = img->comps[0].dy;
+    img->color_space = OPJ_CLRSPC_SRGB;
+}
+
+JPEG2000ImageDecoder::JPEG2000ImageDecoder(Format format, AlphaOption alphaOption, GammaAndColorProfileOption gammaAndColorProfileOption)
+    : ScalableImageDecoder(alphaOption, gammaAndColorProfileOption)
+    , m_format(format)
+{
+}
+
+ScalableImageDecoderFrame* JPEG2000ImageDecoder::frameBufferAtIndex(size_t index)
+{
+    if (index)
+        return nullptr;
+
+    if (m_frameBufferCache.isEmpty())
+        m_frameBufferCache.grow(1);
+
+    auto& frame = m_frameBufferCache[0];
+    if (!frame.isComplete())
+        decode(false, isAllDataReceived());
+    return &frame;
+}
+
+void JPEG2000ImageDecoder::decode(bool onlySize, bool allDataReceived)
+{
+    if (failed())
+        return;
+
+    std::unique_ptr<opj_codec_t, void(*)(opj_codec_t*)> decoder(opj_create_decompress(m_format == Format::JP2 ? OPJ_CODEC_JP2 : OPJ_CODEC_J2K), opj_destroy_codec);
+    if (!decoder) {
+        setFailed();
+        return;
+    }
+
+    opj_dparameters_t parameters;
+    opj_set_default_decoder_parameters(&parameters);
+    if (!opj_setup_decoder(decoder.get(), &parameters)) {
+        setFailed();
+        return;
+    }
+
+    std::unique_ptr<opj_stream_t, void(*)(opj_stream_t*)> stream(opj_stream_default_create(OPJ_TRUE), opj_stream_destroy);
+    if (!stream) {
+        setFailed();
+        return;
+    }
+
+    struct Reader {
+        SharedBuffer& data;
+        size_t offset;
+    } reader = { *m_data, 0 };
+
+    opj_stream_set_user_data(stream.get(), &reader, nullptr);
+    opj_stream_set_user_data_length(stream.get(), m_data->size());
+    opj_stream_set_read_function(stream.get(), [](void* buffer, OPJ_SIZE_T bytes, void* userData) -> OPJ_SIZE_T {
+        auto& reader = *static_cast<Reader*>(userData);
+        if (reader.offset == reader.data.size())
+            return -1;
+
+        OPJ_SIZE_T length = reader.offset + bytes > reader.data.size() ? reader.data.size() - reader.offset : bytes;
+        memcpy(buffer, reader.data.data(), length);
+        reader.offset += length;
+
+        return length;
+    });
+    opj_stream_set_skip_function(stream.get(), [](OPJ_OFF_T bytes, void* userData) -> OPJ_OFF_T {
+        auto& reader = *static_cast<Reader*>(userData);
+
+        OPJ_OFF_T skip = reader.offset + bytes > reader.data.size() ? reader.data.size() - reader.offset : bytes;
+        reader.offset += skip;
+
+        return skip;
+    });
+    opj_stream_set_seek_function(stream.get(), [](OPJ_OFF_T bytes, void* userData) -> OPJ_BOOL {
+        auto& reader = *static_cast<Reader*>(userData);
+
+        if (static_cast<unsigned>(bytes) > reader.data.size())
+            return OPJ_FALSE;
+
+        reader.offset = bytes;
+
+        return OPJ_TRUE;
+    });
+
+    opj_image_t* imagePtr = nullptr;
+    if (!opj_read_header(stream.get(), decoder.get(), &imagePtr)) {
+        if (allDataReceived)
+            setFailed();
+        opj_image_destroy(imagePtr);
+        return;
+    }
+
+    std::unique_ptr<opj_image_t, void(*)(opj_image_t*)> image(imagePtr, opj_image_destroy);
+    setSize({ static_cast<int>(image->x1 - image->x0), static_cast<int>(image->y1 - image->y0) });
+    if (onlySize)
+        return;
+
+    if (!opj_decode(decoder.get(), stream.get(), image.get())) {
+        if (allDataReceived)
+            setFailed();
+        return;
+    }
+
+    if (image->color_space == OPJ_CLRSPC_UNSPECIFIED) {
+        if (image->numcomps == 3 && image->comps[0].dx == image->comps[0].dy && image->comps[1].dx != 1)
+            image->color_space = OPJ_CLRSPC_SYCC;
+        else if (image->numcomps <= 2)
+            image->color_space = OPJ_CLRSPC_GRAY;
+    }
+
+    if (image->color_space == OPJ_CLRSPC_SYCC) {
+        if (image->numcomps < 3)
+            image->color_space = OPJ_CLRSPC_GRAY;
+        else if ((image->comps[0].dx == 1)
+            && (image->comps[1].dx == 2)
+            && (image->comps[2].dx == 2)
+            && (image->comps[0].dy == 1)
+            && (image->comps[1].dy == 2)
+            && (image->comps[2].dy == 2)) {
+            // Horizontal and vertical sub-sample.
+            sycc420ToRGB(image.get());
+        } else if ((image->comps[0].dx == 1)
+            && (image->comps[1].dx == 2)
+            && (image->comps[2].dx == 2)
+            && (image->comps[0].dy == 1)
+            && (image->comps[1].dy == 1)
+            && (image->comps[2].dy == 1)) {
+            // Horizontal sub-sample only.
+            sycc422ToRGB(image.get());
+        } else if ((image->comps[0].dx == 1)
+            && (image->comps[1].dx == 1)
+            && (image->comps[2].dx == 1)
+            && (image->comps[0].dy == 1)
+            && (image->comps[1].dy == 1)
+            && (image->comps[2].dy == 1)) {
+            // No sub-sample.
+            sycc444ToRGB(image.get());
+        }
+    }
+
+    if (image->color_space != OPJ_CLRSPC_SRGB || image->numcomps > 4 || image->numcomps < 3) {
+        // Unsupported format.
+        setFailed();
+        return;
+    }
+
+    auto& buffer = m_frameBufferCache[0];
+    if (!buffer.initialize(scaledSize(), m_premultiplyAlpha)) {
+        setFailed();
+        return;
+    }
+
+    buffer.setDecodingStatus(DecodingStatus::Partial);
+    buffer.setHasAlpha(false);
+
+    int adjust[4] = { 0, 0, 0, 0 };
+    for (OPJ_UINT32 component = 0; component < image->numcomps; ++component) {
+        if (!image->comps[component].data) {
+            setFailed();
+            break;
+        }
+
+        if (image->comps[component].prec > 8)
+            adjust[component] = image->comps[component].prec - 8;
+    }
+
+    bool subsampling = image->comps[0].dx != 1 || image->comps[0].dy != 1 || image->comps[1].dx != 1 || image->comps[1].dy != 1 || image->comps[2].dx != 1 || image->comps[2].dy != 1;
+    unsigned char nonTrivialAlphaMask = 0;
+    const IntRect& frameRect = buffer.backingStore()->frameRect();
+    for (int y = 0; y < frameRect.height(); ++y) {
+        for (int x = 0; x < frameRect.width(); ++x) {
+            int r, g, b, a;
+
+            int offset;
+            if (subsampling) {
+                int subX = frameRect.width() / image->comps[0].w;
+                int subY = frameRect.height() / image->comps[0].h;
+                offset = (y / subY) * image->comps[0].w + (x / subX);
+            } else
+                offset = y * frameRect.width() + x;
+
+            r = image->comps[0].data[offset];
+            r += (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0);
+            if (subsampling) {
+                int subX = frameRect.width() / image->comps[1].w;
+                int subY = frameRect.height() / image->comps[1].h;
+                offset = (y / subY) * image->comps[1].w + (x / subX);
+            }
+            g = image->comps[1].data[offset];
+            g += (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0);
+            if (subsampling) {
+                int subX = frameRect.width() / image->comps[2].w;
+                int subY = frameRect.height() / image->comps[2].h;
+                offset = (y / subY) * image->comps[2].w + (x / subX);
+            }
+            b = image->comps[2].data[offset];
+            b += (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0);
+
+            if (image->numcomps > 3) {
+                if (subsampling) {
+                    int subX = frameRect.width() / image->comps[3].w;
+                    int subY = frameRect.height() / image->comps[3].h;
+                    offset = (y / subY) * image->comps[3].w + (x / subX);
+                }
+                a = image->comps[3].data[offset];
+                a += (image->comps[3].sgnd ? 1 << (image->comps[3].prec - 1) : 0);
+            }
+
+            int adjustedRed = (r >> adjust[0]) + ((r >> (adjust[0] - 1)) % 2);
+            int adjustedGreen = (g >> adjust[1]) + ((g >> (adjust[1] - 1)) % 2);
+            int adjustedBlue = (b >> adjust[2]) + ((b >> (adjust[2] - 1)) % 2);
+            int adjustedAlpha = image->numcomps > 3 ? (a >> adjust[3]) + ((a >> (adjust[3] - 1)) % 2) : 0xFF;
+            buffer.backingStore()->setPixel(x, y, adjustedRed, adjustedGreen, adjustedBlue, adjustedAlpha);
+            nonTrivialAlphaMask |= (255 - adjustedAlpha);
+        }
+    }
+
+    buffer.setDecodingStatus(DecodingStatus::Complete);
+    if (nonTrivialAlphaMask && !buffer.hasAlpha())
+        buffer.setHasAlpha(true);
+}
+
+} // namespace WebCore
+
+#endif // USE(OPENJPEG)
diff --git a/Source/WebCore/platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.h b/Source/WebCore/platform/image-decoders/jpeg2000/JPEG2000ImageDecoder.h
new file mode 100644 (file)
index 0000000..7e5f4e5
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "ScalableImageDecoder.h"
+
+#if USE(OPENJPEG)
+
+namespace WebCore {
+
+class JPEG2000ImageDecoder final : public ScalableImageDecoder {
+public:
+    enum class Format { JP2, J2K };
+    static Ref<ScalableImageDecoder> create(Format format, AlphaOption alphaOption, GammaAndColorProfileOption gammaAndColorProfileOption)
+    {
+        return adoptRef(*new JPEG2000ImageDecoder(format, alphaOption, gammaAndColorProfileOption));
+    }
+
+    virtual ~JPEG2000ImageDecoder() = default;
+
+    // ScalableImageDecoder
+    String filenameExtension() const override { return m_format == Format::JP2 ? "jp2"_s : "j2k"_s; }
+    ScalableImageDecoderFrame* frameBufferAtIndex(size_t index) override;
+
+private:
+    JPEG2000ImageDecoder(Format, AlphaOption, GammaAndColorProfileOption);
+
+    void decode(bool onlySize, bool allDataReceived);
+    void tryDecodeSize(bool allDataReceived) override { decode(true, allDataReceived); }
+
+    Format m_format;
+};
+
+} // namespace WebCore
+
+#endif // USE(OPENJPEG)
index 5e5529e..9f3d1a3 100644 (file)
@@ -83,6 +83,7 @@ WEBKIT_OPTION_DEFINE(ENABLE_WAYLAND_TARGET "Whether to enable support for the Wa
 WEBKIT_OPTION_DEFINE(USE_LIBNOTIFY "Whether to enable the default web notification implementation." PUBLIC ON)
 WEBKIT_OPTION_DEFINE(USE_LIBHYPHEN "Whether to enable the default automatic hyphenation implementation." PUBLIC ON)
 WEBKIT_OPTION_DEFINE(USE_LIBSECRET "Whether to enable the persistent credential storage using libsecret." PUBLIC ON)
+WEBKIT_OPTION_DEFINE(USE_OPENJPEG "Whether to enable support for JPEG2000 images." PUBLIC ON)
 WEBKIT_OPTION_DEFINE(USE_WOFF2 "Whether to enable support for WOFF2 Web Fonts." PUBLIC ON)
 
 # Private options specific to the GTK+ port. Changing these options is
@@ -389,6 +390,16 @@ if (USE_LIBHYPHEN)
     endif ()
 endif ()
 
+if (USE_OPENJPEG)
+    find_package(OpenJPEG)
+    if (NOT OpenJPEG_FOUND)
+        message(FATAL_ERROR "libopenjpeg is needed for USE_OPENJPEG.")
+    endif ()
+    if ("${OPENJPEG_MAJOR_VERSION}.${OPENJPEG_MINOR_VERSION}.${OPENJPEG_BUILD_VERSION}" VERSION_LESS "2.2.0")
+        message(FATAL_ERROR "libopenjpeg 2.2.0 is required for USE_OPENJPEG.")
+    endif ()
+endif ()
+
 if (USE_WOFF2)
     find_package(WOFF2Dec 1.0.2)
     if (NOT WOFF2DEC_FOUND)
index 5f311b8..7ea7540 100644 (file)
@@ -44,6 +44,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_RTC PRIVATE ${ENABLE_EXPERIMENTAL_FE
 # there is a strong reason we should support changing the value of the option,
 # and the option is not relevant to any other WebKit ports.
 WEBKIT_OPTION_DEFINE(ENABLE_GTKDOC "Whether or not to use generate gtkdoc." PUBLIC OFF)
+WEBKIT_OPTION_DEFINE(USE_OPENJPEG "Whether to enable support for JPEG2000 images." PUBLIC ON)
 WEBKIT_OPTION_DEFINE(USE_WOFF2 "Whether to enable support for WOFF2 Web Fonts." PUBLIC ON)
 WEBKIT_OPTION_DEFINE(ENABLE_WPE_QT_API "Whether to enable support for the Qt5/QML plugin" PUBLIC OFF)
 
@@ -86,6 +87,16 @@ find_package(WebP REQUIRED)
 find_package(WPE REQUIRED)
 find_package(ZLIB REQUIRED)
 
+if (USE_OPENJPEG)
+    find_package(OpenJPEG)
+    if (NOT OpenJPEG_FOUND)
+        message(FATAL_ERROR "libopenjpeg is needed for USE_OPENJPEG.")
+    endif ()
+    if ("${OPENJPEG_MAJOR_VERSION}.${OPENJPEG_MINOR_VERSION}.${OPENJPEG_BUILD_VERSION}" VERSION_LESS "2.2.0")
+        message(FATAL_ERROR "libopenjpeg 2.2.0 is required for USE_OPENJPEG.")
+    endif ()
+endif ()
+
 if (USE_WOFF2)
     find_package(WOFF2Dec 1.0.2)
     if (NOT WOFF2DEC_FOUND)
index e15d028..2c9f996 100644 (file)
@@ -1,3 +1,15 @@
+2019-01-24  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK][WPE] Support JPEG 2000 images
+        https://bugs.webkit.org/show_bug.cgi?id=186272
+
+        Reviewed by Žan Doberšek.
+
+        Add OpenJPEG to jhbuild since 2.2.0 version is required and it's not available in debian stable.
+
+        * gtk/jhbuild.modules:
+        * wpe/jhbuild.modules:
+
 2019-01-23  David Kilzer  <ddkilzer@apple.com>
 
         check-webkit-style should warn when using soft-linking macros in a header
index f32bfaf..a40d75f 100644 (file)
@@ -31,6 +31,7 @@
       <dep package="libsecret"/>
       <dep package="libgpg-error"/>
       <dep package="libgcrypt"/>
+      <dep package="openjpeg"/>
       <if condition-set="linux">
           <dep package="xdg-dbus-proxy"/>
           <dep package="xserver"/>
     </dependencies>
   </autotools>
 
+  <cmake id="openjpeg">
+    <branch repo="github-tarball"
+            module="uclouvain/openjpeg/archive/v${version}.tar.gz"
+            checkoutdir="openjpeg-${version}"
+            version="2.3.0"
+            hash="sha256:3dc787c1bb6023ba846c2a0d9b1f6e179f1cd255172bde9eb75b01f1e6c7d71a"/>
+  </cmake>
+
   <!-- Dependencies listed below this point are not thought to affect test results, and are only
        included because they themselves depend on other dependencies built by jhbuild. -->
 
index 617ce3e..ecd59c4 100644 (file)
@@ -24,6 +24,7 @@
       <dep package="libgcrypt"/>
       <dep package="libepoxy"/>
       <dep package="wayland-protocols"/>
+      <dep package="openjpeg"/>
     </dependencies>
   </metamodule>
 
     </dependencies>
   </meson>
 
+  <cmake id="openjpeg">
+    <branch repo="github-tarball"
+            module="uclouvain/openjpeg/archive/v${version}.tar.gz"
+            checkoutdir="openjpeg-${version}"
+            version="2.3.0"
+            hash="sha256:3dc787c1bb6023ba846c2a0d9b1f6e179f1cd255172bde9eb75b01f1e6c7d71a"/>
+  </cmake>
+
   <distutils id="meson" python3="1">
     <branch repo="github-tarball"
             version="0.48.1"