2 #include "modules/colorModel.h" // using color coefficients for RGB->gray conversion
6 //// Non-member functions
7 static vector
<double> getColorPSNR(const QImage
&a
,const QImage
&b
) {
8 int width
= a
.width(), height
= a
.height();
11 int sum
, sumR
, sumG
, sumB
;
12 sum
= sumR
= sumG
= sumB
= 0;
13 for (y
=0; y
<height
; ++y
) {
14 line1
= (QRgb
*)a
.scanLine(y
);
15 line2
= (QRgb
*)b
.scanLine(y
);
16 for (x
=0; x
<width
; ++x
) {
17 sum
+= sqr( getGray(line1
[x
]) - getGray(line2
[x
]) );
18 sumR
+= sqr( qRed(line1
[x
]) - qRed(line2
[x
]) );
19 sumG
+= sqr( qGreen(line1
[x
]) - qGreen(line2
[x
]) );
20 sumB
+= sqr( qBlue(line1
[x
]) - qBlue(line2
[x
]) );
23 vector
<double> result(4);
28 double mul
= double(width
*height
) * double(sqr(255));
29 for (vector
<double>::iterator it
=result
.begin(); it
!=result
.end(); ++it
)
30 *it
= 10.0 * log10(mul
/ *it
);
33 static QString
getPSNRmessage(const QImage
&img1
,const QImage
&img2
) {
34 vector
<double> psnr
= getColorPSNR(img1
,img2
);
35 return QObject::tr("gray PSNR: %3 dB\nR,G,B: %4,%5,%6 dB") .arg(psnr
[3],0,'f',2)
36 .arg(psnr
[0],0,'f',2) .arg(psnr
[1],0,'f',2) .arg(psnr
[2],0,'f',2);
41 ImageViewer::ImageViewer(QApplication
&app
)
42 : modules_settings( IRoot::newCompatibleModule() ), modules_encoding(0)
43 , zoom(0), lastPath(QDir::current().filePath("x"))
44 , readAct(this), writeAct(this), compareAct(this), exitAct(this)
45 , settingsAct(this), encodeAct(this), saveAct(this)
46 , loadAct(this), clearAct(this), iterateAct(this), zoomIncAct(this), zoomDecAct(this) {
47 // try to load guessed language translation
48 if ( translator
.load( "lang-"+QLocale::system().name(), app
.applicationDirPath() ) )
49 app
.installTranslator(&translator
);
50 // create a scrolling area with a label for viewing images
51 imageLabel
= new QLabel(this);
52 imageLabel
->setBackgroundRole(QPalette::Dark
);
53 imageLabel
->setSizePolicy(QSizePolicy::Ignored
,QSizePolicy::Ignored
);
55 QScrollArea
*scrollArea
= new QScrollArea(this);
56 scrollArea
->setBackgroundRole(QPalette::Dark
);
57 scrollArea
->setWidget(imageLabel
);
58 setCentralWidget(scrollArea
);
60 setStatusBar(new QStatusBar(this));
71 void ImageViewer::createActions() {
72 // create all actions and connect them to the appropriate slots
74 aConnect( &name##Act, SIGNAL(triggered()), this, SLOT(name()) );
75 #define AS(name,signal) \
76 aConnect( &name##Act, SIGNAL(triggered()), this, SLOT(signal()) );
95 void ImageViewer::createMenus() {
96 imageMenu
.addAction(&readAct
);
97 imageMenu
.addAction(&writeAct
);
98 imageMenu
.addSeparator();
99 imageMenu
.addAction(&compareAct
);
100 imageMenu
.addSeparator();
101 imageMenu
.addAction(&exitAct
);
103 compMenu
.addAction(&settingsAct
);
104 compMenu
.addAction(&encodeAct
);
105 compMenu
.addAction(&saveAct
);
107 decompMenu
.addAction(&loadAct
);
108 decompMenu
.addAction(&clearAct
);
109 decompMenu
.addAction(&iterateAct
);
110 decompMenu
.addSeparator();
111 decompMenu
.addAction(&zoomIncAct
);
112 decompMenu
.addAction(&zoomDecAct
);
114 menuBar()->addMenu(&imageMenu
);
115 menuBar()->addMenu(&compMenu
);
116 menuBar()->addMenu(&decompMenu
);
117 //menuBar()->addMenu(langMenu);
118 //menuBar()->addMenu(helpMenu);
120 void ImageViewer::translateUi() {
121 setWindowTitle(tr("Fractal Image Compressor"));
122 // set action names and shortcuts
123 #define A(name,shortcut,text) \
124 name##Act.setText(tr(text)); \
125 name##Act.setShortcut(tr(shortcut));
126 A(read
, "Ctrl+R", "Read...")
127 A(write
, "Ctrl+W", "Write...")
128 A(compare
, "", "Compare to...")
129 A(exit
, "Ctrl+Q", "Quit")
131 A(settings
, "", "Settings")
132 A(encode
, " ", "Start encoding")
133 A(save
, "Ctrl+S", "Save FIC...")
135 A(load
, "Ctrl+L", "Load FIC...")
136 A(clear
, "Ctrl+C", "Clear image")
137 A(iterate
, "Ctrl+I", "Iterate image")
138 A(zoomInc
, "Ctrl++", "Increase zoom")
139 A(zoomDec
, "Ctrl+-", "Decrease zoom")
142 // set the tiles of menu items
143 #define M(name,title) \
144 name##Menu.setTitle(tr(title));
146 M(comp
, "&Compression");
147 M(decomp
, "&Decompression");
150 void ImageViewer::updateActions() {
151 bool pixmapOk
= imageLabel
->pixmap();
152 IRoot::Mode mode
= modules_encoding
? modules_encoding
->getMode() : IRoot::Clear
;
154 //readAct.setEnabled(true);
155 writeAct
.setEnabled(pixmapOk
);
156 compareAct
.setEnabled(pixmapOk
);
157 //exitAct.setEnabled(true);
159 //settingsAct.setEnabled(true);
160 encodeAct
.setEnabled(pixmapOk
);
161 saveAct
.setEnabled( mode
!= IRoot::Clear
);
163 //loadAct.setEnabled(true);
164 clearAct
.setEnabled ( mode
!= IRoot::Clear
);
165 iterateAct
.setEnabled( mode
!= IRoot::Clear
);
166 zoomIncAct
.setEnabled( mode
!= IRoot::Clear
&& zoom
<3 );
167 zoomDecAct
.setEnabled( mode
!= IRoot::Clear
&& zoom
>0 );
171 void ImageViewer::read() {
173 QString fname
= QFileDialog::getOpenFileName( this, tr("Read image file")
174 , lastDir(), tr("PNG images (*.png)\nAll files (*.*)") );
178 lastPath
.setPath(fname
);
179 // try to load, check for errors
181 if (image
.isNull()) {
182 QMessageBox::information( this, tr("Error"), tr("Cannot open %1.").arg(fname
) );
185 // convert to 32-bits
186 if (image
.depth()<32)
187 image
= image
.convertToFormat(QImage::Format_RGB32
);
189 changePixmap(QPixmap::fromImage(image
));
192 void ImageViewer::write() {
194 QString fname
= QFileDialog::getSaveFileName( this, tr("Write image file")
195 , lastDir(), tr("PNG images (*.png)\nAll files (*.*)") );
198 lastPath
.setPath(fname
);
199 // try to save the image
200 if ( !imageLabel
->pixmap()->save(fname
) ) {
201 QMessageBox::information( this, tr("Error"), tr("Cannot write file %1.").arg(fname
) );
206 void ImageViewer::compare() {
207 // let the user choose a file
208 QString fname
= QFileDialog::getOpenFileName
209 ( this, tr("Compare to image"), lastDir()
210 , tr("PNG images (*.png)\nJFIF images (*.jpg *.jpeg)\nAll files (*.*)") );
213 lastPath
.setPath(fname
);
214 // open the file as an image, check it's got the same dimensions as the diplayed one
216 image
= image
.convertToFormat(QImage::Format_RGB32
);
217 if (image
.isNull()) {
218 QMessageBox::information( this, tr("Error"), tr("Cannot open %1.").arg(fname
) );
221 if ( image
.width()!=imageLabel
->pixmap()->width()
222 || image
.height()!=imageLabel
->pixmap()->height() ) {
223 QMessageBox::information
224 ( this, tr("Error"), tr("Images don't have the same dimensions.").arg(fname
) );
227 // compute the PSNRs and display them
228 QString message
= getPSNRmessage( image
, imageLabel
->pixmap()->toImage() );
229 QMessageBox::information( this, tr("Comparison"), message
);
231 void ImageViewer::settings() {
232 IRoot
*newSettings
= clone(modules_settings
);
233 SettingsDialog
dialog(this,newSettings
);
234 newSettings
= dialog
.getSettings();
236 // the dialog wasn't cancelled -> swap with the current and new settings
237 swap(newSettings
,modules_settings
);
240 void ImageViewer::encode() {
241 EncodingProgress::create(this);
243 void ImageViewer::encDone() {
245 IRoot
*modules_encoded
= EncodingProgress::destroy(encMsecs
);
247 if (modules_encoded
) { // encoding successful - iterate the image and display some info
248 // replace the old state
249 delete modules_encoding
;
250 modules_encoding
= modules_encoded
;
252 QImage beforeImg
= modules_encoding
->toImage();
256 modules_encoding
->decodeAct(MTypes::Clear
);
257 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
258 int decMsecs
= decTime
.elapsed();
260 QImage afterImg
= modules_encoding
->toImage();
261 changePixmap( QPixmap::fromImage(afterImg
) );
263 QString message
= tr("Time to encode: %1 seconds\nTime to decode: %2 seconds\n")
264 .arg(encMsecs
/1000.0) .arg(decMsecs
/1000.0) + getPSNRmessage(beforeImg
,afterImg
);
265 QMessageBox::information( this, tr("encoded"), message
);
271 void ImageViewer::save() {
272 // get a filename to suggest
273 QFileInfo
finfo(lastPath
.path());
274 QString fname
= finfo
.dir().filePath( finfo
.completeBaseName() + tr(".fic") );
275 fname
= QFileDialog::getSaveFileName
276 ( this, tr("Save encoded image"), fname
, tr("FIC images (*.fic)") );
279 lastPath
.setPath(fname
);
280 if ( !modules_encoding
->toFile( fname
.toStdString().c_str() ) )
281 QMessageBox::information( this, tr("Error"), tr("Cannot write file %1.").arg(fname
) );
283 void ImageViewer::load() {
284 QString fname
= QFileDialog::getOpenFileName
285 ( this, tr("Load encoded image"), lastDir(), tr("FIC images (*.fic)") );
288 lastPath
.setPath(fname
);
289 // IRoot needs to be loaded from cleared state
290 IRoot
*modules_old
= modules_encoding
;
291 modules_encoding
= clone(modules_settings
,Module::ShallowCopy
);
294 bool error
= !file2string( fname
.toStdString().c_str(), decData
);
296 stringstream
stream(decData
);
298 error
= !modules_encoding
->fromStream( stream
, zoom
);
302 QMessageBox::information( this, tr("Error"), tr("Cannot load file %1.").arg(fname
) );
303 swap(modules_encoding
,modules_old
);
304 } else { // loading was successful
305 swap(encData
,decData
);
306 modules_encoding
->decodeAct(Clear
);
307 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
308 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
314 void ImageViewer::clear() {
315 modules_encoding
->decodeAct(Clear
);
316 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
319 void ImageViewer::iterate() {
320 modules_encoding
->decodeAct(Iterate
);
321 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
324 void ImageViewer::zoomInc() {
327 --zoom
, QMessageBox::information( this, tr("Error"), tr("Zooming failed.") );
329 void ImageViewer::zoomDec() {
333 ++zoom
, QMessageBox::information( this, tr("Error"), tr("Zooming failed.") );
336 bool ImageViewer::rezoom() {
338 if ( encData
.empty() ) {
339 if ( !modules_encoding
->toStream(stream
) )
341 encData
= stream
.str();
345 IRoot
*newRoot
= clone(modules_settings
,Module::ShallowCopy
);
346 if ( newRoot
->fromStream(stream
,zoom
) ) {
347 delete modules_encoding
;
348 modules_encoding
= newRoot
;
354 modules_encoding
->decodeAct(MTypes::Clear
);
355 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
356 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
362 //// SettingsDialog class
364 SettingsDialog::SettingsDialog( ImageViewer
*parent
, IRoot
*settingsHolder
)
365 : QDialog(parent
,Qt::Dialog
), settings(settingsHolder
) {
366 setWindowTitle(tr("Compression settings"));
368 // create a grid layout for the dialog
369 QGridLayout
*layout
= new QGridLayout
;
372 this->treeWidget
= new QTreeWidget(this);
373 treeWidget
->setHeaderLabel(tr("Modules"));
374 layout
->addWidget(treeWidget
,0,0);
375 // add a group-box to display settings of a module
376 this->setBox
= new QGroupBox(this);
377 layout
->addWidget(setBox
,0,1);
378 // add a load/save button-box and connect it to a slot of this dialog
379 this->loadSaveButtons
= new QDialogButtonBox
380 ( QDialogButtonBox::Open
|QDialogButtonBox::Save
, Qt::Horizontal
, this );
381 aConnect( loadSaveButtons
, SIGNAL(clicked(QAbstractButton
*))
382 , this, SLOT(loadSaveClick(QAbstractButton
*)) );
383 layout
->addWidget(loadSaveButtons
,1,0,Qt::AlignLeft
);
384 // add a button-box and connect the clicking actions
385 QDialogButtonBox
*buttons
= new QDialogButtonBox
386 ( QDialogButtonBox::Ok
|QDialogButtonBox::Cancel
, Qt::Horizontal
, this );
387 layout
->addWidget(buttons
,1,1); // the right-bottom cell
388 aConnect( buttons
, SIGNAL(accepted()), this, SLOT(accept()) );
389 aConnect( buttons
, SIGNAL(rejected()), this, SLOT(reject()) );
391 QTreeWidgetItem
*treeRoot
= new QTreeWidgetItem
;
392 treeWidget
->addTopLevelItem(treeRoot
);
393 treeRoot
->setText( 0, tr("Root") );
395 aConnect( treeWidget
, SIGNAL(currentItemChanged(QTreeWidgetItem
*,QTreeWidgetItem
*))
396 , this, SLOT(currentItemChanged(QTreeWidgetItem
*,QTreeWidgetItem
*)) );
400 void SettingsDialog::initialize() {
401 QTreeWidgetItem
*treeRoot
= treeWidget
->topLevelItem(0);
402 assert( treeRoot
&& settings
&& setBox
);
403 clearContainer( treeRoot
->takeChildren() );
404 settings
->adjustSettings(-1,treeRoot
,setBox
);
405 treeWidget
->setCurrentItem(treeRoot
);
406 treeWidget
->expandAll();
408 void SettingsDialog::currentItemChanged(QTreeWidgetItem
*curItem
,QTreeWidgetItem
*) {
409 // get the module that should show its settings
411 Module
*curMod
= static_cast<Module
*>( curItem
->data(0,Qt::UserRole
).value
<void*>() );
413 // clear the settings box and make the module fill it
414 clearContainer( setBox
->children() );
415 setBox
->setTitle( tr("%1 module settings") .arg(curMod
->info().name
) );
416 curMod
->adjustSettings(-1,0,setBox
);
418 void SettingsDialog::settingChanges(int which
) {
419 QTreeWidgetItem
*tree
= treeWidget
->currentItem();
420 Module
*module
= static_cast<Module
*>( tree
->data(0,Qt::UserRole
).value
<void*>() );
421 module
->adjustSettings(which
,tree
,setBox
);
423 void SettingsDialog::loadSaveClick(QAbstractButton
*button
) {
424 QDialogButtonBox::StandardButton test
= loadSaveButtons
->standardButton(button
);
426 case QDialogButtonBox::Open
: { // open-button has been clicked
427 // ask for the name to load from
428 QString fname
= QFileDialog::getOpenFileName( this, tr("Load settings file")
429 , parentViewer().lastDir(), tr("FIC settings (*.fms)") );
432 // try to load the settings
433 IRoot
*newSettings
= clone(settings
,Module::ShallowCopy
);
434 if ( newSettings
->allSettingsFromFile(fname
.toStdString().c_str()) ) {
436 settings
= newSettings
;
439 QMessageBox::information( this, tr("Error")
440 , tr("Cannot load settings from %1.").arg(fname
) );
444 case QDialogButtonBox::Save
: { // save-button has been clicked
445 // ask for the name to save in
446 QString fname
= QFileDialog::getSaveFileName( this, tr("Save settings file")
447 , parentViewer().lastDir(), tr("FIC settings (*.fms)") );
450 // try to save the settings
451 if ( !settings
->allSettingsToFile(fname
.toStdString().c_str()) )
452 QMessageBox::information( this, tr("Error")
453 , tr("Cannot save settings into %1.").arg(fname
) );
460 //// GUI-related Module members
462 QWidget
* newSettingsWidget(Module::ChoiceType type
,QWidget
*parent
) {
466 case Module::IntLog2
:
467 result
= new QSpinBox(parent
);
470 result
= new QDoubleSpinBox(parent
);
472 case Module::ModuleCombo
:
474 result
= new QComboBox(parent
);
482 void Module::adjustSettings(int which
,QTreeWidgetItem
*myTree
,QGroupBox
*setBox
) {
484 // no change has really happened
487 // I should create the subtree -> store this-pointer as data
488 assert( myTree
->childCount() == 0 );
489 myTree
->setData( 0, Qt::UserRole
, QVariant::fromValue((void*)this) );
494 // find child modules and make them create their subtrees
495 const SettingsTypeItem
*setType
= info().setType
;
496 const SettingsItem
*setItem
= settings
;
497 for (; setType
->type
.type
!=Stop
; ++setItem
,++setType
)
498 if ( setType
->type
.type
== ModuleCombo
) {
499 // it is a module -> label its subtree-root and recurse (polymorphically)
501 if ( !setItem
->m
->info().setLength
)
502 continue; // skipping modules with no settings
503 QTreeWidgetItem
*childTree
= new QTreeWidgetItem(myTree
);
504 childTree
->setText( 0, QObject::tr(setType
->label
) );
505 setItem
->m
->adjustSettings( -1, childTree
, 0 );
508 int setLength
= info().setLength
;
509 if ( setBox
&& setLength
) {
510 // fill the group-box
511 QGridLayout
*layout
= new QGridLayout(setBox
);
512 setBox
->setLayout(layout
);
513 const SettingsTypeItem
*typeItem
= info().setType
;
514 for (int i
=0; i
<setLength
; ++i
,++typeItem
) {
515 // add a line - one option
516 QString
desc(typeItem
->desc
);
517 QLabel
*label
= new QLabel( typeItem
->label
, setBox
);
518 label
->setToolTip(desc
);
520 QWidget
*widget
= newSettingsWidget( typeItem
->type
.type
, setBox
);
521 settingsType2widget( widget
, *typeItem
);
522 settings2widget( widget
, i
);
523 widget
->setToolTip(desc
);
524 label
->setBuddy(widget
);
525 layout
->addWidget( label
, i
, 0 );
526 layout
->addWidget( widget
, i
, 1 );
528 SignalChanger
*changer
= new SignalChanger(i
,widget
,typeItem
->type
.type
);
529 aConnect( changer
, SIGNAL(notify(int)) // assuming the setBox's
530 , setBox
->parent(), SLOT(settingChanges(int)) ); // parent is SettingsDialog
534 // which>=0... a setting has changed -> get the change from the widget
535 assert( which
< info().setLength
);
536 QLayoutItem
*item
= setBox
->layout()->itemAt(2*which
+1);
538 widget2settings( item
->widget() , which
);
539 // handle module-type settings
540 const SettingsTypeItem
*setType
= info().setType
;
541 if ( setType
[which
].type
.type
== ModuleCombo
) {
543 // get the new module id and check whether it has really changed
544 SettingsItem
&setItem
= settings
[which
];
545 int newId
= (*setType
->type
.data
.compatIDs
)[setItem
.val
.i
];
546 if ( newId
== setItem
.m
->info().id
)
548 // replace the child module
549 clearContainer( myTree
->takeChildren() );
551 setItem
.m
= ModuleFactory::newModule(newId
);
552 adjustSettings( -1, myTree
, 0 );
554 // update module defaults
555 ModuleFactory::changeDefaultSettings(*this);
558 void Module::widget2settings(const QWidget
*widget
,int which
) {
559 assert( 0<=which
&& which
<info().setLength
);
560 switch( info().setType
[which
].type
.type
) {
563 settings
[which
].val
.i
= debugCast
<const QSpinBox
*>(widget
)->value();
566 settings
[which
].val
.f
= debugCast
<const QDoubleSpinBox
*>(widget
)->value();
570 settings
[which
].val
.i
= debugCast
<const QComboBox
*>(widget
)->currentIndex();
576 void Module::settings2widget(QWidget
*widget
,int which
) {
577 assert( 0<=which
&& which
<info().setLength
);
578 switch( info().setType
[which
].type
.type
) {
581 debugCast
<QSpinBox
*>(widget
)->setValue( settings
[which
].val
.i
);
584 debugCast
<QDoubleSpinBox
*>(widget
)->setValue( settings
[which
].val
.f
);
588 debugCast
<QComboBox
*>(widget
)->setCurrentIndex( settings
[which
].val
.i
);
598 ItemAdder(QWidget
*widget
): box( debugCast
<QComboBox
*>(widget
) ) {}
599 void operator()(int i
) {
600 box
->addItem( ModuleFactory::moduleName(i
) );
601 box
->setItemData( box
->count()-1
602 , QObject::tr(ModuleFactory::moduleDescription(i
)), Qt::ToolTipRole
);
606 void Module::settingsType2widget(QWidget
*widget
,const SettingsTypeItem
&typeItem
) {
608 switch( typeItem
.type
.type
) {
610 debugCast
<QSpinBox
*>(widget
)->setPrefix(QObject::tr("2^"));
613 debugCast
<QSpinBox
*>(widget
)->
614 setRange( typeItem
.type
.data
.i
[0], typeItem
.type
.data
.i
[1] );
617 debugCast
<QDoubleSpinBox
*>(widget
)->
618 setRange( typeItem
.type
.data
.f
[0], typeItem
.type
.data
.f
[1] );
621 const vector
<int> &modules
= *typeItem
.type
.data
.compatIDs
;
622 for_each( modules
.begin(), modules
.end(), ItemAdder(widget
) );
626 debugCast
<QComboBox
*>(widget
)->
627 addItems( QString(typeItem
.type
.data
.text
).split('\n') );
636 void ImageViewer::mousePressEvent(QMouseEvent
*event
) {
637 // check the event, get the clicking point coordinates relative to the image
638 if ( !event
|| !modules_encoding
639 || modules_encoding
->getMode() == IRoot::Clear
)
641 const QPoint click
= imageLabel
->mapFrom( this, event
->pos() );
643 QPixmap pixmap
= *imageLabel
->pixmap();
644 if ( click
.isNull() )
647 static QWidget
*debugWidget
= 0;
649 debugWidget
= modules_encoding
->debugModule(pixmap
,click
);
650 debugWidget
->setParent(this);
651 debugWidget
->setWindowFlags(Qt::Dialog
);
654 changePixmap(pixmap
);
658 EncodingProgress
*EncodingProgress::instance
= 0;