File indexing completed on 2024-12-29 04:50:59

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "pdfimage.h"
0008 #include "pdfimage_p.h"
0009 #include "pdfdocument_p.h"
0010 #include "popplerglobalparams_p.h"
0011 #include "popplerutils_p.h"
0012 
0013 #include <QDebug>
0014 #include <QScopedValueRollback>
0015 
0016 #include <Gfx.h>
0017 #include <GlobalParams.h>
0018 #include <PDFDoc.h>
0019 #include <Stream.h>
0020 #include <OutputDev.h>
0021 
0022 using namespace KItinerary;
0023 
0024 // legacy image loading
0025 #if KPOPPLER_VERSION < QT_VERSION_CHECK(0, 69, 0)
0026 namespace KItinerary {
0027 class ImageLoaderOutputDevice : public OutputDev
0028 {
0029 public:
0030     ImageLoaderOutputDevice(PdfImagePrivate *dd);
0031 
0032     bool interpretType3Chars() override { return false; }
0033     bool needNonText() override { return true; }
0034     bool upsideDown() override { return false; }
0035     bool useDrawChar() override { return false; }
0036 
0037     void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, int *maskColors, bool inlineImg) override;
0038     QImage image() const { return m_image; }
0039 
0040 private:
0041     PdfImagePrivate *d;
0042     QImage m_image;
0043 };
0044 
0045 ImageLoaderOutputDevice::ImageLoaderOutputDevice(PdfImagePrivate* dd)
0046     : d(dd)
0047 {
0048 }
0049 
0050 void ImageLoaderOutputDevice::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, int *maskColors, bool inlineImg)
0051 {
0052     Q_UNUSED(state)
0053     Q_UNUSED(height)
0054     Q_UNUSED(width)
0055     Q_UNUSED(interpolate)
0056     Q_UNUSED(maskColors)
0057     Q_UNUSED(inlineImg)
0058 
0059     if (!colorMap || !colorMap->isOk() || !ref) {
0060         return;
0061     }
0062 
0063     if (ref->isRef() && d->refNum() != ref->getRef().num) {
0064         return;
0065     }
0066 
0067     m_image = d->load(str, colorMap);
0068 }
0069 }
0070 #endif
0071 
0072 static inline bool isColor(GfxRGB rgb)
0073 {
0074     enum { Threshold = 72 * 256 }; // GfxComp is stored as color value * 255
0075 
0076     // barcode images for SNCF and Renfe for example are anti-aliased, so we cannot simply filter for black or white
0077     // KLM/AF use tinted barcodes, so checking for R = G = B doesn't help either
0078     return std::abs(rgb.r - rgb.g) > Threshold || std::abs(rgb.r - rgb.b) > Threshold || std::abs(rgb.g - rgb.b) > Threshold;
0079 }
0080 
0081 QImage PdfImagePrivate::load(Stream* str, GfxImageColorMap* colorMap)
0082 {
0083     if (m_format == QImage::Format_Mono) { // bitmasks are not stored as image streams
0084         auto img = QImage(m_sourceWidth, m_sourceHeight, QImage::Format_Mono); // TODO implicit Format_Grayscale8 conversion
0085         str->reset();
0086         const int rowSize = (m_sourceWidth + 7) / 8;
0087         for (int y = 0; y < m_sourceHeight; ++y) {
0088             auto imgData = img.scanLine(y);
0089             for (int x = 0; x < rowSize; x++) {
0090                 const auto c = str->getChar();
0091                 *imgData++ = c ^ 0xff;
0092             }
0093         }
0094 
0095         if (!m_ref.isNull()) {
0096             m_page->m_doc->m_imageData[m_ref] = img;
0097         } else {
0098             m_inlineImageData = img;
0099         }
0100         return img;
0101     }
0102 
0103     auto img = QImage(m_sourceWidth, m_sourceHeight, (m_loadingHints & PdfImage::ConvertToGrayscaleHint) ? QImage::Format_Grayscale8 : m_format);
0104     const auto bytesPerPixel = colorMap->getNumPixelComps();
0105     std::unique_ptr<ImageStream> imgStream(new ImageStream(str, m_sourceWidth, bytesPerPixel, colorMap->getBits()));
0106     imgStream->reset();
0107 
0108     switch (m_format) {
0109         case QImage::Format_RGB888:
0110             for (int i = 0; i < m_sourceHeight; ++i) {
0111                 const auto row = imgStream->getLine();
0112                 auto imgData = img.scanLine(i);
0113                 GfxRGB rgb;
0114                 for (int j = 0; j < m_sourceWidth; ++j) {
0115                     colorMap->getRGB(row + (j * bytesPerPixel), &rgb);
0116                     if ((m_loadingHints & PdfImage::AbortOnColorHint) && isColor(rgb)) {
0117                         return {};
0118                     }
0119                     if ((m_loadingHints & PdfImage::ConvertToGrayscaleHint)) {
0120                         *imgData++ = colToByte(rgb.g); // technically not correct but good enough
0121                     } else {
0122                         *imgData++ = colToByte(rgb.r);
0123                         *imgData++ = colToByte(rgb.g);
0124                         *imgData++ = colToByte(rgb.b);
0125                     }
0126                 }
0127             }
0128             break;
0129         case QImage::Format_Grayscale8:
0130             for (int i = 0; i < m_sourceHeight; ++i) {
0131                 const auto row = imgStream->getLine();
0132                 auto imgData = img.scanLine(i);
0133                 GfxGray gray;
0134                 for (int j = 0; j < m_sourceWidth; ++j) {
0135                     colorMap->getGray(row + j, &gray);
0136                     *imgData++ = colToByte(gray);
0137                 }
0138             }
0139             break;
0140         default:
0141             break;
0142     }
0143     imgStream->close();
0144 
0145     if (!m_ref.isNull()) {
0146         m_page->m_doc->m_imageData[m_ref] = img;
0147     } else {
0148         m_inlineImageData = img;
0149     }
0150     return img;
0151 }
0152 
0153 QImage PdfImagePrivate::load()
0154 {
0155     const auto it = m_page->m_doc->m_imageData.find(m_ref);
0156     if (it != m_page->m_doc->m_imageData.end()) {
0157         return (*it).second;
0158     }
0159 
0160     PopplerGlobalParams gp;
0161 
0162 #if KPOPPLER_VERSION >= QT_VERSION_CHECK(0, 69, 0)
0163     const auto xref = m_page->m_doc->m_popplerDoc->getXRef();
0164     const auto obj = xref->fetch(refNum(), refGen());
0165 
0166     switch (m_ref.m_type) {
0167         case PdfImageType::Image:
0168             return load(obj.getStream(), m_colorMap.get());
0169         case PdfImageType::Mask:
0170         {
0171             const auto dict = obj.getStream()->getDict();
0172             const auto maskObj = dict->lookup("Mask");
0173             return load(maskObj.getStream(), m_colorMap.get());
0174         }
0175         case PdfImageType::SMask:
0176             return {}; // TODO
0177     }
0178 
0179     return {};
0180 #else
0181     if (m_ref.m_type != PdfImageType::Image) {
0182         return {};
0183     }
0184 
0185     std::unique_ptr<ImageLoaderOutputDevice> device(new ImageLoaderOutputDevice(this));
0186     m_page->m_doc->m_popplerDoc->displayPageSlice(device.get(), m_page->m_pageNum + 1, 72, 72, 0, false, true, false, -1, -1, -1, -1);
0187     return device->image();
0188 #endif
0189 }
0190 
0191 
0192 PdfImage::PdfImage()
0193     : d(new PdfImagePrivate)
0194 {
0195 }
0196 
0197 PdfImage::PdfImage(const PdfImage&) = default;
0198 PdfImage::~PdfImage() = default;
0199 PdfImage& PdfImage::operator=(const PdfImage&) = default;
0200 
0201 int PdfImage::height() const
0202 {
0203     if (d->m_format == QImage::Format_Invalid) {
0204         return d->m_height;
0205     }
0206     return d->m_transform.map(QRectF(0, 0, 1, -1)).boundingRect().height();
0207 }
0208 
0209 int PdfImage::width() const
0210 {
0211     if (d->m_format == QImage::Format_Invalid) {
0212         return d->m_width;
0213     }
0214     return d->m_transform.map(QRectF(0, 0, 1, -1)).boundingRect().width();
0215 }
0216 
0217 int PdfImage::sourceHeight() const
0218 {
0219     return d->m_sourceHeight;
0220 }
0221 
0222 int PdfImage::sourceWidth() const
0223 {
0224     return d->m_sourceWidth;
0225 }
0226 
0227 QTransform PdfImage::transform() const
0228 {
0229     return d->m_transform;
0230 }
0231 
0232 void PdfImage::setLoadingHints(LoadingHints hints)
0233 {
0234     d->m_loadingHints = hints;
0235 }
0236 
0237 QImage PdfImage::image() const
0238 {
0239     if (!d->m_inlineImageData.isNull()) {
0240         return d->m_inlineImageData;
0241     }
0242     if (d->m_format == QImage::Format_Invalid) {
0243         return d->m_vectorPicture.renderToImage();
0244     }
0245     return d->load();
0246 }
0247 
0248 bool PdfImage::hasObjectId() const
0249 {
0250     return !d->m_ref.isNull();
0251 }
0252 
0253 PdfImageRef PdfImage::objectId() const
0254 {
0255     return d->m_ref;
0256 }
0257 
0258 bool PdfImage::isVectorImage() const
0259 {
0260     return !d->m_vectorPicture.isNull();
0261 }
0262 
0263 int PdfImage::pathElementsCount() const
0264 {
0265     return d->m_vectorPicture.pathElementsCount();
0266 }
0267 
0268 bool PdfImage::hasAspectRatioTransform() const
0269 {
0270     return d->m_format != QImage::Format_Invalid && (d->m_width != d->m_sourceWidth || d->m_height != d->m_sourceHeight);
0271 }
0272 
0273 QImage PdfImage::applyAspectRatioTransform(const QImage &image) const
0274 {
0275     return image.scaled(d->m_width, d->m_height);
0276 }
0277 
0278 #include "moc_pdfimage.cpp"