lib Library API Documentation

koPictureEps.cc

00001 /* This file is part of the KDE project
00002    Copyright (c) 2001 Simon Hausmann <hausmann@kde.org>
00003    Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018    Boston, MA 02111-1307, USA.
00019 */
00020 
00021 #include <unistd.h>
00022 #include <stdio.h>
00023 
00024 #include <qbuffer.h>
00025 #include <qpainter.h>
00026 #include <qpaintdevicemetrics.h>
00027 #include <qfile.h>
00028 #include <qtextstream.h>
00029 #include <qregexp.h>
00030 #include <qimage.h>
00031 #include <qpixmap.h>
00032 #include <qapplication.h>
00033 #include <qdragobject.h>
00034 
00035 #include <kglobal.h>
00036 #include <kdebug.h>
00037 #include <kdeversion.h>
00038 #if ! KDE_IS_VERSION( 3,1,90 )
00039 #include <kdebugclasses.h>
00040 #endif
00041 #include <ktempfile.h>
00042 #include <kprocess.h>
00043 
00044 #include "koPictureKey.h"
00045 #include "koPictureBase.h"
00046 #include "koPictureEps.h"
00047 
00048 
00049 KoPictureEps::KoPictureEps(void) : m_psStreamStart(0), m_psStreamLength(0), m_cacheIsInFastMode(true)
00050 {
00051     // Forbid QPixmap to cache the X-Window resources (Yes, it is slower!)
00052     m_cachedPixmap.setOptimization(QPixmap::MemoryOptim);
00053 }
00054 
00055 KoPictureEps::~KoPictureEps(void)
00056 {
00057 }
00058 
00059 KoPictureBase* KoPictureEps::newCopy(void) const
00060 {
00061     return new KoPictureEps(*this);
00062 }
00063 
00064 KoPictureType::Type KoPictureEps::getType(void) const
00065 {
00066     return KoPictureType::TypeEps;
00067 }
00068 
00069 bool KoPictureEps::isNull(void) const
00070 {
00071     return m_rawData.isNull();
00072 }
00073 
00074 QImage KoPictureEps::scaleWithGhostScript(const QSize& size, const int resolutionx, const int resolutiony )
00075 {
00076     if (!m_boundingBox.width() || !m_boundingBox.height())
00077     {
00078         kdDebug(30003) << "EPS image has a null size! (in KoPictureEps::scaleWithGhostScript)" << endl;
00079         return QImage();
00080     }
00081 
00082     // ### TODO: do not call GhostScript up to three times for each re-scaling (one call of GhostScript should be enough to know which device is available: gs --help)
00083     // png16m is better, but not always available -> fallback to bmp16m, then fallback to ppm (256 colors)
00084     QImage img;
00085     int ret = tryScaleWithGhostScript(img, size, resolutionx, resolutiony, "png16m");
00086     if ( ret == -1 )
00087     {
00088         ret = tryScaleWithGhostScript(img, size, resolutionx, resolutiony, "bmp16m");
00089         if ( ret == -1 )
00090         {
00091             ret = tryScaleWithGhostScript(img, size, resolutionx, resolutiony, "ppm");
00092             if ( ret == -1 )
00093                 kdError(30003) << "Image from GhostScript cannot be loaded (in KoPictureEps::scaleWithGhostScript)" << endl;
00094         }
00095     }
00096     return img;
00097 }
00098 
00099 // Helper method for scaleWithGhostScript. Returns 1 on success, 0 on error, -1 if nothing generated
00100 // (in which case another 'output device' can be tried)
00101 int KoPictureEps::tryScaleWithGhostScript(QImage &image, const QSize& size, const int resolutionx, const int resolutiony, const char* device )
00102 // Based on the code of the file kdelibs/kimgio/eps.cpp
00103 {
00104     kdDebug(30003) << "Sampling with GhostScript, using device " << device << " (in KoPictureEps::tryScaleWithGhostScript)" << endl;
00105 
00106     KTempFile tmpFile;
00107     tmpFile.setAutoDelete(true);
00108 
00109     if ( tmpFile.status() )
00110     {
00111         kdError(30003) << "No KTempFile! (in KoPictureEps::tryScaleWithGhostScript)" << endl;
00112         return 0; // error
00113     }
00114 
00115     const int wantedWidth = size.width();
00116     const int wantedHeight = size.height();
00117     const double xScale = double(size.width()) / double(m_boundingBox.width());
00118     const double yScale = double(size.height()) / double(m_boundingBox.height());
00119 
00120     // create GS command line
00121 
00122     QString cmdBuf ( "gs -sOutputFile=" );
00123     cmdBuf += KProcess::quote(tmpFile.name());
00124     cmdBuf += " -q -g";
00125     cmdBuf += QString::number( wantedWidth );
00126     cmdBuf += "x";
00127     cmdBuf += QString::number( wantedHeight );
00128 
00129     if ( ( resolutionx > 0) && ( resolutiony > 0) )
00130     {
00131 #if 0
00132         // Do not play with resolution for now.
00133         // It brings more problems at print than solutions
00134         cmdBuf += " -r";
00135         cmdBuf += QString::number( resolutionx );
00136         cmdBuf += "x";
00137         cmdBuf += QString::number( resolutiony );
00138 #endif
00139     }
00140 
00141     cmdBuf += " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE=";
00142     cmdBuf += device;
00143     //cmdBuf += " -c 255 255 255 setrgbcolor fill 0 0 0 setrgbcolor";
00144     cmdBuf += " -";
00145     cmdBuf += " -c showpage quit";
00146 
00147     // run ghostview
00148 
00149     FILE* ghostfd = popen (QFile::encodeName(cmdBuf), "w");
00150 
00151     if ( ghostfd == 0 )
00152     {
00153         kdError(30003) << "No connection to GhostScript (in KoPictureEps::tryScaleWithGhostScript)" << endl;
00154         return 0; // error
00155     }
00156 
00157     // The translation is needed as GhostScript (7.07) cannot handle negative values in the bounding box otherwise.
00158     fprintf (ghostfd, "\n%d %d translate\n", -qRound(m_boundingBox.left()*xScale), -qRound(m_boundingBox.top()*yScale));
00159     fprintf (ghostfd, "%g %g scale\n", xScale, yScale);
00160 
00161     // write image to gs
00162 
00163     fwrite( m_rawData.data() + m_psStreamStart, sizeof(char), m_psStreamLength, ghostfd);
00164 
00165     pclose ( ghostfd );
00166 
00167     // load image
00168     if( !image.load (tmpFile.name()) )
00169     {
00170         // It failed - maybe the device isn't supported by gs
00171         return -1;
00172     }
00173     if ( image.size() != size ) // this can happen due to rounding problems
00174     {
00175         //kdDebug(30003) << "fixing size to " << size.width() << "x" << size.height()
00176         //          << " (was " << image.width() << "x" << image.height() << ")" << endl;
00177         image = image.scale( size ); // hmm, smoothScale instead?
00178     }
00179     kdDebug(30003) << "Image parameters: " << image.width() << "x" << image.height() << "x" << image.depth() << endl;
00180     return 1; // success
00181 }
00182 
00183 void KoPictureEps::scaleAndCreatePixmap(const QSize& size, bool fastMode, const int resolutionx, const int resolutiony )
00184 {
00185     kdDebug(30003) << "KoPictureEps::scaleAndCreatePixmap " << size << " " << (fastMode?QString("fast"):QString("slow"))
00186         << " resolutionx: " << resolutionx << " resolutiony: " << resolutiony << endl;
00187     if ((size==m_cachedSize)
00188         && ((fastMode) || (!m_cacheIsInFastMode)))
00189     {
00190         // The cached pixmap has already the right size
00191         // and:
00192         // - we are in fast mode (We do not care if the re-size was done slowly previously)
00193         // - the re-size was already done in slow mode
00194         kdDebug(30003) << "Already cached!" << endl;
00195         return;
00196     }
00197 
00198     // Slow mode can be very slow, especially at high zoom levels -> configurable
00199     if ( !isSlowResizeModeAllowed() )
00200     {
00201         kdDebug(30003) << "User has disallowed slow mode!" << endl;
00202         fastMode = true;
00203     }
00204 
00205     // We cannot use fast mode, if nothing was ever cached.
00206     if ( fastMode && !m_cachedSize.isEmpty())
00207     {
00208         kdDebug(30003) << "Fast scaling!" << endl;
00209         // Slower than caching a QImage, but faster than re-sampling!
00210         QImage image( m_cachedPixmap.convertToImage() );
00211         m_cachedPixmap=image.scale( size );
00212         m_cacheIsInFastMode=true;
00213         m_cachedSize=size;
00214     }
00215     else
00216     {
00217         QTime time;
00218         time.start();
00219 
00220         QApplication::setOverrideCursor( Qt::waitCursor );
00221         m_cachedPixmap = scaleWithGhostScript( size, resolutionx, resolutiony );
00222         QApplication::restoreOverrideCursor();
00223         m_cacheIsInFastMode=false;
00224         m_cachedSize=size;
00225 
00226         kdDebug(30003) << "Time: " << (time.elapsed()/1000.0) << " s" << endl;
00227     }
00228     kdDebug(30003) << "New size: " << size << endl;
00229 }
00230 
00231 void KoPictureEps::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode)
00232 {
00233     if ( !width || !height )
00234         return;
00235 
00236     QSize screenSize( width, height );
00237     //kdDebug() << "KoPictureEps::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl;
00238 
00239     QPaintDeviceMetrics metrics (painter.device());
00240     kdDebug(30003) << "Metrics: X: " << metrics.logicalDpiX() << " x Y: " << metrics.logicalDpiX() << " (in KoPictureEps::draw)" << endl;
00241 
00242     if ( painter.device()->isExtDev() ) // Is it an external device (i.e. printer)
00243     {
00244         kdDebug(30003) << "Drawing for a printer (in KoPictureEps::draw)" << endl;
00245         // For printing, always re-sample the image, as a printer has never the same resolution than a display.
00246         QImage image( scaleWithGhostScript( screenSize, metrics.logicalDpiX(), metrics.logicalDpiY() ) );
00247         // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawImage
00248         // translates it to the (x,y) point -> we need (x+sx, y+sy).
00249         painter.drawImage( x + sx, y + sy, image, sx, sy, sw, sh );
00250     }
00251     else // No, it is simply a display
00252     {
00253         scaleAndCreatePixmap(screenSize, fastMode, metrics.logicalDpiX(), metrics.logicalDpiY() );
00254 
00255         // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap
00256         // translates it to the (x,y) point -> we need (x+sx, y+sy).
00257         painter.drawPixmap( x + sx, y + sy, m_cachedPixmap, sx, sy, sw, sh );
00258     }
00259 }
00260 
00261 bool KoPictureEps::extractPostScriptStream( void )
00262 {
00263     kdDebug(30003) << "KoPictureEps::extractPostScriptStream" << endl;
00264     QDataStream data( m_rawData, IO_ReadOnly );
00265     data.setByteOrder( QDataStream::LittleEndian );
00266     Q_UINT32 magic, offset, length;
00267     data >> magic;
00268     data >> offset;
00269     data >> length;
00270     if ( !length )
00271     {
00272         kdError(30003) << "Length of PS stream is zero!" << endl;
00273         return false;
00274     }
00275     if ( offset+length>m_rawData.size() )
00276     {
00277         kdError(30003) << "Data stream of the EPSF file is longer than file: " << offset << "+" << length << ">" << m_rawData.size() << endl;
00278         return false;
00279     }
00280     m_psStreamStart = offset;
00281     m_psStreamLength = length;
00282     return true;
00283 }
00284 
00285 QString KoPictureEps::readLine( const QByteArray& array, const uint start, const uint length, uint& pos, bool& lastCharWasCr )
00286 {
00287     QString strLine;
00288     const uint finish = kMin( start + length, array.size() );
00289     for ( ; pos < finish; ++pos ) // We are starting at pos
00290     {
00291         const char ch = array[ pos ]; // Read one character
00292         if ( ch == '\n' )
00293         {
00294             if ( lastCharWasCr )
00295             {
00296                 // We have a line feed following a Carriage Return
00297                 // As the Carriage Return has already ended the previous line,
00298                 // discard this Line Feed.
00299                 lastCharWasCr = false;
00300             }
00301             else
00302             {
00303                 // We have a normal Line Feed, therefore we end the line
00304                 break;
00305             }
00306         }
00307         else if ( ch == '\r' )
00308         {
00309             // We have a Carriage Return, therefore we end the line
00310             lastCharWasCr = true;
00311             break;
00312         }
00313         else if ( ch == char(12) ) // Form Feed
00314         { // ### TODO: can a FF happen in PostScript?
00315             // Ignore the form feed
00316             continue;
00317         }
00318         else
00319         {
00320             strLine += ch;
00321             lastCharWasCr = false;
00322         }
00323     }
00324     return strLine;
00325 }
00326 
00327 
00328 bool KoPictureEps::load(const QByteArray& array, const QString& /* extension */ )
00329 {
00330 
00331     kdDebug(30003) << "KoPictureEps::load" << endl;
00332     // First, read the raw data
00333     m_rawData=array;
00334 
00335     if (m_rawData.isNull())
00336     {
00337         kdError(30003) << "No data was loaded!" << endl;
00338         return false;
00339     }
00340 
00341     if ( ( m_rawData[0]==char(0xc5) ) && ( m_rawData[1]==char(0xd0) )
00342         && ( m_rawData[2]==char(0xd3) ) && ( m_rawData[3]==char(0xc6) ) )
00343     {
00344         // We have a so-called "MS-DOS EPS file", we have to extract the PostScript stream
00345         if (!extractPostScriptStream()) // Changes m_rawData
00346             return false;
00347     }
00348     else
00349     {
00350         m_psStreamStart = 0;
00351         m_psStreamLength = m_rawData.size();
00352     }
00353 
00354     QString lineBox; // Line with the bounding box
00355     bool lastWasCr = false; // Was the last character of the line a carriage return?
00356     uint pos = m_psStreamStart; // We start to search the bounding box at the start of the PostScript stream
00357     QString line( readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr ) );
00358     kdDebug(30003) << "Header: " << line << endl;
00359     if (!line.startsWith("%!"))
00360     {
00361         kdError(30003) << "Not a PostScript file!" << endl;
00362         return false;
00363     }
00364     QRect rect;
00365     for(;;)
00366     {
00367         ++pos; // Get over the previous line end (CR or LF)
00368         line = readLine( m_rawData,  m_psStreamStart, m_psStreamLength, pos, lastWasCr );
00369         kdDebug(30003) << "Checking line: " << line << endl;
00370         // ### TODO: it seems that the bounding box can be delayed in the trailer (GhostScript 7.07 does not support it either.)
00371         if (line.startsWith("%%BoundingBox:"))
00372         {
00373             lineBox=line;
00374             break;
00375         }
00376         else if (!line.startsWith("%%"))
00377             break; // Not a EPS comment anymore, so abort as we are not in the EPS header anymore
00378     }
00379     if (lineBox.isEmpty())
00380     {
00381         kdError(30003) << "KoPictureEps::load: could not find bounding box!" << endl;
00382         return false;
00383     }
00384     QRegExp exp("(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)");
00385     // ### FIXME: check return value of search, as the bounding box could be marked as "delayed" (What is the exact text then?)
00386     exp.search(lineBox);
00387     kdDebug(30003) << "Reg. Exp. Found: " << exp.capturedTexts() << endl;
00388     rect.setLeft((int)exp.cap(1).toDouble());
00389     rect.setTop((int)exp.cap(2).toDouble());
00390     rect.setRight((int)exp.cap(3).toDouble());
00391     rect.setBottom((int)exp.cap(4).toDouble());
00392     m_boundingBox=rect;
00393     m_originalSize=rect.size();
00394     kdDebug(30003) << "Rect: " << rect << " Size: "  << m_originalSize << endl;
00395     return true;
00396 }
00397 
00398 bool KoPictureEps::save(QIODevice* io)
00399 {
00400     // We save the raw data, to avoid damaging the file by many load/save cycles
00401     Q_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns Q_LONG but size() Q_ULONG!
00402     return (size==m_rawData.size());
00403 }
00404 
00405 QSize KoPictureEps::getOriginalSize(void) const
00406 {
00407     return m_originalSize;
00408 }
00409 
00410 QPixmap KoPictureEps::generatePixmap(const QSize& size, bool smoothScale)
00411 {
00412     scaleAndCreatePixmap(size,!smoothScale, 0, 0);
00413     return m_cachedPixmap;
00414 }
00415 
00416 QString KoPictureEps::getMimeType(const QString&) const
00417 {
00418     return "image/x-eps";
00419 }
00420 
00421 QImage KoPictureEps::generateImage(const QSize& size)
00422 {
00423     // 0, 0 == resolution unknown
00424     return scaleWithGhostScript(size, 0, 0);
00425 }
00426 
00427 void KoPictureEps::clearCache(void)
00428 {
00429     m_cachedPixmap.resize(0, 0);
00430     m_cacheIsInFastMode=true;
00431     m_cachedSize=QSize();
00432 }
KDE Logo
This file is part of the documentation for lib Library Version 1.3.5.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Fri Mar 11 11:47:42 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003