Skip to content

Commit

Permalink
cairo: Fix rendering of JPX images with transparency
Browse files Browse the repository at this point in the history
Handle transparent JPX images, they may contain RGBA data
when no ColorSpace pdf dict is defined or when SMaskInData
is non-zero.

PDF files posted in below issues are fixed by this commit:
https://gitlab.freedesktop.org/poppler/poppler/-/issues/1486
mozilla/pdf.js#16782
mozilla/pdf.js#11306
mozilla/pdf.js#17416

Inspired by related fix in pdf.js:
mozilla/pdf.js#18204

While working on this commit we also succesfully addressed
regressions that emerged for the following files:
mozilla/pdf.js#18896
https://bugs.freedesktop.org/attachment.cgi?id=49294

Issue #1486
  • Loading branch information
nbenitez authored and tsdgeos committed Nov 10, 2024
1 parent b6a783b commit 69da0db
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 19 deletions.
17 changes: 15 additions & 2 deletions poppler/CairoOutputDev.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3254,6 +3254,7 @@ void CairoOutputDev::setMimeData(GfxState *state, Stream *str, Object *ref, GfxI
case csDeviceGray:
case csCalGray:
case csDeviceRGB:
case csDeviceRGBA:
case csCalRGB:
case csDeviceCMYK:
case csICCBased:
Expand Down Expand Up @@ -3301,6 +3302,7 @@ class RescaleDrawImage : public CairoRescaleBox
const int *maskColors;
int current_row;
bool imageError;
bool fromRGBA;

public:
~RescaleDrawImage() override;
Expand All @@ -3315,6 +3317,7 @@ class RescaleDrawImage : public CairoRescaleBox
width = widthA;
current_row = -1;
imageError = false;
fromRGBA = colorMap->getColorSpace()->getMode() == csDeviceRGBA;

/* TODO: Do we want to cache these? */
imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
Expand Down Expand Up @@ -3371,7 +3374,8 @@ class RescaleDrawImage : public CairoRescaleBox
unsigned char *buffer;
ptrdiff_t stride;

image = cairo_image_surface_create(maskColors ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height);
image = cairo_image_surface_create(maskColors || fromRGBA ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height);

if (cairo_surface_status(image)) {
goto cleanup;
}
Expand All @@ -3390,7 +3394,7 @@ class RescaleDrawImage : public CairoRescaleBox
// to create an image the size of the source image which may
// exceed cairo's 32767x32767 image size limit (and also saves a
// lot of memory).
image = cairo_image_surface_create(maskColors ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, scaledWidth, scaledHeight);
image = cairo_image_surface_create(maskColors || fromRGBA ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, scaledWidth, scaledHeight);
if (cairo_surface_status(image)) {
goto cleanup;
}
Expand Down Expand Up @@ -3434,6 +3438,15 @@ class RescaleDrawImage : public CairoRescaleBox
row_data[i] = ((int)colToByte(rgb.r) << 16) | ((int)colToByte(rgb.g) << 8) | ((int)colToByte(rgb.b) << 0);
p++;
}
} else if (fromRGBA) {
// Case of transparent JPX images, they contain RGBA data · Issue #1486
GfxDeviceRGBAColorSpace *rgbaCS = dynamic_cast<GfxDeviceRGBAColorSpace *>(colorMap->getColorSpace());
if (rgbaCS) {
rgbaCS->getARGBPremultipliedLine(pix, row_data, width);
} else {
error(errSyntaxWarning, -1, "CairoOutputDev: Unexpected fallback from RGBA to RGB");
colorMap->getRGBLine(pix, row_data, width);
}
} else {
colorMap->getRGBLine(pix, row_data, width);
}
Expand Down
44 changes: 34 additions & 10 deletions poppler/Gfx.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4166,13 +4166,14 @@ void Gfx::doImage(Object *ref, Stream *str, bool inlineImg)
int maskWidth, maskHeight;
bool maskInvert;
bool maskInterpolate;
bool hasAlpha;
Stream *maskStr;
int i, n;

// get info from the stream
bits = 0;
csMode = streamCSNone;
str->getImageParams(&bits, &csMode);
str->getImageParams(&bits, &csMode, &hasAlpha);

