From 990c49046af668a46f2ee0902b7155b0217d9182 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 19 Oct 2019 14:17:50 +0200 Subject: [PATCH] Soft masks for JPEG 2000 --- PdfForQtLib/sources/pdfcolorspaces.h | 4 -- PdfForQtLib/sources/pdfimage.cpp | 102 +++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 19 deletions(-) diff --git a/PdfForQtLib/sources/pdfcolorspaces.h b/PdfForQtLib/sources/pdfcolorspaces.h index 67b36d4..1c459e0 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.h +++ b/PdfForQtLib/sources/pdfcolorspaces.h @@ -141,10 +141,6 @@ public: /// Returns number of color channels unsigned int getColorChannels() const { return m_components; } - // TODO: Implement alpha channel functionality - /// Returns number of alpha transparency channels (0 or 1) - unsigned int getAlphaChannels() const { return 0; } - bool isValid() const { return m_width && m_height && m_components && m_bitsPerComponent; } const unsigned char* getRow(unsigned int rowIndex) const; diff --git a/PdfForQtLib/sources/pdfimage.cpp b/PdfForQtLib/sources/pdfimage.cpp index 638cd0c..b99408e 100644 --- a/PdfForQtLib/sources/pdfimage.cpp +++ b/PdfForQtLib/sources/pdfimage.cpp @@ -66,21 +66,12 @@ PDFImage PDFImage::createImage(const PDFDocument* document, throw PDFException(PDFTranslationContext::tr("Image has not data.")); } - // TODO: Implement SMaskInData - - for (const char* notImplementedKey : { "SMaskInData" }) - { - if (dictionary->hasKey(notImplementedKey)) - { - throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Not implemented image property '%2'.").arg(QString::fromLatin1(notImplementedKey))); - } - } - PDFImageData::MaskingType maskingType = PDFImageData::MaskingType::None; std::vector mask; std::vector decode = loader.readNumberArrayFromDictionary(dictionary, "Decode"); bool imageMask = loader.readBooleanFromDictionary(dictionary, "ImageMask", false); std::vector matte = loader.readNumberArrayFromDictionary(dictionary, "Matte"); + PDFInteger sMaskInData = loader.readIntegerFromDictionary(dictionary, "SMaskInData", 0); if (isSoftMask && (imageMask || dictionary->hasKey("Mask") || dictionary->hasKey("SMask"))) { @@ -447,11 +438,39 @@ PDFImage PDFImage::createImage(const PDFDocument* document, // If we have a valid image, then adjust it if (jpegImage) { + // This image type can have color space defined in the data (definition of color space in PDF + // is only optional). So, if we doesn't have a color space, then we must determine it from the data. + if (!image.m_colorSpace) + { + switch (jpegImage->color_space) + { + case OPJ_CLRSPC_SRGB: + image.m_colorSpace.reset(new PDFDeviceRGBColorSpace()); + break; + + case OPJ_CLRSPC_GRAY: + image.m_colorSpace.reset(new PDFDeviceGrayColorSpace()); + break; + + case OPJ_CLRSPC_CMYK: + image.m_colorSpace.reset(new PDFDeviceCMYKColorSpace()); + break; + + default: + imageData.errors.push_back(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Unknown color space for JPEG 2000 image."))); + break; + } + } + // First we must check, if all components are valid (i.e has same width/height/precision) + std::vector ordinaryComponents; + std::vector alphaComponents; + bool valid = true; const OPJ_UINT32 componentCount = jpegImage->numcomps; - for (OPJ_UINT32 i = 1; i < componentCount; ++i) + ordinaryComponents.reserve(componentCount); + for (OPJ_UINT32 i = 0; i < componentCount; ++i) { if (jpegImage->comps[0].w != jpegImage->comps[i].w || jpegImage->comps[0].h != jpegImage->comps[i].h || @@ -461,12 +480,37 @@ PDFImage PDFImage::createImage(const PDFDocument* document, valid = false; break; } + else + { + // Fill in ordinary component, or alpha component + if (!jpegImage->comps[i].alpha) + { + ordinaryComponents.push_back(i); + } + else + { + alphaComponents.push_back(i); + } + } } - // TODO: Include alpha channel functionality - mask in image - if (valid) { + const size_t colorSpaceComponentCount = image.m_colorSpace->getColorComponentCount(); + const bool hasAlphaChannel = !alphaComponents.empty(); + + if (colorSpaceComponentCount < ordinaryComponents.size()) + { + // We have too much ordinary components + imageData.errors.push_back(PDFRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("JPEG 2000 image has too much non-alpha channels. Ignoring %1 channels.").arg(ordinaryComponents.size() - colorSpaceComponentCount))); + } + + if (alphaComponents.size() > 1) + { + // We support only one alpha channel component + imageData.errors.push_back(PDFRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("JPEG 2000 image has too much alpha channels. Ignoring %1 alpha channels.").arg(alphaComponents.size() - 1))); + } + const OPJ_UINT32 w = jpegImage->comps[0].w; const OPJ_UINT32 h = jpegImage->comps[0].h; const OPJ_UINT32 prec = jpegImage->comps[0].prec; @@ -501,7 +545,8 @@ PDFImage PDFImage::createImage(const PDFDocument* document, }; // Variables for image data. We convert all components to the 8-bit format - unsigned int components = jpegImage->numcomps; + const size_t ordinaryComponentCount = ordinaryComponents.size(); + unsigned int components = static_cast(qMin(ordinaryComponentCount, colorSpaceComponentCount)); unsigned int bitsPerComponent = 8; unsigned int width = w; unsigned int height = h; @@ -517,13 +562,40 @@ PDFImage PDFImage::createImage(const PDFDocument* document, int index = stride * row + col * components + componentIndex; Q_ASSERT(index < imageDataBuffer.size()); - imageDataBuffer[index] = transformValue(jpegImage->comps[componentIndex].data[w * row + col]); + imageDataBuffer[index] = transformValue(jpegImage->comps[ordinaryComponents[componentIndex]].data[w * row + col]); } } } image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask), qMove(decode), qMove(matte)); valid = image.m_imageData.isValid(); + + // Handle the alpha channel buffer - create soft mask. If SMaskInData equals to 1, then alpha channel is used. + // If SMaskInData equals to 2, then premultiplied alpha channel is used. + if (hasAlphaChannel && (sMaskInData == 1 || sMaskInData == 2)) + { + const int alphaStride = w; + QByteArray alphaDataBuffer(width * height, 0); + const OPJ_UINT32 alphaComponentIndex = alphaComponents.front(); + for (unsigned int row = 0; row < h; ++row) + { + for (unsigned int col = 0; col < w; ++col) + { + int index = alphaStride * row + col; + Q_ASSERT(index < alphaDataBuffer.size()); + + alphaDataBuffer[index] = transformValue(jpegImage->comps[alphaComponentIndex].data[w * row + col]); + } + } + + if (sMaskInData == 2) + { + matte.resize(ordinaryComponentCount, 0.0); + } + + image.m_softMask = PDFImageData(1, bitsPerComponent, width, height, alphaStride, PDFImageData::MaskingType::None, qMove(alphaDataBuffer), { }, { }, qMove(matte)); + image.m_imageData.setMaskingType(PDFImageData::MaskingType::SoftMask); + } } else {