Sunday 30 September 2012

Gambas: #1 PhotoViewer

Does the world need yet another Photo Viewer application?

 

I doubt it. But I needed something which would help with my photographic work-flow, so I wrote my own.


With three keen SLR camera users in our house, I noticed that at least two of us were following a similar process. Once we had transferred our photos to our Linux powered laptops, we would:-
  1. Review each photo in turn
  2. Delete the "no-hope" pictures
  3. Rename any we wanted to keep that did not require edits
  4. Launch GIMP for those that did require edits
  5. Show our best efforts to long suffering friends & relatives
  6. Review camera settings when asked "How did you do that?"

So I set about making a new Photo Viewer application using my language of choice: Gambas









PhotoViewer running on Lubuntu



Gambas is a kind of Basic language dialect and has a nice, visual, integrated development environment (IDE). Once you've written and debugged your application, you can create a Linux installation package in a number of formats including ".deb" which is suitable for Debian, Lubuntu and other Ubuntu distributions.

Its an obvious choice for someone like me, who used to work quite a bit with VB.classic, and swore he'd never look at another line of C++ code after leaving industry and drifting into semi-retirement.

This application code is written in Gambas2, but would probably be OK in Gambas3, and I suspect it could be re-written in Visual Basic without too much trouble.

This application relies upon the Linux package "exif" to provide data from the image about camera settings. The "Edit with GIMP" menu item requires the installation of GIMP 2.6, but this could easily be changed or extended to include your favourite graphics editor.

There are just 3 screen components plus a timer on the one and only form (FMain):-

The menu bar includes the following menus & items:-





...and this is the FMain.class code. Sorry the full indentation has been lost, but at least I have hand-coloured the comments green:-


' Gambas class file
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' PhotoViewer
'------------
'Yet another Photo Viewer application, but this pulls together the options
'that I need when working on photos.
'Need to install the following Linux packages for this app:-
'   "exif"        {essential}
'   "gimp-2.6"    {optional}
'
'Steve Davis
'July 2012
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


PUBLIC strPhotoPath AS String
PUBLIC blnBigPic AS Boolean 'use most of screen for photo

PUBLIC SUB _new()
DIM strTest AS String
DIM strInitialPath AS String  'initial dir for File Picker

  'Only run one instance of this app
  EXEC ["pgrep", "-f", "-l", Application.Name & ".gambas"] WAIT TO strTest
  IF Split(Trim$(strTest), gb.newLine).Count > 1 THEN
    'Already running, so quit
    QUIT
  ENDIF

  'We need exif to extract photo data
  strTest = ""
  EXEC ["whereis", "exif"] TO strTest
  IF InStr(strTest, "bin/exif") = 0 THEN
    Message.Info("Please install: exif", "close")
    QUIT
  ENDIF  
 
  'check for GIMP as this is our editor
  strTest = ""
  EXEC ["whereis", "gimp"] TO strTest
  IF InStr(strTest, "bin/gimp") = 0 THEN
    mnuGimp.Enabled = FALSE
  ENDIF  
 
  'load user settings
  strInitialPath = Settings["Settings/InitDir", User.Home & "/Pictures"]
  IF NOT Exist(strInitialPath) THEN
    strInitialPath = User.Home
  ENDIF
  FilePicker.Dir = strInitialPath
  FilePicker.ShowHidden = FALSE 
  IF Settings["Settings/DataFilter", "true"] = "true" THEN
    mnuFilter.Checked = TRUE
  ENDIF  
END

PUBLIC SUB Form_Open()

END

PUBLIC SUB tmrStart_Timer()
'now form has loaded, draw controls

  tmrStart.Stop
  DrawControls
END

PUBLIC SUB DrawControls()
  WITH FilePicker
    .Width = ME.Width * 0.55
    .Height = ME.Height * 0.4
    .Left = ME.Width * 0.01
    .Top = ME.Height * 0.01
    .Filter = ["*.png;*.jpg;*.jpeg;*.bmp", "Picture files"]
    .Visible = TRUE
  END WITH    
  WITH pBox
    .Width = ME.Width * 0.55
    .Height = ME.Height * 0.54
    .Left = ME.Width * 0.01
    .Top = ME.Height * 0.01 + FilePicker.Top + FilePicker.Height
    .Visible = TRUE
  END WITH    
  WITH gViewExif
    .Width = ME.Width * 0.42
    .Height = ME.Height * 0.95
    .Left = ME.Width * 0.57
    .Top = ME.Height * 0.01
    .Columns.Count = 2
    .Columns[0].Width = gViewExif.Width * 0.4
    .Columns[1].Width = gViewExif.Width * 0.6   
    .Visible = TRUE
  END WITH  
