By making the transition from Gambas 2 to Gambas 3, I've been able to increase the functionality of my Photo Viewer application, and make a few improvements along the way.
A new Gambas component has allowed me to add image histograms, rotation and normalisation to my project.
This new component is called gb.image.effect, and although it includes methods to despeckle, emboss, invert, oil-paint and solarize an image, I've only made use of a couple of the available methods.
We have now been using the original PhotoViewer (http://captainbodgit.blogspot.co.uk/2012/09/gambas-1-photoviewer.html) for a couple of months and I've had a chance to assess feedback from my user-base (...by which I mean that I've had a few cutting remarks from Lady Bodgit).
Just revisiting the initial work-flow requirements, I've now added 3 more operations:-
- Review each photo in turn
- Delete the "no-hope" pictures
- Rename any we wanted to keep that did not require edits
- Rotate images as necessary (usually 90deg CW)
- Assess exposure by viewing image histogram
- Normalise if necessary
- Launch GIMP for those that did require further editing
- Show our best efforts to long suffering friends & relatives
- Review camera settings when asked "How did you do that?"
- The image should be as large as possible
- Stepping through the images should be easy (e.g. by using the arrow keys)
- The camera (EXIF) data should be retained following simple edits
Folder & File Selection
Experience now shows that the FileChooser component was not a good choice for this application. It takes up too much screen space!The simple solution was to use a DirChooser to select the target directory, and only display it when the user selects a directory. By using a DrawingArea to display each photo in turn, and setting the controls property Focus=TRUE it is then just a matter of writing some code in the controls KeyPress event.
.
strDir = DirSelect.Value
astrFiles = New String[]
astrFiles = Dir(strDir, "*.{jpg,jpeg,JPG,JPEG}").Sort()
.
.
Public Sub daPhoto_KeyPress()
.
.
Select Case Key.Code
Case Key.Right, Key.Down
Inc intCurrentFile
Case Key.Left, Key.Up
Dec intCurrentFile
.
.
End Select
LoadPhoto(strDir & "/" & astrFiles[intCurrentFile]
End
The Camera (EXIF) Data
The grid containing the camera (exif) data has also been moved off the main form, and is now launched on its own form, where the property Stacking=Above. This avoids it getting "lost" and allows it to be moved around the screen or easily closed when not required. The new histogram is handled and displayed in the same way.Other Hot-keys
Since the daPhoto_KeyPress event is responding to our keys, I've also expanded the Select Case section above to include the following keys:-- PageUp = show first photo
- PageDown = show last photo
- Del = delete the current photo
- R or r = rename the current photo
- H or h = display the histogram
- D or d = display EXIF data
This is in addition to the traditional top menu bar, and a right-click pop-up menu which is enabled by setting the main forms property PopupMenu=mnuPop (the name of the menu from the menu editor).
Image Histogram
Gambas 3 allows you to extract pixel intensity data for red, blue and green channels (although at the time of writing, the red & blue channels appear to be swapped over).Most image editors (e.g. The GIMP) provide displays of this data in the form of histograms. The basic use of this information is to determine if the photo was correctly exposed by viewing a combined RGB histogram (see Exposure Explained: http://captainbodgit.blogspot.co.uk/2012/10/photography-1-exposure-explained.html).
I decided to include 5 histogram options, which the user selects (one at a time) by clicking on the histogram. The default histo is the sum of RGB:-
.
hImage = Image.Load(strDir & "/" & astrFiles[intCurrentFile]) 'current photo
hHisto = hImage.Histogram()
For index = 0 To 255
lngRGBPixel = 0 'lngRGBPixel will be the plot value
Select Case intHistoType
Case RED_HISTO '1 = Red Histo
lngRGBPixel = hHisto[Image.Red, index]
Case GREEN_HISTO '2 = Green Histo
lngRGBPixel = hHisto[Image.Green, index]
Case BLUE_HISTO '3 = Blue Histo
lngRGBPixel = hHisto[Image.Blue, index]
Case RGB_MAX_HISTO '4 = Max value from R, G or B
lngRGBPixel = hHisto[Image.Red, index]
If hHisto[Image.Green, index] > lngRGBPixel Then
lngRGBPixel = hHisto[Image.green, index]
Endif
If hHisto[Image.Blue, index] > lngRGBPixel Then
lngRGBPixel = hHisto[Image.Blue, index]
Endif
Case Else '0 = RGB_SUM_HISTO {Sum(RGB)}
lngRGBPixel = hHisto[Image.Red, index] + hHisto[Image.Green, index] + hHisto[Image.Blue, index]
End Select
'create array of plot values
myHisto[index] = lngRGBPixel
If lngRGBPixel > lngMaxPixel Then
'Save largest (lngMaxPixel) for scaling the histo plot area
lngMaxPixel = lngRGBPixel
Endif
Next
....'draw histo
Normalisation
This is about taking the image pixel range and stretching it to fill the available range (e.g. if your image only contained pixels values in the range 50 to 200, the "Normalize" option with stretch the range to cover 0 to 255, improving the photo by increasing the tonal range.This can be implemented something like this:-
.
.
hImage = Image.Load(strDir & "/" & astrFiles[intCurrentFile]) 'current photo
hImage.Normalize
DrawPhoto(hImage) 'DrawPhoto scales the image & draws on daPhoto DrawingArea
Image Rotation
This is also quite simple:-.
hImage = Image.Load(strDir & "/" & astrFiles[intCurrentFile]) 'current photo
hImage.Rotate(Rad(intAngle)) 'intAngle = 90 for CCW or 270 for CW
'now scale and draw on daPhoto
.
Saving Changes & Exif Data
To save changes back to the current file name, you can simply:-
hImage.Save(strDir & "/" & astrFiles[intCurrentFile])
However, this only saves the image, not the Exif data.
I can't see a direct way of saving the image + Exif in Gambas, so I use another program called: jhead
First thing to do is check if jhead is installed when PhotoViewer is launched:-
Public Sub _new()
Dim strTest As String
Exec ["whereis", "jhead"] To strTest
If Instr(strTest, "bin/jhead") = 0 Then
Message.Info("Please install: jhead", "close")
Quit
Endif
When the user tries to edit a file (i.e. rotate or normalise), we can save a copy of the file in a temporary directory, and then once the edit is complete, we copy the exif data from the temporary copy to the newly edited version. Something like this:-
strNewFile = strDir & "/" & astrFiles[intCurrentFile]
Try Exec ["jhead", "-te", strOrgFileCopy, strNewFile]
If ERROR Then
Message.Error("Don't Panic!", "close")
Endif
Just to clarify, the jhead syntax for exif data copy is:-
jhead -te sourceFile destinationFileOnly the exif data is changed in the destinationFile, not the jpeg image.
Conclusion
Its tempting to use all the features of the gb.Image.Effect component in this project and create a graphics editor. But the requirement was just for an app that would match our work-flow when assessing SLR photos, so any further changes are likely to be minor. However, I'll certainly spend some more time experimenting with this interesting Gambas component.PhotoViewer 3 |
No comments:
Post a Comment