File indexing completed on 2024-05-05 16:39:02

0001 /* This file is part of the KDE project
0002    Copyright (C) 1998-2002 Carsten Pfeiffer <pfeiffer@kde.org>
0003 
0004    This program is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU General Public
0006    License as published by the Free Software Foundation, version 2.
0007 
0008    This program is distributed in the hope that it will be useful,
0009    but WITHOUT ANY WARRANTY; without even the implied warranty of
0010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0011     General Public License for more details.
0012 
0013    You should have received a copy of the GNU General Public License
0014    along with this program; see the file COPYING.  If not, write to
0015    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0016    Boston, MA 02110-1301, USA.
0017 */
0018 
0019 #include "imlibwidget.h"
0020 
0021 #include <KCursor>
0022 
0023 #include <QApplication>
0024 #include <QCloseEvent>
0025 #include <QColor>
0026 #include <QDesktopWidget>
0027 #include <QFile>
0028 #include <QImage>
0029 #include <QPalette>
0030 #include <QtGlobal>
0031 
0032 #include <assert.h>
0033 #include <stdlib.h>
0034 #include <unistd.h>
0035 #include <sys/time.h>
0036 
0037 #include "filecache.h"
0038 #include "kuickdata.h"
0039 #include "kuickfile.h"
0040 #include "kuickimage.h"
0041 #include "kuickshow_debug.h"
0042 #include "imagemods.h"
0043 
0044 
0045 const int ImlibWidget::ImlibOffset = 256;
0046 
0047 ImlibWidget::ImlibWidget( ImData *_idata, QWidget *parent ) :
0048   QWidget( parent )
0049 {
0050     idata       = _idata;
0051     deleteImData    = false;
0052     deleteImlibData     = true;
0053 
0054     if ( !idata ) { // if no imlib configuration was given, create one ourself
0055     idata = new ImData;
0056     deleteImData = true;
0057     }
0058 
0059     ImlibInitParams par;
0060 
0061     // PARAMS_PALETTEOVERRIDE taken out because of segfault in imlib :o(
0062     par.flags = ( PARAMS_REMAP | PARAMS_VISUALID |
0063           PARAMS_FASTRENDER | PARAMS_HIQUALITY | PARAMS_DITHER |
0064           PARAMS_IMAGECACHESIZE | PARAMS_PIXMAPCACHESIZE );
0065 
0066     Visual* defaultvis = DefaultVisual(getX11Display(), getX11Screen());
0067 
0068     par.paletteoverride = idata->ownPalette ? 1 : 0;
0069     par.remap           = idata->fastRemap ? 1 : 0;
0070     par.fastrender      = idata->fastRender ? 1 : 0;
0071     par.hiquality       = idata->dither16bit ? 1 : 0;
0072     par.dither          = idata->dither8bit ? 1 : 0;
0073     par.visualid        = defaultvis->visualid;
0074     uint maxcache       = idata->maxCache;
0075 
0076     // 0 == no cache
0077     par.imagecachesize  = maxcache * 1024;
0078     par.pixmapcachesize = maxcache * 1024;
0079 
0080     id = Imlib_init_with_params( getX11Display(), &par );
0081 
0082     init();
0083 }
0084 
0085 
0086 ImlibWidget::ImlibWidget( ImData *_idata, ImlibData *_id, QWidget *parent )
0087     : QWidget( parent )
0088 {
0089     id              = _id;
0090     idata           = _idata;
0091     deleteImData    = false;
0092     deleteImlibData = false;
0093 
0094     if ( !idata ) {
0095     idata = new ImData;
0096     deleteImData = true;
0097     }
0098 
0099     init();
0100 }
0101 
0102 
0103 void ImlibWidget::init()
0104 {
0105     int w = 1; // > 0 for XCreateWindow
0106     int h = 1;
0107     myBackgroundColor = Qt::black;
0108     m_kuim              = 0L;
0109     m_kuickFile = 0L;
0110 
0111     if ( !id )
0112     qFatal("ImlibWidget: Imlib not initialized, aborting.");
0113 
0114     setAttribute( Qt::WA_DeleteOnClose );
0115     setAutoRender( true );
0116 
0117     setPalette( QPalette( myBackgroundColor ));
0118     setBackgroundRole( QPalette::Window );
0119 
0120     imageCache = new ImageCache( id, 4 ); // cache 4 images (FIXME?)
0121     connect( imageCache, SIGNAL( sigBusy() ), SLOT( setBusyCursor() ));
0122     connect( imageCache, SIGNAL( sigIdle() ), SLOT( restoreCursor() ));
0123 
0124     win = XCreateSimpleWindow(getX11Display(), winId(), 0,0,w,h,0,0,0);
0125 }
0126 
0127 ImlibWidget::~ImlibWidget()
0128 {
0129     delete imageCache;
0130     if ( deleteImlibData && id ) free ( id );
0131     if ( win ) XDestroyWindow( getX11Display(), win );
0132     if ( deleteImData ) delete idata;
0133 }
0134 
0135 QUrl ImlibWidget::url() const
0136 {
0137     if ( m_kuickFile )
0138         return m_kuickFile->url();
0139 
0140     return QUrl();
0141 }
0142 
0143 KuickFile * ImlibWidget::currentFile() const
0144 {
0145     return m_kuickFile;
0146 }
0147 
0148 // tries to load "filename" and returns the according KuickImage *
0149 // or 0L if unsuccessful
0150 KuickImage * ImlibWidget::loadImageInternal( KuickFile * file )
0151 {
0152     assert( file->isAvailable() );
0153 
0154     // apply default image modifications
0155     mod.brightness = idata->brightness + ImlibOffset;
0156     mod.contrast = idata->contrast + ImlibOffset;
0157     mod.gamma = idata->gamma + ImlibOffset;
0158 
0159     KuickImage *kuim = imageCache->getKuimage( file );
0160     bool wasCached = true;
0161     if ( !kuim ) {
0162         wasCached = false;
0163         kuim = imageCache->loadImage( file, mod );
0164     }
0165 
0166     if ( !kuim ) {// couldn't load file, maybe corrupt or wrong format
0167         qWarning("ImlibWidget: can't load image %s", qUtf8Printable(file->url().toDisplayString()));
0168     return 0L;
0169     }
0170 
0171     loaded( kuim, wasCached ); // maybe upscale/downscale/rotate in subclasses
0172 
0173     return kuim;
0174 }
0175 
0176 // overridden in subclass
0177 void ImlibWidget::loaded( KuickImage *, bool /*wasCached*/ )
0178 {
0179 }
0180 
0181 bool ImlibWidget::loadImage( const QUrl& url )
0182 {
0183     return loadImage( FileCache::self()->getFile( url ));
0184 }
0185 
0186 bool ImlibWidget::loadImage( KuickFile * file )
0187 {
0188     if ( file->waitForDownload( this ) != KuickFile::OK)
0189         return false;
0190 
0191     KuickImage *kuim = loadImageInternal( file );
0192     // FIXME - check everywhere if we have a kuim or not!
0193 
0194     if ( kuim ) {
0195     m_kuim = kuim;
0196     autoUpdate( true ); // -> updateWidget() -> updateGeometry()
0197     m_kuickFile = file;
0198         return true;
0199     }
0200 
0201     return false;
0202 }
0203 
0204 
0205 bool ImlibWidget::cacheImage( const QUrl& url )
0206 {
0207 //    qDebug("cache image: %s", url.url().latin1());
0208     KuickFile *file = FileCache::self()->getFile( url );
0209     if ( file->isAvailable() )
0210         return cacheImage( file );
0211     else {
0212         if ( !file->download() ) {
0213             return false;
0214         }
0215         connect( file, SIGNAL( downloaded( KuickFile * )), SLOT( cacheImage( KuickFile * )) );
0216         return true; // optimistic
0217     }
0218 }
0219 
0220 bool ImlibWidget::cacheImage( KuickFile * file )
0221 {
0222 //    qDebug("cache image: %s", file->url().url().latin1());
0223     KuickImage *kuim = loadImageInternal( file );
0224     if ( kuim ) {
0225         kuim->renderPixmap();
0226         return true;
0227     }
0228     return false;
0229 }
0230 
0231 
0232 void ImlibWidget::showImage()
0233 {
0234     XMapWindow( getX11Display(), win );
0235     XSync( getX11Display(), False );
0236 }
0237 
0238 
0239 // -256..256
0240 void ImlibWidget::setBrightness( int factor )
0241 {
0242     mod.brightness = factor + ImlibOffset;
0243     setImageModifier();
0244 
0245     autoUpdate();
0246 }
0247 
0248 
0249 // -256..256
0250 void ImlibWidget::setContrast( int factor )
0251 {
0252     mod.contrast = factor + ImlibOffset;
0253     setImageModifier();
0254 
0255     autoUpdate();
0256 }
0257 
0258 
0259 // -256..256
0260 void ImlibWidget::setGamma( int factor )
0261 {
0262     mod.gamma = factor + ImlibOffset;
0263     setImageModifier();
0264 
0265     autoUpdate();
0266 }
0267 
0268 
0269 Rotation ImlibWidget::rotation() const
0270 {
0271     return m_kuim ? m_kuim->absRotation() : ROT_0;
0272 }
0273 
0274 FlipMode ImlibWidget::flipMode()  const
0275 {
0276     return m_kuim ? m_kuim->flipMode() : FlipNone;
0277 }
0278 
0279 void ImlibWidget::zoomImage( float factor )
0280 {
0281     if ( factor == 1 || factor == 0 || !m_kuim )
0282     return;
0283 
0284     int newWidth  = (int) (factor * (float) m_kuim->width());
0285     int newHeight = (int) (factor * (float) m_kuim->height());
0286 
0287     if ( canZoomTo( newWidth, newHeight ) )
0288     {
0289         m_kuim->resize( newWidth, newHeight, idata->smoothScale ? KuickImage::SMOOTH : KuickImage::FAST );
0290         autoUpdate( true );
0291     }
0292 }
0293 
0294 bool ImlibWidget::canZoomTo( int newWidth, int newHeight )
0295 {
0296     if ( newWidth <= 2 || newHeight <= 2 ) // minimum size for an image is 2x2 pixels
0297     return false;
0298 
0299     return true;
0300 }
0301 
0302 
0303 void ImlibWidget::showImageOriginalSize()
0304 {
0305     if ( !m_kuim )
0306     return;
0307 
0308     m_kuim->restoreOriginalSize();
0309     autoUpdate( true );
0310 
0311     showImage();
0312 }
0313 
0314 bool ImlibWidget::autoRotate( KuickImage *kuim )
0315 {
0316     /* KFileMetaInfo disappered in KF5.
0317      * TODO: find alternative to KFileMetaInfo
0318 
0319     KFileMetaInfo metadatas( kuim->file().localFile() );
0320     if ( !metadatas.isValid() )
0321         return false;
0322 
0323     KFileMetaInfoItem metaitem = metadatas.item("Orientation");
0324     if ( !metaitem.isValid() || metaitem.value().isNull() )
0325         return false;
0326 
0327 
0328     switch ( metaitem.value().toInt() )
0329     {
0330         //  Orientation:
0331         //  1:      normal
0332         //  2:      flipped horizontally
0333         //  3:      ROT 180
0334         //  4:      flipped vertically
0335         //  5:      ROT 90 -> flip horizontally
0336         //  6:      ROT 90
0337         //  7:      ROT 90 -> flip vertically
0338         //  8:      ROT 270
0339 
0340         case 1:
0341         default:
0342             kuim->rotateAbs( ROT_0 );
0343             break;
0344         case 2:
0345             kuim->flipAbs( FlipHorizontal );
0346             break;
0347         case 3:
0348             kuim->rotateAbs( ROT_180 );
0349             break;
0350         case 4:
0351             kuim->flipAbs( FlipVertical );
0352             break;
0353         case 5:
0354             kuim->rotateAbs( ROT_90 );
0355             kuim->flipAbs( FlipHorizontal );
0356             break;
0357         case 6:
0358             kuim->rotateAbs( ROT_90 );
0359             break;
0360         case 7:
0361             kuim->rotateAbs( ROT_90 );
0362             kuim->flipAbs( FlipVertical );
0363             break;
0364         case 8:
0365             kuim->rotateAbs( ROT_270 );
0366             break;
0367     }
0368     */
0369 
0370     return true;
0371 }
0372 
0373 
0374 void ImlibWidget::setRotation( Rotation rot )
0375 {
0376     if ( m_kuim )
0377     {
0378         if ( m_kuim->rotateAbs( rot ) )
0379             autoUpdate( true );
0380     }
0381 }
0382 
0383 
0384 // slots connected to Accels and popupmenu
0385 void ImlibWidget::rotate90()
0386 {
0387     if ( !m_kuim )
0388     return;
0389 
0390     m_kuim->rotate( ROT_90 );
0391     rotated( m_kuim, ROT_90 );
0392     autoUpdate( true );
0393 }
0394 
0395 void ImlibWidget::rotate180()
0396 {
0397     if ( !m_kuim )
0398     return;
0399 
0400     m_kuim->rotate( ROT_180 );
0401     rotated( m_kuim, ROT_180 );
0402     autoUpdate();
0403 }
0404 
0405 void ImlibWidget::rotate270()
0406 {
0407     if ( !m_kuim )
0408     return;
0409 
0410     m_kuim->rotate( ROT_270 );
0411     rotated( m_kuim, ROT_270 );
0412     autoUpdate( true );
0413 }
0414 
0415 
0416 // should this go into a subclass?
0417 void ImlibWidget::flipHoriz()
0418 {
0419     if ( !m_kuim )
0420     return;
0421 
0422     m_kuim->flip( FlipHorizontal );
0423     autoUpdate();
0424 }
0425 
0426 void ImlibWidget::flipVert()
0427 {
0428     if ( !m_kuim )
0429     return;
0430 
0431     m_kuim->flip( FlipVertical );
0432     autoUpdate();
0433 }
0434 // end slots
0435 
0436 
0437 void ImlibWidget::setFlipMode( int mode )
0438 {
0439     if ( !m_kuim )
0440         return;
0441 
0442     if ( m_kuim->flipAbs( mode ) )
0443     autoUpdate();
0444 }
0445 
0446 
0447 void ImlibWidget::updateWidget( bool geometryUpdate )
0448 {
0449     if ( !m_kuim )
0450     return;
0451 
0452 //     if ( geometryUpdate )
0453 //         XUnmapWindow( getX11Display(), win );// remove the old image -> no flicker
0454 
0455     XSetWindowBackgroundPixmap( getX11Display(), win, m_kuim->pixmap() );
0456 
0457     if ( geometryUpdate )
0458     updateGeometry( m_kuim->width(), m_kuim->height() );
0459 
0460     XClearWindow( getX11Display(), win );
0461 
0462     showImage();
0463 }
0464 
0465 
0466 // here we just use the size of m_kuim, may be overridden in subclass
0467 void ImlibWidget::updateGeometry( int w, int h )
0468 {
0469     XMoveWindow( getX11Display(), win, 0, 0 ); // center?
0470     XResizeWindow( getX11Display(), win, w, h );
0471     resize( w, h );
0472 }
0473 
0474 
0475 void ImlibWidget::closeEvent( QCloseEvent *e )
0476 {
0477     e->accept();
0478     QWidget::closeEvent( e );
0479 }
0480 
0481 
0482 void ImlibWidget::setBackgroundColor( const QColor& color )
0483 {
0484     myBackgroundColor = color;
0485     setPalette( QPalette( myBackgroundColor ));
0486     repaint(); // FIXME - necessary at all?
0487 }
0488 
0489 const QColor& ImlibWidget::backgroundColor() const
0490 {
0491     return myBackgroundColor;
0492 }
0493 
0494 
0495 void ImlibWidget::setImageModifier()
0496 {
0497     if ( !m_kuim )
0498     return;
0499 
0500     Imlib_set_image_modifier( id, m_kuim->imlibImage(), &mod );
0501     m_kuim->setDirty( true );
0502 }
0503 
0504 int ImlibWidget::imageWidth() const
0505 {
0506     return m_kuim ? m_kuim->width() : 0;
0507 }
0508 
0509 int ImlibWidget::imageHeight() const
0510 {
0511     return m_kuim ? m_kuim->height() : 0;
0512 }
0513 
0514 void ImlibWidget::setBusyCursor()
0515 {
0516     if ( testAttribute( Qt::WA_SetCursor ) )
0517         m_oldCursor = cursor();
0518     else
0519         m_oldCursor = QCursor();
0520 
0521     setCursor( QCursor( Qt::WaitCursor ) );
0522 }
0523 
0524 void ImlibWidget::restoreCursor()
0525 {
0526     if ( cursor().shape() == QCursor(Qt::WaitCursor).shape() ) // only if nobody changed the cursor in the meantime!
0527     setCursor( m_oldCursor );
0528 }
0529 /*
0530 // Reparenting a widget in Qt in fact means destroying the old X window of the widget
0531 // and creating a new one. And since the X window used for the Imlib image is a child
0532 // of this widget's X window, destroying this widget's X window would mean also
0533 // destroying the Imlib image X window. Therefore it needs to be temporarily reparented
0534 // away and reparented back to the new X window.
0535 // Reparenting may happen e.g. when doing the old-style (non-NETWM) fullscreen changes.
0536 void ImlibWidget::reparent( QWidget* parent, Qt::WFlags f, const QPoint& p, bool showIt )
0537 {
0538     XWindowAttributes attr;
0539     XGetWindowAttributes( getX11Display(), win, &attr );
0540     XUnmapWindow( getX11Display(), win );
0541     XReparentWindow( getX11Display(), win, attr.root, 0, 0 );
0542     QWidget::reparent( parent, f, p, showIt );
0543     XReparentWindow( getX11Display(), win, winId(), attr.x, attr.y );
0544     if( attr.map_state != IsUnmapped )
0545         XMapWindow( getX11Display(), win );
0546 }
0547 */
0548 void ImlibWidget::rotated( KuickImage *, int )
0549 {
0550 }
0551 
0552 
0553 int ImlibWidget::getX11Screen() const
0554 {
0555     return QApplication::desktop()->screenNumber(this);
0556 }
0557 
0558 
0559 //----------
0560 
0561 
0562 // uhh ugly, we have two lists to map from filename to KuickImage :-/
0563 ImageCache::ImageCache( ImlibData *id, int maxImages )
0564 {
0565     myId        = id;
0566     idleCount   = 0;
0567     myMaxImages = maxImages;
0568 }
0569 
0570 
0571 ImageCache::~ImageCache()
0572 {
0573     while ( !kuickList.isEmpty() ) {
0574         delete kuickList.takeFirst();
0575     }
0576     fileList.clear();
0577 }
0578 
0579 
0580 void ImageCache::setMaxImages( int maxImages )
0581 {
0582     myMaxImages = maxImages;
0583     int count   = kuickList.count();
0584     while ( count > myMaxImages ) {
0585     delete kuickList.takeLast();
0586     fileList.removeLast();
0587     count--;
0588     }
0589 }
0590 
0591 void ImageCache::slotBusy()
0592 {
0593     if ( idleCount == 0 )
0594     emit sigBusy();
0595 
0596     idleCount++;
0597 }
0598 
0599 void ImageCache::slotIdle()
0600 {
0601     idleCount--;
0602 
0603     if ( idleCount == 0 )
0604     emit sigIdle();
0605 }
0606 
0607 
0608 KuickImage * ImageCache::getKuimage( KuickFile * file )
0609 {
0610     if ( !file )
0611     return 0L;
0612 
0613     assert( file->isAvailable() ); // debug build
0614     if ( file->waitForDownload( 0L ) != KuickFile::OK ) // and for users
0615         return 0L;
0616 
0617     KuickImage *kuim = 0L;
0618     int index = fileList.indexOf( file );
0619     if ( index != -1 )
0620     {
0621         if ( index == 0 )
0622             kuim = kuickList.at( 0 );
0623 
0624         // need to reorder the lists, otherwise we might delete the current
0625         // image when a new one is cached and the current one is the last!
0626         else {
0627             kuim = kuickList.takeAt( index );
0628             kuickList.insert( 0, kuim );
0629             fileList.removeAll( file );
0630             fileList.prepend( file );
0631         }
0632 
0633         return kuim;
0634     }
0635 
0636     return 0L;
0637 }
0638 
0639 KuickImage * ImageCache::loadImage( KuickFile * file, ImlibColorModifier mod)
0640 {
0641     KuickImage *kuim = 0L;
0642     if ( !file || !file->isAvailable() )
0643         return 0L;
0644 
0645     slotBusy();
0646 
0647     // #ifndef NDEBUG
0648     //         struct timeval tms1, tms2;
0649     //         gettimeofday( &tms1, NULL );
0650     // #endif
0651 
0652     ImlibImage *im = Imlib_load_image( myId, QFile::encodeName( file->localFile() ).data() );
0653 
0654     // #ifndef NDEBUG
0655     //         gettimeofday( &tms2, NULL );
0656     //         qDebug("*** LOADING image: %s, took %ld ms", file.toLatin1(),
0657     //                (tms2.tv_usec - tms1.tv_usec)/1000);
0658     // #endif
0659 
0660     slotIdle();
0661     if ( !im ) {
0662         slotBusy();
0663         im = loadImageWithQt( file->localFile() );
0664         slotIdle();
0665         if ( !im )
0666             return 0L;
0667     }
0668 
0669     Imlib_set_image_modifier( myId, im, &mod );
0670     kuim = new KuickImage( file, im, myId );
0671     connect( kuim, SIGNAL( startRendering() ),   SLOT( slotBusy() ));
0672     connect( kuim, SIGNAL( stoppedRendering() ), SLOT( slotIdle() ));
0673 
0674     kuickList.insert( 0, kuim );
0675     fileList.prepend( file );
0676 
0677     if ( kuickList.count() > myMaxImages ) {
0678         //         qDebug(":::: now removing from cache: %s", (*fileList.fromLast()).toLatin1());
0679         delete kuickList.takeLast();
0680         fileList.removeLast();
0681     }
0682 
0683     return kuim;
0684 }
0685 
0686 
0687 // Note: the returned image's filename will not be the real filename (which it usually
0688 // isn't anyway, according to Imlib's sources).
0689 ImlibImage * ImageCache::loadImageWithQt( const QString& fileName ) const
0690 {
0691     qDebug("Trying to load %s with KImageIO...", qUtf8Printable(fileName));
0692 
0693     QImage image( fileName );
0694     if ( image.isNull() )
0695     return 0L;
0696     if ( image.depth() != 32 ) {
0697     image = image.convertToFormat(QImage::Format_RGB32);
0698 
0699     if ( image.isNull() )
0700     return 0L;
0701     }
0702 
0703     // convert to 24 bpp (discard alpha)
0704     int numPixels = image.width() * image.height();
0705     const int NUM_BYTES_NEW  = 3; // 24 bpp
0706     uchar *newImageData = new uchar[numPixels * NUM_BYTES_NEW];
0707     uchar *newData = newImageData;
0708 
0709     int w = image.width();
0710     int h = image.height();
0711 
0712     for (int y = 0; y < h; y++) {
0713     QRgb *scanLine = reinterpret_cast<QRgb *>( image.scanLine(y) );
0714     for (int x = 0; x < w; x++) {
0715         const QRgb& pixel = scanLine[x];
0716         *(newData++) = qRed(pixel);
0717         *(newData++) = qGreen(pixel);
0718         *(newData++) = qBlue(pixel);
0719     }
0720     }
0721 
0722     ImlibImage *im = Imlib_create_image_from_data( myId, newImageData, NULL,
0723                                                    image.width(), image.height() );
0724 
0725     delete[] newImageData;
0726 
0727     return im;
0728 }