Continuing on with my obsession with smaller images, I thought I’d give an example of how to include jpegtran with your Qt app and how to call it using QProcess to optimize jpegs. Why do this? The use-case I had was that I was downloading images from the net from within my application and I wanted to ensure that they were optimized. I could have read them in and used my modifications to QImageWriter & co. to write them back out, but we are dealing with JPEG which is a lossy format, so I wanted a lossless way to optimize the images.
I use the Qt library and qmake to build my bloodstain pattern analysis software. qmake is a great tool for cross-platform development because it lets you use one relatively succinct description [a .pro file] to generate Xcode projects, MSVC projects, and makefiles. On the Mac OS X side of things, I use one qmake .pro file to generate three xcode projects: release, beta, and demo. My directory structure looks like this:
Overall this works well, but there’s an issue with the Info.plist file.
If you are using the QImageWriter class in Qt to write JPEG files, you may have noticed that it creates large files.
Digging into the code, I looked at what jpegtran [which is what ImageOptim is running] did to reduce file size. It turns out there are two options you can turn on in libjpeg to reduce file size when writing out JPEGs: optimize and progressive scan. These are both lossless operations, so by turning both of these options on we can reduce the size of the image files without sacrificing any quality.
If I turn on just the optimize option, the file size reduces to 287 kB. Turning both optimize and progressive on reduces the files size to… 266 kB – the same as ImageOptim.
I should also note that writing PNGs using QImageWriter is almost useless given the size of the files it produces [at 100% quality]. The same window capture I mention above results in a 6.6 MB PNG! This can be reduced to a much more manageable 242 kB if you run it through ImageOptim. The problem is, unlike JPEG, the time to optimize a PNG is not trivial, so even if there are switches to do something similar, the time it takes to optimize would result in long delays writing out the PNG file. I haven’t fully investigated this, so there may yet be a solution.
To handle the JPEG issue, I filed a Qt report and patch: Support additional JPEG write options: ‘optimize’ and ‘progressive’ [QTBUG-20075].
The patch modifies the following files:
It adds two options to the QImageWriter class: ImageOption::Optimize and ImageOption::Progressive. It is used like this:
QImage image( someImage );
QImageWriter imageWriter( filePath );
imageWriter.setOptimize( true );
imageWriter.setProgressive( true );
imageWriter.write( image );
Magic! Smaller JPEG files for free. I hope this patch will eventually make its way into the release version of Qt.
I can’t see any reason why the optimize option within libjpeg defaults to false in the first place – any ideas?
Qt is a great development framework. I’ve been using it for years and I’m still discovering new & improved ways to do things. For example, I had a rather clunky bit of code to put menu items in the help menu which would open up various pages on my website.
void slotWebsite( void );
void slotForums( void );
void helpMenuOpenURL( const QString &inURL );
void setupMenus( void );
// Set up actions and menus
void myMainWindow::setupMenus( void )
QMenuBar *theMenuBar = menuBar();
QMenu *helpMenu = theMenuBar->addMenu( tr( "Help" ) );
QAction *website = new QAction( tr( "Website" ), this );
connect( website, SIGNAL(triggered()), this, SLOT(slotWebsite()) );
helpMenu->addAction( website );
QAction *forums = new QAction( tr( "Support Forums Online" ), this );
connect( forums, SIGNAL(triggered()), this, SLOT(slotForums()) );
helpMenu->addAction( forums );
void myMainWindow::slotWebsite( void )
helpMenuOpenURL( "http://example.com" );
void myMainWindow::slotForums( void )
helpMenuOpenURL( "http://example.com/forum/" );
// Open a given URL in the default browser
// NOTE: Do not pass inURL from user input unless you've validated it...
void myMainWindow::helpMenuOpenURL( const QString &inURL )
QApplication::setOverrideCursor( Qt::BusyCursor );
// NOTE: I don't compile for Linux, so this is untested
QProcess::startDetached( "kfmclient exec \"" + inURL + '"' );
#elif defined( Q_WS_MAC )
QProcess::startDetached( "open \"" + inURL + '"' );
#elif defined( Q_WS_WIN )
QProcess::startDetached( "rundll32.exe url.dll,FileProtocolHandler " + inURL );
Wow. That’s chunky code. Any additional menu items required a declaration of another one line function which of course meant modifying the header and the cpp file. Messy.
Then I discovered QSignalMapper which let me tie a QString to a given QAction so I would only need one slot to handle any URL opening.