// get stream dict
dict = str->getDict();
Expand Down Expand Up @@ -4308,6 +4309,21 @@ void Gfx::doImage(Object *ref, Stream *str, bool inlineImg)
if (obj1.isNull()) {
obj1 = dict->lookup("CS");
}
bool haveColorSpace = !obj1.isNull();
bool haveRGBA = false;
if (str->getKind() == strJPX && (csMode == streamCSDeviceRGB || csMode == streamCSDeviceCMYK)) {
// Case of transparent JPX image, they may contain RGBA data
// when have no ColorSpace or when SMaskInData=1 · Issue #1486
if (!haveColorSpace) {
haveRGBA = hasAlpha;
} else {
Object smaskInData = dict->lookup("SMaskInData");
if (smaskInData.isInt() && smaskInData.getInt()) {
haveRGBA = true;
}
}
}

if (obj1.isName() && inlineImg) {
Object obj2 = res->lookupColorSpace(obj1.getName());
if (!obj2.isNull()) {
Expand All @@ -4316,7 +4332,7 @@ void Gfx::doImage(Object *ref, Stream *str, bool inlineImg)
}
std::unique_ptr<GfxColorSpace> colorSpace;

if (!obj1.isNull()) {
if (!obj1.isNull() && !haveRGBA) {
char *tempIntent = nullptr;
Object objIntent = dict->lookup("Intent");
if (objIntent.isName()) {
Expand All @@ -4339,18 +4355,26 @@ void Gfx::doImage(Object *ref, Stream *str, bool inlineImg)
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
}
} else if (csMode == streamCSDeviceRGB) {
Object objCS = res->lookupColorSpace("DefaultRGB");
if (objCS.isNull()) {
colorSpace = std::make_unique<GfxDeviceRGBColorSpace>();
if (haveRGBA) {
colorSpace = std::make_unique<GfxDeviceRGBAColorSpace>();
} else {
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
Object objCS = res->lookupColorSpace("DefaultRGB");
if (objCS.isNull()) {
colorSpace = std::make_unique<GfxDeviceRGBColorSpace>();
} else {
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
}
}
} else if (csMode == streamCSDeviceCMYK) {
Object objCS = res->lookupColorSpace("DefaultCMYK");
if (objCS.isNull()) {
colorSpace = std::make_unique<GfxDeviceCMYKColorSpace>();
if (haveRGBA) {
colorSpace = std::make_unique<GfxDeviceRGBAColorSpace>();
} else {
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
Object objCS = res->lookupColorSpace("DefaultCMYK");
if (objCS.isNull()) {
colorSpace = std::make_unique<GfxDeviceCMYKColorSpace>();
} else {
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
}
}
}
if (!colorSpace) {
Expand Down
28 changes: 27 additions & 1 deletion poppler/GfxState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ static const GfxBlendModeInfo gfxBlendModeNames[] = { { "Normal", gfxBlendNormal
//
// NB: This must match the GfxColorSpaceMode enum defined in
// GfxState.h
static const char *gfxColorSpaceModeNames[] = { "DeviceGray", "CalGray", "DeviceRGB", "CalRGB", "DeviceCMYK", "Lab", "ICCBased", "Indexed", "Separation", "DeviceN", "Pattern" };
static const char *gfxColorSpaceModeNames[] = { "DeviceGray", "CalGray", "DeviceRGB", "CalRGB", "DeviceCMYK", "Lab", "ICCBased", "Indexed", "Separation", "DeviceN", "Pattern", "DeviceRGBA" };

#define nGfxColorSpaceModes ((sizeof(gfxColorSpaceModeNames) / sizeof(char *)))

Expand Down Expand Up @@ -1018,6 +1018,32 @@ void GfxDeviceRGBColorSpace::getDefaultColor(GfxColor *color) const
color->c[2] = 0;
}

//------------------------------------------------------------------------
// GfxDeviceRGBAColorSpace
//------------------------------------------------------------------------

GfxDeviceRGBAColorSpace::GfxDeviceRGBAColorSpace() { }

GfxDeviceRGBAColorSpace::~GfxDeviceRGBAColorSpace() { }

std::unique_ptr<GfxColorSpace> GfxDeviceRGBAColorSpace::copy() const
{
return std::make_unique<GfxDeviceRGBAColorSpace>();
}

void GfxDeviceRGBAColorSpace::getARGBPremultipliedLine(unsigned char *in, unsigned int *out, int length)
{
unsigned char *p;
int i;

// Conversion from 'in' RGBA to 'out' ARGB32_PREMULTIPLIED (used by Cairo)
for (i = 0, p = in; i < length; i++, p += 4) {
// This applies alpha component p[3] to each RGB values (using bitwise division)
// so final result in out[i] is ARGB32 with premultiplied alpha.
out[i] = p[3] << 24 | (p[0] * p[3] >> 8) << 16 | (p[1] * p[3] >> 8) << 8 | (p[2] * p[3] >> 8) << 0;
}
}

//------------------------------------------------------------------------
// GfxCalRGBColorSpace
//------------------------------------------------------------------------
Expand Down
23 changes: 22 additions & 1 deletion poppler/GfxState.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ enum GfxColorSpaceMode
csIndexed,
csSeparation,
csDeviceN,
csPattern
csPattern,
csDeviceRGBA // used for transparent JPX images, they contain RGBA data · Issue #1486
};

// This shall hold a cmsHPROFILE handle.
Expand Down Expand Up @@ -424,6 +425,26 @@ class POPPLER_PRIVATE_EXPORT GfxDeviceRGBColorSpace : public GfxColorSpace
private:
};

//------------------------------------------------------------------------
// GfxDeviceRGBAColorSpace
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxDeviceRGBAColorSpace : public GfxDeviceRGBColorSpace
{
public:
GfxDeviceRGBAColorSpace();
~GfxDeviceRGBAColorSpace() override;
std::unique_ptr<GfxColorSpace> copy() const override;
GfxColorSpaceMode getMode() const override { return csDeviceRGBA; }

int getNComps() const override { return 4; }

// GfxDeviceRGBAColorSpace-specific access
void getARGBPremultipliedLine(unsigned char *in, unsigned int *out, int length);

private:
};

//------------------------------------------------------------------------
// GfxCalRGBColorSpace
//------------------------------------------------------------------------
Expand Down
6 changes: 5 additions & 1 deletion poppler/JPEG2000Stream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,26 @@ bool JPXStream::isBinary(bool last) const
return str->isBinary(true);
}

void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode)
void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode, bool *hasAlpha)
{
if (unlikely(priv->inited == false)) {
init();
}

*bitsPerComponent = 8;
*hasAlpha = false;
int numComps = (priv->image) ? priv->image->numcomps : 1;
if (priv->image) {
if (priv->image->color_space == OPJ_CLRSPC_SRGB && numComps == 4) {
numComps = 3;
*hasAlpha = true;
} else if (priv->image->color_space == OPJ_CLRSPC_SYCC && numComps == 4) {
numComps = 3;
*hasAlpha = true;
} else if (numComps == 2) {
numComps = 1;
} else if (numComps > 4) {
*hasAlpha = true;
numComps = 4;
}
}
Expand Down
2 changes: 1 addition & 1 deletion poppler/JPEG2000Stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class JPXStream : public FilterStream
int lookChar() override;
GooString *getPSFilter(int psLevel, const char *indent) override;
bool isBinary(bool last = true) const override;
void getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode) override;
void getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode, bool *hasAlpha) override;

