mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-03-10 00:10:10 +01:00
Soft masks for JPEG 2000
This commit is contained in:
parent
ac644c16a4
commit
990c49046a
@ -141,10 +141,6 @@ public:
|
|||||||
/// Returns number of color channels
|
/// Returns number of color channels
|
||||||
unsigned int getColorChannels() const { return m_components; }
|
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; }
|
bool isValid() const { return m_width && m_height && m_components && m_bitsPerComponent; }
|
||||||
|
|
||||||
const unsigned char* getRow(unsigned int rowIndex) const;
|
const unsigned char* getRow(unsigned int rowIndex) const;
|
||||||
|
@ -66,21 +66,12 @@ PDFImage PDFImage::createImage(const PDFDocument* document,
|
|||||||
throw PDFException(PDFTranslationContext::tr("Image has not data."));
|
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;
|
PDFImageData::MaskingType maskingType = PDFImageData::MaskingType::None;
|
||||||
std::vector<PDFInteger> mask;
|
std::vector<PDFInteger> mask;
|
||||||
std::vector<PDFReal> decode = loader.readNumberArrayFromDictionary(dictionary, "Decode");
|
std::vector<PDFReal> decode = loader.readNumberArrayFromDictionary(dictionary, "Decode");
|
||||||
bool imageMask = loader.readBooleanFromDictionary(dictionary, "ImageMask", false);
|
bool imageMask = loader.readBooleanFromDictionary(dictionary, "ImageMask", false);
|
||||||
std::vector<PDFReal> matte = loader.readNumberArrayFromDictionary(dictionary, "Matte");
|
std::vector<PDFReal> matte = loader.readNumberArrayFromDictionary(dictionary, "Matte");
|
||||||
|
PDFInteger sMaskInData = loader.readIntegerFromDictionary(dictionary, "SMaskInData", 0);
|
||||||
|
|
||||||
if (isSoftMask && (imageMask || dictionary->hasKey("Mask") || dictionary->hasKey("SMask")))
|
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 we have a valid image, then adjust it
|
||||||
if (jpegImage)
|
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)
|
// First we must check, if all components are valid (i.e has same width/height/precision)
|
||||||
|
|
||||||
|
std::vector<OPJ_UINT32> ordinaryComponents;
|
||||||
|
std::vector<OPJ_UINT32> alphaComponents;
|
||||||
|
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
const OPJ_UINT32 componentCount = jpegImage->numcomps;
|
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 ||
|
if (jpegImage->comps[0].w != jpegImage->comps[i].w ||
|
||||||
jpegImage->comps[0].h != jpegImage->comps[i].h ||
|
jpegImage->comps[0].h != jpegImage->comps[i].h ||
|
||||||
@ -461,12 +480,37 @@ PDFImage PDFImage::createImage(const PDFDocument* document,
|
|||||||
valid = false;
|
valid = false;
|
||||||
break;
|
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)
|
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 w = jpegImage->comps[0].w;
|
||||||
const OPJ_UINT32 h = jpegImage->comps[0].h;
|
const OPJ_UINT32 h = jpegImage->comps[0].h;
|
||||||
const OPJ_UINT32 prec = jpegImage->comps[0].prec;
|
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
|
// 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<unsigned int>(qMin(ordinaryComponentCount, colorSpaceComponentCount));
|
||||||
unsigned int bitsPerComponent = 8;
|
unsigned int bitsPerComponent = 8;
|
||||||
unsigned int width = w;
|
unsigned int width = w;
|
||||||
unsigned int height = h;
|
unsigned int height = h;
|
||||||
@ -517,13 +562,40 @@ PDFImage PDFImage::createImage(const PDFDocument* document,
|
|||||||
int index = stride * row + col * components + componentIndex;
|
int index = stride * row + col * components + componentIndex;
|
||||||
Q_ASSERT(index < imageDataBuffer.size());
|
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));
|
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask), qMove(decode), qMove(matte));
|
||||||
valid = image.m_imageData.isValid();
|
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
|
else
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user