Code Example
Tested with
Compatibility
Symbian
Article
Image editing techniques and algorithms using Qt
This article explains how to edit images at the pixel level using Qt in order to implement a number of visual effects. The effects demonstrated include: grey scale, blur, sharpen, drawing to a frame or metal, and changing the image saturation, brightness, or warmth.
Introduction
In this article we'll discuss different techniques and algorithms to modify an image using Qt, but before we continue you must know some principles to work with images.
- There are 2 main classes to represent an image in Qt: QImage and QPixmap; we can also use QBitmap for monochrome images like masks, and QPicture stores QPainter drawing commands.
-
- QPixmap is the recommended class to use when we want to draw images on the screen because it's the quickest way to do it. The problem with QPixmap is that we cannot access to an individual pixel to read it and modify it.
- QImage is faster than QPixmap in IO operations and gives us access to the individual pixel information. It's the class that we'll use in this article to edit images.
- If you are managing big images, like photos taken with the camera, it's recommended to work with a downsized image to show it on the screen as a preview image unless we want to allow users to zoom in the image. There are two ways to load an image from a file and downsize it:
-
- Load the image into a QImage or QPixmap and resize it later:
- Using QImageReader setting the target size before loading the image into a QImage - QImageReader can't load an image into aQPixmap but it's easy to create a QPixmap from a QImage with the static method QPixmap::fromImage(QImage img). This method is faster and you won't need the memory required to load the full size image:
- Every image is made by pixels and every pixel is formed by 3 color channels: red, green and blue, and an alpha channel that contains the transparency value (JPG and other image formats don't support transparency). These channels have values between 0 and 255 and black color is formed when these 3 color channels are 0 while white is represented when the value of the 3 channels is 255. To represent a color in this article we'll refer to it as RGB(red, green, blue) being red, green and blue the value of these 3 channels.
- To simplify the code below we are reading pixel by pixel, but reading a full line with the method QImage::scanLine(int i) is more efficient. This is an example on how the code would look like reading pixels line by line with the first technique we'll describe: converting an image to grey scale.
Grey Scale
First technique we'll see is converting an image to grey scale. We must know that grey tones have similar if not the same value in the 3 color channels, so our strategy will be to modify every pixel and make the RGB channels have the same value. We'll determine this value with the arithmetic mean of the 3 channels so if the original color is RGB(169, 204, 69) the new color will be RGB(147, 147, 147) - (169+204+69)/3 = 147 -.
Brightness
As we said in the introduction of this article, white is represented as RGB(255, 255, 255) while black is RGB(0, 0, 0), so if we want to increase the brightness (the color will be closer to white) we need to increment the value of the 3 channels and decrease it to get a darker image.
In this case we'll use a delta parameter to determine how much we'll increase all the channels, so if delta is negative we'll get a dark image. After adding delta we only need to check the new values are between 0 and 255.
Warm
When we talk about a warm image that's because colors tend to yellow. We don't have a yellow channel, but however yellow is the result of mixing red and green so to get a warm image we'll increase these two channels leaving blue channel with the same value.
We'll use a delta parameter to determine how much we'll increase red and green channels. With a warmer image we can get a retro effect and for example in images where there's sand we'll get a more vivid image.
Cool
If colors in a warm image tend to yellow, with cool images the most intense color is blue. In our method we'll increase the blue channel with a delta value leaving red and green channels unmodified.
Cool images can be related with future, death or simply cold.
Saturation
We've said colors are formed by 3 channels: red, green and blue. Even if that's true, RGB isn't the only way to represent a color and in this case we'll use the HSL format - hue, saturation, lightness. We only need to increase saturation channel to get our saturated image.
Saturated images have more vivid colors and use to be more spectacular, but it's important not to abuse with the saturation or we'll lose the fidelity of the original image.
Blur
This is a bit more complex than what we have made until now. To blur an image we'll use a convolution filter that creates the new color of every pixel according to its original color and the color of the adjacent pixels. There's a matrix called kernel that determines how much influence will have each adjacent pixel in this calculation.
Our original pixel will be in the center of this matrix and for that reason we'll use matrices with an odd number of rows and columns. Note that we won't modify the pixels that are in the borders of the image because we don't have all the adjacent pixels we need, although we could modify the method to use only the available pixels.
Let's see an example on how we calculate the RGB value for a pixel. These are the matrices that represent the red, green and blue channels of our pixel and its adjacent pixels - our pixel is in the center of the matrix:
And our kernel is:
And this is how we calculate our new value after applying the filter:
So we went from RGB(200, 21, 72) to RGB(159, 23, 72). You can notice that the biggest change was in the red channel where there's a big difference in the value of the adjacent pixels.
One of the cases when we'll want to use blur with an image is when we have portraits. Blur will help us to hide skin flaws.
Sharpen
To sharpen an image we'll use again a convolution filter as the one we used to blur it, but with a different kernel. Now, the kernel will have negative values in the adjacent pixels.
Sharpening is useful with blurry images, it helps to improve the level of detail.
Drawing a Frame
Drawing a frame is quite simple, we only need to draw the frame over our original image. In our method we suppose we have a picture with the same size of our frame, but we could need to resize the frame or even use 4 images for the 4 borders to keep the thickness of the frame when we resize it.
Drawing on Metal
This is a sample on how we can combine several techniques to get an effect. These are the steps we follow to get this effect:
- Adjust the brightness of the image to get a darker picture.
- Convert the result to grey scale.
- We draw the grey picture over a metal texture applying an opacity of 50%
Blurred Frame
To finalize, we'll see another combined effect. This time we want to blur the exterior part of the picture so the focus is in the center.
We'll use an image that serves as mask to determine what part of the image will be blurred. These are the steps we take:
- We get a full blurred image from our original picture.
- Using one of QPainter's composition modes we get a blurred frame with the shape of our mask. You can learn more about QPainter's composition modes in QPainter documentation.
- We draw our blurred frame over the original image.
Sample App
You can download the source code of a sample app with all the methods we have seen in this article. In this app it's included 3 sample images with the same size, 462x260, to test the different methods. You simply select one of these pictures and touch one of the buttons to apply the desired effect.
Download: File:Image Editor.zip
Summary
This article should serve you as an entry point to image editing, but possibilities are endless. You can modify these methods, combine them, use other techniques... imagination is your only limit.
Daliusd - Grey scale
Overall nice listing of algorithms but my recommendation for grey scale algorithm is to use luma formula Y = 0.2126 R + 0.7152 G + 0.0722 B from Luma instead of giving similar weight to each color.daliusd 15:55, 10 May 2012 (EEST)
Hamishwillee - Awesome
Hi Sheenmue,
Great article. I like in particular how you've kept it focussed with clear code samples and good comparative images. This would make it much easier for anyone who wanted to create an image editing app.
Given the aim of this code is "re-use" I might have suggested that you did not make this compromise: "To simplify the code below we are reading pixel by pixel, but reading a full line with the method QImage::scanLine(int i) is more efficient.". That said, if people understand the first code, it should be possible for them to adapt it to reading a while line.
FYI, I subedited to add more links to reference documentation, and added MeeGo category - I know its untested, but there is nothing here that shouldn't work.
Regards
Hamishhamishwillee 09:09, 11 May 2012 (EEST)
Chintandave er - nice article !
Hi Sheenmue,
Nice article. Thanks for that.
Chintan.Chintandave er 09:25, 11 May 2012 (EEST)
Sheenmue -
Thanks for you modifications Hamishwillee, it's the first time I write an article in the wiki and I'm not used to the conventions.
I really wanted to keep the code simple because even if this code can be reused, as Daliusd pointed with his comment there are many ways to do the same thing and it can be quite more complex, and I wanted this article could serve as an entry point to image editing so I prefer the code is easy to understand than efficient. For example, I remember the first time I made a game, it was after my first year in university and I knew how to code but I didn't know how a game worked. It was thanks to the documentation I found in the old Siemens Mobile developer website and Forum Nokia - yeah, it was quite some time ago :) - that I understood it, a game loop with 3 steps: read the input from the user, update the game, draw the result on the screen. After that I could write my own games, and I wanted that with the article for people that don't know how image editing works.
But anyway, your suggestion gave me an idea and I have written an example on how to modify a method to read a full line from an image.Sheenmue 18:22, 11 May 2012 (EEST)
Hamishwillee - Cool!
Hi Sheenmue
You're welcome. Thanks for explaining your reasoning - its certainly a good argument for keeping things simple.
One thing that did occur to me is that if you do this calculation in a UI thread it could make the UI unresponsive. It may be worth putting in a comment on this and linking to topics on how to do the image manipulation in a worker thread (like Multi-Threaded Image Processing using Qt Camera). I believe you can do your processing in a QImage on another thread, but not in a QPixmap. Anyway, just something for you to think about.
Regards Hamish
Sheenmue -
Yes, it's possible using QPainter with QImage in a different thread, with QPixmap it's not recommended or you can end up with a blank image if UI thread is updated at the same time -if it's not updated at the same time it should work fine too, but using standard UI controls that's hard to manage-.
This weekend I'll try to update the sample app processing the images in a secondary thread and add the advice in the introduction section. I'll also add an animation while the image is being updated.Sheenmue 19:02, 17 May 2012 (EEST)
Hamishwillee - Thanks!
Sounds great, if you have time. If you don't, then given the nature of your article, just treating multithreading as a separate issue (ie explaining the need and linking to instructions) would be sufficient.
FYI, Tuesday is deadline for next selection round.hamishwillee 08:25, 25 May 2012 (EEST)