pdfimage.cpp 6.16 KB
Newer Older
Volker Krause's avatar
Volker Krause committed
1
/*
2
    SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
Volker Krause's avatar
Volker Krause committed
3

4
    SPDX-License-Identifier: LGPL-2.0-or-later
Volker Krause's avatar
Volker Krause committed
5
6
7
8
9
*/

#include "pdfimage.h"
#include "pdfimage_p.h"
#include "pdfdocument_p.h"
10
#include "popplerglobalparams_p.h"
Volker Krause's avatar
Volker Krause committed
11
12
#include "popplerutils_p.h"

13
#include <QDebug>
Volker Krause's avatar
Volker Krause committed
14
15
16
17
18
19
20
#include <QScopedValueRollback>

#ifdef HAVE_POPPLER
#include <Gfx.h>
#include <GlobalParams.h>
#include <PDFDoc.h>
#include <Stream.h>
21
#include <OutputDev.h>
Volker Krause's avatar
Volker Krause committed
22
23
24
25
26
#endif

using namespace KItinerary;

#ifdef HAVE_POPPLER
27
// legacy image loading
28
#if KPOPPLER_VERSION < QT_VERSION_CHECK(0, 69, 0)
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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)
{
Laurent Montel's avatar
Laurent Montel committed
55
56
57
58
59
60
    Q_UNUSED(state)
    Q_UNUSED(height)
    Q_UNUSED(width)
    Q_UNUSED(interpolate)
    Q_UNUSED(maskColors)
    Q_UNUSED(inlineImg)
61
62
63
64
65
66
67
68
69
70
71
72
73
74

    if (!colorMap || !colorMap->isOk() || !ref) {
        return;
    }

    if (ref->isRef() && d->m_refNum != ref->getRef().num) {
        return;
    }

    m_image = d->load(str, colorMap);
}
}
#endif

75
76
77
78
79
80
81
82
83
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;
}

Volker Krause's avatar
Volker Krause committed
84
85
QImage PdfImagePrivate::load(Stream* str, GfxImageColorMap* colorMap)
{
86
    auto img = QImage(m_sourceWidth, m_sourceHeight, (m_loadingHints & PdfImage::ConvertToGrayscaleHint) ? QImage::Format_Grayscale8 : m_format);
Volker Krause's avatar
Volker Krause committed
87
88
89
90
91
92
93
94
95
96
97
98
    const auto bytesPerPixel = colorMap->getNumPixelComps();
    std::unique_ptr<ImageStream> 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);
99
100
101
                    if ((m_loadingHints & PdfImage::AbortOnColorHint) && isColor(rgb)) {
                        return {};
                    }
102
103
104
105
106
107
108
                    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);
                    }
Volker Krause's avatar
Volker Krause committed
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
                }
            }
            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;
}
131
#endif
Volker Krause's avatar
Volker Krause committed
132

133
QImage PdfImagePrivate::load()
Volker Krause's avatar
Volker Krause committed
134
{
135
136
137
    const auto it = m_page->m_doc->m_imageData.find(m_refNum);
    if (it != m_page->m_doc->m_imageData.end()) {
        return (*it).second;
Volker Krause's avatar
Volker Krause committed
138
139
    }

140
#ifdef HAVE_POPPLER
141
    PopplerGlobalParams gp;
Volker Krause's avatar
Volker Krause committed
142

143
#if KPOPPLER_VERSION >= QT_VERSION_CHECK(0, 69, 0)
144
145
146
147
148
149
150
    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<ImageLoaderOutputDevice> 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();
Volker Krause's avatar
Volker Krause committed
151
#endif
152
153
154

#else
    return {};
Volker Krause's avatar
Volker Krause committed
155
#endif
156
157
}

Volker Krause's avatar
Volker Krause committed
158
159
160
161
162
163
164
165
166
167
168
169

PdfImage::PdfImage()
    : d(new PdfImagePrivate)
{
}

PdfImage::PdfImage(const PdfImage&) = default;
PdfImage::~PdfImage() = default;
PdfImage& PdfImage::operator=(const PdfImage&) = default;

int PdfImage::height() const
{
170
171
172
    if (d->m_format == QImage::Format_Invalid) {
        return d->m_height;
    }
173
    return d->m_transform.map(QRectF(0, 0, 1, -1)).boundingRect().height();
Volker Krause's avatar
Volker Krause committed
174
175
176
177
}

int PdfImage::width() const
{
178
179
180
    if (d->m_format == QImage::Format_Invalid) {
        return d->m_width;
    }
181
    return d->m_transform.map(QRectF(0, 0, 1, -1)).boundingRect().width();
Volker Krause's avatar
Volker Krause committed
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
}

int PdfImage::sourceHeight() const
{
    return d->m_sourceHeight;
}

int PdfImage::sourceWidth() const
{
    return d->m_sourceWidth;
}

QTransform PdfImage::transform() const
{
    return d->m_transform;
}

199
200
201
202
203
void PdfImage::setLoadingHints(LoadingHints hints)
{
    d->m_loadingHints = hints;
}

Volker Krause's avatar
Volker Krause committed
204
205
QImage PdfImage::image() const
{
206
207
208
209
    if (d->m_format == QImage::Format_Invalid) {
        return d->m_vectorPicture.renderToImage();
    }

210
    const auto img = d->load();
211
    if (!img.isNull() && (d->m_width != d->m_sourceWidth || d->m_height != d->m_sourceHeight)) {
Volker Krause's avatar
Volker Krause committed
212
213
214
215
216
        return img.scaled(d->m_width, d->m_height);
    }
    return img;
}

217
218
219
220
221
bool PdfImage::hasObjectId() const
{
    return d->m_refNum >= 0;
}

Volker Krause's avatar
Volker Krause committed
222
223
224
225
int PdfImage::objectId() const
{
    return d->m_refNum;
}