Add CMYK image output support to QPdfWriter

If the user gives us a CMYK image, save it as such.

[ChangeLog][QtGui][QPdfWriter] QPdfWriter can now save images in CMYK
format directly.

This work has been kindly sponsored by the QGIS project
(https://qgis.org/).

Change-Id: I4e5ba93970a545987e38d9a3b2e15b0d64bb72b6
Reviewed-by: Albert Astals Cid <aacid@kde.org>
Reviewed-by: Lars Knoll <lars@knoll.priv.no>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
bb10
Giuseppe D'Angelo 2023-10-24 02:27:14 +02:00
parent a3fae5faa0
commit 3c6598c359
2 changed files with 86 additions and 35 deletions

View File

@ -7,6 +7,7 @@
#include "qplatformdefs.h"
#include <private/qcmyk_p.h>
#include <private/qfont_p.h>
#include <private/qmath_p.h>
#include <private/qpainter_p.h>
@ -2419,7 +2420,7 @@ int QPdfEnginePrivate::writeCompressed(const char *src, int len)
return len;
}
int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth,
int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, WriteImageOption option,
int maskObject, int softMaskObject, bool dct, bool isMono)
{
int image = addXrefEntry(-1);
@ -2429,7 +2430,8 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
"/Width %d\n"
"/Height %d\n", width, height);
if (depth == 1) {
switch (option) {
case WriteImageOption::Monochrome:
if (!isMono) {
xprintf("/ImageMask true\n"
"/Decode [1 0]\n");
@ -2437,10 +2439,21 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
xprintf("/BitsPerComponent 1\n"
"/ColorSpace /DeviceGray\n");
}
} else {
break;
case WriteImageOption::Grayscale:
xprintf("/BitsPerComponent 8\n"
"/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray");
"/ColorSpace /DeviceGray\n");
break;
case WriteImageOption::RGB:
xprintf("/BitsPerComponent 8\n"
"/ColorSpace /DeviceRGB\n");
break;
case WriteImageOption::CMYK:
xprintf("/BitsPerComponent 8\n"
"/ColorSpace /DeviceCMYK\n");
break;
}
if (maskObject > 0)
xprintf("/Mask %d 0 R\n", maskObject);
if (softMaskObject > 0)
@ -3042,7 +3055,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
format = QImage::Format_Mono;
} else {
*bitmap = false;
if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) {
if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_CMYK32) {
image = image.convertToFormat(QImage::Format_ARGB32);
format = QImage::Format_ARGB32;
}
@ -3050,7 +3063,6 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
int w = image.width();
int h = image.height();
int d = image.depth();
if (format == QImage::Format_Mono) {
int bytesPerLine = (w + 7) >> 3;
@ -3061,7 +3073,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
memcpy(rawdata, image.constScanLine(y), bytesPerLine);
rawdata += bytesPerLine;
}
object = writeImage(data, w, h, d, 0, 0, false, is_monochrome(img.colorTable()));
object = writeImage(data, w, h, WriteImageOption::Monochrome, 0, 0, false, is_monochrome(img.colorTable()));
} else {
QByteArray softMaskData;
bool dct = false;
@ -3073,10 +3085,14 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
QBuffer buffer(&imageData);
QImageWriter writer(&buffer, "jpeg");
writer.setQuality(94);
if (format == QImage::Format_CMYK32) {
// PDFs require CMYK colors not to be inverted in the JPEG encoding
writer.setSubType("CMYK");
}
writer.write(image);
dct = true;
if (format != QImage::Format_RGB32) {
if (format != QImage::Format_RGB32 && format != QImage::Format_CMYK32) {
softMaskData.resize(w * h);
uchar *sdata = (uchar *)softMaskData.data();
for (int y = 0; y < h; ++y) {
@ -3091,41 +3107,59 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
}
} else {
imageData.resize(grayscale ? w * h : 3 * w * h);
uchar *data = (uchar *)imageData.data();
softMaskData.resize(w * h);
uchar *sdata = (uchar *)softMaskData.data();
for (int y = 0; y < h; ++y) {
const QRgb *rgb = (const QRgb *)image.constScanLine(y);
if (format == QImage::Format_CMYK32) {
imageData.resize(grayscale ? w * h : w * h * 4);
uchar *data = (uchar *)imageData.data();
const qsizetype bytesPerLine = image.bytesPerLine();
if (grayscale) {
for (int x = 0; x < w; ++x) {
*(data++) = qGray(*rgb);
uchar alpha = qAlpha(*rgb);
*sdata++ = alpha;
hasMask |= (alpha < 255);
hasAlpha |= (alpha != 0 && alpha != 255);
++rgb;
for (int y = 0; y < h; ++y) {
const uint *cmyk = (const uint *)image.constScanLine(y);
for (int x = 0; x < w; ++x)
*data++ = qGray(QCmyk32::fromCmyk32(*cmyk++).toColor().rgba());
}
} else {
for (int x = 0; x < w; ++x) {
*(data++) = qRed(*rgb);
*(data++) = qGreen(*rgb);
*(data++) = qBlue(*rgb);
uchar alpha = qAlpha(*rgb);
*sdata++ = alpha;
hasMask |= (alpha < 255);
hasAlpha |= (alpha != 0 && alpha != 255);
++rgb;
for (int y = 0; y < h; ++y) {
uchar *start = data + y * w * 4;
memcpy(start, image.constScanLine(y), bytesPerLine);
}
}
} else {
imageData.resize(grayscale ? w * h : 3 * w * h);
uchar *data = (uchar *)imageData.data();
softMaskData.resize(w * h);
uchar *sdata = (uchar *)softMaskData.data();
for (int y = 0; y < h; ++y) {
const QRgb *rgb = (const QRgb *)image.constScanLine(y);
if (grayscale) {
for (int x = 0; x < w; ++x) {
*(data++) = qGray(*rgb);
uchar alpha = qAlpha(*rgb);
*sdata++ = alpha;
hasMask |= (alpha < 255);
hasAlpha |= (alpha != 0 && alpha != 255);
++rgb;
}
} else {
for (int x = 0; x < w; ++x) {
*(data++) = qRed(*rgb);
*(data++) = qGreen(*rgb);
*(data++) = qBlue(*rgb);
uchar alpha = qAlpha(*rgb);
*sdata++ = alpha;
hasMask |= (alpha < 255);
hasAlpha |= (alpha != 0 && alpha != 255);
++rgb;
}
}
}
}
if (format == QImage::Format_RGB32)
if (format == QImage::Format_RGB32 || format == QImage::Format_CMYK32)
hasAlpha = hasMask = false;
}
int maskObject = 0;
int softMaskObject = 0;
if (hasAlpha) {
softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0);
softMaskObject = writeImage(softMaskData, w, h, WriteImageOption::Grayscale, 0, 0);
} else if (hasMask) {
// dither the soft mask to 1bit and add it. This also helps PDF viewers
// without transparency support
@ -3141,9 +3175,18 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
mdata += bytesPerLine;
}
maskObject = writeImage(mask, w, h, 1, 0, 0);
maskObject = writeImage(mask, w, h, WriteImageOption::Monochrome, 0, 0);
}
object = writeImage(imageData, w, h, grayscale ? 8 : 32,
const WriteImageOption option = [&]() {
if (grayscale)
return WriteImageOption::Grayscale;
if (format == QImage::Format_CMYK32)
return WriteImageOption::CMYK;
return WriteImageOption::RGB;
}();
object = writeImage(imageData, w, h, option,
maskObject, softMaskObject, dct);
}
imageCache.insert(serial_no, object);

View File

@ -304,7 +304,15 @@ private:
QDataStream* stream;
int streampos;
int writeImage(const QByteArray &data, int width, int height, int depth,
enum class WriteImageOption
{
Monochrome,
Grayscale,
RGB,
CMYK,
};
int writeImage(const QByteArray &data, int width, int height, WriteImageOption option,
int maskObject, int softMaskObject, bool dct = false, bool isMono = false);
void writePage();