END

PUBLIC SUB DrawPhotoBox(blnLarge AS Boolean)
  IF blnLarge THEN
    WITH pBox
      .Width = ME.Width * 0.95
      .Height = ME.Height * 0.9
      .Left = ME.Width * 0.01
      .Top = ME.Height * 0.01
      .Raise
    END WITH
    FilePicker.Hide
    gViewExif.Hide
  ELSE
    WITH pBox
      .Width = ME.Width * 0.55
      .Height = ME.Height * 0.54
      .Left = ME.Width * 0.01
      .Top = ME.Height * 0.01 + FilePicker.Top + FilePicker.Height
    END WITH
    FilePicker.Show
    gViewExif.Show
  ENDIF
END

PUBLIC SUB FilePicker_Change()
'user has selected a photo...so load it
  strPhotoPath = FilePicker.SelectedPath
  LoadPhoto
END

PUBLIC SUB LoadPhoto()
DIM strPhotoName AS String 
DIM picPhoto AS NEW Picture
DIM fFactor AS Float
 
  IF strPhotoPath <> "" THEN
    strPhotoName = Mid(strPhotoPath, RInStr(strPhotoPath, "/") + 1)
    strPhotoName = Mid(strPhotoName, 1, InStr(strPhotoName, ".") - 1)
    IF InStr(UCase(strPhotoPath), ".") > 0 THEN
      pBox.Hide
      DrawPhotoBox(mnuBigPic.Value)
      picPhoto = Picture.Load(strPhotoPath)
      IF (picPhoto.Width * 4 / 5.63) > picPhoto.Height THEN
        fFactor = pBox.Width / picPhoto.Width
      ELSE
        fFactor = pBox.Height / picPhoto.Height
      ENDIF
      pBox.Resize(picPhoto.Width * fFactor, picPhoto.Height * fFactor)
      pBox.Picture = picPhoto
      pBox.Stretch = TRUE
      pBox.Show
      GetExif()
    ENDIF
  ENDIF
CATCH  
  'file does not exist in this dir!
  IF Error.code <> -1 THEN
    Message.Info("Error: " & Error.Code & " - " & Error.Text, "close")
  ENDIF
END

PUBLIC FUNCTION GetExif() AS String
DIM strEXIF AS String
DIM index AS Integer
DIM strParam AS String
 
  gViewExif.Clear
  gViewExif.Rows.Count = 1
  EXEC ["exif", strPhotoPath] TO strEXIF
  IF InStr(strEXIF, "Model") > 0 THEN
    strEXIF = Mid(strEXIF, InStr(strEXIF, "Model"))
    index = 0
    DO UNTIL InStr(strEXIF, "|") = 0
      IF mnuFilter.Checked = TRUE THEN  'select limited data
        strParam = Mid(strEXIF, 1, InStr(strEXIF, "|") - 1)
        IF FilterExif(strParam) = TRUE THEN
          gViewExif.Rows.Count = index + 1
          gViewExif.Row = index
          gViewExif[index, 0].Text = Mid(strEXIF, 1, InStr(strEXIF, "|") - 1)
          strEXIF = Mid(strEXIF, InStr(strEXIF, "|") + 1)
          gViewExif[index, 1].Text = Mid(strEXIF, 1, InStr(strEXIF, Chr(10)) - 1)
          INC index
        ENDIF
      ELSE  'show all data
        gViewExif.Rows.Count = index + 1
        gViewExif.Row = index
        gViewExif[index, 0].Text = Mid(strEXIF, 1, InStr(strEXIF, "|") - 1)
        strEXIF = Mid(strEXIF, InStr(strEXIF, "|") + 1)
        gViewExif[index, 1].Text = Mid(strEXIF, 1, InStr(strEXIF, Chr(10)) - 1)
        INC index
      ENDIF
      strEXIF = Mid(strEXIF, InStr(strEXIF, Chr(10)) + 1)
    LOOP
    gViewExif.Row = 0
  ELSE
    gViewExif[0, 0].Text = "No EXIF Data"
  ENDIF
