diff --git a/src/generic/genericpdfextractor.cpp b/src/generic/genericpdfextractor.cpp index 31c546c..3af68dc 100644 --- a/src/generic/genericpdfextractor.cpp +++ b/src/generic/genericpdfextractor.cpp @@ -1,151 +1,151 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "genericpdfextractor_p.h" #include "genericuic918extractor_p.h" #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; enum { MaxPageCount = 10, // maximum in the current test set is 6 MaxFileSize = 4000000, // maximum in the current test set is 980kB // unit is 1/72 inch, assuming landscape orientation MinTargetImageHeight = 28, MinTargetImageWidth = 36, MaxTargetImageHeight = 252, MaxTargetImageWidth = 252, }; GenericPdfExtractor::GenericPdfExtractor() = default; GenericPdfExtractor::~GenericPdfExtractor() = default; void GenericPdfExtractor::setBarcodeDecoder(BarcodeDecoder *decoder) { m_barcodeDecoder = decoder; } void GenericPdfExtractor::setContextDate(const QDateTime &dt) { m_contextDate = dt; } std::vector GenericPdfExtractor::extract(PdfDocument *doc) { std::vector result; // stay away from documents that are atypically large for what we are looking for // that's just unnecessarily eating up resources if (doc->pageCount() > MaxPageCount || doc->fileSize() > MaxFileSize) { return result; } m_imageIds.clear(); for (int i = 0; i < doc->pageCount(); ++i) { const auto page = doc->page(i); for (int j = 0; j < page.imageCount(); ++j) { auto img = page.image(j); - img.setLoadingHints(PdfImage::AbortOnColorHint); // we only care about b/w-ish images for barcode detection + img.setLoadingHints(PdfImage::AbortOnColorHint | PdfImage::ConvertToGrayscaleHint); // we only care about b/w-ish images for barcode detection if (img.hasObjectId() && m_imageIds.find(img.objectId()) != m_imageIds.end()) { continue; } if (!maybeBarcode(img)) { continue; } auto r = extractImage(img); if (!r.barcode.isNull() || !r.result.isEmpty()) { r.pageNum = i; result.push_back(r); } if (img.hasObjectId()) { m_imageIds.insert(img.objectId()); } } } return result; } GenericExtractor::Result GenericPdfExtractor::extractImage(const PdfImage &img) { const auto imgData = img.image(); if (imgData.isNull()) { // can happen due to AbortOnColorHint return {}; } const auto b = m_barcodeDecoder->decodeBinary(imgData); if (Uic9183Parser::maybeUic9183(b)) { QJsonArray result; GenericUic918Extractor::extract(b, result, m_contextDate); if (!result.isEmpty()) { return GenericExtractor::Result{result, b, -1}; } return {}; } if (b.isEmpty()) { return extractBarcode(m_barcodeDecoder->decodeString(imgData)); } else { return extractBarcode(QString::fromUtf8(b)); } } GenericExtractor::Result GenericPdfExtractor::extractBarcode(const QString &code) { if (code.isEmpty()) { return {}; } if (IataBcbpParser::maybeIataBcbp(code)) { const auto res = IataBcbpParser::parse(code, m_contextDate.date()); const auto jsonLd = JsonLdDocument::toJson(res); return {jsonLd, code, -1}; } return {{}, code, -1}; } bool GenericPdfExtractor::maybeBarcode(const PdfImage &img, BarcodeDecoder::BarcodeTypes hint) { const auto w = img.width(); const auto h = img.height(); if (!BarcodeDecoder::isPlausibleSize(img.sourceWidth(), img.sourceHeight()) || !BarcodeDecoder::isPlausibleAspectRatio(w, h, hint)) { return false; } // image target size checks if (std::min(w, h) < MinTargetImageHeight || std::max(w, h) < MinTargetImageWidth || h > MaxTargetImageHeight || w > MaxTargetImageWidth) { return false; } return true; } diff --git a/src/pdf/pdfimage.cpp b/src/pdf/pdfimage.cpp index c68a16a..5512506 100644 --- a/src/pdf/pdfimage.cpp +++ b/src/pdf/pdfimage.cpp @@ -1,231 +1,235 @@ /* Copyright (C) 2019 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "pdfimage.h" #include "pdfimage_p.h" #include "pdfdocument_p.h" #include "popplerutils_p.h" #include #include #ifdef HAVE_POPPLER #include #include #include #include #include #endif using namespace KItinerary; #ifdef HAVE_POPPLER // legacy image loading #ifndef HAVE_POPPLER_0_69 namespace KItinerary { class ImageLoaderOutputDevice : public OutputDev { public: ImageLoaderOutputDevice(PdfImagePrivate *dd); bool interpretType3Chars() override { return false; } bool needNonText() override { return true; } bool upsideDown() override { return false; } bool useDrawChar() override { return false; } void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, int *maskColors, bool inlineImg) override; QImage image() const { return m_image; } private: PdfImagePrivate *d; QImage m_image; }; ImageLoaderOutputDevice::ImageLoaderOutputDevice(PdfImagePrivate* dd) : d(dd) { } void ImageLoaderOutputDevice::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, int *maskColors, bool inlineImg) { Q_UNUSED(state); Q_UNUSED(height); Q_UNUSED(width); Q_UNUSED(interpolate); Q_UNUSED(maskColors); Q_UNUSED(inlineImg); if (!colorMap || !colorMap->isOk() || !ref) { return; } if (ref->isRef() && d->m_refNum != ref->getRef().num) { return; } m_image = d->load(str, colorMap); } } #endif static inline bool isColor(GfxRGB rgb) { enum { Threshold = 72 * 256 }; // GfxComp is stored as color value * 255 // barcode images for SNCF and Renfe for example are anti-aliased, so we cannot simply filter for black or white // KLM/AF use tinted barcodes, so checking for R = G = B doesn't help either return std::abs(rgb.r - rgb.g) > Threshold || std::abs(rgb.r - rgb.b) > Threshold || std::abs(rgb.g - rgb.b) > Threshold; } QImage PdfImagePrivate::load(Stream* str, GfxImageColorMap* colorMap) { - auto img = QImage(m_sourceWidth, m_sourceHeight, m_format); + auto img = QImage(m_sourceWidth, m_sourceHeight, (m_loadingHints & PdfImage::ConvertToGrayscaleHint) ? QImage::Format_Grayscale8 : m_format); const auto bytesPerPixel = colorMap->getNumPixelComps(); std::unique_ptr imgStream(new ImageStream(str, m_sourceWidth, bytesPerPixel, colorMap->getBits())); imgStream->reset(); switch (m_format) { case QImage::Format_RGB888: for (int i = 0; i < m_sourceHeight; ++i) { const auto row = imgStream->getLine(); auto imgData = img.scanLine(i); GfxRGB rgb; for (int j = 0; j < m_sourceWidth; ++j) { colorMap->getRGB(row + (j * bytesPerPixel), &rgb); if ((m_loadingHints & PdfImage::AbortOnColorHint) && isColor(rgb)) { return {}; } - *imgData++ = colToByte(rgb.r); - *imgData++ = colToByte(rgb.g); - *imgData++ = colToByte(rgb.b); + if ((m_loadingHints & PdfImage::ConvertToGrayscaleHint)) { + *imgData++ = colToByte(rgb.g); // technically not correct but good enough + } else { + *imgData++ = colToByte(rgb.r); + *imgData++ = colToByte(rgb.g); + *imgData++ = colToByte(rgb.b); + } } } break; case QImage::Format_Grayscale8: for (int i = 0; i < m_sourceHeight; ++i) { const auto row = imgStream->getLine(); auto imgData = img.scanLine(i); GfxGray gray; for (int j = 0; j < m_sourceWidth; ++j) { colorMap->getGray(row + j, &gray); *imgData++ = colToByte(gray); } } break; default: break; } imgStream->close(); m_page->m_doc->m_imageData[m_refNum] = img; return img; } #endif QImage PdfImagePrivate::load() { const auto it = m_page->m_doc->m_imageData.find(m_refNum); if (it != m_page->m_doc->m_imageData.end()) { return (*it).second; } #ifdef HAVE_POPPLER QScopedValueRollback globalParamResetter(globalParams, PopplerUtils::globalParams()); #ifdef HAVE_POPPLER_0_69 const auto xref = m_page->m_doc->m_popplerDoc->getXRef(); const auto obj = xref->fetch(m_refNum, m_refGen); return load(obj.getStream(), m_colorMap.get()); #else std::unique_ptr device(new ImageLoaderOutputDevice(this)); m_page->m_doc->m_popplerDoc->displayPageSlice(device.get(), m_page->m_pageNum + 1, 72, 72, 0, false, true, false, -1, -1, -1, -1); return device->image(); #endif #else return {}; #endif } PdfImage::PdfImage() : d(new PdfImagePrivate) { } PdfImage::PdfImage(const PdfImage&) = default; PdfImage::~PdfImage() = default; PdfImage& PdfImage::operator=(const PdfImage&) = default; int PdfImage::height() const { if (d->m_format == QImage::Format_Invalid) { return d->m_height; } return d->m_transform.map(QRectF(0, 0, 1, -1)).boundingRect().height(); } int PdfImage::width() const { if (d->m_format == QImage::Format_Invalid) { return d->m_width; } return d->m_transform.map(QRectF(0, 0, 1, -1)).boundingRect().width(); } int PdfImage::sourceHeight() const { return d->m_sourceHeight; } int PdfImage::sourceWidth() const { return d->m_sourceWidth; } QTransform PdfImage::transform() const { return d->m_transform; } void PdfImage::setLoadingHints(LoadingHints hints) { d->m_loadingHints = hints; } QImage PdfImage::image() const { if (d->m_format == QImage::Format_Invalid) { return d->m_vectorPicture.renderToImage(); } const auto img = d->load(); if (d->m_width != d->m_sourceWidth || d->m_height != d->m_sourceHeight) { return img.scaled(d->m_width, d->m_height); } return img; } bool PdfImage::hasObjectId() const { return d->m_refNum >= 0; } int PdfImage::objectId() const { return d->m_refNum; } diff --git a/src/pdf/pdfimage.h b/src/pdf/pdfimage.h index 729278d..60cae77 100644 --- a/src/pdf/pdfimage.h +++ b/src/pdf/pdfimage.h @@ -1,97 +1,98 @@ /* Copyright (C) 2019 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KITINERARY_PDFIMAGE_H #define KITINERARY_PDFIMAGE_H #include "kitinerary_export.h" #include #include class QImage; class QTransform; namespace KItinerary { class PdfImagePrivate; /** An image in a PDF document. */ class KITINERARY_EXPORT PdfImage { Q_GADGET Q_PROPERTY(int width READ width) Q_PROPERTY(int height READ height) public: PdfImage(); PdfImage(const PdfImage&); ~PdfImage(); PdfImage& operator=(const PdfImage&); /** Width of the image in PDF 1/72 dpi coordinates. */ int width() const; /** Height of the image in PDF 1/72 dpi coordinates. */ int height() const; /** Height of the source image. */ int sourceHeight() const; /** Width of the source image. */ int sourceWidth() const; /** Transformation from source image to final size/position on the page. * Values are 1/72 inch. */ QTransform transform() const; /** Hints for loading image data. */ enum LoadingHint { NoHint = 0, ///< Load image data as-is. The default. AbortOnColorHint = 1, ///< Abort loading when encountering a non black/white pixel, as a shortcut for barcode detection. + ConvertToGrayscaleHint = 2, ///< Convert to QImage::Format_Grayscale8 during loading. More efficient than converting later if all you need is grayscale. }; Q_DECLARE_FLAGS(LoadingHints, LoadingHint) /** Sets image loading hints. */ void setLoadingHints(LoadingHints hints); /** The source image with display transformations applied. */ QImage image() const; /** Returns whether this image has an object id. * Vector graphic "images" don't have that. */ bool hasObjectId() const; /** PDF-internal unique identifier of this image. * Use this to detect multiple occurrences of the same image in different * places, if that reduces e.g. computation cost. */ int objectId() const; private: friend class PdfExtractorOutputDevice; friend class PdfPagePrivate; friend class PdfPage; QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::PdfImage) Q_DECLARE_OPERATORS_FOR_FLAGS(KItinerary::PdfImage::LoadingHints) #endif // KITINERARY_PDFIMAGE_H