2019-02-04 21:34:12 +01:00
/*
Strawberry Music Player
This file was part of Amarok .
Copyright 2003 - 2005 , Max Howell < max . howell @ methylblue . com >
Copyright 2005 , Mark Kretschmann < markey @ web . de >
Copyright 2009 - 2010 , David Sansome < davidsansome @ gmail . com >
Copyright 2010 , 2014 , John Maguire < john . maguire @ gmail . com >
Strawberry is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Strawberry is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with Strawberry . If not , see < http : //www.gnu.org/licenses/>.
*/
2018-02-27 18:06:05 +01:00
# include "blockanalyzer.h"
2018-05-01 00:41:33 +02:00
# include <cstdlib>
2018-02-27 18:06:05 +01:00
# include <cmath>
2018-05-01 00:41:33 +02:00
# include <QWidget>
# include <QPixmap>
2018-02-27 18:06:05 +01:00
# include <QPainter>
2018-05-01 00:41:33 +02:00
# include <QPalette>
# include <QColor>
# include "analyzerbase.h"
# include "fht.h"
2018-02-27 18:06:05 +01:00
2021-03-21 18:53:02 +01:00
const int BlockAnalyzer : : kHeight = 2 ;
const int BlockAnalyzer : : kWidth = 4 ;
2021-07-02 18:45:53 +02:00
const int BlockAnalyzer : : kMinRows = 3 ; // arbitrary
const int BlockAnalyzer : : kMinColumns = 32 ; // arbitrary
2021-03-21 18:53:02 +01:00
const int BlockAnalyzer : : kMaxColumns = 256 ; // must be 2**n
const int BlockAnalyzer : : kFadeSize = 90 ;
2018-02-27 18:06:05 +01:00
2018-09-30 15:32:21 +02:00
const char * BlockAnalyzer : : kName = QT_TRANSLATE_NOOP ( " AnalyzerContainer " , " Block analyzer " ) ;
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
BlockAnalyzer : : BlockAnalyzer ( QWidget * parent )
2018-02-27 18:06:05 +01:00
: Analyzer : : Base ( parent , 9 ) ,
2018-09-30 15:32:21 +02:00
columns_ ( 0 ) ,
rows_ ( 0 ) ,
y_ ( 0 ) ,
barpixmap_ ( 1 , 1 ) ,
2019-02-04 21:34:12 +01:00
topbarpixmap_ ( kWidth , kHeight ) ,
scope_ ( kMinColumns ) ,
2018-09-30 15:32:21 +02:00
store_ ( 1 < < 8 , 0 ) ,
2019-02-04 21:34:12 +01:00
fade_bars_ ( kFadeSize ) ,
2018-09-30 15:32:21 +02:00
fade_pos_ ( 1 < < 8 , 50 ) ,
2019-04-08 18:46:11 +02:00
fade_intensity_ ( 1 < < 8 , 32 ) ,
2021-07-11 07:40:57 +02:00
step_ ( 0 ) {
2018-09-30 15:32:21 +02:00
2019-02-04 21:34:12 +01:00
setMinimumSize ( kMinColumns * ( kWidth + 1 ) - 1 , kMinRows * ( kHeight + 1 ) - 1 ) ; //-1 is padding, no drawing takes place there
setMaximumWidth ( kMaxColumns * ( kWidth + 1 ) - 1 ) ;
2018-02-27 18:06:05 +01:00
// mxcl says null pixmaps cause crashes, so let's play it safe
2021-09-12 21:24:22 +02:00
std : : fill ( fade_bars_ . begin ( ) , fade_bars_ . end ( ) , QPixmap ( 1 , 1 ) ) ;
2018-09-30 15:32:21 +02:00
2018-02-27 18:06:05 +01:00
}
2018-05-01 00:41:33 +02:00
void BlockAnalyzer : : resizeEvent ( QResizeEvent * e ) {
2018-09-30 15:32:21 +02:00
2018-02-27 18:06:05 +01:00
QWidget : : resizeEvent ( e ) ;
2018-09-30 15:32:21 +02:00
background_ = QPixmap ( size ( ) ) ;
2018-02-27 18:06:05 +01:00
canvas_ = QPixmap ( size ( ) ) ;
2021-03-21 18:53:02 +01:00
const int oldRows = rows_ ;
2018-02-27 18:06:05 +01:00
// all is explained in analyze()..
2019-02-04 21:34:12 +01:00
// +1 to counter -1 in maxSizes, trust me we need this!
2021-03-21 18:53:02 +01:00
columns_ = qMin ( static_cast < int > ( static_cast < double > ( width ( ) + 1 ) / ( kWidth + 1 ) ) + 1 , kMaxColumns ) ;
2021-09-12 21:24:22 +02:00
rows_ = static_cast < int > ( static_cast < double > ( height ( ) + 1 ) / ( kHeight + 1 ) ) ;
2018-02-27 18:06:05 +01:00
// this is the y-offset for drawing from the top of the widget
2019-02-04 21:34:12 +01:00
y_ = ( height ( ) - ( rows_ * ( kHeight + 1 ) ) + 2 ) / 2 ;
2018-02-27 18:06:05 +01:00
2018-09-30 15:32:21 +02:00
scope_ . resize ( columns_ ) ;
2018-02-27 18:06:05 +01:00
2018-09-30 15:32:21 +02:00
if ( rows_ ! = oldRows ) {
2019-02-04 21:34:12 +01:00
barpixmap_ = QPixmap ( kWidth , rows_ * ( kHeight + 1 ) ) ;
2018-02-27 18:06:05 +01:00
2021-09-12 21:24:22 +02:00
std : : fill ( fade_bars_ . begin ( ) , fade_bars_ . end ( ) , QPixmap ( kWidth , rows_ * ( kHeight + 1 ) ) ) ;
2018-02-27 18:06:05 +01:00
2018-09-30 15:32:21 +02:00
yscale_ . resize ( rows_ + 1 ) ;
2018-02-27 18:06:05 +01:00
2021-03-21 18:53:02 +01:00
const int PRE = 1 , PRO = 1 ; // PRE and PRO allow us to restrict the range somewhat
2018-02-27 18:06:05 +01:00
2021-08-23 21:21:08 +02:00
for ( int z = 0 ; z < rows_ ; + + z ) {
2018-09-30 15:32:21 +02:00
yscale_ [ z ] = 1 - ( log10 ( PRE + z ) / log10 ( PRE + rows_ + PRO ) ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
2018-09-30 15:32:21 +02:00
yscale_ [ rows_ ] = 0 ;
2018-02-27 18:06:05 +01:00
determineStep ( ) ;
paletteChange ( palette ( ) ) ;
}
drawBackground ( ) ;
2018-09-30 15:32:21 +02:00
2018-02-27 18:06:05 +01:00
}
void BlockAnalyzer : : determineStep ( ) {
2018-09-30 15:32:21 +02:00
2018-05-01 00:41:33 +02:00
// falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels)
2018-02-27 18:06:05 +01:00
// I calculated the value 30 based on some trial and error
// the fall time of 30 is too slow on framerates above 50fps
2021-03-21 18:53:02 +01:00
const double fallTime = static_cast < double > ( timeout ( ) < 20 ? 20 * rows_ : 30 * rows_ ) ;
2018-09-30 15:32:21 +02:00
step_ = double ( rows_ * timeout ( ) ) / fallTime ;
2018-02-27 18:06:05 +01:00
}
2018-09-30 15:32:21 +02:00
void BlockAnalyzer : : framerateChanged ( ) {
2018-02-27 18:06:05 +01:00
determineStep ( ) ;
}
2018-09-30 15:32:21 +02:00
void BlockAnalyzer : : transform ( Analyzer : : Scope & s ) {
2018-02-27 18:06:05 +01:00
for ( uint x = 0 ; x < s . size ( ) ; + + x ) s [ x ] * = 2 ;
2019-02-04 21:34:12 +01:00
fht_ - > spectrum ( s . data ( ) ) ;
fht_ - > scale ( s . data ( ) , 1.0 / 20 ) ;
2018-09-30 15:32:21 +02:00
// the second half is pretty dull, so only show it if the user has a large analyzer by setting to scope_.size() if large we prevent interpolation of large analyzers, this is good!
2019-02-04 21:34:12 +01:00
s . resize ( scope_ . size ( ) < = kMaxColumns / 2 ? kMaxColumns / 2 : scope_ . size ( ) ) ;
2018-02-27 18:06:05 +01:00
}
2018-09-30 15:32:21 +02:00
void BlockAnalyzer : : analyze ( QPainter & p , const Analyzer : : Scope & s , bool new_frame ) {
2018-02-27 18:06:05 +01:00
// y = 2 3 2 1 0 2
// . . . . # .
// . . . # # .
// # . # # # #
// # # # # # #
//
// visual aid for how this analyzer works.
// y represents the number of blanks
// y starts from the top and increases in units of blocks
2018-09-30 15:32:21 +02:00
// yscale_ looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 }
2018-02-27 18:06:05 +01:00
// if it contains 6 elements there are 5 rows in the analyzer
if ( ! new_frame ) {
p . drawPixmap ( 0 , 0 , canvas_ ) ;
return ;
}
QPainter canvas_painter ( & canvas_ ) ;
2018-09-30 15:32:21 +02:00
Analyzer : : interpolate ( s , scope_ ) ;
2018-02-27 18:06:05 +01:00
// Paint the background
2018-09-30 15:32:21 +02:00
canvas_painter . drawPixmap ( 0 , 0 , background_ ) ;
2018-02-27 18:06:05 +01:00
2021-03-26 21:30:13 +01:00
for ( int x = 0 , y = 0 ; x < static_cast < int > ( scope_ . size ( ) ) ; + + x ) {
2018-02-27 18:06:05 +01:00
// determine y
2018-09-30 15:32:21 +02:00
for ( y = 0 ; scope_ [ x ] < yscale_ [ y ] ; + + y ) continue ;
2018-02-27 18:06:05 +01:00
2019-02-04 21:34:12 +01:00
// This is opposite to what you'd think, higher than y means the bar is lower than y (physically)
2021-08-23 21:21:08 +02:00
if ( static_cast < double > ( y ) > store_ [ x ] ) {
2019-02-04 21:34:12 +01:00
y = static_cast < int > ( store_ [ x ] + = step_ ) ;
2021-08-23 21:21:08 +02:00
}
else {
2018-09-30 15:32:21 +02:00
store_ [ x ] = y ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
2019-02-04 21:34:12 +01:00
// If y is lower than fade_pos_, then the bar has exceeded the height of the fadeout
2018-02-27 18:06:05 +01:00
// if the fadeout is quite faded now, then display the new one
2019-02-04 21:34:12 +01:00
if ( y < = fade_pos_ [ x ] /*|| fade_intensity_[x] < kFadeSize / 3*/ ) {
2018-09-30 15:32:21 +02:00
fade_pos_ [ x ] = y ;
2019-02-04 21:34:12 +01:00
fade_intensity_ [ x ] = kFadeSize ;
2018-02-27 18:06:05 +01:00
}
2018-09-30 15:32:21 +02:00
if ( fade_intensity_ [ x ] > 0 ) {
2021-03-21 18:53:02 +01:00
const int offset = - - fade_intensity_ [ x ] ;
const int y2 = y_ + ( fade_pos_ [ x ] * ( kHeight + 1 ) ) ;
2020-04-23 21:08:28 +02:00
canvas_painter . drawPixmap ( x * ( kWidth + 1 ) , y2 , fade_bars_ [ offset ] , 0 , 0 , kWidth , height ( ) - y2 ) ;
2018-02-27 18:06:05 +01:00
}
2018-09-30 15:32:21 +02:00
if ( fade_intensity_ [ x ] = = 0 ) fade_pos_ [ x ] = rows_ ;
2018-02-27 18:06:05 +01:00
2018-09-30 15:32:21 +02:00
// REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing, rows_ means none are
2019-02-04 21:34:12 +01:00
canvas_painter . drawPixmap ( x * ( kWidth + 1 ) , y * ( kHeight + 1 ) + y_ , * bar ( ) , 0 , y * ( kHeight + 1 ) , bar ( ) - > width ( ) , bar ( ) - > height ( ) ) ;
2018-02-27 18:06:05 +01:00
}
2021-08-23 21:21:08 +02:00
for ( int x = 0 ; x < store_ . size ( ) ; + + x ) {
2019-02-04 21:34:12 +01:00
canvas_painter . drawPixmap ( x * ( kWidth + 1 ) , static_cast < int > ( store_ [ x ] ) * ( kHeight + 1 ) + y_ , topbarpixmap_ ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
p . drawPixmap ( 0 , 0 , canvas_ ) ;
2018-09-30 15:32:21 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-21 20:00:31 +02:00
static inline void adjustToLimits ( const int b , int & f , int & amount ) {
2018-09-30 15:32:21 +02:00
2018-05-01 00:41:33 +02:00
// with a range of 0-255 and maximum adjustment of amount, maximise the difference between f and b
2018-02-27 18:06:05 +01:00
if ( b < f ) {
if ( b > 255 - f ) {
amount - = f ;
f = 0 ;
}
else {
amount - = ( 255 - f ) ;
f = 255 ;
}
}
else {
if ( f > 255 - b ) {
amount - = f ;
f = 0 ;
}
else {
amount - = ( 255 - f ) ;
f = 255 ;
}
}
2018-09-30 15:32:21 +02:00
2018-02-27 18:06:05 +01:00
}
/**
* Clever contrast function
*
* It will try to adjust the foreground color such that it contrasts well with
* the background
* It won ' t modify the hue of fg unless absolutely necessary
* @ return the adjusted form of fg
*/
2021-03-21 18:53:02 +01:00
QColor ensureContrast ( const QColor & bg , const QColor & fg , int amount = 150 ) ;
QColor ensureContrast ( const QColor & bg , const QColor & fg , int amount ) {
2018-09-30 15:32:21 +02:00
2018-02-27 18:06:05 +01:00
class OutputOnExit {
public :
2019-02-04 21:34:12 +01:00
explicit OutputOnExit ( const QColor & color ) : c ( color ) { }
2018-02-27 18:06:05 +01:00
~ OutputOnExit ( ) {
2021-03-26 21:30:13 +01:00
int h = 0 , s = 0 , v = 0 ;
2018-02-27 18:06:05 +01:00
c . getHsv ( & h , & s , & v ) ;
}
private :
2018-09-30 15:32:21 +02:00
const QColor & c ;
2021-06-20 19:04:08 +02:00
Q_DISABLE_COPY ( OutputOnExit )
2018-02-27 18:06:05 +01:00
} ;
OutputOnExit allocateOnTheStack ( fg ) ;
2021-03-26 21:30:13 +01:00
int bh = 0 , bs = 0 , bv = 0 ;
int fh = 0 , fs = 0 , fv = 0 ;
2018-02-27 18:06:05 +01:00
bg . getHsv ( & bh , & bs , & bv ) ;
fg . getHsv ( & fh , & fs , & fv ) ;
int dv = abs ( bv - fv ) ;
// value is the best measure of contrast
// if there is enough difference in value already, return fg unchanged
2019-02-04 21:34:12 +01:00
if ( dv > static_cast < int > ( amount ) ) return fg ;
2018-02-27 18:06:05 +01:00
int ds = abs ( bs - fs ) ;
// saturation is good enough too. But not as good. TODO adapt this a little
2019-02-04 21:34:12 +01:00
if ( ds > static_cast < int > ( amount ) ) return fg ;
2018-02-27 18:06:05 +01:00
int dh = abs ( bh - fh ) ;
if ( dh > 120 ) {
2019-08-22 18:45:32 +02:00
// a third of the colour wheel automatically guarantees contrast
2018-02-27 18:06:05 +01:00
// but only if the values are high enough and saturations significant enough
// to allow the colours to be visible and not be shades of grey or black
// check the saturation for the two colours is sufficient that hue alone can
// provide sufficient contrast
2021-08-23 21:21:08 +02:00
if ( ds > amount / 2 & & ( bs > 125 & & fs > 125 ) ) {
2018-02-27 18:06:05 +01:00
return fg ;
2021-08-23 21:21:08 +02:00
}
else if ( dv > amount / 2 & & ( bv > 125 & & fv > 125 ) ) {
2018-02-27 18:06:05 +01:00
return fg ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
}
if ( fs < 50 & & ds < 40 ) {
// low saturation on a low saturation is sad
const int tmp = 50 - fs ;
fs = 50 ;
2021-08-23 21:21:08 +02:00
if ( static_cast < int > ( amount ) > tmp ) {
2019-02-04 21:34:12 +01:00
amount - = tmp ;
2021-08-23 21:21:08 +02:00
}
else {
2019-02-04 21:34:12 +01:00
amount = 0 ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
}
// test that there is available value to honor our contrast requirement
2021-03-21 18:53:02 +01:00
if ( 255 - dv < amount ) {
2018-02-27 18:06:05 +01:00
// we have to modify the value and saturation of fg
// adjustToLimits( bv, fv, amount );
// see if we need to adjust the saturation
2021-03-21 18:53:02 +01:00
if ( amount > 0 ) adjustToLimits ( bs , fs , amount ) ;
2018-02-27 18:06:05 +01:00
// see if we need to adjust the hue
2019-02-04 21:34:12 +01:00
if ( static_cast < int > ( amount ) > 0 )
fh + = static_cast < int > ( amount ) ; // cycles around;
2018-02-27 18:06:05 +01:00
return QColor : : fromHsv ( fh , fs , fv ) ;
}
2021-08-23 21:21:08 +02:00
if ( fv > bv & & bv > static_cast < int > ( amount ) ) {
2019-02-04 21:34:12 +01:00
return QColor : : fromHsv ( fh , fs , bv - static_cast < int > ( amount ) ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
2021-08-23 21:21:08 +02:00
if ( fv < bv & & fv > static_cast < int > ( amount ) ) {
2021-03-21 18:53:02 +01:00
return QColor : : fromHsv ( fh , fs , fv - amount ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
2021-08-23 21:21:08 +02:00
if ( fv > bv & & ( 255 - fv > static_cast < int > ( amount ) ) ) {
2021-03-21 18:53:02 +01:00
return QColor : : fromHsv ( fh , fs , fv + amount ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
2021-08-23 21:21:08 +02:00
if ( fv < bv & & ( 255 - bv > static_cast < int > ( amount ) ) ) {
2021-03-21 18:53:02 +01:00
return QColor : : fromHsv ( fh , fs , bv + amount ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
return Qt : : blue ;
2021-08-23 21:21:08 +02:00
2018-02-27 18:06:05 +01:00
}
2018-09-30 15:32:21 +02:00
void BlockAnalyzer : : paletteChange ( const QPalette & ) {
2020-07-18 04:12:50 +02:00
const QColor bg = palette ( ) . color ( QPalette : : Window ) ;
2018-02-27 18:06:05 +01:00
const QColor fg = ensureContrast ( bg , palette ( ) . color ( QPalette : : Highlight ) ) ;
2018-09-30 15:32:21 +02:00
topbarpixmap_ . fill ( fg ) ;
2018-02-27 18:06:05 +01:00
2019-02-04 21:34:12 +01:00
const double dr = 15 * static_cast < double > ( bg . red ( ) - fg . red ( ) ) / ( rows_ * 16 ) ;
const double dg = 15 * static_cast < double > ( bg . green ( ) - fg . green ( ) ) / ( rows_ * 16 ) ;
const double db = 15 * static_cast < double > ( bg . blue ( ) - fg . blue ( ) ) / ( rows_ * 16 ) ;
2018-02-27 18:06:05 +01:00
const int r = fg . red ( ) , g = fg . green ( ) , b = fg . blue ( ) ;
bar ( ) - > fill ( bg ) ;
QPainter p ( bar ( ) ) ;
2021-08-23 21:21:08 +02:00
for ( int y = 0 ; y < rows_ ; + + y ) {
2018-02-27 18:06:05 +01:00
// graduate the fg color
2019-02-04 21:34:12 +01:00
p . fillRect ( 0 , y * ( kHeight + 1 ) , kWidth , kHeight , QColor ( r + static_cast < int > ( dr * y ) , g + static_cast < int > ( dg * y ) , b + static_cast < int > ( db * y ) ) ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
{
2020-07-18 04:12:50 +02:00
const QColor bg2 = palette ( ) . color ( QPalette : : Window ) . darker ( 112 ) ;
2018-02-27 18:06:05 +01:00
// make a complimentary fadebar colour
// TODO dark is not always correct, dumbo!
2021-03-26 21:30:13 +01:00
int h = 0 , s = 0 , v = 0 ;
2020-07-18 04:12:50 +02:00
palette ( ) . color ( QPalette : : Window ) . darker ( 150 ) . getHsv ( & h , & s , & v ) ;
2020-04-23 21:08:28 +02:00
const QColor fg2 ( QColor : : fromHsv ( h + 120 , s , v ) ) ;
2018-02-27 18:06:05 +01:00
2020-04-23 21:08:28 +02:00
const double dr2 = fg2 . red ( ) - bg2 . red ( ) ;
const double dg2 = fg2 . green ( ) - bg2 . green ( ) ;
const double db2 = fg2 . blue ( ) - bg2 . blue ( ) ;
const int r2 = bg2 . red ( ) , g2 = bg2 . green ( ) , b2 = bg2 . blue ( ) ;
2018-02-27 18:06:05 +01:00
// Precalculate all fade-bar pixmaps
2021-09-12 21:24:22 +02:00
for ( int y = 0 ; y < kFadeSize ; + + y ) {
2020-07-18 04:12:50 +02:00
fade_bars_ [ y ] . fill ( palette ( ) . color ( QPalette : : Window ) ) ;
2018-09-30 15:32:21 +02:00
QPainter f ( & fade_bars_ [ y ] ) ;
2021-03-21 18:53:02 +01:00
for ( int z = 0 ; z < rows_ ; + + z ) {
2019-02-04 21:34:12 +01:00
const double Y = 1.0 - ( log10 ( kFadeSize - y ) / log10 ( kFadeSize ) ) ;
2020-04-23 21:08:28 +02:00
f . fillRect ( 0 , z * ( kHeight + 1 ) , kWidth , kHeight , QColor ( r2 + static_cast < int > ( dr2 * Y ) , g2 + static_cast < int > ( dg2 * Y ) , b2 + static_cast < int > ( db2 * Y ) ) ) ;
2018-02-27 18:06:05 +01:00
}
}
}
drawBackground ( ) ;
2018-09-30 15:32:21 +02:00
2018-02-27 18:06:05 +01:00
}
void BlockAnalyzer : : drawBackground ( ) {
2018-09-30 15:32:21 +02:00
2019-02-04 21:34:12 +01:00
if ( background_ . isNull ( ) ) {
return ;
}
2020-07-18 04:12:50 +02:00
const QColor bg = palette ( ) . color ( QPalette : : Window ) ;
2019-07-08 22:19:14 +02:00
const QColor bgdark = bg . darker ( 112 ) ;
2018-02-27 18:06:05 +01:00
2018-09-30 15:32:21 +02:00
background_ . fill ( bg ) ;
QPainter p ( & background_ ) ;
2019-02-04 21:34:12 +01:00
2020-06-14 17:02:47 +02:00
if ( ! p . paintEngine ( ) ) return ;
2019-02-04 21:34:12 +01:00
2021-08-23 21:21:08 +02:00
for ( int x = 0 ; x < columns_ ; + + x ) {
for ( int y = 0 ; y < rows_ ; + + y ) {
2019-02-04 21:34:12 +01:00
p . fillRect ( x * ( kWidth + 1 ) , y * ( kHeight + 1 ) + y_ , kWidth , kHeight , bgdark ) ;
2021-08-23 21:21:08 +02:00
}
}
2018-02-27 18:06:05 +01:00
}