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"