Exploring the IMAGE PowerShell Module
In part one of this series I showed the finished version of photo-tagging script I’ve been using. I based my work (which is available for download) on James Brundage’s PSImageTools module for PowerShell which is part of the PowerPack included with the Windows 7 Resource kit (and downloadable independently). In this post I want to show the building blocks that were in the original library provide and the ones I added.
Producing a modified image using this module usually means working to the following pattern:
- Read an image
- Create a set of filters
- Apply the filters to the image
- Save the modified image
If you are wondering what a filter is, that will become clear in a moment. James B’s original module had these commands.
Get-Image |
Loads an image from a file |
Add-CropFilter |
Creates a filter to crop the image to a given size |
Add-OverlayFilter |
Creates a filter to an overlay such as a watermark or copyright notice |
Add-RotateFlipFilter |
Creates a filter to rotate the image in multiples of 90 degrees or to mirror it vertically or horizontally |
Add-ScaleFilter |
Creates a filter to resize the image |
Set-ImageFilter |
Applies a set of filters to one or more images |
Get-ImageProperty |
Gets Items of EXIF data from an image |
ConvertTo-Bitmap |
Loads a file, applies a conversion filter to it, and saves it as a BMP |
ConvertTo-Jpeg |
Loads a file, applies a conversion filter to it, and saves it as a JPG |
Copy-ImageIntoOrganizedFolder |
Organizes pictures into folders based on EXIF data |
You can see there are 4 kinds of filter with their own commands in the list and each one makes some modification to the image: cropping, scaling, rotating, or adding an overlay. Inside the two ConvertTo commands, a 5th kind of filter, conversion,is used and I added a function to create filters to do that. I made some changes to the existing functions to give better flexibility with how they can be called, and added some further functions, mostly to work with EXIF data embedded in the image file. The full list of functions I added is as follows:
Save-Image |
Not strictly required but it is a logical command to have at the end of a pipe line, instead of calling a method of the image object |
New-ImageFilter |
Not strictly required either but it makes the syntax of adding filters more logical |
New-Overlay |
Takes text and font information and creates a bitmap with the text in that font |
Add-ConversionFilter |
Creates a conversion filter for JPG, GIF, TIF, BMP or PNG format (as used in ConvertTo-Jpeg / Bitmap without applying it to an image or saving it) |
Add-ExifFilter |
Adds a filter to set EXIF data |
Copy-Image |
Copies one or more images, renaming, rotating and setting title keyword tags in the process. |
Get-EXIF |
Returns an object representing the EXIF data of the image |
Get-EXIFItem |
Returns a single item of EXIF data using its EXIF ID (the common IDs are defined as constants |
Get-PentaxMakerNoteProperty |
Decodes information from the Maker-Note Exif field, I have only implemented this for Pentax data |
Get-PentaxExif |
Similar to Get-Exif but with Maker-Note fields for Pentax |
The image below was resized and labelled using these commands. The first step is to create an image to act as an overlay: I’m going to a copyright notice in Red red text, in 32 point Arial
PS> $Overlay = New-overlay -text "© James O'Neill 2008" -size 32 -TypeFace "Arial" ` -color "red" -filename "$Pwd\overLay.jpg"
I’m using a picture I took in 2008: and I could have used a more complex command to build the text from the date taken field in the EXIF data. Next I’m going to create a chain of filters to:
- Resize my image to be 800 pixels high (the aspect ratio is preserved by default),
- Add my overlay
- Set the EXIF fields for the keyword-tags, title and Copyright information
- Save the image as a JPEG with a 70/100 quality rating
Despite the multi-line formatting here, this is a single PowerShell command: $filter = new-Filter | add | add | add...
PS> $filter = new-Imagefilter | Add-ScaleFilter -passThru -height 800 -width 65535 | `` Add-OverlayFilter -passThru –top 75``0 –left 0 –`` image $Overlay | Add-ExifFilter -passThru -ExifID $ExifIDKeywords -typeName "vectorofbyte" -string "Ocean" | Add-ExifFilter -passThru -ExifID $ExifIDTitle -typeName "vectorofbyte" -string "StingRay" | Add-ExifFilter -passThru -ExifID $ExifidCopyright ``-typeName "String" -value "© James O'Neill 2008" | Add-ConversionFilter -passThru –typeName jpg -quality 70
Given a set of filters, a script can get an image, apply the filters to it and save it. Originally these 3 steps needed 3 commands to be piped together like this
PS> Get-Image C:\Users\Jamesone\Pictures\IMG_3333.JPG | Set-ImageFilter -filter $filter | Save-image -fileName {$_.FullName -replace ".jpg$","-small.jpg"}
I streamlined this first by changing James B’s Set-ImageFilter
so that if it is given something other than an image object, it hands it to Get-Image
. In other words Get-Image X | Set-Image
is reduced to Set-Image X
(and I made sure X could be path, including one with wild cards or one or more file objects) . After processing I added a -savepath
parameter so that set-image –SavePath P
is the same as Set-Image | Save-Image P
. P can be a path, or script block which becomes a path, or empty to over-write the image. Get an image, apply the filters to it and save it becomes a singe command.
PS> Set-ImageFilter –Image ".\IMG_3333.JPG" -filter $filter ` –SaveName {$_.FullName -replace ".jpg$","-small.jpg"}
The workflow for my photos typically begins with copying files from a memory card, replacing the start of the filename - like the “IMG_” in the example above - with text like “DIVE” (I try to keep the sequential numbers the camera stamps on the pictures as a basis for a unique ID). Next, I rotate any which were shot in portrait format so they display correctly and finally I add descriptive information to the EXIF data: keyword tags like “Ocean” and titles like “Stingray”. So it made sense to create a copy-image
function which would handle all of that in one command. The only part of this which hasn’t already appeared is rotation. The Orientation EXIF field contains 8 to show the image has been rotated 90 degrees, 6 indicates 270 degrees of rotation, and 1 to show the image is correctly rotated, so it is a question of read the data, and depending on what we find add filters to rotate and reset the orientation data.
$orient = Get-ExifItem -image $image -ExifID $ExifIDOrientation if ($orient -eq 8) {Add-RotateFlipFilter -filter $filter -angle 270 Add-exifFilter -filter $filter -ExifID $ExifIDOrientation` -value 1 -typeid $ExifUnsignedInteger }
There is similar code to deal with rotation in the opposite direction, and rotation is just another filter like adding the EXIF data for keywords or title, all job of Copy-Image
does is to build a chain of filters to add Title and Keyword tags and rotate the image, determine the full path the new copy should be saved to and invoke Set-ImageFilter.
To make it more flexible, gave Copy-Image
the ability to add filters to an existing filter chain: in the part one you could see Copy-GPSImage
which finds the GPS data to apply to a picture and produces a series of filters from it: these filters are passed on to Copy-Image
which does the rest.
The last aspect of Copy-Image
to look at is renaming: -Replace
has become one of my favourite PowerShell operators. It takes a regular expression and a block of text, and replaces all instances of expression found in a string with the text. Regular expressions can be complex but “IMG”
is perfectly valid so if I have a lot of pictures to name as “OX-” for “Oxford” I can call the function with a replace
parameter of "IMG","OX-"
. Inside Copy-Image
, the parameter $replace
is used with the -replace
operator (using PowerShell’s ability to treat “img”,”ox” as one parameter in two parts). $savePath
is worked out as follows:
if ($replace) {$SavePath= join-path -Path $Destination ` -ChildPath ((Split-Path $image.FullName -Leaf) -Replace $replace)} else {$SavePath= join-path -Path $Destination ` -ChildPath (Split-Path $image.FullName -Leaf) }
As mentioned above I went to some trouble to make sure the functions can
accept image objects or names of image files or file objects – because at different times, different ones will suit me. So all of the following are valid ways to copy multiple files from my memory card to the current directory ($pwd
), renaming, rotating and applying the keyword tag “oxfordshire”
PS[1]> Copy-Image E:\DCIM\100PENTX\img4422*.jpg -Destination $pwd ` -Rotate -keywords "oxfordshire" -replace "IMG","OX-" PS[2]> dir E:\DCIM\100PENTX\img4422*.jpg | Copy-Image -Destination $pwd ` -Rotate -keywords "oxfordshire" -replace "IMG","OX-" PS[3]> get-image E:\DCIM\100PENTX\img4422*.jpg | Copy-Image -Destination $pwd ` -Rotate -keywords "oxfordshire" -replace "IMG","OX-" PS[4]> $i = get-image E:\DCIM\100PENTX\img4422*.jpg; Copy-Image $i -Destination $pwd ` -Rotate -keywords "oxfordshire" -replace "IMG","OX-" PS[5]> dir E:\DCIM\100PENTX\img4422*.jpg | get-image | Copy-Image -Destination $pwd ` -Rotate -keywords "oxfordshire" -replace "IMG","OX-"
Of course if I have the GPS data from taking the logger with me on a walk I can use Copy-GPSImage to geotag the files as they are copied, and in the next part I’ll look at how the GPS data is processed.
Comments
- Anonymous
May 26, 2014
This (frequently updated) post contains links to useful PowerShell Modules. If you have a suggestion