1 #include <sstream> // needed for ::load and ::rezoom
4 #include "modules/colorModel.h" // using color coefficients for RGB->gray conversion
8 //// Non-member functions
9 static vector
<double> getColorPSNR(const QImage
&a
,const QImage
&b
) {
10 int width
= a
.width(), height
= a
.height();
13 int sum
, sumR
, sumG
, sumB
;
14 sum
= sumR
= sumG
= sumB
= 0;
15 for (y
=0; y
<height
; ++y
) {
16 line1
= (QRgb
*)a
.scanLine(y
);
17 line2
= (QRgb
*)b
.scanLine(y
);
18 for (x
=0; x
<width
; ++x
) {
19 sum
+= sqr( getGray(line1
[x
]) - getGray(line2
[x
]) );
20 sumR
+= sqr( qRed(line1
[x
]) - qRed(line2
[x
]) );
21 sumG
+= sqr( qGreen(line1
[x
]) - qGreen(line2
[x
]) );
22 sumB
+= sqr( qBlue(line1
[x
]) - qBlue(line2
[x
]) );
25 vector
<double> result(4);
30 double mul
= double(width
*height
) * double(sqr(255));
31 for (vector
<double>::iterator it
=result
.begin(); it
!=result
.end(); ++it
)
32 *it
= 10.0 * log10(mul
/ *it
);
35 static QString
getPSNRmessage(const QImage
&img1
,const QImage
&img2
) {
36 vector
<double> psnr
= getColorPSNR(img1
,img2
);
37 return QObject::tr("gray PSNR: %3 dB\nR,G,B: %4,%5,%6 dB") .arg(psnr
[3],0,'f',2)
38 .arg(psnr
[0],0,'f',2) .arg(psnr
[1],0,'f',2) .arg(psnr
[2],0,'f',2);
43 ImageViewer::ImageViewer(QApplication
&app
)
44 : modules_settings( IRoot::newCompatibleModule() ), modules_encoding(0)
45 , zoom(0), lastPath(QDir::current().filePath("x"))
46 , readAct(this), writeAct(this), compareAct(this), exitAct(this)
47 , settingsAct(this), encodeAct(this), saveAct(this)
48 , loadAct(this), clearAct(this), iterateAct(this), zoomIncAct(this), zoomDecAct(this) {
49 // try to load guessed language translation
50 if ( translator
.load( "lang-"+QLocale::system().name(), app
.applicationDirPath() ) )
51 app
.installTranslator(&translator
);
52 // create a scrolling area with a label for viewing images
53 imageLabel
= new QLabel(this);
54 imageLabel
->setBackgroundRole(QPalette::Dark
);
55 imageLabel
->setSizePolicy(QSizePolicy::Ignored
,QSizePolicy::Ignored
);
57 QScrollArea
*scrollArea
= new QScrollArea(this);
58 scrollArea
->setBackgroundRole(QPalette::Dark
);
59 scrollArea
->setWidget(imageLabel
);
60 setCentralWidget(scrollArea
);
62 setStatusBar(new QStatusBar(this));
73 void ImageViewer::createActions() {
74 // create all actions and connect them to the appropriate slots
76 aConnect( &name##Act, SIGNAL(triggered()), this, SLOT(name()) );
77 #define AS(name,signal) \
78 aConnect( &name##Act, SIGNAL(triggered()), this, SLOT(signal()) );
97 void ImageViewer::createMenus() {
98 imageMenu
.addAction(&readAct
);
99 imageMenu
.addAction(&writeAct
);
100 imageMenu
.addSeparator();
101 imageMenu
.addAction(&compareAct
);
102 imageMenu
.addSeparator();
103 imageMenu
.addAction(&exitAct
);
105 compMenu
.addAction(&settingsAct
);
106 compMenu
.addAction(&encodeAct
);
107 compMenu
.addAction(&saveAct
);
109 decompMenu
.addAction(&loadAct
);
110 decompMenu
.addAction(&clearAct
);
111 decompMenu
.addAction(&iterateAct
);
112 decompMenu
.addSeparator();
113 decompMenu
.addAction(&zoomIncAct
);
114 decompMenu
.addAction(&zoomDecAct
);
116 menuBar()->addMenu(&imageMenu
);
117 menuBar()->addMenu(&compMenu
);
118 menuBar()->addMenu(&decompMenu
);
119 //menuBar()->addMenu(langMenu);
120 //menuBar()->addMenu(helpMenu);
122 void ImageViewer::translateUi() {
123 setWindowTitle(tr("Fractal Image Compressor"));
124 // set action names and shortcuts
125 #define A(name,shortcut,text) \
126 name##Act.setText(tr(text)); \
127 name##Act.setShortcut(tr(shortcut));
128 A(read
, "Ctrl+R", "Read...")
129 A(write
, "Ctrl+W", "Write...")
130 A(compare
, "", "Compare to...")
131 A(exit
, "Ctrl+Q", "Quit")
133 A(settings
, "", "Settings")
134 A(encode
, " ", "Start encoding")
135 A(save
, "Ctrl+S", "Save FIC...")
137 A(load
, "Ctrl+L", "Load FIC...")
138 A(clear
, "Ctrl+C", "Clear image")
139 A(iterate
, "Ctrl+I", "Iterate image")
140 A(zoomInc
, "Ctrl++", "Increase zoom")
141 A(zoomDec
, "Ctrl+-", "Decrease zoom")
144 // set the tiles of menu items
145 #define M(name,title) \
146 name##Menu.setTitle(tr(title));
148 M(comp
, "&Compression");
149 M(decomp
, "&Decompression");
152 void ImageViewer::updateActions() {
153 bool pixmapOk
= imageLabel
->pixmap();
154 IRoot::Mode mode
= modules_encoding
? modules_encoding
->getMode() : IRoot::Clear
;
156 //readAct.setEnabled(true);
157 writeAct
.setEnabled(pixmapOk
);
158 compareAct
.setEnabled(pixmapOk
);
159 //exitAct.setEnabled(true);
161 //settingsAct.setEnabled(true);
162 encodeAct
.setEnabled(pixmapOk
);
163 saveAct
.setEnabled( mode
!= IRoot::Clear
);
165 //loadAct.setEnabled(true);
166 clearAct
.setEnabled ( mode
!= IRoot::Clear
);
167 iterateAct
.setEnabled( mode
!= IRoot::Clear
);
168 zoomIncAct
.setEnabled( mode
!= IRoot::Clear
&& zoom
<3 );
169 zoomDecAct
.setEnabled( mode
!= IRoot::Clear
&& zoom
>0 );
173 void ImageViewer::read() {
175 QString fname
= QFileDialog::getOpenFileName( this, tr("Read image file")
176 , lastDir(), tr("PNG images (*.png)\nAll files (*.*)") );
180 lastPath
.setPath(fname
);
181 // try to load, check for errors
183 if (image
.isNull()) {
184 QMessageBox::information( this, tr("Error"), tr("Cannot open %1.").arg(fname
) );
187 // convert to 24-bits
188 if ( image
.format() != QImage::Format_RGB32
)
189 image
= image
.convertToFormat(QImage::Format_RGB32
);
191 changePixmap(QPixmap::fromImage(image
));
194 void ImageViewer::write() {
196 QString fname
= QFileDialog::getSaveFileName( this, tr("Write image file")
197 , lastDir(), tr("PNG images (*.png)\nAll files (*.*)") );
200 lastPath
.setPath(fname
);
201 // try to save the image
202 if ( !imageLabel
->pixmap()->save(fname
) ) {
203 QMessageBox::information( this, tr("Error"), tr("Cannot write file %1.").arg(fname
) );
208 void ImageViewer::compare() {
209 // let the user choose a file
210 QString fname
= QFileDialog::getOpenFileName
211 ( this, tr("Compare to image"), lastDir()
212 , tr("PNG images (*.png)\nJFIF images (*.jpg *.jpeg)\nAll files (*.*)") );
215 lastPath
.setPath(fname
);
216 // open the file as an image, check it's got the same dimensions as the diplayed one
218 if ( image
.format() != QImage::Format_RGB32
)
219 image
= image
.convertToFormat(QImage::Format_RGB32
);
220 if (image
.isNull()) {
221 QMessageBox::information( this, tr("Error"), tr("Cannot open %1.").arg(fname
) );
224 if ( image
.width()!=imageLabel
->pixmap()->width()
225 || image
.height()!=imageLabel
->pixmap()->height() ) {
226 QMessageBox::information
227 ( this, tr("Error"), tr("Images don't have the same dimensions.").arg(fname
) );
230 // compute the PSNRs and display them
231 QString message
= getPSNRmessage( image
, imageLabel
->pixmap()->toImage() );
232 QMessageBox::information( this, tr("Comparison"), message
);
234 void ImageViewer::settings() {
235 IRoot
*newSettings
= modules_settings
->clone();
236 SettingsDialog
dialog(this,newSettings
);
237 newSettings
= dialog
.getSettings();
239 // the dialog wasn't cancelled -> swap with the current and new settings
240 swap(newSettings
,modules_settings
);
243 void ImageViewer::encode() {
244 EncodingProgress::create(this);
246 void ImageViewer::encDone() {
248 IRoot
*modules_encoded
= EncodingProgress::destroy(encMsecs
);
250 if (modules_encoded
) { // encoding successful - iterate the image and display some info
252 // replace the old state
253 delete modules_encoding
;
254 modules_encoding
= modules_encoded
;
256 QImage beforeImg
= modules_encoding
->toImage();
260 modules_encoding
->decodeAct(MTypes::Clear
);
261 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
262 int decMsecs
= decTime
.elapsed();
264 QImage afterImg
= modules_encoding
->toImage();
265 changePixmap( QPixmap::fromImage(afterImg
) );
267 QString message
= tr("Time to encode: %1 seconds\nTime to decode: %2 seconds\n")
268 .arg(encMsecs
/1000.0) .arg(decMsecs
/1000.0) + getPSNRmessage(beforeImg
,afterImg
);
269 QMessageBox::information( this, tr("encoded"), message
);
275 void ImageViewer::save() {
276 // get a filename to suggest
277 QFileInfo
finfo(lastPath
.path());
278 QString fname
= finfo
.dir().filePath( finfo
.completeBaseName() + tr(".fic") );
279 fname
= QFileDialog::getSaveFileName
280 ( this, tr("Save encoded image"), fname
, tr("FIC images (*.fic)") );
283 lastPath
.setPath(fname
);
284 if ( !modules_encoding
->toFile( fname
.toStdString().c_str() ) )
285 QMessageBox::information( this, tr("Error"), tr("Cannot write file %1.").arg(fname
) );
287 void ImageViewer::load() {
288 QString fname
= QFileDialog::getOpenFileName
289 ( this, tr("Load encoded image"), lastDir(), tr("FIC images (*.fic)") );
292 lastPath
.setPath(fname
);
293 // IRoot needs to be loaded from cleared state
294 IRoot
*modules_old
= modules_encoding
;
295 modules_encoding
= modules_settings
->clone(Module::ShallowCopy
);
298 bool error
= !file2string( fname
.toStdString().c_str(), decData
);
300 stringstream
stream(decData
);
301 error
= !modules_encoding
->fromStream( stream
, zoom
);
305 QMessageBox::information( this, tr("Error"), tr("Cannot load file %1.").arg(fname
) );
306 swap(modules_encoding
,modules_old
);
307 } else { // loading was successful
308 swap(encData
,decData
);
309 modules_encoding
->decodeAct(Clear
);
310 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
311 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
317 void ImageViewer::clear() {
318 modules_encoding
->decodeAct(Clear
);
319 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
322 void ImageViewer::iterate() {
323 modules_encoding
->decodeAct(Iterate
);
324 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
327 void ImageViewer::zoomInc() {
330 --zoom
, QMessageBox::information( this, tr("Error"), tr("Zooming failed.") );
332 void ImageViewer::zoomDec() {
336 ++zoom
, QMessageBox::information( this, tr("Error"), tr("Zooming failed.") );
339 bool ImageViewer::rezoom() {
340 // create a stream that contains the "saved image"
342 if ( encData
.empty() ) { // cache is empty - we have to create it (save the image)
343 if ( !modules_encoding
->toStream(stream
) )
345 encData
= stream
.str();
346 } else // reusing the cache
348 // reload the image from the stream
349 IRoot
*newRoot
= modules_settings
->clone(Module::ShallowCopy
);
350 if ( newRoot
->fromStream(stream
,zoom
) ) {
351 delete modules_encoding
;
352 modules_encoding
= newRoot
;
357 // successfully reloaded -> auto-iterate the image and show it
358 modules_encoding
->decodeAct(MTypes::Clear
);
359 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
360 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
366 //// SettingsDialog class
368 SettingsDialog::SettingsDialog( ImageViewer
*parent
, IRoot
*settingsHolder
)
369 : QDialog(parent
,Qt::Dialog
), settings(settingsHolder
) {
370 setWindowTitle(tr("Compression settings"));
372 // create a grid layout for the dialog
373 QGridLayout
*layout
= new QGridLayout
;
376 this->treeWidget
= new QTreeWidget(this);
377 treeWidget
->setHeaderLabel(tr("Modules"));
378 layout
->addWidget(treeWidget
,0,0);
379 // add a group-box to display settings of a module
380 this->setBox
= new QGroupBox(this);
381 layout
->addWidget(setBox
,0,1);
382 // add a load/save button-box and connect it to a slot of this dialog
383 this->loadSaveButtons
= new QDialogButtonBox
384 ( QDialogButtonBox::Open
|QDialogButtonBox::Save
, Qt::Horizontal
, this );
385 aConnect( loadSaveButtons
, SIGNAL(clicked(QAbstractButton
*))
386 , this, SLOT(loadSaveClick(QAbstractButton
*)) );
387 layout
->addWidget(loadSaveButtons
,1,0,Qt::AlignLeft
);
388 // add a button-box and connect the clicking actions
389 QDialogButtonBox
*buttons
= new QDialogButtonBox
390 ( QDialogButtonBox::Ok
|QDialogButtonBox::Cancel
, Qt::Horizontal
, this );
391 layout
->addWidget(buttons
,1,1); // the right-bottom cell
392 aConnect( buttons
, SIGNAL(accepted()), this, SLOT(accept()) );
393 aConnect( buttons
, SIGNAL(rejected()), this, SLOT(reject()) );
395 QTreeWidgetItem
*treeRoot
= new QTreeWidgetItem
;
396 treeWidget
->addTopLevelItem(treeRoot
);
397 treeRoot
->setText( 0, tr("Root") );
399 aConnect( treeWidget
, SIGNAL(currentItemChanged(QTreeWidgetItem
*,QTreeWidgetItem
*))
400 , this, SLOT(currentItemChanged(QTreeWidgetItem
*,QTreeWidgetItem
*)) );
404 void SettingsDialog::initialize() {
405 QTreeWidgetItem
*treeRoot
= treeWidget
->topLevelItem(0);
406 ASSERT( treeRoot
&& settings
&& setBox
);
408 clearQtContainer( treeRoot
->takeChildren() );
410 settings
->adjustSettings(-1,treeRoot
,setBox
);
411 treeWidget
->setCurrentItem(treeRoot
);
412 treeWidget
->expandAll();
414 void SettingsDialog::currentItemChanged(QTreeWidgetItem
*curItem
,QTreeWidgetItem
*) {
415 // get the module that should show its settings
417 Module
*curMod
= static_cast<Module
*>( curItem
->data(0,Qt::UserRole
).value
<void*>() );
419 // clear the settings box and make the module fill it
420 clearQtContainer( setBox
->children() );
421 setBox
->setTitle( tr("%1 module settings") .arg(curMod
->info().name
) );
422 curMod
->adjustSettings(-1,0,setBox
);
424 void SettingsDialog::settingChanges(int which
) {
425 QTreeWidgetItem
*tree
= treeWidget
->currentItem();
426 Module
*module
= static_cast<Module
*>( tree
->data(0,Qt::UserRole
).value
<void*>() );
427 module
->adjustSettings(which
,tree
,setBox
);
429 void SettingsDialog::loadSaveClick(QAbstractButton
*button
) {
430 QDialogButtonBox::StandardButton test
= loadSaveButtons
->standardButton(button
);
432 case QDialogButtonBox::Open
: { // open-button has been clicked
433 // ask for the name to load from
434 QString fname
= QFileDialog::getOpenFileName( this, tr("Load settings file")
435 , parentViewer().lastDir(), tr("FIC settings (*.fms)") );
438 // try to load the settings
439 IRoot
*newSettings
= settings
->clone(Module::ShallowCopy
);
440 if ( newSettings
->allSettingsFromFile(fname
.toStdString().c_str()) ) {
442 settings
= newSettings
;
445 QMessageBox::information( this, tr("Error")
446 , tr("Cannot load settings from %1.").arg(fname
) );
450 case QDialogButtonBox::Save
: { // save-button has been clicked
451 // ask for the name to save in
452 QString fname
= QFileDialog::getSaveFileName( this, tr("Save settings file")
453 , parentViewer().lastDir(), tr("FIC settings (*.fms)") );
456 // try to save the settings
457 if ( !settings
->allSettingsToFile(fname
.toStdString().c_str()) )
458 QMessageBox::information( this, tr("Error")
459 , tr("Cannot save settings into %1.").arg(fname
) );
466 //// GUI-related Module members
468 QWidget
* newSettingsWidget(Module::ChoiceType type
,QWidget
*parent
) {
472 case Module::IntLog2
:
473 result
= new QSpinBox(parent
);
476 result
= new QDoubleSpinBox(parent
);
478 case Module::ModuleCombo
:
480 result
= new QComboBox(parent
);
488 void Module::adjustSettings(int which
,QTreeWidgetItem
*myTree
,QGroupBox
*setBox
) {
490 // no change has really happened
493 // I should create the subtree -> store this-pointer as data
494 ASSERT( myTree
->childCount() == 0 );
495 myTree
->setData( 0, Qt::UserRole
, QVariant::fromValue((void*)this) );
500 // find child modules and make them create their subtrees
501 const SettingTypeItem
*setType
= info().setType
;
502 const SettingItem
*setItem
= settings
;
503 for (; setType
->type
.type
!=Stop
; ++setItem
,++setType
)
504 if ( setType
->type
.type
== ModuleCombo
) {
505 // it is a module -> label its subtree-root and recurse (polymorphically)
507 if ( !setItem
->m
->info().setLength
)
508 continue; // skipping modules with no settings
509 QTreeWidgetItem
*childTree
= new QTreeWidgetItem(myTree
);
510 childTree
->setText( 0, QObject::tr(setType
->label
) );
511 setItem
->m
->adjustSettings( -1, childTree
, 0 );
514 int setLength
= info().setLength
;
515 if ( setBox
&& setLength
) {
516 // fill the group-box
517 QGridLayout
*layout
= new QGridLayout(setBox
);
518 setBox
->setLayout(layout
);
519 const SettingTypeItem
*typeItem
= info().setType
;
520 for (int i
=0; i
<setLength
; ++i
,++typeItem
) {
521 // add a line - one option
522 QString
desc(typeItem
->desc
);
523 QLabel
*label
= new QLabel( typeItem
->label
, setBox
);
524 label
->setToolTip(desc
);
526 QWidget
*widget
= newSettingsWidget( typeItem
->type
.type
, setBox
);
527 settingsType2widget( widget
, *typeItem
);
528 settings2widget( widget
, i
);
529 widget
->setToolTip(desc
);
530 label
->setBuddy(widget
);
531 layout
->addWidget( label
, i
, 0 );
532 layout
->addWidget( widget
, i
, 1 );
534 SignalChanger
*changer
= new SignalChanger(i
,widget
,typeItem
->type
.type
);
535 aConnect( changer
, SIGNAL(notify(int)) // assuming the setBox's
536 , setBox
->parent(), SLOT(settingChanges(int)) ); // parent is SettingsDialog
540 // which>=0... a setting has changed -> get the change from the widget
541 ASSERT( which
< info().setLength
);
542 QLayoutItem
*item
= setBox
->layout()->itemAt(2*which
+1);
544 widget2settings( item
->widget() , which
);
545 // handle module-type settings
546 const SettingTypeItem
&setType
= info().setType
[which
];
547 if ( setType
.type
.type
== ModuleCombo
) {
549 // get the new module id and check whether it has really changed
550 SettingItem
&setItem
= settings
[which
];
551 int newId
= (*setType
.type
.data
.compatIDs
)[setItem
.val
.i
];
552 if ( newId
== setItem
.m
->info().id
)
554 // replace the child module
555 clearQtContainer( myTree
->takeChildren() );
557 setItem
.m
= ModuleFactory::newModule(newId
);
558 adjustSettings( -1, myTree
, 0 );
560 // update module defaults
561 ModuleFactory::changeDefaultSettings(*this);
564 void Module::widget2settings(const QWidget
*widget
,int which
) {
565 ASSERT( 0<=which
&& which
<info().setLength
);
566 switch( info().setType
[which
].type
.type
) {
569 settings
[which
].val
.i
= debugCast
<const QSpinBox
*>(widget
)->value();
572 settings
[which
].val
.f
= debugCast
<const QDoubleSpinBox
*>(widget
)->value();
576 settings
[which
].val
.i
= debugCast
<const QComboBox
*>(widget
)->currentIndex();
582 void Module::settings2widget(QWidget
*widget
,int which
) {
583 ASSERT( 0<=which
&& which
<info().setLength
);
584 switch( info().setType
[which
].type
.type
) {
587 debugCast
<QSpinBox
*>(widget
)->setValue( settings
[which
].val
.i
);
590 debugCast
<QDoubleSpinBox
*>(widget
)->setValue( settings
[which
].val
.f
);
594 debugCast
<QComboBox
*>(widget
)->setCurrentIndex( settings
[which
].val
.i
);
604 ItemAdder(QWidget
*widget
): box( debugCast
<QComboBox
*>(widget
) ) {}
605 void operator()(int i
) {
606 const Module::TypeInfo
&info
= ModuleFactory::prototype(i
).info();
607 box
->addItem(info
.name
);
608 box
->setItemData( box
->count()-1, QObject::tr(info
.desc
), Qt::ToolTipRole
);
612 void Module::settingsType2widget(QWidget
*widget
,const SettingTypeItem
&typeItem
) {
614 const SettingType
&type
= typeItem
.type
;
618 debugCast
<QSpinBox
*>(widget
)->setPrefix(QObject::tr("2^"));
621 debugCast
<QSpinBox
*>(widget
)->setRange( type
.data
.i
[0], type
.data
.i
[1] );
622 // try to guess a reasonable step for the spin-box
623 int range
= type
.data
.i
[1]-type
.data
.i
[0];
624 int step
= (int)exp10(floor(log10(range
/5)));
627 debugCast
<QSpinBox
*>(widget
)->setSingleStep(step
);
631 debugCast
<QDoubleSpinBox
*>(widget
)->setRange( type
.data
.f
[0], type
.data
.f
[1] );
632 // try to guess a reasonable step for the spin-box
633 float range
= type
.data
.f
[1]-type
.data
.f
[0];
634 debugCast
<QDoubleSpinBox
*>(widget
)->setSingleStep( exp10(floor(log10(range
/5))) );
638 const vector
<int> &modules
= *typeItem
.type
.data
.compatIDs
;
639 for_each( modules
, ItemAdder(widget
) );
643 debugCast
<QComboBox
*>(widget
)->addItems( QString(type
.data
.text
).split('\n') );
652 void ImageViewer::mousePressEvent(QMouseEvent
*event
) {
653 // check the event, get the clicking point coordinates relative to the image
654 if ( !event
|| !modules_encoding
655 || modules_encoding
->getMode() == IRoot::Clear
)
657 const QPoint click
= imageLabel
->mapFrom( this, event
->pos() );
659 QPixmap pixmap
= *imageLabel
->pixmap();
660 if ( click
.isNull() )
663 static QWidget
*debugWidget
= 0;
665 debugWidget
= modules_encoding
->debugModule(pixmap
,click
);
666 debugWidget
->setParent(this);
667 debugWidget
->setWindowFlags(Qt::Dialog
);
670 changePixmap(pixmap
);
675 EncodingProgress
*EncodingProgress::instance
= 0;