File indexing completed on 2024-05-12 05:46:31
0001 /**************************************************************************************** 0002 * Copyright (c) 2003-2005 Max Howell <max.howell@methylblue.com> * 0003 * Copyright (c) 2005 Mark Kretschmann <kretschmann@kde.org> * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify it under * 0006 * the terms of the GNU General Public License as published by the Free Software * 0007 * Foundation; either version 2 of the License, or (at your option) any later * 0008 * version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0012 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0013 * * 0014 * You should have received a copy of the GNU General Public License along with * 0015 * this program. If not, see <http://www.gnu.org/licenses/>. * 0016 ****************************************************************************************/ 0017 0018 #include "blockAnalyzer.h" 0019 0020 #include <cmath> 0021 0022 #include <QPainter> //paletteChange() 0023 0024 static inline uint myMax( uint v1, uint v2 ) { return v1 > v2 ? v1 : v2; } 0025 0026 BlockAnalyzer::BlockAnalyzer( QWidget *parent ) 0027 : Analyzer::Base2D( parent, 9 ) 0028 , m_columns( 0 ) //uint 0029 , m_rows( 0 ) //uint 0030 , m_y( 0 ) //uint 0031 , m_barPixmap( 1, 1 ) //null qpixmaps cause crashes 0032 , m_topBarPixmap( WIDTH, HEIGHT ) 0033 , m_scope( MIN_COLUMNS ) //Scope 0034 , m_store( 1 << 8, 0 ) //vector<uint> 0035 , m_fade_bars( FADE_SIZE ) //vector<QPixmap> 0036 , m_fade_pos( 1 << 8, 50 ) //vector<uint> 0037 , m_fade_intensity( 1 << 8, 32 ) //vector<uint> 0038 { 0039 setMinimumSize( MIN_COLUMNS*(WIDTH+1) -1, MIN_ROWS*(HEIGHT+1) -1 ); //-1 is padding, no drawing takes place there 0040 setMaximumWidth( MAX_COLUMNS*(WIDTH+1) -1 ); 0041 setMaximumHeight( MIN_ROWS*(HEIGHT+1) -1 ); 0042 0043 // mxcl says null pixmaps cause crashes, so let's play it safe 0044 for (int i = 0; i < FADE_SIZE; ++i) 0045 m_fade_bars[i] = QPixmap( 1, 1 ); 0046 } 0047 0048 BlockAnalyzer::~BlockAnalyzer() 0049 { 0050 } 0051 0052 void 0053 BlockAnalyzer::resizeEvent( QResizeEvent *e ) 0054 { 0055 Analyzer::Base2D::resizeEvent( e ); 0056 0057 const uint oldRows = m_rows; 0058 0059 //all is explained in analyze().. 0060 //+1 to counter -1 in maxSizes, trust me we need this! 0061 m_columns = qMin<uint>( uint(double(width()+1) / (WIDTH+1)), MAX_COLUMNS ); 0062 m_rows = uint(double(height()+1) / (HEIGHT+1)); 0063 0064 //this is the y-offset for drawing from the top of the widget 0065 m_y = (height() - (m_rows * (HEIGHT+1)) + 2) / 2; 0066 0067 m_scope.resize( m_columns ); 0068 0069 if( m_rows != oldRows ) { 0070 m_barPixmap = QPixmap( WIDTH, m_rows*(HEIGHT+1) ); 0071 0072 for (int i = 0; i < FADE_SIZE; ++i) 0073 m_fade_bars[i] = QPixmap( WIDTH, m_rows*(HEIGHT+1) ); 0074 0075 m_yscale.resize( m_rows + 1 ); 0076 0077 const float PRE = 1, PRO = 1; //PRE and PRO allow us to restrict the range somewhat 0078 0079 for( uint z = 0; z < m_rows; ++z ) 0080 m_yscale[z] = 1 - (log10( PRE+z ) / log10( PRE+m_rows+PRO )); 0081 0082 m_yscale[m_rows] = 0; 0083 0084 determineStep(); 0085 paletteChange( palette() ); 0086 } 0087 else if( width() > e->oldSize().width() || height() > e->oldSize().height() ) 0088 drawBackground(); 0089 0090 analyze( m_scope ); 0091 } 0092 0093 void 0094 BlockAnalyzer::determineStep() 0095 { 0096 // falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels) 0097 // I calculated the value 30 based on some trial and error 0098 0099 const double fallTime = 30 * m_rows; 0100 m_step = double(m_rows * 80) / fallTime; //80 = ~milliseconds between signals with audio data 0101 } 0102 0103 void 0104 BlockAnalyzer::transform( QVector<float> &s ) //pure virtual 0105 { 0106 for(int x = 0; x < s.size(); ++x) 0107 s[x] *= 2; 0108 0109 float *front = static_cast<float*>( &s.front() ); 0110 0111 m_fht->spectrum( front ); 0112 m_fht->scale( front, 1.0 / 20 ); 0113 0114 //the second half is pretty dull, so only show it if the user has a large analyzer 0115 //by setting to m_scope.size() if large we prevent interpolation of large analyzers, this is good! 0116 s.resize( m_scope.size() <= MAX_COLUMNS/2 ? MAX_COLUMNS/2 : m_scope.size() ); 0117 } 0118 0119 void 0120 BlockAnalyzer::analyze( const QVector<float> &s ) 0121 { 0122 Analyzer::interpolate( s, m_scope ); 0123 update(); 0124 } 0125 0126 void 0127 BlockAnalyzer::paintEvent(QPaintEvent*) 0128 { 0129 // y = 2 3 2 1 0 2 0130 // . . . . # . 0131 // . . . # # . 0132 // # . # # # # 0133 // # # # # # # 0134 // 0135 // visual aid for how this analyzer works. 0136 // y represents the number of blanks 0137 // y starts from the top and increases in units of blocks 0138 0139 // m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 } 0140 // if it contains 6 elements there are 5 rows in the analyzer 0141 0142 0143 QPainter p( this ); 0144 0145 // Paint the background 0146 p.drawPixmap(0, 0, m_background); 0147 // p.fillRect(rect(), palette().color( QPalette::Active, QPalette::Window )); 0148 0149 uint y; 0150 0151 for(int x = 0; x < m_scope.size(); ++x) 0152 { 0153 // determine y 0154 for( y = 0; m_scope[x] < m_yscale[y]; ++y ) 0155 ; 0156 0157 // this is opposite to what you'd think, higher than y 0158 // means the bar is lower than y (physically) 0159 if( (float)y > m_store[x] ) 0160 y = uint(m_store[x] += m_step); 0161 else 0162 m_store[x] = y; 0163 0164 // if y is lower than m_fade_pos, then the bar has exceeded the height of the fadeout 0165 // if the fadeout is quite faded now, then display the new one 0166 if(y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) { 0167 m_fade_pos[x] = y; 0168 m_fade_intensity[x] = FADE_SIZE; 0169 } 0170 0171 if( m_fade_intensity[x] > 0 ) { 0172 const uint offset = --m_fade_intensity[x]; 0173 const uint y = m_y + (m_fade_pos[x] * (HEIGHT+1)); 0174 p.drawPixmap( x*(WIDTH+1), y, m_fade_bars[offset], 0, 0, WIDTH, height() - y ); 0175 } 0176 0177 if( m_fade_intensity[x] == 0 ) 0178 m_fade_pos[x] = m_rows; 0179 0180 //REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, m_rows means none are 0181 p.drawPixmap( x*(WIDTH+1), y*(HEIGHT+1) + m_y, *bar(), 0, y*(HEIGHT+1), -1, -1 ); 0182 } 0183 0184 for( uint x = 0; x < m_store.size(); ++x ) 0185 p.drawPixmap( x*(WIDTH+1), int(m_store[x])*(HEIGHT+1) + m_y, m_topBarPixmap ); 0186 } 0187 0188 0189 0190 0191 0192 static inline void 0193 adjustToLimits( int &b, int &f, uint &amount ) 0194 { 0195 // with a range of 0-255 and maximum adjustment of amount, 0196 // maximise the difference between f and b 0197 0198 if( b < f ) { 0199 if( b > 255 - f ) { 0200 amount -= f; 0201 f = 0; 0202 } else { 0203 amount -= (255 - f); 0204 f = 255; 0205 } 0206 } 0207 else { 0208 if( f > 255 - b ) { 0209 amount -= f; 0210 f = 0; 0211 } else { 0212 amount -= (255 - f); 0213 f = 255; 0214 } 0215 } 0216 } 0217 0218 /** 0219 * Clever contrast function 0220 * 0221 * It will try to adjust the foreground color such that it contrasts well with the background 0222 * It won't modify the hue of fg unless absolutely necessary 0223 * @return the adjusted form of fg 0224 */ 0225 QColor 0226 ensureContrast( const QColor &bg, const QColor &fg, uint _amount = 150 ) 0227 { 0228 class OutputOnExit { 0229 public: 0230 OutputOnExit( const QColor &color ) : c( color ) {} 0231 ~OutputOnExit() { int h,s,v; c.getHsv( &h, &s, &v ); } 0232 private: 0233 const QColor &c; 0234 }; 0235 0236 // hack so I don't have to cast everywhere 0237 #define amount static_cast<int>(_amount) 0238 // #define STAMP debug() << (QValueList<int>() << fh << fs << fv); 0239 // #define STAMP1( string ) debug() << string << ": " << (QValueList<int>() << fh << fs << fv); 0240 // #define STAMP2( string, value ) debug() << string << "=" << value << ": " << (QValueList<int>() << fh << fs << fv); 0241 0242 OutputOnExit allocateOnTheStack( fg ); 0243 0244 int bh, bs, bv; 0245 int fh, fs, fv; 0246 0247 bg.getHsv( &bh, &bs, &bv ); 0248 fg.getHsv( &fh, &fs, &fv ); 0249 0250 int dv = abs( bv - fv ); 0251 0252 // STAMP2( "DV", dv ); 0253 0254 // value is the best measure of contrast 0255 // if there is enough difference in value already, return fg unchanged 0256 if( dv > amount ) 0257 return fg; 0258 0259 int ds = abs( bs - fs ); 0260 0261 // STAMP2( "DS", ds ); 0262 0263 // saturation is good enough too. But not as good. TODO adapt this a little 0264 if( ds > amount ) 0265 return fg; 0266 0267 int dh = abs( bh - fh ); 0268 0269 // STAMP2( "DH", dh ); 0270 0271 if( dh > 120 ) { 0272 // a third of the colour wheel automatically guarentees contrast 0273 // but only if the values are high enough and saturations significant enough 0274 // to allow the colours to be visible and not be shades of grey or black 0275 0276 // check the saturation for the two colours is sufficient that hue alone can 0277 // provide sufficient contrast 0278 if( ds > amount / 2 && (bs > 125 && fs > 125) ) 0279 // STAMP1( "Sufficient saturation difference, and hues are compliemtary" ); 0280 return fg; 0281 else if( dv > amount / 2 && (bv > 125 && fv > 125) ) 0282 // STAMP1( "Sufficient value difference, and hues are compliemtary" ); 0283 return fg; 0284 0285 // STAMP1( "Hues are complimentary but we must modify the value or saturation of the contrasting colour" ); 0286 0287 //but either the colours are two desaturated, or too dark 0288 //so we need to adjust the system, although not as much 0289 ///_amount /= 2; 0290 } 0291 0292 if( fs < 50 && ds < 40 ) { 0293 // low saturation on a low saturation is sad 0294 const int tmp = 50 - fs; 0295 fs = 50; 0296 if( amount > tmp ) 0297 _amount -= tmp; 0298 else 0299 _amount = 0; 0300 } 0301 0302 // test that there is available value to honor our contrast requirement 0303 if( 255 - dv < amount ) 0304 { 0305 // we have to modify the value and saturation of fg 0306 //adjustToLimits( bv, fv, amount ); 0307 0308 // STAMP 0309 0310 // see if we need to adjust the saturation 0311 if( amount > 0 ) 0312 adjustToLimits( bs, fs, _amount ); 0313 0314 // STAMP 0315 0316 // see if we need to adjust the hue 0317 if( amount > 0 ) 0318 fh += amount; // cycles around; 0319 0320 // STAMP 0321 0322 return QColor::fromHsv( fh, fs, fv ); 0323 } 0324 0325 // STAMP 0326 0327 if( fv > bv && bv > amount ) 0328 return QColor::fromHsv( fh, fs, bv - amount ); 0329 0330 // STAMP 0331 0332 if( fv < bv && fv > amount ) 0333 return QColor::fromHsv( fh, fs, fv - amount ); 0334 0335 // STAMP 0336 0337 if( fv > bv && (255 - fv > amount) ) 0338 return QColor::fromHsv( fh, fs, fv + amount ); 0339 0340 // STAMP 0341 0342 if( fv < bv && (255 - bv > amount ) ) 0343 return QColor::fromHsv( fh, fs, bv + amount ); 0344 0345 // STAMP 0346 // debug() << "Something went wrong!\n"; 0347 0348 return Qt::blue; 0349 0350 #undef amount 0351 // #undef STAMP 0352 } 0353 0354 void 0355 BlockAnalyzer::paletteChange( const QPalette& ) 0356 { 0357 const QColor bg = palette().color(QPalette::Active, QPalette::Window); 0358 const QColor fg = ensureContrast(bg, palette().color(QPalette::Active, QPalette::WindowText)); 0359 0360 m_topBarPixmap.fill( fg ); 0361 0362 const double dr = 15*double(bg.red() - fg.red()) / (m_rows*16); 0363 const double dg = 15*double(bg.green() - fg.green()) / (m_rows*16); 0364 const double db = 15*double(bg.blue() - fg.blue()) / (m_rows*16); 0365 const int r = fg.red(), g = fg.green(), b = fg.blue(); 0366 0367 bar()->fill( bg ); 0368 0369 QPainter p( bar() ); 0370 for( int y = 0; (uint)y < m_rows; ++y ) 0371 //graduate the fg color 0372 p.fillRect( 0, y*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*y), g+int(dg*y), b+int(db*y) ) ); 0373 0374 { 0375 const QColor bg = palette().color( QPalette::Active, QPalette::Window ).darker(112); 0376 0377 //make a complimentary fadebar colour 0378 //TODO dark is not always correct, dumbo! 0379 int h,s,v; palette().color( QPalette::Active, QPalette::Window ).darker(150).getHsv( &h, &s, &v ); 0380 const QColor fg = QColor::fromHsv( h + 60, s, v ); 0381 0382 const double dr = fg.red() - bg.red(); 0383 const double dg = fg.green() - bg.green(); 0384 const double db = fg.blue() - bg.blue(); 0385 const int r = bg.red(), g = bg.green(), b = bg.blue(); 0386 0387 // Precalculate all fade-bar pixmaps 0388 for(int y = 0; y < FADE_SIZE; ++y) { 0389 m_fade_bars[y].fill( palette().color( QPalette::Active, QPalette::Window ) ); 0390 QPainter f( &m_fade_bars[y] ); 0391 for( int z = 0; (uint)z < m_rows; ++z ) { 0392 const double Y = 1.0 - (log10( static_cast<float>(FADE_SIZE) - y ) / log10( static_cast<float>(FADE_SIZE) )); 0393 f.fillRect( 0, z*(HEIGHT+1), WIDTH, HEIGHT, QColor( r+int(dr*Y), g+int(dg*Y), b+int(db*Y) ) ); 0394 } 0395 } 0396 } 0397 0398 drawBackground(); 0399 } 0400 0401 void 0402 BlockAnalyzer::drawBackground() 0403 { 0404 const QColor bg = palette().color( QPalette::Active, QPalette::Window); 0405 const QColor bgdark = bg.darker(112); 0406 0407 m_background.fill( bg ); 0408 0409 QPainter p( &m_background ); 0410 for( int x = 0; (uint)x < m_columns; ++x ) 0411 for( int y = 0; (uint)y < m_rows; ++y ) 0412 p.fillRect( x*(WIDTH+1), y*(HEIGHT+1) + m_y, WIDTH, HEIGHT, bgdark ); 0413 0414 }