File indexing completed on 2024-05-19 04:35:22
0001 /* 0002 SPDX-FileCopyrightText: 2006 Pino Toscano <toscano.pino@tiscali.it> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "generator_tiff.h" 0008 0009 #include <QBuffer> 0010 #include <QDateTime> 0011 #include <QFile> 0012 #include <QFileInfo> 0013 #include <QImage> 0014 #include <QList> 0015 #include <QPainter> 0016 #include <QPrinter> 0017 0018 #include <KAboutData> 0019 #include <KLocalizedString> 0020 #include <QDebug> 0021 0022 #include <core/document.h> 0023 #include <core/fileprinter.h> 0024 #include <core/page.h> 0025 #include <core/utils.h> 0026 0027 #include <tiff.h> 0028 #include <tiffio.h> 0029 0030 #define TiffDebug 4714 0031 0032 tsize_t okular_tiffReadProc(thandle_t handle, tdata_t buf, tsize_t size) 0033 { 0034 QIODevice *device = static_cast<QIODevice *>(handle); 0035 return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1; 0036 } 0037 0038 tsize_t okular_tiffWriteProc(thandle_t handle, tdata_t buf, tsize_t size) 0039 { 0040 QIODevice *device = static_cast<QIODevice *>(handle); 0041 return device->write(static_cast<char *>(buf), size); 0042 } 0043 0044 toff_t okular_tiffSeekProc(thandle_t handle, toff_t offset, int whence) 0045 { 0046 QIODevice *device = static_cast<QIODevice *>(handle); 0047 switch (whence) { 0048 case SEEK_SET: 0049 device->seek(offset); 0050 break; 0051 case SEEK_CUR: 0052 device->seek(device->pos() + offset); 0053 break; 0054 case SEEK_END: 0055 device->seek(device->size() + offset); 0056 break; 0057 } 0058 0059 return device->pos(); 0060 } 0061 0062 int okular_tiffCloseProc(thandle_t handle) 0063 { 0064 Q_UNUSED(handle) 0065 return 0; 0066 } 0067 0068 toff_t okular_tiffSizeProc(thandle_t handle) 0069 { 0070 QIODevice *device = static_cast<QIODevice *>(handle); 0071 return device->size(); 0072 } 0073 0074 int okular_tiffMapProc(thandle_t, tdata_t *, toff_t *) 0075 { 0076 return 0; 0077 } 0078 0079 void okular_tiffUnmapProc(thandle_t, tdata_t, toff_t) 0080 { 0081 } 0082 0083 class TIFFGenerator::Private 0084 { 0085 public: 0086 Private() 0087 : tiff(nullptr) 0088 , dev(nullptr) 0089 { 0090 } 0091 0092 TIFF *tiff; 0093 QByteArray data; 0094 QIODevice *dev; 0095 }; 0096 0097 static QDateTime convertTIFFDateTime(const char *tiffdate) 0098 { 0099 if (!tiffdate) { 0100 return QDateTime(); 0101 } 0102 0103 return QDateTime::fromString(QString::fromLatin1(tiffdate), QStringLiteral("yyyy:MM:dd HH:mm:ss")); 0104 } 0105 0106 static void adaptSizeToResolution(TIFF *tiff, ttag_t whichres, double dpi, uint32_t *size) 0107 { 0108 float resvalue = 1.0; 0109 uint16_t resunit = 0; 0110 if (!TIFFGetField(tiff, whichres, &resvalue) || !TIFFGetFieldDefaulted(tiff, TIFFTAG_RESOLUTIONUNIT, &resunit)) { 0111 return; 0112 } 0113 0114 float newsize = *size / resvalue; 0115 switch (resunit) { 0116 case RESUNIT_INCH: 0117 *size = (uint32_t)(newsize * dpi); 0118 break; 0119 case RESUNIT_CENTIMETER: 0120 *size = (uint32_t)(newsize * 10.0 / 25.4 * dpi); 0121 break; 0122 case RESUNIT_NONE: 0123 break; 0124 } 0125 } 0126 0127 static Okular::Rotation readTiffRotation(TIFF *tiff) 0128 { 0129 uint32_t tiffOrientation = 0; 0130 0131 if (!TIFFGetField(tiff, TIFFTAG_ORIENTATION, &tiffOrientation)) { 0132 return Okular::Rotation0; 0133 } 0134 0135 Okular::Rotation ret = Okular::Rotation0; 0136 switch (tiffOrientation) { 0137 case ORIENTATION_TOPLEFT: 0138 case ORIENTATION_TOPRIGHT: 0139 ret = Okular::Rotation0; 0140 break; 0141 case ORIENTATION_BOTRIGHT: 0142 case ORIENTATION_BOTLEFT: 0143 ret = Okular::Rotation180; 0144 break; 0145 case ORIENTATION_LEFTTOP: 0146 case ORIENTATION_LEFTBOT: 0147 ret = Okular::Rotation270; 0148 break; 0149 case ORIENTATION_RIGHTTOP: 0150 case ORIENTATION_RIGHTBOT: 0151 ret = Okular::Rotation90; 0152 break; 0153 } 0154 0155 return ret; 0156 } 0157 0158 OKULAR_EXPORT_PLUGIN(TIFFGenerator, "libokularGenerator_tiff.json") 0159 0160 TIFFGenerator::TIFFGenerator(QObject *parent, const QVariantList &args) 0161 : Okular::Generator(parent, args) 0162 , d(new Private) 0163 { 0164 setFeature(Threaded); 0165 setFeature(PrintNative); 0166 setFeature(PrintToFile); 0167 setFeature(ReadRawData); 0168 } 0169 0170 TIFFGenerator::~TIFFGenerator() 0171 { 0172 if (d->tiff) { 0173 TIFFClose(d->tiff); 0174 d->tiff = nullptr; 0175 } 0176 0177 delete d; 0178 } 0179 0180 bool TIFFGenerator::loadDocument(const QString &fileName, QVector<Okular::Page *> &pagesVector) 0181 { 0182 QFile *qfile = new QFile(fileName); 0183 qfile->open(QIODevice::ReadOnly); 0184 d->dev = qfile; 0185 d->data = QFile::encodeName(QFileInfo(*qfile).fileName()); 0186 return loadTiff(pagesVector, d->data.constData()); 0187 } 0188 0189 bool TIFFGenerator::loadDocumentFromData(const QByteArray &fileData, QVector<Okular::Page *> &pagesVector) 0190 { 0191 d->data = fileData; 0192 QBuffer *qbuffer = new QBuffer(&d->data); 0193 qbuffer->open(QIODevice::ReadOnly); 0194 d->dev = qbuffer; 0195 return loadTiff(pagesVector, "<stdin>"); 0196 } 0197 0198 bool TIFFGenerator::loadTiff(QVector<Okular::Page *> &pagesVector, const char *name) 0199 { 0200 d->tiff = TIFFClientOpen(name, "r", d->dev, okular_tiffReadProc, okular_tiffWriteProc, okular_tiffSeekProc, okular_tiffCloseProc, okular_tiffSizeProc, okular_tiffMapProc, okular_tiffUnmapProc); 0201 if (!d->tiff) { 0202 delete d->dev; 0203 d->dev = nullptr; 0204 d->data.clear(); 0205 return false; 0206 } 0207 0208 loadPages(pagesVector); 0209 0210 return true; 0211 } 0212 0213 bool TIFFGenerator::doCloseDocument() 0214 { 0215 // closing the old document 0216 if (d->tiff) { 0217 TIFFClose(d->tiff); 0218 d->tiff = nullptr; 0219 delete d->dev; 0220 d->dev = nullptr; 0221 d->data.clear(); 0222 m_pageMapping.clear(); 0223 } 0224 0225 return true; 0226 } 0227 0228 QImage TIFFGenerator::image(Okular::PixmapRequest *request) 0229 { 0230 bool generated = false; 0231 QImage img; 0232 0233 if (TIFFSetDirectory(d->tiff, mapPage(request->page()->number()))) { 0234 int rotation = request->page()->rotation(); 0235 uint32_t width = 1; 0236 uint32_t height = 1; 0237 uint32_t orientation = 0; 0238 TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width); 0239 TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height); 0240 0241 if (!TIFFGetField(d->tiff, TIFFTAG_ORIENTATION, &orientation)) { 0242 orientation = ORIENTATION_TOPLEFT; 0243 } 0244 0245 QImage image(width, height, QImage::Format_RGB32); 0246 uint32_t *data = reinterpret_cast<uint32_t *>(image.bits()); 0247 0248 // read data 0249 if (TIFFReadRGBAImageOriented(d->tiff, width, height, data, orientation) != 0) { 0250 // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue 0251 uint32_t size = width * height; 0252 for (uint32_t i = 0; i < size; ++i) { 0253 uint32_t red = (data[i] & 0x00FF0000) >> 16; 0254 uint32_t blue = (data[i] & 0x000000FF) << 16; 0255 data[i] = (data[i] & 0xFF00FF00) + red + blue; 0256 } 0257 0258 int reqwidth = request->width(); 0259 int reqheight = request->height(); 0260 if (rotation % 2 == 1) { 0261 qSwap(reqwidth, reqheight); 0262 } 0263 img = image.scaled(reqwidth, reqheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 0264 0265 generated = true; 0266 } 0267 } 0268 0269 if (!generated) { 0270 img = QImage(request->width(), request->height(), QImage::Format_RGB32); 0271 img.fill(qRgb(255, 255, 255)); 0272 } 0273 0274 return img; 0275 } 0276 0277 Okular::DocumentInfo TIFFGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const 0278 { 0279 Okular::DocumentInfo docInfo; 0280 if (d->tiff) { 0281 if (keys.contains(Okular::DocumentInfo::MimeType)) { 0282 docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("image/tiff")); 0283 } 0284 0285 if (keys.contains(Okular::DocumentInfo::Description)) { 0286 char *buffer = nullptr; 0287 TIFFGetField(d->tiff, TIFFTAG_IMAGEDESCRIPTION, &buffer); 0288 docInfo.set(Okular::DocumentInfo::Description, buffer ? QString::fromLatin1(buffer) : QString()); 0289 } 0290 0291 if (keys.contains(Okular::DocumentInfo::Producer)) { 0292 char *buffer = nullptr; 0293 TIFFGetField(d->tiff, TIFFTAG_SOFTWARE, &buffer); 0294 docInfo.set(Okular::DocumentInfo::Producer, buffer ? QString::fromLatin1(buffer) : QString()); 0295 } 0296 0297 if (keys.contains(Okular::DocumentInfo::Copyright)) { 0298 char *buffer = nullptr; 0299 TIFFGetField(d->tiff, TIFFTAG_COPYRIGHT, &buffer); 0300 docInfo.set(Okular::DocumentInfo::Copyright, buffer ? QString::fromLatin1(buffer) : QString()); 0301 } 0302 0303 if (keys.contains(Okular::DocumentInfo::Author)) { 0304 char *buffer = nullptr; 0305 TIFFGetField(d->tiff, TIFFTAG_ARTIST, &buffer); 0306 docInfo.set(Okular::DocumentInfo::Author, buffer ? QString::fromLatin1(buffer) : QString()); 0307 } 0308 0309 if (keys.contains(Okular::DocumentInfo::CreationDate)) { 0310 char *buffer = nullptr; 0311 TIFFGetField(d->tiff, TIFFTAG_DATETIME, &buffer); 0312 QDateTime date = convertTIFFDateTime(buffer); 0313 docInfo.set(Okular::DocumentInfo::CreationDate, date.isValid() ? QLocale().toString(date, QLocale::LongFormat) : QString()); 0314 } 0315 } 0316 0317 return docInfo; 0318 } 0319 0320 void TIFFGenerator::loadPages(QVector<Okular::Page *> &pagesVector) 0321 { 0322 if (!d->tiff) { 0323 return; 0324 } 0325 0326 tdir_t dirs = TIFFNumberOfDirectories(d->tiff); 0327 pagesVector.resize(dirs); 0328 tdir_t realdirs = 0; 0329 0330 uint32_t width = 0; 0331 uint32_t height = 0; 0332 0333 const QSizeF dpi = Okular::Utils::realDpi(nullptr); 0334 for (tdir_t i = 0; i < dirs; ++i) { 0335 if (!TIFFSetDirectory(d->tiff, i)) { 0336 continue; 0337 } 0338 0339 if (TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width) != 1 || TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height) != 1) { 0340 continue; 0341 } 0342 0343 adaptSizeToResolution(d->tiff, TIFFTAG_XRESOLUTION, dpi.width(), &width); 0344 adaptSizeToResolution(d->tiff, TIFFTAG_YRESOLUTION, dpi.height(), &height); 0345 0346 Okular::Page *page = new Okular::Page(realdirs, width, height, readTiffRotation(d->tiff)); 0347 pagesVector[realdirs] = page; 0348 0349 m_pageMapping[realdirs] = i; 0350 0351 ++realdirs; 0352 } 0353 0354 pagesVector.resize(realdirs); 0355 } 0356 0357 Okular::Document::PrintError TIFFGenerator::print(QPrinter &printer) 0358 { 0359 uint32_t width = 0; 0360 uint32_t height = 0; 0361 0362 QPainter p(&printer); 0363 0364 QList<int> pageList = Okular::FilePrinter::pageList(printer, document()->pages(), document()->currentPage() + 1, document()->bookmarkedPageList()); 0365 0366 for (int i = 0; i < pageList.count(); ++i) { 0367 if (!TIFFSetDirectory(d->tiff, mapPage(pageList[i] - 1))) { 0368 continue; 0369 } 0370 0371 if (TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width) != 1 || TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height) != 1) { 0372 continue; 0373 } 0374 0375 QImage image(width, height, QImage::Format_RGB32); 0376 uint32_t *data = reinterpret_cast<uint32_t *>(image.bits()); 0377 0378 // read data 0379 if (TIFFReadRGBAImageOriented(d->tiff, width, height, data, ORIENTATION_TOPLEFT) != 0) { 0380 // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue 0381 uint32_t size = width * height; 0382 for (uint32_t j = 0; j < size; ++j) { 0383 uint32_t red = (data[j] & 0x00FF0000) >> 16; 0384 uint32_t blue = (data[j] & 0x000000FF) << 16; 0385 data[j] = (data[j] & 0xFF00FF00) + red + blue; 0386 } 0387 } 0388 0389 if (i != 0) { 0390 printer.newPage(); 0391 } 0392 0393 QSize targetSize = printer.pageRect(QPrinter::Unit::DevicePixel).size().toSize(); 0394 0395 if ((image.width() < targetSize.width()) && (image.height() < targetSize.height())) { 0396 // draw small images at 100% (don't scale up) 0397 p.drawImage(0, 0, image); 0398 } else { 0399 // fit to page 0400 p.drawImage(0, 0, image.scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); 0401 } 0402 } 0403 0404 return Okular::Document::NoPrintError; 0405 } 0406 0407 int TIFFGenerator::mapPage(int page) const 0408 { 0409 QHash<int, int>::const_iterator it = m_pageMapping.find(page); 0410 if (it == m_pageMapping.end()) { 0411 qCWarning(OkularTiffDebug) << "Requesting unmapped page" << page << ":" << m_pageMapping; 0412 return -1; 0413 } 0414 return it.value(); 0415 } 0416 0417 Q_LOGGING_CATEGORY(OkularTiffDebug, "org.kde.okular.generators.tiff", QtWarningMsg) 0418 0419 #include "generator_tiff.moc"