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 }