CATCH
  Message.Error("Error: " & Error.Text, "close")
END

PUBLIC SUB mnuExit_Click()

  QUIT
END

PUBLIC SUB mnuGimp_Click()

    IF strPhotoPath <> "" THEN
      EXEC ["gimp-2.6", strPhotoPath]
    ELSE
      Message.Warning("Nothing selected to edit", "close")
    ENDIF
END

PUBLIC SUB mnuRename_Click()
DIM lngReply AS Long
DIM strPath AS String
DIM strExtension AS String
DIM strNewName AS String
DIM strNewPathName AS String

    IF strPhotoPath = "" THEN
      Message.Warning("Hey, no photo selected to rename!", "close")
    ELSE
      strNewName = InputBox("Enter new file name (without extension) or cancel", "Change File Name?", "")
      IF strNewName <> "" THEN
        strPath = Mid(strPhotoPath, 1, RInStr(strPhotoPath, "/"))
        strExtension = Mid(strPhotoPath, InStr(strPhotoPath, ".")) 
        strNewPathName = strPath & strNewName & strExtension       
        IF Exist(strNewPathName) THEN
          Message.Warning("No chance! This file name already exists.", "Close")
        ELSE
          TRY MOVE strPhotoPath TO strNewPathName
          IF ERROR THEN
            Message.Info("Sorry mate! That didn't work!", "Close")
          ELSE
            strPhotoPath = strNewPathName
            'need to do this to update FileChooser
            FilePicker.SelectedPath = strNewName       
            FilePicker.Dir = strPath
          ENDIF
        ENDIF
      ENDIF
    ENDIF
END

PUBLIC SUB mnuDelete_Click()
DIM lngReply AS Long

  IF strPhotoPath = "" THEN
    Message.Warning("You didn't select a photo for deletion!", "close")
  ELSE
    lngReply = Message.Question("Are you sure you want to delete: " & strPhotoPath & "?", "Yes", "No")
    FilePicker.Id
    IF lngReply = 1 THEN
      TRY KILL strPhotoPath
      IF NOT ERROR THEN
        pBox.Hide
        gViewExif.Clear
        gViewExif.Rows.Count = 1
      ENDIF
    ENDIF
  ENDIF
END

PUBLIC SUB mnuAbout_Click()
DIM strVersion AS String

  strVersion = Application.Version
  Message.Info("PhotoViewer version: " & strVersion & " by Steve Davis", "close")
END

PUBLIC SUB mnuBigPic_Click()

  LoadPhoto
END

PUBLIC SUB mnuDefaultDir_Click()

  Settings["Settings/InitDir"] = User.Home & "/Pictures"
  mnuDefaultDir.Checked = TRUE
  mnuCurrDir.Checked = FALSE
END

PUBLIC SUB mnuCurrDir_Click()

  Settings["Settings/InitDir"] = FilePicker.Dir
  mnuCurrDir.Checked = TRUE
  mnuDefaultDir.Checked = FALSE
END

PUBLIC FUNCTION FilterExif(strParam AS String) AS Boolean
'just display data for parameters containing the following strings:-

  IF InStr(LCase(strParam), "model") > 0 THEN
    RETURN TRUE
  ENDIF  
  IF InStr(LCase(strParam), "exposure") > 0 THEN
    RETURN TRUE
  ENDIF
  IF InStr(LCase(strParam), "f-n") > 0 THEN
    RETURN TRUE
  ENDIF
  IF InStr(LCase(strParam), "iso") > 0 THEN
    RETURN TRUE
  ENDIF
  IF InStr(LCase(strParam), "length") > 0 THEN
    RETURN TRUE
  ENDIF  
  IF InStr(LCase(strParam), "white") > 0 THEN
    RETURN TRUE
  ENDIF  
END

PUBLIC SUB mnuFilter_Click()

  IF mnuFilter.Checked = TRUE THEN
    Settings["Settings/DataFilter"] = "true"
  ELSE
    Settings["Settings/DataFilter"] = "false"
  ENDIF
END


One feature I'd still like to add is a histogram display. I took a quick look at the "ImageJ" stuff, but haven't worked out what to do with it. But if anyone has any ideas.....

Gambas:  http://gambasdoc.org/help?v2






No comments:

Post a Comment