some more work on collabsible albums. I think I will need to optimize the playlist...
[amarok.git] / src / osd.cpp
blob218b874116f38c5b29691ac3e318662cc3f3f15b
1 /*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * osd.cpp: Shows some text in a pretty way independent to the WM
8 * begin: Fre Sep 26 2003
9 * copyright: (C) 2004 Christian Muehlhaeuser <chris@chris.de>
10 * (C) 2004-2006 Seb Ruiz <ruiz@kde.org>
11 * (C) 2004, 2005 Max Howell
12 * (C) 2005 Gábor Lehel <illissius@gmail.com>
15 #include "osd.h"
17 #include "amarok.h"
18 #include "amarokconfig.h"
19 #include "collectiondb.h" //for albumCover location
20 #include "debug.h"
21 #include "enginecontroller.h"
22 //if osdUsePlaylistColumns()
23 #include "podcastbundle.h"
24 #include "qstringx.h"
25 #include "StarManager.h"
27 #include <KApplication>
28 #include <KStandardDirs> //locate
30 #include <QBitmap>
31 #include <QDesktopWidget>
32 #include <QMouseEvent>
33 #include <QPainter>
34 #include <QPixmap>
35 #include <QRegExp>
36 #include <QTimer>
37 #include <QVector>
39 namespace ShadowEngine
41 QImage makeShadow( const QPixmap &textPixmap, const QColor &bgColor );
45 #define MOODBAR_HEIGHT 20
48 OSDWidget::OSDWidget( QWidget *parent, const char *name )
49 : QWidget( parent, Qt::Window | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint
50 | Qt::FramelessWindowHint | Qt::CustomizeWindowHint )
51 , m_duration( 2000 )
52 , m_timer( new QTimer( this ) )
53 , m_alignment( Middle )
54 , m_screen( 0 )
55 , m_y( MARGIN )
56 , m_drawShadow( false )
57 , m_rating( 0 )
58 , m_volume( false )
60 setObjectName( name );
61 setFocusPolicy( Qt::NoFocus );
62 unsetColors();
64 connect( m_timer, SIGNAL(timeout()), SLOT(hide()) );
65 connect( CollectionDB::instance(), SIGNAL( ratingChanged( const QString&, int ) ),
66 this, SLOT( ratingChanged( const QString&, int ) ) );
68 //or crashes, KWindowSystem bug I think, crashes in QWidget::icon()
69 kapp->setTopWidget( this );
72 void
73 OSDWidget::show( const QString &text, QImage newImage )
75 if ( !newImage.isNull() )
77 m_cover = newImage;
78 int w = m_scaledCover.width();
79 int h = m_scaledCover.height();
80 m_scaledCover = m_cover.scaled( w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
82 if( isShown() )
83 show();
86 void
87 OSDWidget::ratingChanged( const QString& path, int rating )
89 Meta::TrackPtr track = EngineController::instance()->currentTrack();
90 if( !track )
91 return;
92 if( track->playableUrl().isLocalFile() && track->playableUrl().path() == path )
93 ratingChanged( rating );
96 void
97 OSDWidget::ratingChanged( const short rating )
99 m_text = '\n' + i18n( "Rating changed" );
100 setRating( rating ); //Checks isEnabled() before doing anything
102 show();
106 void
107 OSDWidget::volChanged( unsigned char volume )
109 if ( isEnabled() )
111 m_volume = true;
112 m_newvolume = volume;
113 m_text = m_newvolume ? i18n("Volume: %1%", m_newvolume) : i18n("Mute");
115 show();
119 void
120 OSDWidget::show() //virtual
122 if ( !isEnabled() || m_text.isEmpty() )
123 return;
125 const uint M = fontMetrics().width( 'x' );
127 const QRect oldGeometry = QRect( pos(), size() );
128 const QRect newGeometry = determineMetrics( M );
130 if( newGeometry.width() > 0 && newGeometry.height() > 0 )
132 m_m = M;
133 m_size = newGeometry.size();
134 //render( M, newGeometry.size() );
135 setGeometry( newGeometry );
136 QWidget::show();
137 // bitBlt( this, 0, 0, &m_buffer );
139 if( m_duration ) //duration 0 -> stay forever
140 m_timer->start( m_duration, true ); //calls hide()
142 else
143 warning() << "Attempted to make an invalid sized OSD\n";
145 update();
148 QRect
149 OSDWidget::determineMetrics( const uint M )
151 // sometimes we only have a tiddly cover
152 const QSize minImageSize = m_cover.size().boundedTo( QSize(100,100) );
154 // determine a sensible maximum size, don't cover the whole desktop or cross the screen
155 const QSize margin( (M + MARGIN) * 2, (M + MARGIN) * 2 ); //margins
156 const QSize image = m_cover.isNull() ? QSize( 0, 0 ) : minImageSize;
157 const QSize max = QApplication::desktop()->screen( m_screen )->size() - margin;
159 // If we don't do that, the boundingRect() might not be suitable for drawText() (Qt issue N67674)
160 m_text.replace( QRegExp(" +\n"), "\n" );
161 // remove consecutive line breaks
162 m_text.replace( QRegExp("\n+"), "\n" );
164 // The osd cannot be larger than the screen
165 QRect rect = fontMetrics().boundingRect( 0, 0, max.width() - image.width(), max.height(),
166 Qt::AlignCenter | Qt::WordBreak, m_text );
168 if( m_volume )
170 static const QString tmp = QString ("******").insert( 3,
171 ( i18n("Volume: 100%").length() >= i18n("Mute").length() )?
172 i18n("Volume: 100%") : i18n("Mute") );
174 QRect tmpRect = fontMetrics().boundingRect( 0, 0,
175 max.width() - image.width(), max.height() - fontMetrics().height(),
176 Qt::AlignCenter | Qt::WordBreak, tmp );
177 tmpRect.setHeight( tmpRect.height() + fontMetrics().height() / 2 );
179 rect = tmpRect;
182 if( m_rating )
184 QPixmap* star = StarManager::instance()->getStar( 1 );
185 if( rect.width() < star->width() * 5 )
186 rect.setWidth( star->width() * 5 ); //changes right edge position
187 rect.setHeight( rect.height() + star->height() + M ); //changes bottom edge pos
190 if( useMoodbar() )
191 rect.setHeight( rect.height() + MOODBAR_HEIGHT + M );
193 if( !m_cover.isNull() )
195 const int availableWidth = max.width() - rect.width() - M; //WILL be >= (minImageSize.width() - M)
197 m_scaledCover = m_cover.scaled(
198 qMin( availableWidth, m_cover.width() ),
199 qMin( rect.height(), m_cover.height() ),
200 Qt::KeepAspectRatio, Qt::SmoothTransformation ); //this will force us to be with our bounds
202 int shadowWidth = 0;
203 if( m_drawShadow && !m_scaledCover.hasAlpha() &&
204 ( m_scaledCover.width() > 22 || m_scaledCover.height() > 22 ) )
205 shadowWidth = static_cast<uint>( m_scaledCover.width() / 100.0 * 6.0 );
207 const int widthIncludingImage = rect.width()
208 + m_scaledCover.width()
209 + shadowWidth
210 + M; //margin between text + image
212 rect.setWidth( widthIncludingImage );
215 // expand in all directions by M
216 rect.adjust( -M, -M, M, M );
218 const QSize newSize = rect.size();
219 const QRect screen = QApplication::desktop()->screenGeometry( m_screen );
220 QPoint newPos( MARGIN, m_y );
222 switch( m_alignment )
224 case Left:
225 break;
227 case Right:
228 newPos.rx() = screen.width() - MARGIN - newSize.width();
229 break;
231 case Center:
232 newPos.ry() = (screen.height() - newSize.height()) / 2;
234 //FALL THROUGH
236 case Middle:
237 newPos.rx() = (screen.width() - newSize.width()) / 2;
238 break;
241 //ensure we don't dip below the screen
242 if ( newPos.y() + newSize.height() > screen.height() - MARGIN )
243 newPos.ry() = screen.height() - MARGIN - newSize.height();
245 // correct for screen position
246 newPos += screen.topLeft();
248 return QRect( newPos, rect.size() );
251 void
252 OSDWidget::paintEvent( QPaintEvent* )
254 /// render with margin/spacing @param M and @param size
256 uint M = m_m;
257 QSize size = m_size;
259 QPoint point;
260 QRect rect( point, size );
261 rect.adjust( 0, 0, -1, -1 );
263 // From qt sources
264 const uint xround = (M * 200) / size.width();
265 const uint yround = (M * 200) / size.height();
267 { /// apply the mask
268 static QBitmap mask;
270 mask.resize( size );
271 mask.fill( Qt::color0 );
273 QPainter p( &mask );
274 p.setBrush( Qt::color1 );
275 p.drawRoundRect( rect, xround, yround );
276 setMask( mask );
279 QColor shadowColor;
281 int h,s,v;
282 foregroundColor().getHsv( &h, &s, &v );
283 shadowColor = v > 128 ? Qt::black : Qt::white;
286 int align = Qt::AlignCenter | Qt::WordBreak;
288 QPainter p( this );
290 p.fillRect( rect, backgroundColor() );
292 p.setPen( backgroundColor().dark() );
293 p.drawRoundRect( rect, xround, yround );
295 rect.adjust( M, M, -M, -M );
297 if( !m_cover.isNull() )
299 QRect r( rect );
300 r.setTop( (size.height() - m_scaledCover.height()) / 2 );
301 r.setSize( m_scaledCover.size() );
303 if( !m_scaledCover.hasAlpha() && m_drawShadow &&
304 ( m_scaledCover.width() > 22 || m_scaledCover.height() > 22 ) ) {
305 // don't draw a shadow for eg, the Amarok icon
306 QImage shadow;
307 const uint shadowSize = static_cast<uint>( m_scaledCover.width() / 100.0 * 6.0 );
309 const QString folder = Amarok::saveLocation( "covershadow-cache/" );
310 const QString file = QString( "shadow_albumcover%1x%2.png" ).arg( m_scaledCover.width() + shadowSize )
311 .arg( m_scaledCover.height() + shadowSize );
312 if ( QFile::exists( folder + file ) )
313 shadow.load( folder + file );
314 else {
315 shadow.load( KStandardDirs::locate( "data", "amarok/images/shadow_albumcover.png" ) );
316 shadow = shadow.scaled( m_scaledCover.width() + shadowSize, m_scaledCover.height() + shadowSize,
317 Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
318 shadow.save( folder + file, "PNG" );
321 QPixmap target;
322 target.convertFromImage( shadow ); //FIXME slow
323 copyBlt( &target, 0, 0, &m_scaledCover );
324 m_scaledCover = target;
325 r.setTop( (size.height() - m_scaledCover.height()) / 2 );
326 r.setSize( m_scaledCover.size() );
329 p.drawPixmap( r.topLeft(), m_scaledCover );
331 rect.rLeft() += m_scaledCover.width() + M;
334 QPixmap* star = StarManager::instance()->getStar( m_rating/2 );
335 int graphicsHeight = 0;
337 if( useMoodbar() )
339 QPixmap moodbar
340 = m_moodbarBundle.moodbar().draw( rect.width(), MOODBAR_HEIGHT );
341 QRect r( rect );
342 r.setTop( rect.bottom() - moodbar.height()
343 - (m_rating ? star->height() + M : 0) );
344 graphicsHeight += moodbar.height() + M;
346 p.drawPixmap( r.left(), r.top(), moodbar );
347 m_moodbarBundle = MetaBundle();
350 if( m_rating > 0 )
352 QRect r( rect );
354 //Align to center...
355 r.setLeft(( rect.left() + rect.width() / 2 ) - star->width() * m_rating / 4 );
356 r.setTop( rect.bottom() - star->height() );
357 graphicsHeight += star->height() + M;
359 bool half = m_rating%2;
361 if( half )
363 QPixmap* halfStar = StarManager::instance()->getHalfStar( m_rating/2 + 1 );
364 p.drawPixmap( r.left() + star->width() * ( m_rating / 2 ), r.top(), *halfStar );
365 star = StarManager::instance()->getStar( m_rating/2 + 1 );
368 for( int i = 0; i < m_rating/2; i++ )
370 p.drawPixmap( r.left() + i * star->width(), r.top(), *star );
373 m_rating = 0;
376 rect.setBottom( rect.bottom() - graphicsHeight );
378 // Draw "shadow" text effect (black outline)
379 if( m_drawShadow )
381 QPixmap pixmap( rect.size() + QSize(10,10) );
382 pixmap.fill( Qt::black );
384 QPainter p2( &pixmap );
385 p2.setFont( font() );
386 p2.setPen( Qt::white );
387 p2.setBrush( Qt::white );
388 p2.drawText( QRect(QPoint(5,5), rect.size()), align , m_text );
389 p2.end();
391 p.drawImage( rect.topLeft() - QPoint(5,5), ShadowEngine::makeShadow( pixmap, shadowColor ) );
394 p.setPen( foregroundColor() );
395 p.setFont( font() );
396 p.drawText( rect, align, m_text );
399 bool
400 OSDWidget::event( QEvent *e )
402 switch( e->type() )
404 case QEvent::ApplicationPaletteChange:
405 if( !AmarokConfig::osdUseCustomColors() )
406 unsetColors(); //use new palette's colours
407 return true;
409 default:
410 return QWidget::event( e );
414 void
415 OSDWidget::mousePressEvent( QMouseEvent* )
417 hide();
420 void
421 OSDWidget::unsetColors()
423 const QColorGroup c = QApplication::palette().active();
425 setPaletteForegroundColor( c.highlightedText() );
426 setPaletteBackgroundColor( c.highlight() );
429 void
430 OSDWidget::setScreen( int screen )
432 #if 0
433 const int n = QApplication::desktop()->numScreens();
434 m_screen = (screen >= n) ? n-1 : screen;
435 #endif
438 bool
439 OSDWidget::useMoodbar( void )
441 return (m_moodbarBundle.moodbar().state() == Moodbar::Loaded &&
442 AmarokConfig::showMoodbar() );
445 ////// OSDPreviewWidget below /////////////////////
447 #include <kcursor.h>
448 #include <kiconloader.h>
449 #include <klocale.h>
451 namespace Amarok
453 QImage icon() { return QImage( KIconLoader().iconPath( "amarok", -KIconLoader::SizeHuge ) ); }
456 OSDPreviewWidget::OSDPreviewWidget( QWidget *parent )
457 : OSDWidget( parent, "osdpreview" )
458 , m_dragging( false )
460 m_text = i18n( "OSD Preview - drag to reposition" );
461 m_duration = 0;
462 m_alignment = Center;
463 m_cover = Amarok::icon();
466 void OSDPreviewWidget::mousePressEvent( QMouseEvent *event )
468 m_dragOffset = event->pos();
470 if( event->button() == Qt::LeftButton && !m_dragging ) {
471 grabMouse( Qt::SizeAllCursor );
472 m_dragging = true;
477 void OSDPreviewWidget::mouseReleaseEvent( QMouseEvent * /*event*/ )
479 if( m_dragging )
481 m_dragging = false;
482 releaseMouse();
484 // compute current Position && offset
485 QDesktopWidget *desktop = QApplication::desktop();
486 int currentScreen = desktop->screenNumber( pos() );
488 if( currentScreen != -1 ) {
489 // set new data
490 m_screen = currentScreen;
491 m_y = QWidget::y();
493 emit positionChanged();
499 void OSDPreviewWidget::mouseMoveEvent( QMouseEvent *e )
501 if( m_dragging && this == mouseGrabber() )
503 // Here we implement a "snap-to-grid" like positioning system for the preview widget
505 const QRect screen = QApplication::desktop()->screenGeometry( m_screen );
506 const uint hcenter = screen.width() / 2;
507 const uint eGlobalPosX = e->globalPos().x() - screen.left();
508 const uint snapZone = screen.width() / 24;
510 QPoint destination = e->globalPos() - m_dragOffset - screen.topLeft();
511 int maxY = screen.height() - height() - MARGIN;
512 if( destination.y() < MARGIN ) destination.ry() = MARGIN;
513 if( destination.y() > maxY ) destination.ry() = maxY;
515 if( eGlobalPosX < (hcenter-snapZone) ) {
516 m_alignment = Left;
517 destination.rx() = MARGIN;
519 else if( eGlobalPosX > (hcenter+snapZone) ) {
520 m_alignment = Right;
521 destination.rx() = screen.width() - MARGIN - width();
523 else {
524 const uint eGlobalPosY = e->globalPos().y() - screen.top();
525 const uint vcenter = screen.height()/2;
527 destination.rx() = hcenter - width()/2;
529 if( eGlobalPosY >= (vcenter-snapZone) && eGlobalPosY <= (vcenter+snapZone) )
531 m_alignment = Center;
532 destination.ry() = vcenter - height()/2;
534 else m_alignment = Middle;
537 destination += screen.topLeft();
539 move( destination );
545 ////// Amarok::OSD below /////////////////////
547 #include "metabundle.h"
549 Amarok::OSD::OSD(): OSDWidget( 0 )
551 connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString&, const QString& ) ),
552 this, SLOT( slotCoverChanged( const QString&, const QString& ) ) );
553 connect( CollectionDB::instance(), SIGNAL( imageFetched( const QString& ) ),
554 this, SLOT( slotImageChanged( const QString& ) ) );
557 void
558 Amarok::OSD::show( const MetaBundle &bundle ) //slot
560 #if 0 //TODO:PORT
561 QString text = "";
562 if( bundle.url().isEmpty() )
563 text = i18n( "No track playing" );
565 else
567 QVector<QString> tags;
568 tags.append(bundle.prettyTitle());
569 for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i )
570 tags.append(bundle.prettyText( i ));
572 if( bundle.length() <= 0 )
573 tags[PlaylistItem::Length+1].clear();
575 if( AmarokConfig::osdUsePlaylistColumns() )
577 QString tag;
578 QVector<int> availableTags; //eg, ones that aren't empty
579 static const QList<int> parens = //display these in parentheses
580 QList<int>() << PlaylistItem::PlayCount << PlaylistItem::Year << PlaylistItem::Comment
581 << PlaylistItem::Genre << PlaylistItem::Length << PlaylistItem::Bitrate
582 << PlaylistItem::LastPlayed << PlaylistItem::Score << PlaylistItem::Filesize;
583 OSDWidget::setMoodbar();
584 OSDWidget::setRating( 0 );
585 for( int i = 0, n = Playlist::instance()->numVisibleColumns(); i < n; ++i )
587 const int column = Playlist::instance()->mapToLogicalColumn( i );
588 if( !tags.at( column + 1 ).isEmpty() && column != PlaylistItem::Rating )
589 availableTags.append(column);
590 if( column == PlaylistItem::Rating )
591 OSDWidget::setRating( bundle.rating() );
592 else if( column == PlaylistItem::Mood )
593 OSDWidget::setMoodbar( bundle );
596 for( int n = availableTags.count(), i = 0; i < n; ++i )
598 const int column = availableTags.at( i );
599 QString append = ( i == 0 ) ? ""
600 : ( n > 1 && i == n / 2 ) ? "\n"
601 : ( parens.contains( column ) || parens.contains( availableTags.at( i - 1 ) ) ) ? " "
602 : i18n(" - ");
603 append += ( parens.contains( column ) ? "(%1)" : "%1" );
604 text += append.arg( tags.at( column + 1 ) );
607 else
609 QMap<QString, QString> args;
610 args["prettytitle"] = bundle.prettyTitle();
611 for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i )
612 args[bundle.exactColumnName( i ).toLower()] = bundle.prettyText( i );
614 if( bundle.length() <= 0 )
615 args["length"].clear();
618 uint time=EngineController::instance()->engine()->position();
619 uint sec=(time/1000)%60; //is there a better way to calculate the time?
620 time /= 1000;
621 uint min=(time/60)%60;
622 time /= 60;
623 uint hour=(time/60)%60;
624 QString timeformat="";
625 if(hour!=0)
627 timeformat += QString::number(hour);
628 timeformat +=':';
630 timeformat +=QString::number(min);
631 timeformat +=':';
632 if(sec<10)
633 timeformat +='0';
634 timeformat +=QString::number(sec);
635 args["elapsed"]=timeformat;
636 QStringx osd = AmarokConfig::osdText();
638 // hacky, but works...
639 if( osd.contains( "%rating" ) )
640 OSDWidget::setRating( AmarokConfig::useRatings() ? bundle.rating() : 0 );
641 else
642 OSDWidget::setRating( 0 );
644 osd.replace( "%rating", "" );
646 if( osd.contains( "%moodbar" ) && AmarokConfig::showMoodbar() )
647 OSDWidget::setMoodbar( bundle );
648 osd.replace( "%moodbar", "" );
650 text = osd.namedOptArgs( args );
652 // KDE 3.3 rejects \n in the .kcfg file, and KConfig turns \n into \\n, so...
653 text.replace( "\\n", "\n" );
656 if ( AmarokConfig::osdCover() ) {
657 //avoid showing the generic cover. we can overwrite this by passing an arg.
658 //get large cover for scaling if big cover needed
660 QString location = QString();
661 if( bundle.podcastBundle() )
662 location = CollectionDB::instance()->podcastImage( bundle, false, 0 );
663 else
664 location = CollectionDB::instance()->albumImage( bundle, false, 0 );
666 if ( location.find( "nocover" ) != -1 )
667 setImage( Amarok::icon() );
668 else
669 setImage( QImage( location ) );
672 text = text.trimmed();
675 if( text.isEmpty() )
676 text = MetaBundle::prettyTitle( bundle.url().fileName() ).trimmed();
678 if( text.startsWith( "- " ) ) //When we only have a title tag, _something_ prepends a fucking hyphen. Remove that.
679 text = text.mid( 2 );
681 if( text.isEmpty() ) //still
682 text = i18n("No information available for this track");
684 OSDWidget::show( text );
685 #endif
688 void
689 Amarok::OSD::applySettings()
691 setAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) );
692 setDuration( AmarokConfig::osdDuration() );
693 setEnabled( AmarokConfig::osdEnabled() );
694 setOffset( AmarokConfig::osdYOffset() );
695 setScreen( AmarokConfig::osdScreen() );
696 setFont( AmarokConfig::osdFont() );
697 setDrawShadow( AmarokConfig::osdDrawShadow() );
699 if( AmarokConfig::osdUseCustomColors() )
701 setTextColor( AmarokConfig::osdTextColor() );
703 else unsetColors();
706 void
707 Amarok::OSD::forceToggleOSD()
709 if ( !isShown() ) {
710 const bool b = isEnabled();
711 setEnabled( true );
712 //TODO paort 2.0: fix this
713 //show( EngineController::instance()->bundle() );
714 setEnabled( b );
716 else
717 hide();
720 void
721 Amarok::OSD::slotCoverChanged( const QString &artist, const QString &album )
723 #if 0
724 if( AmarokConfig::osdCover() && artist == EngineController::instance()->bundle().artist()
725 && album == EngineController::instance()->bundle().album() )
727 QString location = CollectionDB::instance()->albumImage( artist, album, false, 0 );
729 if( location.find( "nocover" ) != -1 )
730 setImage( Amarok::icon() );
731 else
732 setImage( location );
734 #endif
737 void
738 Amarok::OSD::slotImageChanged( const QString &remoteURL )
740 #if 0
741 QString url = EngineController::instance()->bundle().url().url();
742 PodcastEpisodeBundle peb;
743 if( CollectionDB::instance()->getPodcastEpisodeBundle( url, &peb ) )
745 PodcastChannelBundle pcb;
746 if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent().url(), &pcb ) )
748 if( pcb.imageURL().url() == remoteURL )
750 QString location = CollectionDB::instance()->podcastImage( remoteURL, false, 0 );
751 if( location == CollectionDB::instance()->notAvailCover( false, 0 ) )
752 setImage( Amarok::icon() );
753 else
754 setImage( location );
758 #endif
762 /* Code copied from kshadowengine.cpp
764 * Copyright (C) 2003 Laur Ivan <laurivan@eircom.net>
766 * Many thanks to:
767 * - Bernardo Hung <deciare@gta.igs.net> for the enhanced shadow
768 * algorithm (currently used)
769 * - Tim Jansen <tim@tjansen.de> for the API updates and fixes.
771 * This library is free software; you can redistribute it and/or
772 * modify it under the terms of the GNU Library General Public
773 * License version 2 as published by the Free Software Foundation.
775 * This library is distributed in the hope that it will be useful,
776 * but WITHOUT ANY WARRANTY; without even the implied warranty of
777 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
778 * Library General Public License for more details.
780 * You should have received a copy of the GNU Library General Public License
781 * along with this library; see the file COPYING.LIB. If not, write to
782 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
783 * Boston, MA 02110-1301, USA.
786 namespace ShadowEngine
788 // Not sure, doesn't work above 10
789 static const int MULTIPLICATION_FACTOR = 3;
790 // Multiplication factor for pixels directly above, under, or next to the text
791 static const double AXIS_FACTOR = 2.0;
792 // Multiplication factor for pixels diagonal to the text
793 static const double DIAGONAL_FACTOR = 0.1;
794 // Self explanatory
795 static const int MAX_OPACITY = 200;
797 double decay( QImage&, int, int );
799 QImage makeShadow( const QPixmap& textPixmap, const QColor &bgColor )
801 const int w = textPixmap.width();
802 const int h = textPixmap.height();
803 const int bgr = bgColor.red();
804 const int bgg = bgColor.green();
805 const int bgb = bgColor.blue();
807 int alphaShadow;
809 // This is the source pixmap
810 QImage img = textPixmap.toImage();
812 QImage result( w, h, QImage::Format_ARGB32 );
813 result.fill( 0 ); // fill with black
815 static const int M = 5;
816 for( int i = M; i < w - M; i++) {
817 for( int j = M; j < h - M; j++ )
819 alphaShadow = (int) decay( img, i, j );
821 result.setPixel( i,j, qRgba( bgr, bgg , bgb, qMin( MAX_OPACITY, alphaShadow ) ) );
825 return result;
828 double decay( QImage& source, int i, int j )
830 //if ((i < 1) || (j < 1) || (i > source.width() - 2) || (j > source.height() - 2))
831 // return 0;
833 double alphaShadow;
834 alphaShadow =(qGray(source.pixel(i-1,j-1)) * DIAGONAL_FACTOR +
835 qGray(source.pixel(i-1,j )) * AXIS_FACTOR +
836 qGray(source.pixel(i-1,j+1)) * DIAGONAL_FACTOR +
837 qGray(source.pixel(i ,j-1)) * AXIS_FACTOR +
839 qGray(source.pixel(i ,j+1)) * AXIS_FACTOR +
840 qGray(source.pixel(i+1,j-1)) * DIAGONAL_FACTOR +
841 qGray(source.pixel(i+1,j )) * AXIS_FACTOR +
842 qGray(source.pixel(i+1,j+1)) * DIAGONAL_FACTOR) / MULTIPLICATION_FACTOR;
844 return alphaShadow;
848 #include "osd.moc"