generator_tiff.cpp 12.8 KB
Newer Older
Pino Toscano's avatar
Pino Toscano committed
1 2 3 4 5 6 7 8 9
/***************************************************************************
 *   Copyright (C) 2006 by Pino Toscano <toscano.pino@tiscali.it>          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

Albert Astals Cid's avatar
Albert Astals Cid committed
10 11
#include "generator_tiff.h"

12
#include <qabstractfileengine.h>
13
#include <qbuffer.h>
Pino Toscano's avatar
Pino Toscano committed
14
#include <qdatetime.h>
Pino Toscano's avatar
Pino Toscano committed
15 16 17
#include <qfile.h>
#include <qimage.h>
#include <qlist.h>
18
#include <qpainter.h>
19 20
#include <QtGui/QPrinter>

21
#include <kaboutdata.h>
22
#include <kdebug.h>
Pino Toscano's avatar
Pino Toscano committed
23
#include <kglobal.h>
24
#include <klocale.h>
Pino Toscano's avatar
Pino Toscano committed
25

26 27
#include <okular/core/document.h>
#include <okular/core/page.h>
John Layt's avatar
John Layt committed
28
#include <okular/core/fileprinter.h>
29
#include <okular/core/utils.h>
30

Pino Toscano's avatar
Pino Toscano committed
31 32 33
#include <tiff.h>
#include <tiffio.h>

34 35
#define TiffDebug 4714

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
tsize_t okular_tiffReadProc( thandle_t handle, tdata_t buf, tsize_t size )
{
    QIODevice * device = static_cast< QIODevice * >( handle );
    return device->isReadable() ? device->read( static_cast< char * >( buf ), size ) : -1;
}

tsize_t okular_tiffWriteProc( thandle_t handle, tdata_t buf, tsize_t size )
{
    QIODevice * device = static_cast< QIODevice * >( handle );
    return device->write( static_cast< char * >( buf ), size );
}

toff_t okular_tiffSeekProc( thandle_t handle, toff_t offset, int whence )
{
    QIODevice * device = static_cast< QIODevice * >( handle );
    switch ( whence )
    {
        case SEEK_SET:
            device->seek( offset );
            break;
        case SEEK_CUR:
            device->seek( device->pos() + offset );
            break;
        case SEEK_END:
            device->seek( device->size() + offset );
            break;
    }

    return device->pos();
}

int okular_tiffCloseProc( thandle_t handle )
{
    Q_UNUSED( handle )
    return 0;
}

toff_t okular_tiffSizeProc( thandle_t handle )
{
    QIODevice * device = static_cast< QIODevice * >( handle );
    return device->size();
}

int okular_tiffMapProc( thandle_t, tdata_t *, toff_t * )
{
    return 0;
}

void okular_tiffUnmapProc( thandle_t, tdata_t, toff_t )
{
}


Pino Toscano's avatar
Pino Toscano committed
89 90 91 92
class TIFFGenerator::Private
{
    public:
        Private()
93
          : tiff( 0 ), dev( 0 ) {}
Pino Toscano's avatar
Pino Toscano committed
94 95

        TIFF* tiff;
96 97
        QByteArray data;
        QIODevice* dev;
Pino Toscano's avatar
Pino Toscano committed
98 99
};

Pino Toscano's avatar
Pino Toscano committed
100 101 102 103 104 105 106
static QDateTime convertTIFFDateTime( const char* tiffdate )
{
    if ( !tiffdate )
        return QDateTime();

    return QDateTime::fromString( QString::fromLatin1( tiffdate ), "yyyy:MM:dd HH:mm:ss" );
}
Pino Toscano's avatar
Pino Toscano committed
107

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
static void adaptSizeToResolution( TIFF *tiff, ttag_t whichres, double dpi, uint32 *size )
{
    float resvalue = 1.0;
    uint16 resunit = 0;
    if ( !TIFFGetField( tiff, whichres, &resvalue )
         || !TIFFGetField( tiff, TIFFTAG_RESOLUTIONUNIT, &resunit ) )
        return;

    float newsize = *size / resvalue;
    switch ( resunit )
    {
        case RESUNIT_INCH:
            *size = (uint32)( newsize * dpi );
            break;
        case RESUNIT_CENTIMETER:
            *size = (uint32)( newsize * 10.0 / 25.4 * dpi );
            break;
        case RESUNIT_NONE:
            break;
    }
}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
static Okular::Rotation readTiffRotation( TIFF *tiff )
{
    uint32 tiffOrientation = 0;

    if ( !TIFFGetField( tiff, TIFFTAG_ORIENTATION, &tiffOrientation ) )
        return Okular::Rotation0;

    Okular::Rotation ret = Okular::Rotation0;
    switch ( tiffOrientation )
    {
        case ORIENTATION_TOPLEFT:
        case ORIENTATION_TOPRIGHT:
            ret = Okular::Rotation0;
            break;
        case ORIENTATION_BOTRIGHT:
        case ORIENTATION_BOTLEFT:
            ret = Okular::Rotation180;
            break;
        case ORIENTATION_LEFTTOP:
        case ORIENTATION_LEFTBOT:
            ret = Okular::Rotation270;
            break;
        case ORIENTATION_RIGHTTOP:
        case ORIENTATION_RIGHTBOT:
            ret = Okular::Rotation90;
            break;
    }

    return ret;
}

161
static KAboutData createAboutData()
Pino Toscano's avatar
Pino Toscano committed
162
{
163
    KAboutData aboutData(
164 165
         "okular_tiff",
         "okular_tiff",
166
         ki18n( "TIFF Backend" ),
167
         "0.2",
168 169
         ki18n( "A TIFF backend" ),
         KAboutData::License_GPL,
170 171
         ki18n( "© 2006-2008 Pino Toscano" ),
         ki18nc( "This represents the libtiff version, as string with copyrights as well; can be left as-is.", "%1" ).subs( TIFFGetVersion() )
172
    );
173 174 175 176 177 178 179 180 181 182 183 184 185
    aboutData.addAuthor( ki18n( "Pino Toscano" ), KLocalizedString(), "pino@kde.org" );
    return aboutData;
}

OKULAR_EXPORT_PLUGIN( TIFFGenerator, createAboutData() )

TIFFGenerator::TIFFGenerator( QObject *parent, const QVariantList &args )
    : Okular::Generator( parent, args ),
      d( new Private ), m_docInfo( 0 )
{
    setFeature( Threaded );
    setFeature( PrintNative );
    setFeature( PrintToFile );
186
    setFeature( ReadRawData );
Pino Toscano's avatar
Pino Toscano committed
187 188 189 190 191 192 193 194 195 196
}

TIFFGenerator::~TIFFGenerator()
{
    if ( d->tiff )
    {
        TIFFClose( d->tiff );
        d->tiff = 0;
    }

197
    delete m_docInfo;
Pino Toscano's avatar
Pino Toscano committed
198 199 200
    delete d;
}

201
bool TIFFGenerator::loadDocument( const QString & fileName, QVector<Okular::Page*> & pagesVector )
202
{
203 204 205
    QFile* qfile = new QFile( fileName );
    qfile->open( QIODevice::ReadOnly );
    d->dev = qfile;
206 207 208 209 210 211 212 213 214 215 216
    QAbstractFileEngine *fileEngine = QAbstractFileEngine::create( fileName );
    if ( fileEngine )
    {
        d->data = QFile::encodeName( fileEngine->fileName( QAbstractFileEngine::BaseName ) );
        delete fileEngine;
    }
    else
    {
        d->data = QByteArray( "okular.tiff" );
    }
    d->tiff = TIFFClientOpen( d->data.constData(), "r", d->dev,
217 218 219
                  okular_tiffReadProc, okular_tiffWriteProc, okular_tiffSeekProc,
                  okular_tiffCloseProc, okular_tiffSizeProc,
                  okular_tiffMapProc, okular_tiffUnmapProc );
220
    if ( !d->tiff )
221 222 223
    {
        delete d->dev;
        d->dev = 0;
224
        d->data.clear();
225
        return false;
226 227 228 229 230 231 232 233 234 235 236 237 238
    }

    loadPages( pagesVector );

    return true;
}

bool TIFFGenerator::loadDocumentFromData( const QByteArray & fileData, QVector< Okular::Page * > & pagesVector )
{
    d->data = fileData;
    QBuffer* qbuffer = new QBuffer( &d->data );
    qbuffer->open( QIODevice::ReadOnly );
    d->dev = qbuffer;
239
    d->tiff = TIFFClientOpen( "<stdin>", "r", d->dev,
240 241 242 243 244 245 246 247 248
                  okular_tiffReadProc, okular_tiffWriteProc, okular_tiffSeekProc,
                  okular_tiffCloseProc, okular_tiffSizeProc,
                  okular_tiffMapProc, okular_tiffUnmapProc );
    if ( !d->tiff )
    {
        delete d->dev;
        d->dev = 0;
        d->data.clear();
        return false;
249
    }
250

Pino Toscano's avatar
Pino Toscano committed
251
    loadPages( pagesVector );
252 253 254 255

    return true;
}

256
bool TIFFGenerator::doCloseDocument()
Pino Toscano's avatar
Pino Toscano committed
257 258 259 260 261 262
{
    // closing the old document
    if ( d->tiff )
    {
        TIFFClose( d->tiff );
        d->tiff = 0;
263 264 265
        delete d->dev;
        d->dev = 0;
        d->data.clear();
266 267
        delete m_docInfo;
        m_docInfo = 0;
268
        m_pageMapping.clear();
Pino Toscano's avatar
Pino Toscano committed
269 270 271 272 273
    }

    return true;
}

274
QImage TIFFGenerator::image( Okular::PixmapRequest * request )
Pino Toscano's avatar
Pino Toscano committed
275 276
{
    bool generated = false;
277
    QImage img;
Pino Toscano's avatar
Pino Toscano committed
278

279
    if ( TIFFSetDirectory( d->tiff, mapPage( request->page()->number() ) ) )
Pino Toscano's avatar
Pino Toscano committed
280
    {
281
        int rotation = request->page()->rotation();
282 283
        uint32 width = 1;
        uint32 height = 1;
284
        uint32 orientation = 0;
285 286
        TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width );
        TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height );
Pino Toscano's avatar
Pino Toscano committed
287 288 289
        if ( rotation % 2 == 1 )
            qSwap( width, height );

290 291 292
        if ( !TIFFGetField( d->tiff, TIFFTAG_ORIENTATION, &orientation ) )
            orientation = ORIENTATION_TOPLEFT;

Pino Toscano's avatar
Pino Toscano committed
293 294 295 296
        QImage image( width, height, QImage::Format_RGB32 );
        uint32 * data = (uint32 *)image.bits();

        // read data
297
        if ( TIFFReadRGBAImageOriented( d->tiff, width, height, data, orientation ) != 0 )
Pino Toscano's avatar
Pino Toscano committed
298 299 300 301 302 303 304 305 306 307
        {
            // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue
            uint32 size = width * height;
            for ( uint32 i = 0; i < size; ++i )
            {
                uint32 red = ( data[i] & 0x00FF0000 ) >> 16;
                uint32 blue = ( data[i] & 0x000000FF ) << 16;
                data[i] = ( data[i] & 0xFF00FF00 ) + red + blue;
            }

Tobias Koenig's avatar
Tobias Koenig committed
308 309
            int reqwidth = request->width();
            int reqheight = request->height();
Pino Toscano's avatar
Pino Toscano committed
310 311
            if ( rotation % 2 == 1 )
                qSwap( reqwidth, reqheight );
312
            img = image.scaled( reqwidth, reqheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
Pino Toscano's avatar
Pino Toscano committed
313 314 315 316 317 318 319

            generated = true;
        }
    }

    if ( !generated )
    {
320 321
        img = QImage( request->width(), request->height(), QImage::Format_RGB32 );
        img.fill( qRgb( 255, 255, 255 ) );
Pino Toscano's avatar
Pino Toscano committed
322 323
    }

324
    return img;
Pino Toscano's avatar
Pino Toscano committed
325 326
}

327
const Okular::DocumentInfo * TIFFGenerator::generateDocumentInfo()
328 329 330 331 332 333 334
{
    if ( !d->tiff )
        return 0;

    if ( m_docInfo )
        return m_docInfo;

335
    m_docInfo = new Okular::DocumentInfo();
336

337
    m_docInfo->set( Okular::DocumentInfo::MimeType, "image/tiff" );
338 339 340

    char* buffer = 0;
    TIFFGetField( d->tiff, TIFFTAG_IMAGEDESCRIPTION, &buffer );
Pino Toscano's avatar
Pino Toscano committed
341
    m_docInfo->set( Okular::DocumentInfo::Description, buffer ? QString::fromLatin1( buffer ) : i18nc( "Unknown description", "Unknown" ) );
342 343 344

    buffer = 0;
    TIFFGetField( d->tiff, TIFFTAG_SOFTWARE, &buffer );
Pino Toscano's avatar
Pino Toscano committed
345
    m_docInfo->set( Okular::DocumentInfo::Producer, buffer ? QString::fromLatin1( buffer ) : i18nc( "Unknown producer", "Unknown" ) );
346 347 348

    buffer = 0;
    TIFFGetField( d->tiff, TIFFTAG_COPYRIGHT, &buffer );
Pino Toscano's avatar
Pino Toscano committed
349
    m_docInfo->set( Okular::DocumentInfo::Copyright, buffer ? QString::fromLatin1( buffer ) : i18nc( "Unknown copyright statement", "Unknown" ) );
350 351 352

    buffer = 0;
    TIFFGetField( d->tiff, TIFFTAG_ARTIST, &buffer );
Pino Toscano's avatar
Pino Toscano committed
353
    m_docInfo->set( Okular::DocumentInfo::Author, buffer ? QString::fromLatin1( buffer ) : i18nc( "Unknown author", "Unknown" ) );
354

Pino Toscano's avatar
Pino Toscano committed
355 356 357
    buffer = 0;
    TIFFGetField( d->tiff, TIFFTAG_DATETIME, &buffer );
    QDateTime date = convertTIFFDateTime( buffer );
Pino Toscano's avatar
Pino Toscano committed
358
    m_docInfo->set( Okular::DocumentInfo::CreationDate, date.isValid() ? KGlobal::locale()->formatDateTime( date, KLocale::LongDate, true  ) : i18nc( "Unknown creation date", "Unknown" ) );
Pino Toscano's avatar
Pino Toscano committed
359

360 361 362
    return m_docInfo;
}

Pino Toscano's avatar
Pino Toscano committed
363
void TIFFGenerator::loadPages( QVector<Okular::Page*> & pagesVector )
Pino Toscano's avatar
Pino Toscano committed
364 365 366 367 368 369
{
    if ( !d->tiff )
        return;

    tdir_t dirs = TIFFNumberOfDirectories( d->tiff );
    pagesVector.resize( dirs );
370
    tdir_t realdirs = 0;
Pino Toscano's avatar
Pino Toscano committed
371 372 373 374 375 376 377 378 379 380 381 382 383

    uint32 width = 0;
    uint32 height = 0;

    for ( tdir_t i = 0; i < dirs; ++i )
    {
        if ( !TIFFSetDirectory( d->tiff, i ) )
            continue;

        if ( TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ) != 1 ||
             TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ) != 1 )
            continue;

384 385 386
        adaptSizeToResolution( d->tiff, TIFFTAG_XRESOLUTION, Okular::Utils::dpiX(), &width );
        adaptSizeToResolution( d->tiff, TIFFTAG_YRESOLUTION, Okular::Utils::dpiY(), &height );

387
        Okular::Page * page = new Okular::Page( realdirs, width, height, readTiffRotation( d->tiff ) );
388 389 390
        pagesVector[ realdirs ] = page;

        m_pageMapping[ realdirs ] = i;
Pino Toscano's avatar
Pino Toscano committed
391

392
        ++realdirs;
Pino Toscano's avatar
Pino Toscano committed
393
    }
394 395

    pagesVector.resize( realdirs );
Pino Toscano's avatar
Pino Toscano committed
396 397
}

398
bool TIFFGenerator::print( QPrinter& printer )
399 400 401 402 403 404
{
    uint32 width = 0;
    uint32 height = 0;

    QPainter p( &printer );

John Layt's avatar
John Layt committed
405 406 407 408
    QList<int> pageList = Okular::FilePrinter::pageList( printer, document()->pages(),
                                                         document()->bookmarkedPageList() );

    for ( tdir_t i = 0; i < pageList.count(); ++i )
409
    {
410
        if ( !TIFFSetDirectory( d->tiff, mapPage( pageList[i] - 1 ) ) )
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
            continue;

        if ( TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ) != 1 ||
             TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ) != 1 )
            continue;

        QImage image( width, height, QImage::Format_RGB32 );
        uint32 * data = (uint32 *)image.bits();

        // read data
        if ( TIFFReadRGBAImageOriented( d->tiff, width, height, data, ORIENTATION_TOPLEFT ) != 0 )
        {
            // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue
            uint32 size = width * height;
            for ( uint32 i = 0; i < size; ++i )
            {
                uint32 red = ( data[i] & 0x00FF0000 ) >> 16;
                uint32 blue = ( data[i] & 0x000000FF ) << 16;
                data[i] = ( data[i] & 0xFF00FF00 ) + red + blue;
            }
        }

        if ( i != 0 )
            printer.newPage();

        p.drawImage( 0, 0, image );
    }

    return true;
}

442 443 444 445 446 447 448 449 450 451 452
int TIFFGenerator::mapPage( int page ) const
{
    QHash< int, int >::const_iterator it = m_pageMapping.find( page );
    if ( it == m_pageMapping.end() )
    {
        kWarning(TiffDebug) << "Requesting unmapped page" << page << ":" << m_pageMapping;
        return -1;
    }
    return it.value();
}

Pino Toscano's avatar
Pino Toscano committed
453 454
#include "generator_tiff.moc"