int readStream(int nChars, unsigned char *buffer) { return str->doGetChars(nChars, buffer); }

Expand Down
3 changes: 2 additions & 1 deletion poppler/JPXStream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ bool JPXStream::isBinary(bool last) const
return str->isBinary(true);
}

void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode)
void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode, bool *hasAlpha)
{
unsigned int boxType, boxLen, dataLen, csEnum;
unsigned int bpc1, dummy, i;
Expand All @@ -490,6 +490,7 @@ void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMo
bool haveBPC, haveCSMode;

csPrec = 0; // make gcc happy
*hasAlpha = false;
haveBPC = haveCSMode = false;
bufStr->reset();
if (bufStr->lookChar() == 0xff) {
Expand Down
2 changes: 1 addition & 1 deletion poppler/JPXStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ class JPXStream : public FilterStream
int lookChar() override;
GooString *getPSFilter(int psLevel, const char *indent) override;
bool isBinary(bool last = true) const override;
void getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode) override;
void getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode, bool *hasAlpha) override;

private:
void fillReadBuf();
Expand Down
1 change: 1 addition & 0 deletions poppler/PSOutputDev.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6757,6 +6757,7 @@ void PSOutputDev::dumpColorSpaceL2(GfxState *state, GfxColorSpace *colorSpace, b
break;

case csPattern:
case csDeviceRGBA:
//~ unimplemented
break;
}
Expand Down
2 changes: 1 addition & 1 deletion poppler/Stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ class POPPLER_PRIVATE_EXPORT Stream
virtual bool isEncoder() const { return false; }

// Get image parameters which are defined by the stream contents.
virtual void getImageParams(int * /*bitsPerComponent*/, StreamColorSpaceMode * /*csMode*/) { }
virtual void getImageParams(int * /*bitsPerComponent*/, StreamColorSpaceMode * /*csMode*/, bool * /*hasAlpha*/) { }

// Return the next stream in the "stack".
virtual Stream *getNextStream() const { return nullptr; }
Expand Down

0 comments on commit 69da0db

Please sign in to comment.