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 24-bits
186 if ( image
.format() != QImage::Format_RGB32
)
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 if ( image
.format() != QImage::Format_RGB32
)
217 image
= image
.convertToFormat(QImage::Format_RGB32
);
218 if (image
.isNull()) {
219 QMessageBox::information( this, tr("Error"), tr("Cannot open %1.").arg(fname
) );
222 if ( image
.width()!=imageLabel
->pixmap()->width()
223 || image
.height()!=imageLabel
->pixmap()->height() ) {
224 QMessageBox::information
225 ( this, tr("Error"), tr("Images don't have the same dimensions.").arg(fname
) );
228 // compute the PSNRs and display them
229 QString message
= getPSNRmessage( image
, imageLabel
->pixmap()->toImage() );
230 QMessageBox::information( this, tr("Comparison"), message
);
232 void ImageViewer::settings() {
233 IRoot
*newSettings
= modules_settings
->clone();
234 SettingsDialog
dialog(this,newSettings
);
235 newSettings
= dialog
.getSettings();
237 // the dialog wasn't cancelled -> swap with the current and new settings
238 swap(newSettings
,modules_settings
);
241 void ImageViewer::encode() {
242 EncodingProgress::create(this);
244 void ImageViewer::encDone() {
246 IRoot
*modules_encoded
= EncodingProgress::destroy(encMsecs
);
248 if (modules_encoded
) { // encoding successful - iterate the image and display some info
249 // replace the old state
250 delete modules_encoding
;
251 modules_encoding
= modules_encoded
;
253 QImage beforeImg
= modules_encoding
->toImage();
257 modules_encoding
->decodeAct(MTypes::Clear
);
258 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
259 int decMsecs
= decTime
.elapsed();
261 QImage afterImg
= modules_encoding
->toImage();
262 changePixmap( QPixmap::fromImage(afterImg
) );
264 QString message
= tr("Time to encode: %1 seconds\nTime to decode: %2 seconds\n")
265 .arg(encMsecs
/1000.0) .arg(decMsecs
/1000.0) + getPSNRmessage(beforeImg
,afterImg
);
266 QMessageBox::information( this, tr("encoded"), message
);
272 void ImageViewer::save() {
273 // get a filename to suggest
274 QFileInfo
finfo(lastPath
.path());
275 QString fname
= finfo
.dir().filePath( finfo
.completeBaseName() + tr(".fic") );
276 fname
= QFileDialog::getSaveFileName
277 ( this, tr("Save encoded image"), fname
, tr("FIC images (*.fic)") );
280 lastPath
.setPath(fname
);
281 if ( !modules_encoding
->toFile( fname
.toStdString().c_str() ) )
282 QMessageBox::information( this, tr("Error"), tr("Cannot write file %1.").arg(fname
) );
284 void ImageViewer::load() {
285 QString fname
= QFileDialog::getOpenFileName
286 ( this, tr("Load encoded image"), lastDir(), tr("FIC images (*.fic)") );
289 lastPath
.setPath(fname
);
290 // IRoot needs to be loaded from cleared state
291 IRoot
*modules_old
= modules_encoding
;
292 modules_encoding
= modules_settings
->clone(Module::ShallowCopy
);
295 bool error
= !file2string( fname
.toStdString().c_str(), decData
);
297 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() {
337 // create a stream that contains the "saved image"
339 if ( encData
.empty() ) { // cache is empty - we have to create it (save the image)
340 if ( !modules_encoding
->toStream(stream
) )
342 encData
= stream
.str();
343 } else // reusing the cache
345 // reload the image from the stream
346 IRoot
*newRoot
= modules_settings
->clone(Module::ShallowCopy
);
347 if ( newRoot
->fromStream(stream
,zoom
) ) {
348 delete modules_encoding
;
349 modules_encoding
= newRoot
;
354 // successfully reloaded -> auto-iterate the image and show it
355 modules_encoding
->decodeAct(MTypes::Clear
);
356 modules_encoding
->decodeAct(MTypes::Iterate
,AutoIterationCount
);
357 changePixmap( QPixmap::fromImage(modules_encoding
->toImage()) );
363 //// SettingsDialog class
365 SettingsDialog::SettingsDialog( ImageViewer
*parent
, IRoot
*settingsHolder
)
366 : QDialog(parent
,Qt::Dialog
), settings(settingsHolder
) {
367 setWindowTitle(tr("Compression settings"));
369 // create a grid layout for the dialog
370 QGridLayout
*layout
= new QGridLayout
;
373 this->treeWidget
= new QTreeWidget(this);
374 treeWidget
->setHeaderLabel(tr("Modules"));
375 layout
->addWidget(treeWidget
,0,0);
376 // add a group-box to display settings of a module
377 this->setBox
= new QGroupBox(this);
378 layout
->addWidget(setBox
,0,1);
379 // add a load/save button-box and connect it to a slot of this dialog
380 this->loadSaveButtons
= new QDialogButtonBox
381 ( QDialogButtonBox::Open
|QDialogButtonBox::Save
, Qt::Horizontal
, this );
382 aConnect( loadSaveButtons
, SIGNAL(clicked(QAbstractButton
*))
383 , this, SLOT(loadSaveClick(QAbstractButton
*)) );
384 layout
->addWidget(loadSaveButtons
,1,0,Qt::AlignLeft
);
385 // add a button-box and connect the clicking actions
386 QDialogButtonBox
*buttons
= new QDialogButtonBox
387 ( QDialogButtonBox::Ok
|QDialogButtonBox::Cancel
, Qt::Horizontal
, this );
388 layout
->addWidget(buttons
,1,1); // the right-bottom cell
389 aConnect( buttons
, SIGNAL(accepted()), this, SLOT(accept()) );
390 aConnect( buttons
, SIGNAL(rejected()), this, SLOT(reject()) );
392 QTreeWidgetItem
*treeRoot
= new QTreeWidgetItem
;
393 treeWidget
->addTopLevelItem(treeRoot
);
394 treeRoot
->setText( 0, tr("Root") );
396 aConnect( treeWidget
, SIGNAL(currentItemChanged(QTreeWidgetItem
*,QTreeWidgetItem
*))
397 , this, SLOT(currentItemChanged(QTreeWidgetItem
*,QTreeWidgetItem
*)) );
401 void SettingsDialog::initialize() {
402 QTreeWidgetItem
*treeRoot
= treeWidget
->topLevelItem(0);
403 ASSERT( treeRoot
&& settings
&& setBox
);
404 clearContainer( treeRoot
->takeChildren() );
405 settings
->adjustSettings(-1,treeRoot
,setBox
);
406 treeWidget
->setCurrentItem(treeRoot
);
407 treeWidget
->expandAll();
409 void SettingsDialog::currentItemChanged(QTreeWidgetItem
*curItem
,QTreeWidgetItem
*) {
410 // get the module that should show its settings
412 Module
*curMod
= static_cast<Module
*>( curItem
->data(0,Qt::UserRole
).value
<void*>() );
414 // clear the settings box and make the module fill it
415 clearContainer( setBox
->children() );
416 setBox
->setTitle( tr("%1 module settings") .arg(curMod
->info().name
) );
417 curMod
->adjustSettings(-1,0,setBox
);
419 void SettingsDialog::settingChanges(int which
) {
420 QTreeWidgetItem
*tree
= treeWidget
->currentItem();
421 Module
*module
= static_cast<Module
*>( tree
->data(0,Qt::UserRole
).value
<void*>() );
422 module
->adjustSettings(which
,tree
,setBox
);
424 void SettingsDialog::loadSaveClick(QAbstractButton
*button
) {
425 QDialogButtonBox::StandardButton test
= loadSaveButtons
->standardButton(button
);
427 case QDialogButtonBox::Open
: { // open-button has been clicked
428 // ask for the name to load from
429 QString fname
= QFileDialog::getOpenFileName( this, tr("Load settings file")
430 , parentViewer().lastDir(), tr("FIC settings (*.fms)") );
433 // try to load the settings
434 IRoot
*newSettings
= settings
->clone(Module::ShallowCopy
);
435 if ( newSettings
->allSettingsFromFile(fname
.toStdString().c_str()) ) {
437 settings
= newSettings
;
440 QMessageBox::information( this, tr("Error")
441 , tr("Cannot load settings from %1.").arg(fname
) );
445 case QDialogButtonBox::Save
: { // save-button has been clicked
446 // ask for the name to save in
447 QString fname
= QFileDialog::getSaveFileName( this, tr("Save settings file")
448 , parentViewer().lastDir(), tr("FIC settings (*.fms)") );
451 // try to save the settings
452 if ( !settings
->allSettingsToFile(fname
.toStdString().c_str()) )
453 QMessageBox::information( this, tr("Error")
454 , tr("Cannot save settings into %1.").arg(fname
) );
461 //// GUI-related Module members
463 QWidget
* newSettingsWidget(Module::ChoiceType type
,QWidget
*parent
) {
467 case Module::IntLog2
:
468 result
= new QSpinBox(parent
);
471 result
= new QDoubleSpinBox(parent
);
473 case Module::ModuleCombo
:
475 result
= new QComboBox(parent
);
483 void Module::adjustSettings(int which
,QTreeWidgetItem
*myTree
,QGroupBox
*setBox
) {
485 // no change has really happened
488 // I should create the subtree -> store this-pointer as data
489 ASSERT( myTree
->childCount() == 0 );
490 myTree
->setData( 0, Qt::UserRole
, QVariant::fromValue((void*)this) );
495 // find child modules and make them create their subtrees
496 const SettingTypeItem
*setType
= info().setType
;
497 const SettingItem
*setItem
= settings
;
498 for (; setType
->type
.type
!=Stop
; ++setItem
,++setType
)
499 if ( setType
->type
.type
== ModuleCombo
) {
500 // it is a module -> label its subtree-root and recurse (polymorphically)
502 if ( !setItem
->m
->info().setLength
)
503 continue; // skipping modules with no settings
504 QTreeWidgetItem
*childTree
= new QTreeWidgetItem(myTree
);
505 childTree
->setText( 0, QObject::tr(setType
->label
) );
506 setItem
->m
->adjustSettings( -1, childTree
, 0 );
509 int setLength
= info().setLength
;
510 if ( setBox
&& setLength
) {
511 // fill the group-box
512 QGridLayout
*layout
= new QGridLayout(setBox
);
513 setBox
->setLayout(layout
);
514 const SettingTypeItem
*typeItem
= info().setType
;
515 for (int i
=0; i
<setLength
; ++i
,++typeItem
) {
516 // add a line - one option
517 QString
desc(typeItem
->desc
);
518 QLabel
*label
= new QLabel( typeItem
->label
, setBox
);
519 label
->setToolTip(desc
);
521 QWidget
*widget
= newSettingsWidget( typeItem
->type
.type
, setBox
);
522 settingsType2widget( widget
, *typeItem
);
523 settings2widget( widget
, i
);
524 widget
->setToolTip(desc
);
525 label
->setBuddy(widget
);
526 layout
->addWidget( label
, i
, 0 );
527 layout
->addWidget( widget
, i
, 1 );
529 SignalChanger
*changer
= new SignalChanger(i
,widget
,typeItem
->type
.type
);
530 aConnect( changer
, SIGNAL(notify(int)) // assuming the setBox's
531 , setBox
->parent(), SLOT(settingChanges(int)) ); // parent is SettingsDialog
535 // which>=0... a setting has changed -> get the change from the widget
536 ASSERT( which
< info().setLength
);
537 QLayoutItem
*item
= setBox
->layout()->itemAt(2*which
+1);
539 widget2settings( item
->widget() , which
);
540 // handle module-type settings
541 const SettingTypeItem
&setType
= info().setType
[which
];
542 if ( setType
.type
.type
== ModuleCombo
) {
544 // get the new module id and check whether it has really changed
545 SettingItem
&setItem
= settings
[which
];
546 int newId
= (*setType
.type
.data
.compatIDs
)[setItem
.val
.i
];
547 if ( newId
== setItem
.m
->info().id
)
549 // replace the child module
550 clearContainer( myTree
->takeChildren() );
552 setItem
.m
= ModuleFactory::newModule(newId
);
553 adjustSettings( -1, myTree
, 0 );
555 // update module defaults
556 ModuleFactory::changeDefaultSettings(*this);
559 void Module::widget2settings(const QWidget
*widget
,int which
) {
560 ASSERT( 0<=which
&& which
<info().setLength
);
561 switch( info().setType
[which
].type
.type
) {
564 settings
[which
].val
.i
= debugCast
<const QSpinBox
*>(widget
)->value();
567 settings
[which
].val
.f
= debugCast
<const QDoubleSpinBox
*>(widget
)->value();
571 settings
[which
].val
.i
= debugCast
<const QComboBox
*>(widget
)->currentIndex();
577 void Module::settings2widget(QWidget
*widget
,int which
) {
578 ASSERT( 0<=which
&& which
<info().setLength
);
579 switch( info().setType
[which
].type
.type
) {
582 debugCast
<QSpinBox
*>(widget
)->setValue( settings
[which
].val
.i
);
585 debugCast
<QDoubleSpinBox
*>(widget
)->setValue( settings
[which
].val
.f
);
589 debugCast
<QComboBox
*>(widget
)->setCurrentIndex( settings
[which
].val
.i
);
599 ItemAdder(QWidget
*widget
): box( debugCast
<QComboBox
*>(widget
) ) {}
600 void operator()(int i
) {
601 const Module::TypeInfo
&info
= ModuleFactory::prototype(i
).info();
602 box
->addItem(info
.name
);
603 box
->setItemData( box
->count()-1, QObject::tr(info
.desc
), Qt::ToolTipRole
);
607 void Module::settingsType2widget(QWidget
*widget
,const SettingTypeItem
&typeItem
) {
609 const SettingType
&type
= typeItem
.type
;
613 debugCast
<QSpinBox
*>(widget
)->setPrefix(QObject::tr("2^"));
616 debugCast
<QSpinBox
*>(widget
)->setRange( type
.data
.i
[0], type
.data
.i
[1] );
617 // try to guess a reasonable step for the spin-box
618 int range
= type
.data
.i
[1]-type
.data
.i
[0];
619 int step
= (int)exp10(floor(log10(range
/5)));
622 debugCast
<QSpinBox
*>(widget
)->setSingleStep(step
);
626 debugCast
<QDoubleSpinBox
*>(widget
)->setRange( type
.data
.f
[0], type
.data
.f
[1] );
627 // try to guess a reasonable step for the spin-box
628 float range
= type
.data
.f
[1]-type
.data
.f
[0];
629 debugCast
<QDoubleSpinBox
*>(widget
)->setSingleStep( exp10(floor(log10(range
/5))) );
633 const vector
<int> &modules
= *typeItem
.type
.data
.compatIDs
;
634 for_each( modules
.begin(), modules
.end(), ItemAdder(widget
) );
638 debugCast
<QComboBox
*>(widget
)->addItems( QString(type
.data
.text
).split('\n') );
647 void ImageViewer::mousePressEvent(QMouseEvent
*event
) {
648 // check the event, get the clicking point coordinates relative to the image
649 if ( !event
|| !modules_encoding
650 || modules_encoding
->getMode() == IRoot::Clear
)
652 const QPoint click
= imageLabel
->mapFrom( this, event
->pos() );
654 QPixmap pixmap
= *imageLabel
->pixmap();
655 if ( click
.isNull() )
658 static QWidget
*debugWidget
= 0;
660 debugWidget
= modules_encoding
->debugModule(pixmap
,click
);
661 debugWidget
->setParent(this);
662 debugWidget
->setWindowFlags(Qt::Dialog
);
665 changePixmap(pixmap
);
670 EncodingProgress
*EncodingProgress::instance
= 0;