Home

Reading Exif Metadata from Image Files By Folder

2 Comments

In a previous post I demonstrated how to read Exif Metadata from Image Files – Reading Exif Metadata from Image Files. It was suggested by a reader that it would be nice to be able to read a folder of images and put the results in a file.

Here is a script which does that. You select the folder and after processing, a CSV is created in that location containing various Exif info.

ExifInfoByFolder

ExifInfoByFolder-CSVExifInfoByFolder-CSV2ExifInfoByFolder-CSV3

The Exif format and processing was previously discussed so I’ll just comment briefly on the script changes.

The previous version included the below Exif tags –

  • Color Space
  • Contrast
  • Date Taken
  • Exposure Time
  • Exposure Mode
  • f-Stop
  • File Path
  • Flash
  • Focal Length
  • Height
  • Height Resolution
  • ISO
  • Camera Maker
  • Camera Model
  • Metering
  • Orientation
  • Saturation
  • Scene Type
  • Sharpness
  • White Balance
  • Width
  • Width Resolution

In this version I included the below additional Exit tags –

  • Aperture
  • Maximum Aperture
  • Brightness
  • Compressed Bits per Pixel
  • Compression Type
  • Custom Rendered
  • Digital Zoom Ratio
  • Exif Version
  • Exposure Compensation
  • File Source
  • Focal Length in 35mm Format
  • Gain Control
  • Light Source
  • Sensing Method
  • Shutter Speed
  • Software Version
  • Subject Distance
  • Subject Distance Range

The interface was simplified to just a TextBox and three Buttons and a couple Labels. I first hide the console then setup the main WinForm.

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Hide Console
Add-Type -Name Window -Namespace Console -MemberDefinition '
 [DllImport("Kernel32.dll")]
 public static extern IntPtr GetConsoleWindow();

[DllImport("user32.dll")]
 public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
 '
 function Hide-Console {
 $consolePtr = [Console.Window]::GetConsoleWindow()
 [Console.Window]::ShowWindow($consolePtr, 0)
 }
 If (-not $psISE) {hide-console}

# Set Global Values
$global:output = @()
$global:folderName = ""

# Main Form 
$mainForm = New-Object System.Windows.Forms.Form
$mainForm.Font = "Comic Sans MS,8.25"
$mainForm.Text = " Image File Exif MetaData By Folder"
$mainForm.FormBorderStyle = "FixedDialog"
$mainForm.ForeColor = "White"
$mainForm.BackColor = "DarkBlue"
$mainForm.width = 600
$mainForm.height = 250

# Title Label
$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Font = "Comic Sans MS,14"
$titleLabel.ForeColor = "Yellow"
$titleLabel.Location = "30,20"
$titleLabel.Size = "400,30"
$titleLabel.Text = "Image File Exif MetaData by Folder"
$mainForm.Controls.Add($titleLabel)er"
$mainForm.Controls.Add($titleLabel)

Next is the TextBox for the path to the images folder and the Buttons to Browse, Process and Exit.

# Input Box
$textBoxIn = New-Object System.Windows.Forms.TextBox
$textBoxIn.Location = "35, 70"
$textBoxIn.Size = "500, 20"
$textBoxIn.Text = ""
$mainForm.Controls.Add($textBoxIn)

# Input Box Label
$ProcessLabel = New-Object System.Windows.Forms.Label
$ProcessLabel.Location = "35, 100"
$ProcessLabel.Size = "300, 23"
$ProcessLabel.ForeColor = "White" 
$ProcessLabel.Text = "Input Images Folder"
$ProcessLabel.Font = "Comic Sans MS,12"
$mainForm.Controls.Add($ProcessLabel)

# Browse Input Button
$buttonBrowse = New-Object System.Windows.Forms.Button
$buttonBrowse.Location = "35, 150"
$buttonBrowse.Size = "75, 23"
$buttonBrowse.ForeColor = "Red"
$buttonBrowse.BackColor = "White"
$buttonBrowse.Text = "Browse"
$buttonBrowse.add_Click({selectFolder})
$mainForm.Controls.Add($buttonBrowse)

# Process Button
$buttonProcess = New-Object System.Windows.Forms.Button
$buttonProcess.Location = "240,150"
$buttonProcess.Size = "75, 23"
$buttonProcess.ForeColor = "Red"
$buttonProcess.BackColor = "White"
$buttonProcess.Text = "Process"
$buttonProcess.add_Click({processFiles})
$mainForm.Controls.Add($buttonProcess)

# Exit Button 
$exitButton = New-Object System.Windows.Forms.Button
$exitButton.Location = "450,150"
$exitButton.Size = "75,23"
$exitButton.ForeColor = "Red"
$exitButton.BackColor = "White"
$exitButton.Text = "Exit"
$exitButton.add_Click({$mainForm.close()})
$mainForm.Controls.Add($exitButton)

I also added a StatusStrip Class line on the bottom of the form to display progress. In it I show the file names as they’re processed and indicate completion.

# Status Strip and Label
$statusStrip = New-Object System.Windows.Forms.StatusStrip
$statusStrip.ForeColor = "Blue"
$statusStrip.BackColor = "White"

$statusLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
[void]$statusStrip.Items.Add($statusLabel)
$statusLabel.AutoSize = $true
$statusLabel.ForeColor = "Blue"
$statusLabel.BackColor = "White"
$statusLabel.Text = "Ready!"
$mainForm.Controls.Add($statusStrip)

The files are selected by folder using the FolderBrowserDialog class.

function selectFolder {
    $statusLabel.Text = "Ready!"
	$selectForm = New-Object System.Windows.Forms.FolderBrowserDialog
	$getKey = $selectForm.ShowDialog()
	If ($getKey -eq "OK") {
        	$textBoxIn.Text = $selectForm.SelectedPath
	}
} # End SelectFolder

In the main loop I initialize and fill a array with the names of the files. The output filename is set and the StatusStrip is updated. The StatusStrip.Refresh() allows each filename to be written to the Status Line as it is processed without the normally buffering. Finally I attempt to load the image space within a try/catch so that if the load fails I know the file does not contain an image and I set a variable accordingly. The loop will run through all files, skipping the attempt to gathering tags from non-image files.

function processFiles {
    $global:folderName = $textBoxIn.Text

    # Get All Files from Select Path
    $files = @()
    $files = Get-ChildItem $global:folderName
    # Process All Files
    Foreach ($file in $files) {
        $data = "" | Select filename, aperture, maxAperture, brightness, colorSpace, compressedBitsPerPixel, `
                            compression, contrast, customRendered, dateTaken, digitalZoomRatio, exposureTime,`
                            exifVersion, exposureBias, exposureMode, fileSource, fstop, filepath, `
                            flash, focalLength, focalLength35mmFormat, gainControl, height, heightRes, iso, `
                            lightSource, cameraMaker, cameraModel, cameraSoftware, meteringMode, orientation, `
                            saturation, sceneType, sensingMethod, sharpness, shutter, subjectDistance, `
                            subjectDistanceRange, whiteBalance, width, widthRes 

        # Load image from file
        $filefullname = $global:folderName + "\" + $file
        $statusLabel.Text = "Processing " + $filefullname
        $statusStrip.Refresh()
        $imageType = "Y"
        Try {                       
            $photo = [System.Drawing.Image]::FromFile($filefullname)
        } Catch {
            $imageType = "N"
        }

I only gather the image tags if the file is an image type. I try/catch each value just to control error messages. Not all tags exist on every file as different manufactures publish different tags.

        # If Image File Exist Then Get Properties and Write File
        If ($imageType -eq "Y") {
            # Aperture
            Try { 
                $apertureProperty = $photo.GetPropertyItem(37378)
                if ($apertureProperty -ne $null){$aperture = MakeNumber($apertureProperty)}
                if ($aperture -ne $null){$data.aperture = $aperture}
            } Catch {}

Finally the output CSV file is written to disk from the $global:output array it has been gathered into.

    #write output
    $statusLabel.Text = "Complete!"
    $outputFile = $global:folderName + "\"  + "ExifInfo" + ".csv"
    $global:output | Export-CSV $outputFile -NoTypeInformation

The complete script can be downloaded here – ExifInfoByFolder.ps1

Advertisements

PowerShell Convert Text to Braille Image

Leave a comment

Braille characters are small rectangular blocks called cells that contain tiny palpable bumps called raised dots. The number and arrangement of these dots distinguish one character from another. This script calls a Web Service from webservicex.net which translates text into an image of the Braille cells.

Text2Braille

The images created are interesting. They contain randomness but with a certain pattern because of the limited number of cells that can be formed.

Text2Braille_20150104_164737

The script starts with defining the main WinForms Form.

Add-Type -AssemblyName System.Windows.Forms
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$global:outFile = ""

# Main Form 
$mainForm = New-Object System.Windows.Forms.Form
$mainForm.Location = "200, 200"
$mainForm.Font = "Comic Sans MS,9"
$mainForm.FormBorderStyle = "FixedDialog"
$mainForm.Text = " Convert Text to Braille Image"
$mainForm.ForeColor = "White"
$mainForm.BackColor = "Wheat"
$mainForm.Size = "610, 410"

Next is added the Input TextBox and its Label.

# Input Text Label
$inputTextLabel = New-Object System.Windows.Forms.Label
$inputTextLabel.Location = "100,8"
$inputTextLabel.ForeColor = "MediumBlue"
$inputTextLabel.Size = "50, 22"
$inputTextLabel.Text = "Text"
$mainForm.Controls.Add($inputTextLabel)

# Input Text TextBox
$inputTextTextBox = New-Object System.Windows.Forms.TextBox
$inputTextTextBox.Location = "20,30"
$inputTextTextBox.Size = "230,330"
$inputTextTextBox.MultiLine = $true
$inputTextTextBox.ForeColor = "MediumBlue"
$inputTextTextBox.BackColor = "White"
$inputTextTextBox.Text = ""
$mainForm.Controls.Add($inputTextTextBox)

The PictureBox for outputting the returned image is created.

# PictureBox
$pictureBox = New-Object System.Windows.Forms.PictureBox
$pictureBox.Location = "260, 30"
$pictureBox.ClientSize = "230, 330"
$pictureBox.BackColor = "Black"
$pictureBox.SizeMode = "StretchImage"
$mainForm.Controls.Add($pictureBox)

# PictureBox Label
$PictureBoxLabel = New-Object System.Windows.Forms.Label
$PictureBoxLabel.Location = "350,8"
$PictureBoxLabel.ForeColor = "MediumBlue"
$PictureBoxLabel.Size = "110, 22"
$PictureBoxLabel.Text = "Braille"
$mainForm.Controls.Add($PictureBoxLabel)

Another TextBox for the Font Size is setup. Font Size in this context is the size of the Braille cells.

# Font Size Label
$fontSizeLabel = New-Object System.Windows.Forms.Label
$fontSizeLabel.Location = "500,130"
$fontSizeLabel.ForeColor = "MediumBlue"
$fontSizeLabel.Size = "60, 22"
$fontSizeLabel.Text = "Font Size"
$mainForm.Controls.Add($fontSizeLabel)

# Font Size TextBox
$fontSizeTextBox = New-Object System.Windows.Forms.TextBox
$fontSizeTextBox.Location = "560,130"
$fontSizeTextBox.Size = "30,20"
$fontSizeTextBox.ForeColor = "MediumBlue"
$fontSizeTextBox.BackColor = "White"
$fontSizeTextBox.Text = "18"
$mainForm.Controls.Add($fontSizeTextBox)

Finally three Buttons are defined, one each for calling the Web Service function, displaying the image and exiting the script.

# Generate Button
$generateButton = New-Object System.Windows.Forms.Button 
$generateButton.Location = "510,30"
$generateButton.Size = "75,28"
$generateButton.ForeColor = "DarkBlue"
$generateButton.BackColor = "White"
$generateButton.Text = "Convert"
$generateButton.add_Click({GetBraille($scriptPath)})
$mainForm.Controls.Add($generateButton)

# Display Button
$displayButton = New-Object System.Windows.Forms.Button
$displayButton.Location = "510,60"
$displayButton.Size = "75, 28"
$displayButton.BackColor = "White"
$displayButton.ForeColor = "DarkBlue" 
$displayButton.Text = "Display"
$displayButton.add_Click({DisplayImage})
$mainForm.Controls.Add($displayButton)

# Exit Button 
$exitButton = New-Object System.Windows.Forms.Button
$exitButton.Location = "510,90"
$exitButton.Size = "75,28"
$exitButton.ForeColor = "Red"
$exitButton.BackColor = "White"
$exitButton.Text = "Exit"
$exitButton.add_Click({$mainForm.close()})
$mainForm.Controls.Add($exitButton)

The Function calling the Web Service starts with resetting the $error variable. I define the Web Service call using New-WebServiceProxy with the URI pointing to the services WSDL address. I receive the returned image by making the call, passing the text and font size. I set a unique output filename and create the file using Set-Content, inputting a byte stream. Lastly the $error variable s checked for a successful call, translate and file write with the result written.

Function GetBraille {
    $error.clear()
    $getBraille = New-WebServiceProxy -uri "http://www.webservicex.net/braille.asmx?WSDL"
    $returnedImage = $getBraille.BrailleText($inputTextTextBox.Text, $fontSizeTextBox.Text)
    $global:outFile = $scriptPath + "\"  + "Text2Braille_" + (Get-Date -UFormat %Y%m%d_%H%M%S) + ".jpg"
    Set-Content -Path $global:outFile -Value $returnedImage -Encoding Byte
    If($error.count -gt 0) {
        $completedLabel.Text = "Error!"
        $completedLabel.ForeColor = "Red"
    }
    Else {
        $completedLabel.Text = "Success!"
        $completedLabel.ForeColor = "Green"
    }
    PictureBox($global:outFile)
    $getBraille.Dispose()
}

The image is displayed in the PictureBox using the previously written file. If desired, the image can be displayed external of the script using the default system image viewer called with Invoke-Item.

Function PictureBox {
    $file = (Get-Item $global:outFile)
    $image = [System.Drawing.Image]::Fromfile($file)
    $pictureBox.Image = $image
}

Function DisplayImage {
    If ($global:outFile.Length -gt 0) {
        Invoke-Item $global:outFile
    }
}

Download the complete script here – Text2Braille.ps1

PowerShell Image Converter – Grayscale, Negative, Sepia

Leave a comment

This script will take an Image file and convert it to Grayscale, Negative or Sepia. It features a dual display of the before and after images as well as a Progress Bar and Counter to show percentage done.

Grayscale Image –

ImageConverter4

Negative of an Image –

ImageConverter

Finally a Sepia tone image –

ImageConverter3

First I define the main Form and set some variables.

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Global Values
# Global Values
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$global:selectedObject = "GrayScale"
$global:outFile = ""

# WinForm Setup
$mainForm = New-Object System.Windows.Forms.Form
$mainForm.Location = "200, 200"
$mainForm.Font = "Comic Sans MS,9"
$mainForm.FormBorderStyle = "FixedDialog"
$mainForm.ForeColor = "Black"
$mainForm.BackColor = "Bisque"
$mainForm.Text = " PowerShell Image Converter"
$mainForm.Width = 1030
$mainForm.Height = 520

Next comes the three RadioButtons used to select the conversion mode.

# GrayScale RadioButton
$radioButtonGrayScale = New-Object System.Windows.Forms.RadioButton
$radioButtonGrayScale.Location = "40,15"
$radioButtonGrayScale.ForeColor = "Indigo"
$radioButtonGrayScale.Text = "Grayscale"
$radioButtonGrayScale.Checked = $true
$radioButtonGrayScale.add_Click({
    $global:selectedObject = "GrayScale"
})
$mainForm.Controls.Add($radioButtonGrayScale)

# Negative RadioButton
$radioButtonNegative = New-Object System.Windows.Forms.RadioButton
$radioButtonNegative.Location = "200,15"
$radioButtonNegative.ForeColor = "Indigo"
$radioButtonNegative.Text = "Negative"
$radioButtonNegative.add_Click({
    $global:selectedObject = "Negative"
})
$mainForm.Controls.Add($radioButtonNegative)

# Sepia RadioButton
$radioButtonSepia = New-Object System.Windows.Forms.RadioButton
$radioButtonSepia.Location = "360,15"
$radioButtonSepia.ForeColor = "Indigo"
$radioButtonSepia.Text = "Sepia"
$radioButtonSepia.add_Click({
    $global:selectedObject = "Sepia"
})
$mainForm.Controls.Add($radioButtonSepia)

The Input File selection uses the OpenFileDialog Control to select the file. The control is called with the Browse Button. The selected filename is displayed in a TextBox.

# Input Box
$textBoxIn = New-Object System.Windows.Forms.TextBox
$textBoxIn.Location = "15, 50"
$textBoxIn.Size = "460, 20"
$textBoxIn.Text = "Input Image File"
$mainForm.Controls.Add($textBoxIn)

# Browse Input Button
$buttonBrowse = New-Object System.Windows.Forms.Button
$buttonBrowse.Location = "480, 50"
$buttonBrowse.Size = "75, 23"
$buttonBrowse.ForeColor = "MediumBlue" 
$buttonBrowse.Text = "Browse"
$buttonBrowse.add_Click({selectFiles})
$mainForm.Controls.Add($buttonBrowse)

function selectFiles {
	$selectForm = New-Object System.Windows.Forms.OpenFileDialog
	$selectForm.Filter = "All Files (*.*)|*.*"
	$selectForm.InitialDirectory = ".\"
	$selectForm.Title = "Select a File to Process"
	$getKey = $selectForm.ShowDialog()
	If ($getKey -eq "OK") {
            $textBoxIn.Text = $selectForm.FileName
            $completeLabel.Text = "      "
            InputPicture($selectForm.FileName)
	}
} # End SelectFile

Three Buttons are used to start Processing, Display the output in a larger format and Exit the script.

# Process Button
$buttonProcess = New-Object System.Windows.Forms.Button
$buttonProcess.Location = "740,50"
$buttonProcess.Size = "75, 23"
$buttonProcess.ForeColor = "Green" 
$buttonProcess.Text = "Process"
$buttonProcess.add_Click({ProcessButton})
$mainForm.Controls.Add($buttonProcess)

# Display Button
$buttonDisplay = New-Object System.Windows.Forms.Button
$buttonDisplay.Location = "830,50"
$buttonDisplay.Size = "75, 23"
$buttonDisplay.ForeColor = "Green" 
$buttonDisplay.Text = "Display"
$buttonDisplay.add_Click({DisplayImage})
$mainForm.Controls.Add($buttonDisplay)

# Exit Button
$buttonExit = New-Object System.Windows.Forms.Button
$buttonExit.Location = "920,50"
$buttonExit.Size = "75, 23"
$buttonExit.ForeColor = "Red" 
$buttonExit.Text = "Exit"
$buttonExit.add_Click({$mainForm.close()})
$mainForm.Controls.Add($buttonExit)

Two Labels provide information. One states where the output file is being written and the other shows the status of the processing.

# Output Label
$outputLabel = New-Object System.Windows.Forms.Label
$outputLabel.Location = "15, 400"
$outputLabel.Size = "900, 20"
$outputLabel.ForeColor = "DarkBlue"
$outputLabel.Text = "Output Files Written to - " + $scriptPath
$mainForm.Controls.Add($outputLabel)

# Completion Label
$completeLabel = New-Object System.Windows.Forms.Label
$completeLabel.Location = "750, 30"
$completeLabel.Size = "60, 18"
$completeLabel.ForeColor = "Green"
$completeLabel.Text = ""
$mainForm.Controls.Add($completeLabel)

There are two PitcureBoxes displaying the input and output images.

# Input PictureBox
$inputPicture = New-Object System.Windows.Forms.PictureBox
$inputPicture.Location = "15, 90"
$inputPicture.ClientSize = "480, 300"
$inputPicture.BackColor = "Black"
$inputPicture.SizeMode = "Zoom"
$mainForm.Controls.Add($inputPicture)

# Final PictureBox
$finalPicture = New-Object System.Windows.Forms.PictureBox
$finalPicture.Location = "520, 90"
$finalPicture.ClientSize = "480, 300"
$finalPicture.BackColor = "Black"
$finalPicture.SizeMode = "Zoom"
$mainForm.Controls.Add($finalPicture)

I add a ProgressBar to graphically show the progress of the conversion process. I set various values including Style.

# Progress Bar
$ProgressBar = New-Object System.Windows.Forms.ProgressBar
$ProgressBar.Location = "8, 430"
$ProgressBar.Maximum = 1000
$ProgressBar.Minimum = 0
$ProgressBar.Size = "1000, 20"
$progressBar.Step = 10
$progressBar.Style = "Continuous"
$mainForm.Controls.Add($ProgressBar)

The final controls added to the form are the <a title="StatusStrip" href="http://msdn.microsoft.com/en-us/library/system.windows.forms.statusstrip(v=vs.110).aspx" target="_blank">StatusStrip</a> and its Label. I write the percentage complete value in the Label.


# Progress Bar Status Strip & Label
$statusStrip = New-Object System.Windows.Forms.StatusStrip
$statusStrip.BackColor = "Bisque"

$statusLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
[void]$statusStrip.Items.Add($statusLabel)
$statusLabel.AutoSize = $true
$statusLabel.BackColor = "Bisque"
$statusLabel.Text = "Ready"
$mainForm.Controls.Add($statusStrip)

When the Process Button is clicked the ProcessButton Function is called. After initializing some values the effect selected by the RadioButton, global:selectedObject, is called using Invoke-Express.

Function ProcessButton {
    $completeLabel.Text = "        "
    $progressBar.Value = 0
    $finalPicture.Image = $null
    $statusLabel.Text = "Ready"
    Start-Sleep -Milliseconds 200
    Invoke-Expression $global:selectedObject
}

In the Grayscale Function I first cleared $error variable and set the input filename. I define $image, the input image, and set it's width and height to the original image size. I define $bitmap, the output image, and also set it's dimensions to the original size.

Function GrayScale {
    $error.clear()
    # Input Image
    $filename = $textBoxIn.Text
    $image = New-Object System.Drawing.Bitmap $filename
    $total = $image.Width * $image.Height

    # Output Image
    $bitmap = New-Object System.Drawing.Bitmap($image.Width,$image.Height)

Next I setup the main loop and the ProgressBar. The main loop consist of two loops which process each pixel of the image. Inside a loop going across the width of the image an inner loop process each pixel of the height of the image. Each pixel is separated into it's Red, Green and Blue components and those are color values are processed using the Luminosity Method. That method accounts for the way the human eye works and multiples each value by a certain number so the result is each value represents the percentage of light absorbed of that color. You can also use the Average Method which just averages the combined values of the colors for that pixel. After being set each pixel is written to $bitmap.

I run the calculation for the percent done and compare that to a step counter, calling a ProgressBar.PerformStep to increment the ProgressBar if the percentage done is an integer (or just greater an that) resulting in 100 update of the ProgressBar during the run, one for each percent completed. I also update the StatusBar Label with the percent done.

    $percentStep = 0
    foreach($x in 0..($image.Width-1)) {
        foreach($y in 0..($image.Height-1)) {
            $percentDone = ((($x * $image.Height + $y) / $total ) * 100)
            If ([int]$percentDone -gt $percentStep) {
                $progressBar.PerformStep()
                $statusLabel.Text = "{0:N0}" -f $percentDone + "% Complete"
                $percentStep = $percentStep + 1
            }
            $pixelColor = $image.GetPixel($x, $y)
	        $red   = $pixelColor.R
	        $green = $pixelColor.G
	        $blue  = $pixelColor.B
	        # $gray  = ($red + $green + $blue) / 3  # Average Method
	        $gray = ($red*.2126)+($green*.7152)+($blue*.0722)  # Luminosity Method
	        $bitmap.SetPixel($x, $y, [System.Drawing.Color]::FromArgb($gray, $gray, $gray))
        }
    }

When the image has been fully processed it is written to a file with a name which includes date and time for uniqueness. That image file is then displayed in the final PictureBox. A check of $error makes sure there were no issues during the processing or file write.

    $global:outFile = $scriptPath + "\"  + "GrayScale_" + (Get-Date -UFormat %Y%m%d_%H%M%S) + ".bmp"
    $bitmap.Save($global:outFile)
    FinalPicture($Global:outFile)

    $bitmap.Dispose()
    If($error.count -gt 0) {
        $completeLabel.ForeColor = "Red"
        $completeLabel.Text = "Error!"
    } Else {
        $completeLabel.ForeColor = "Green"
        $completeLabel.Text = "Success!"
    }
} # End GrayScale Function

Negative images are processed by reversing the pixel color, done by subtracting the actual value from the max value.

$pixelColor = $image.GetPixel($x, $y)
$red   = $pixelColor.R
$green = $pixelColor.G
$blue  = $pixelColor.B
$newRed   = (255 - $red)
$newGreen = (255 - $green)
$newBlue  = (255 - $blue)
$bitmap.SetPixel($x, $y, [System.Drawing.Color]::FromArgb($newRed, $newGreen, $newBlue))

Sepia images are generated taking a different fixed percentage of each color value, ending with the tinted image. These specific values are the values for sepia tone that are recommended by Microsoft.

$pixelColor = $image.GetPixel($x, $y)
$red   = $pixelColor.R
$green = $pixelColor.G
$blue  = $pixelColor.B
$outRed   = ($red * 0.393) + ($green * 0.769) + ($blue * 0.189)
$outGreen = ($red * 0.349) + ($green * 0.686) + ($blue * 0.168)
$outBlue  = ($red * 0.272) + ($green * 0.534) + ($blue * 0.131)
If ($outRed   -gt 255) {$outred   = 255}
If ($outGreen -gt 255) {$outGreen = 255}
If ($outBlue  -gt 255) {$outBlue  = 255}
$bitmap.SetPixel($x, $y, [System.Drawing.Color]::FromArgb($outRed, $outGreen, $outBlue))

Download the complete script here - ImageConverter.ps1

PowerShell Collage Framed Images

1 Comment

This script is an update to my Single Image Collage Maker script. After populating the new image with a collage of smaller images a larger central image, with optional border, is painted. The resultant effect is a picture framed by many smaller pictures. You can select the number of collage images and their size along with the size and border on the central image.
 

Here are a few examples. First the frame is fully populated with the collage and the central image has a thick border. The Collage images are at 10% size on all the examples.
 
FramedImages_20140719_180457
 
In this one the central image is smaller so more of the collage shows.
 
FramedImages_20140718_185231
 
The one has an sparsely populated collage frame and a transparent background.
 
FramedImages_20140719_181227
 
This last one is just a more abstract image with a complete Collage Frame and a Border.
 
CollageFramedImages_20140719_193156
 
I started with the Single Image Collage script and added a TrackBar for the central image size.
 

# Central Image Reduction TrackBar
$centralReductionTrackBar = New-Object Windows.Forms.TrackBar
$centralReductionTrackBar.Location = "190,170"
$centralReductionTrackBar.Orientation = "Horizontal"
$centralReductionTrackBar.Width = 295
$centralReductionTrackBar.Height = 40
$centralReductionTrackBar.LargeChange = 20
$centralReductionTrackBar.SmallChange = 1
$centralReductionTrackBar.TickFrequency = 5
$centralReductionTrackBar.TickStyle = "TopLeft"
$centralReductionTrackBar.SetRange(1, 100)
$centralReductionTrackBar.Value = 80
$centralReductionTrackBarValue = 80
$centralReductionTrackBar.add_ValueChanged({
    $centralReductionTrackBarValue = $centralReductionTrackBar.Value
    $centralReductionTrackBarLabel.Text = "Central Image Size ($centralReductionTrackBarValue%)"
    $global:centralReductionPercent = $centralReductionTrackBarValue
})
$mainForm.Controls.add($centralReductionTrackBar)

# Central Image Reduction Label
$centralReductionTrackBarLabel = New-Object System.Windows.Forms.Label 
$centralReductionTrackBarLabel.Location = "15,170"
$centralReductionTrackBarLabel.Size = "180,23"
$centralReductionTrackBarLabel.ForeColor = "MediumBlue"
$centralReductionTrackBarLabel.Text = "Central Image Size ($centralReductionTrackBarValue%)"
$mainForm.Controls.Add($centralReductionTrackBarLabel)

 
I removed the Output Image Size ComboBox since the final image is the same size as the input image. In it’s place I added a ComboBox for the Border Size of the central image.
 

# Border Width ComboBox
$borderComboBox = New-Object System.Windows.Forms.ComboBox
$borderComboBox.Location = "870,30"
$borderComboBox.Size = "30,20"
$borderComboBox.ForeColor = "Indigo"
$borderComboBox.BackColor = "White"
[void]$borderComboBox.items.add("0")
[void]$borderComboBox.items.add("1")
[void]$borderComboBox.items.add("2")
[void]$borderComboBox.items.add("3")
[void]$borderComboBox.items.add("4")
[void]$borderComboBox.items.add("5")
[void]$borderComboBox.items.add("6")  
[void]$borderComboBox.items.add("7")  
[void]$borderComboBox.items.add("8")             
$borderComboBox.SelectedIndex = 2
$mainForm.Controls.Add($borderComboBox)

# Border Width ComboBox Label
$borderComboBoxLabel = New-Object System.Windows.Forms.Label 
$borderComboBoxLabel.Location = "770,30"
$borderComboBoxLabel.Size = "90,23"
$borderComboBoxLabel.ForeColor = "MediumBlue"
$borderComboBoxLabel.Text = "Border Width"
$mainForm.Controls.Add($borderComboBoxLabel)

 
For the central image I define the image size and added the Brush for the Border, if selected.
 

    $image = [System.Drawing.Image]::FromFile($textBoxIn.Text)
    $brush = New-Object System.Drawing.TextureBrush($image, "Tile")
    $brushBlack = New-Object System.Drawing.SolidBrush("Black")
    $global:imageWidth  = $image.Width
    $global:imageHeight = $image.Height

 
After the Collage is created I paint the central image. If a Border is selected then I do a DrawRectangle to draw it. The size of the Rectangle is set to be larger than the central image by the border width. The central image is then centered and drawn.
 

    # Paint central image
    If ([int]$borderWidth -ge 1) {
        $bitmapGraphics.FillRectangle($brushBlack, `
            (([int]$global:imageWidth-($global:imageWidth*$centralReduction))/2)-$borderWidth, `
            (([int]$global:imageHeight-($global:imageHeight*$centralReduction))/2)-$borderWidth, `
            ($global:imagewidth*$centralReduction)+([int]$borderWidth*2), `
            ($global:imageheight*$centralReduction)+([int]$borderWidth*2))
    }
    $pointX = (([int]$global:imageWidth-($global:imageWidth*$centralReduction))/2)
    $pointY = (([int]$global:imageHeight-($global:imageHeight*$centralReduction))/2)
    $height = ([int]$global:imageHeight * $centralReduction)
    $width  = ([int]$global:imageWidth * $centralReduction)
    $bitmapGraphics.DrawImage($imageFile, $pointX, $pointY, $width, $height)

 
CollageFramedImage
 
Download the complete script here – CollageFramedImage.ps1

PowerShell Mosaic Pattern Framed Images

1 Comment

This script starts with my previous blog on Mosaic Style Images but adds a ‘clear’ image in the center which appears to be framed by the pattern. You control the size the center image as well as all the properties from the Mosaic Images script.
 
Here are a couple examples. First is one framed by FilledCurves at about 80% of the image.
 
MosaicFramedCurves_20140702_164104
 
This one is FilledRectangles sized at 80% with a Border.
 
MosaicFramedSquares_20140702_164704
 
And this one is a few FilledPolygons with a transparent background with a Border.
 
MosaicFramedPolygons_20140702_165044
 
FilledCurves with a 20% center image and a Transparent Background without a Border.
 
MosaicFramedCurves_20140711_184044
 
The is another example with 80% sized FilledPolygons and a Transparent Background and a Border.
 
MosaicFramedPolygons_20140704_114839
 
Staring with the Mosaic Images script, I added an additional TrackBar to select the percentage of the final centered image relative to the starting image. Select 90% and you get a very thin frame, select 50% and it’s half and half.
 

# Size of Center Image TrackBar
$centerImageTrackBar = New-Object Windows.Forms.TrackBar
$centerImageTrackBar.Location = "160,160"
$centerImageTrackBar.Orientation = "Horizontal"
$centerImageTrackBar.Width = 325
$centerImageTrackBar.Height = 40
$centerImageTrackBar.LargeChange = 10
$centerImageTrackBar.SmallChange = 1
$centerImageTrackBar.TickFrequency = 10
$centerImageTrackBar.TickStyle = "TopLeft"
$centerImageTrackBar.SetRange(1, 100)
$centerImageTrackBar.Value = 80
$centerImageTrackBarValue = 80
$centerImageTrackBar.add_ValueChanged({
    $centerImageTrackBarValue = $centerImageTrackBar.Value
    $centerImageTrackBarLabel.Text = "Center Frame Size % ($centerImageTrackBarValue)"
    $global:reductionSize = $centerImageTrackBarValue
})
$mainForm.Controls.add($centerImageTrackBar)

# Size of Center Image TrackBar Label
$centerImageTrackBarLabel = New-Object System.Windows.Forms.Label 
$centerImageTrackBarLabel.Location = "15,160"
$centerImageTrackBarLabel.Size = "160,23"
$centerImageTrackBarLabel.ForeColor = "MediumBlue"
$centerImageTrackBarLabel.Text = "Center Frame Size % ($centerImageTrackBarValue)"
$mainForm.Controls.Add($centerImageTrackBarLabel)

 
After a Mosaic pattern has been drawn, as in Mosaic Image script, a final centered pattern is drawn at the size specified. Here is the FilledRectangle which is used in the Square, Rectangle and Polygon Patterns. The FilledEllipse is used in Ellipse and Curves Patterns. The final centered pattern size is the selected percentage of the original image size. The central pattern is centered from the upper left corner of the pattern by subtracting the pattern length and width from the original image size and dividing by 2.
 

    $reduction = $global:reductionSize*.01
    # Paint central image
    If ([int]$borderWidth -ge 1) {
        $bitmapGraphics.FillRectangle($brushBlack, `
            (([int]$image.width-($image.width*$reduction))/2)-$borderWidth, `
            (([int]$image.height-($image.height*$reduction))/2)-$borderWidth, `
            ($image.width*$reduction)+([int]$borderWidth*2), `
            ($image.height*$reduction)+([int]$borderWidth*2))
    }
    $bitmapGraphics.FillRectangle($brush, `
            (([int]$image.width-($image.width*$reduction))/2), `
            (([int]$image.height-($image.height*$reduction))/2), `
            $image.width*$reduction, `
            $image.height*$reduction)

 
MosaicFramedImages
 
Download the complete script here – MosaicFramedImages.ps1
 
 
(Thanks to my friend Kevin Bell for providing the simple centering formula while I was lost wondering around in overcomplexity land.)

PowerShell Create Mosaic Style Images

1 Comment

This script uses Graphics.Fill methods with a Brush containing pieces of an image to repaint that image. Adding a Border you get a Mosaic style image. You get different effects using Graphics.FillRectangle, Graphics.FillEllipse, Graphics.FillClosedCurve and Graphics.FillPolygon. The image the Brush is filling with and the new image have the same dimensions so if you painted with enough random fills to cover the entire image you would have the original image back again, providing you have no border. You pick an image and then select the style and number of random image pieces to paint as well as border and background characteristics.
 
Below are some examples. The first is an image fully covered by random squares with a thin border.
 
ImageSquares_20140126_142121
 
This image is fully covered by Ellipses with a thicker border.
 
MosaicEllipses_20140622_142705
 
This image had fewer squares painted on it so the new image is not fully covered. The background was random and a thin border.
 
MosaicSquares_20140622_143637
 
This image is similar to the above except with a transparent background color.
 
MosaicSquares_20140622_143627
 
This image has a few Curves selected and transparent background. ClosedCurves (and Polygons) are made from a list of points and filled and so the below image is made from a single object.
 
MosaicCurves_20140622_153121
 
The image was created by a large amount of Polygons with a black background.
 
MosaicCurves_20140622_143757
 
I started by defining a WinForms Form and setting some Global Variables. I added the Form Title.
 

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Global Values
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$global:numberObjects = 1000
$global:backgroundText = "Black"
$global:outFile = ""

# WinForm Setup
$mainForm = New-Object System.Windows.Forms.Form
$mainForm.Location = "200, 200"
$mainForm.Font = "Comic Sans MS,8.5"
$mainForm.FormBorderStyle = "FixedDialog"
$mainForm.ForeColor = "Black"
$mainForm.BackColor = "Cornsilk"
$mainForm.Text = " Mosaic Image Patterns"
$mainForm.Width = 1030
$mainForm.Height = 500

#Title Label
$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Location = "400, 5"
$titleLabel.Font = "Comic Sans MS,11"
$titleLabel.Size = "300, 23"
$titleLabel.ForeColor = "DarkRed" 
$titleLabel.Text = "Mosaic Image Patterns"
$mainForm.Controls.Add($titleLabel)

 
The Brush Style is collected with RadioButtons. The default is Square, where I set the .Checked = $true value.
 

# Curves Object
$radioButtonCurves = New-Object System.Windows.Forms.RadioButton
$radioButtonCurves.Location = "15,35"
$radioButtonCurves.ForeColor = "Indigo"
$radioButtonCurves.Size = "80,23"
$radioButtonCurves.Text = "Curves"
$radioButtonCurves.add_Click({
    $global:selectedObject = "Curves"
})
$mainForm.Controls.Add($radioButtonCurves)

 
Next is gathered the Input Image filename. I add a TextBox to display the result. A Label and a Button to initiate the selectFiles Function. Lastly I use System.Windows.Forms.OpenFileDialog Control to open a File Browse dialog to select the file.
 

# Input Box
$textBoxIn = New-Object System.Windows.Forms.TextBox
$textBoxIn.Location = "15, 90"
$textBoxIn.Size = "460, 20"
$mainForm.Controls.Add($textBoxIn)

# Input Box Label
$inputLabel = New-Object System.Windows.Forms.Label
$inputLabel.Location = "15, 67"
$inputLabel.Size = "300, 23"
$inputLabel.ForeColor = "MediumBlue" 
$inputLabel.Text = "Input Image File"
$mainForm.Controls.Add($inputLabel)

# Browse Input Button
$buttonBrowse = New-Object System.Windows.Forms.Button
$buttonBrowse.Location = "560, 80"
$buttonBrowse.Size = "75, 23"
$buttonBrowse.ForeColor = "MediumBlue" 
$buttonBrowse.Text = "Browse"
$buttonBrowse.add_Click({selectFiles})
$mainForm.Controls.Add($buttonBrowse)

function selectFiles {
	$selectForm = New-Object System.Windows.Forms.OpenFileDialog
	$selectForm.Filter = "All Files (*.*)|*.*"
	$selectForm.InitialDirectory = ".\"
	$selectForm.Title = "Select a File to Process"
	$getKey = $selectForm.ShowDialog()
	If ($getKey -eq "OK") {
            $textBoxIn.Text = $selectForm.FileName
            $completeLabel.Text = "      "
            InputPicture($selectForm.FileName)
	}
} # End SelectFile

 
The input image is displayed in a PictureBox.
 

# Input PictureBox
$inputPicture = New-Object System.Windows.Forms.PictureBox
$inputPicture.Location = "90, 200"
$inputPicture.ClientSize = "320, 200"
$inputPicture.BackColor = "Black"
$inputPicture.SizeMode = "Zoom"
$mainForm.Controls.Add($inputPicture)

Function InputPicture {
    $file = (Get-Item $selectForm.FileName)
    $image = [System.Drawing.Image]::Fromfile($file)
    $inputPicture.Image = $image
}

 
I get the number of images to be painted with a TrackBar Control. I set label parameters and finally increment number is displayed in the Label.
 

# Number of Objects TrackBar
$numberTrackBar = New-Object Windows.Forms.TrackBar
$numberTrackBar.Location = "160,140"
$numberTrackBar.Orientation = "Horizontal"
$numberTrackBar.Width = 325
$numberTrackBar.Height = 40
$numberTrackBar.LargeChange = 500
$numberTrackBar.SmallChange = 1
$numberTrackBar.TickFrequency = 500
$numberTrackBar.TickStyle = "TopLeft"
$numberTrackBar.SetRange(1, 5000)
$numberTrackBar.Value = 1000
$numberTrackBarValue = 1000
$numberTrackBar.add_ValueChanged({
    $numberTrackBarValue = $numberTrackBar.Value
    $numberTrackBarLabel.Text = "Number Images ($numberTrackBarValue)"
    $global:numberObjects = $numberTrackBarValue
})
$mainForm.Controls.add($numberTrackBar)

# Object Number Label
$numberTrackBarLabel = New-Object System.Windows.Forms.Label 
$numberTrackBarLabel.Location = "15,140"
$numberTrackBarLabel.Size = "160,23"
$numberTrackBarLabel.ForeColor = "MediumBlue"
$numberTrackBarLabel.Text = "Number Images ($numberTrackBarValue)"
$mainForm.Controls.Add($numberTrackBarLabel)

 
Background Color and Border Width are gathered with ComboBoxes.
 

# Background ComboBox
$backgroundComboBox = New-Object System.Windows.Forms.ComboBox
$backgroundComboBox.Location = "590,30"
$backgroundComboBox.Size = "90,20"
$backgroundComboBox.ForeColor = "Indigo"
$backgroundComboBox.BackColor = "White"
[void]$backgroundComboBox.items.add("Black")
[void]$backgroundComboBox.items.add("White")
[void]$backgroundComboBox.items.add("Random")
[void]$backgroundComboBox.items.add("Transparent")         
$backgroundComboBox.SelectedIndex = 0
$mainForm.Controls.Add($backgroundComboBox)

# Background ComboBox Label
$backgroundComboBoxLabel = New-Object System.Windows.Forms.Label 
$backgroundComboBoxLabel.Location = "520,30"
$backgroundComboBoxLabel.Size = "80,20"
$backgroundComboBoxLabel.ForeColor = "MediumBlue"
$backgroundComboBoxLabel.Text = "Background"
$mainForm.Controls.Add($backgroundComboBoxLabel)

 
The Output Image file is written to the same folder where the script resides.
 

# Output Label
$outputLabel = New-Object System.Windows.Forms.Label
$outputLabel.Location = "15, 435"
$outputLabel.Size = "900, 20"
$outputLabel.ForeColor = "DarkBlue"
$outputLabel.Text = "Output Files Written to - " + $scriptPath
$mainForm.Controls.Add($outputLabel)

 
The last Form Controls are the Process, Display and Exit Buttons.
 

# Process Button
$buttonProcess = New-Object System.Windows.Forms.Button
$buttonProcess.Location = "665,80"
$buttonProcess.Size = "75, 23"
$buttonProcess.ForeColor = "Green" 
$buttonProcess.Text = "Process"
$buttonProcess.add_Click({ProcessButton})
$mainForm.Controls.Add($buttonProcess)

# Display Button
$buttonDisplay = New-Object System.Windows.Forms.Button
$buttonDisplay.Location = "770,80"
$buttonDisplay.Size = "75, 23"
$buttonDisplay.ForeColor = "Green" 
$buttonDisplay.Text = "Display"
$buttonDisplay.add_Click({DisplayImage})
$mainForm.Controls.Add($buttonDisplay)

# Exit Button
$buttonExit = New-Object System.Windows.Forms.Button
$buttonExit.Location = "870,80"
$buttonExit.Size = "75, 23"
$buttonExit.ForeColor = "Red" 
$buttonExit.Text = "Exit"
$buttonExit.add_Click({$mainForm.close()})
$mainForm.Controls.Add($buttonExit)

 
In the ProcessButton Function I first initialize $error and the $CompleteLabel values and set default colors. I define the Brush using the input image file as the Texture. I set the Brush to paint the image borders as Black. The output image is defined as the same size as the input image. The Graphics class object for the image is set. The image Background Color is set. Finally the selected function is called using Invoke-Expression.
 

Function ProcessButton {
    $error.clear()
    $completeLabel.Text = "        " 

    # Default Random Color
    $red   = (Get-Random -minimum 0 -maximum 255)
    $green = (Get-Random -minimum 0 -maximum 255)
    $blue  = (Get-Random -minimum 0 -maximum 255)

    # Image Brush
    $image = [System.Drawing.Image]::FromFile($textBoxIn.Text)
    $brush = New-Object System.Drawing.TextureBrush($image, "Tile")
    $brushBlack = New-Object System.Drawing.SolidBrush("Black")

    # Create Image
    $bitmap = New-Object System.Drawing.Bitmap($image.Width,$image.Height)
    $bitmapGraphics = [System.Drawing.Graphics]::FromImage($bitmap)

    # Image Background Color
    If ($backgroundComboBox.Text -eq "Transparent") {
        $bitmap.MakeTransparent()
    } Else {
        If ($backgroundComboBox.Text -eq "Random") {
            $red   = (Get-Random -minimum 0 -maximum 255)
            $green = (Get-Random -minimum 0 -maximum 255)
            $blue  = (Get-Random -minimum 0 -maximum 255)
            $backColor = [System.Drawing.Color]::FromArgb($red, $green, $blue)
            $bitmapGraphics.Clear($backColor)
        } Else {
            $bitmapGraphics.Clear($backgroundComboBox.Text)
        }
    }

    # Call Selected Fill
    Invoke-Expression $global:selectedObject
} # End ProcessButton

 
As an example of the Brushes used to fill the image here is the Squares function. The image we are filling with and the output image are the same size so the position and size of the selected fill are in their original location. Within an iteration the size of the number of selected objects, the X, Y, Height and Width values are randomized. Since this is a square I simply make the height and width values the same. If a border has been selected (width > 0) then a black border is simulated by filling a rectangle in the same location but larger by the border size value. The fill image will overwrite all but the simulated border. The fill uses the FillRectangle method. After the fill iteration has completed the file is written to disk in the same folder as the script with a unique name containing the date, time and brush style. An error check confirms the process success and write a completed status label.
 

Function Squares {
    $i = 0
    While ($i -le $global:numberObjects) { $i++
        $pointX = (Get-Random -minimum -100 -maximum ([int]$image.width + 100))
        $pointY = (Get-Random -minimum -100 -maximum ([int]$image.height + 100))
        $width = (Get-Random -minimum 1 -maximum ([int]$image.height * .25))
        $height = $width
        If ([int]$frameComboBox.Text -ge 1) {
            $bitmapGraphics.FillRectangle($brushBlack, $pointX-$borderComboBox.Text, $pointY-$borderComboBox.Text, $width+([int]$borderComboBox.Text*2), $height+([int]$borderComboBox.Text*2))
        }
        $bitmapGraphics.FillRectangle($brush, $pointX, $pointY, $width, $height)
    }

    $global:outFile = $scriptPath + "\"  + "MosaicSquares_" + (Get-Date -UFormat %Y%m%d_%H%M%S) + ".bmp"
    $bitmap.Save($global:outFile)
    FinalPicture($global:outFile)

    $bitmap.Dispose()
    $bitmapGraphics.Dispose()
    If($error.count -gt 0) {
        $completeLabel.ForeColor = "Red"
        $completeLabel.Text = "Error!"
    } Else {
        $completeLabel.ForeColor = "Green"
        $completeLabel.Text = "Success!"
    }
} # End Squares

 
Polygons (and Curves) use a list of points to define the object to be filled. The list is built and then the object created and filled at once instead of creating a lot of individual fill objects.
 

Function Polygons {
    $i = 0; $polygon = @()
    While ($i -le $global:numberObjects) { $i++
        $polygonNext = New-Object System.Drawing.Point `
            ((Get-Random -minimum 0 -maximum 1023), (Get-Random -minimum 0 -maximum 767)((Get-Random -minimum 0 -maximum $image.width), (Get-Random -minimum 0 -maximum $image.height)))
        $polygon += $polygonNext
    }
    $bitmapGraphics.FillPolygon($brush, $polygon)

    $global:outFile = $scriptPath + "\"  + "MosaicPolygons_" + (Get-Date -UFormat %Y%m%d_%H%M%S) + ".bmp"
    $bitmap.Save($global:outFile)
    FinalPicture($global:outFile)

    $bitmap.Dispose()
    $bitmapGraphics.Dispose()
    If($error.count -gt 0) {
        $completeLabel.ForeColor = "Red"
        $completeLabel.Text = "Error!"
    } Else {
        $completeLabel.ForeColor = "Green"
        $completeLabel.Text = "Success!"
    }
} # End Polygons

 
The output image is displayed in a PictureBox.
 

# Final PictureBox
$finalPicture = New-Object System.Windows.Forms.PictureBox
$finalPicture.Location = "520, 125"
$finalPicture.ClientSize = "480, 300"
$finalPicture.BackColor = "Black"
$finalPicture.SizeMode = "Zoom"
$mainForm.Controls.Add($finalPicture)

Function FinalPicture {
    $file = (Get-Item $global:outFile)
    $image = [System.Drawing.Image]::Fromfile($file)
    $finalPicture.Image = $image
}

 
The image can be displayed in a full sized viewer. This will invoke the system default image viewer and open the output file.
 

Function DisplayImage {
    If ($global:outFile.Length -gt 0) {
        Invoke-Item $global:outFile
    }
}

 
 
MosaicImagePatterns
 
Download the complete script here – MosaicImagePatterns.ps1

PowerShell Draw Cartoon Face

Leave a comment

This script will allow you to create a simple cartoon face controlling a few parameters like the pupil size, nose size and level of smile. Used to create the cartoon face are System.Drawing.Graphics controls like FillEllipse, DrawCurve and DrawLine. The background color is somewhat selectable while the object colors are random.
 
Here are a couple examples from the script –
 
CartoonFace_20140322_170532
 
CartoonFace_20140314_184605
 
In a WinForm Form I defined the Form and set some default values.
 

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Main Form
$mainForm = New-Object Windows.Forms.Form
$mainForm.Location = "200,200"
$mainForm.FormBorderStyle = "FixedDialog"
$mainForm.BackColor = "White"
$mainForm.Font = “Comic Sans MS,8.25"
$mainForm.Text = "Draw Cartoon Face"
$mainForm.size = "520,280"

# Global Values
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$global:pupilValue = 5
$global:eyebrowValue = 5
$global:noseValue = 5
$global:mouthValue = 8
$global:backgroundText = "Black"

 
I collected the object parameters with four TrackBar controls. The TrackBar is setup wiyh a Label to show the name and the values as the TrackBar is moved. I added Plus/Minus symbols to make it easy to see in what direction your changing the values.
 

# Pupil TrackBar
$pupilTrackBar = New-Object Windows.Forms.TrackBar
$pupilTrackBar.Location = "140,20"
$pupilTrackBar.Orientation = "Horizontal"
$pupilTrackBar.Width = 350
$pupilTrackBar.Height = 40
$pupilTrackBar.LargeChange = 5
$pupilTrackBar.SmallChange = 1
$pupilTrackBar.TickFrequency = 1
$pupilTrackBar.TickStyle = "TopLeft"
$pupilTrackBar.SetRange(1, 8)
$pupilTrackBar.Value = 3
$pupilTrackBarValue = 3
$pupilTrackBar.add_ValueChanged({
    $pupilTrackBarValue = $pupilTrackBar.Value
    $pupilTrackBarLabel.Text = "Pupil Size ($pupilTrackBarValue)"
    $global:pupilValue = $pupilTrackBarValue
})
$mainForm.Controls.add($pupilTrackBar)

# Pupil Labels
$pupilTrackBarLabel = New-Object System.Windows.Forms.Label 
$pupilTrackBarLabel.Location = "20,20"
$pupilTrackBarLabel.Size = "100,23"
$pupilTrackBarLabel.ForeColor = "MediumBlue"
$pupilTrackBarLabel.Text = "Pupil Size ($pupilTrackBarValue)"
$mainForm.Controls.Add($pupilTrackBarLabel)
$pupilTrackBarPlus = New-Object System.Windows.Forms.Label 
$pupilTrackBarPlus.Location = "490,20"
$pupilTrackBarPlus.Size = "15,23"
$pupilTrackBarPlus.ForeColor = "Red"
$pupilTrackBarPlus.Text = " +"
$mainForm.Controls.Add($pupilTrackBarPlus)
$pupilTrackBarMinus = New-Object System.Windows.Forms.Label 
$pupilTrackBarMinus.Location = "130,20"
$pupilTrackBarMinus.Size = "15,23"
$pupilTrackBarMinus.ForeColor = "Red"
$pupilTrackBarMinus.Text = "- "
$mainForm.Controls.Add($pupilTrackBarMinus)

 
A ComboBox control provides the selections for the background color.
 

# Background ComboBox
$backgroundComboBox = New-Object System.Windows.Forms.ComboBox
$backgroundComboBox.Location = "145,200"
$backgroundComboBox.Size = "120,20"
$backgroundComboBox.ForeColor = "Indigo"
$backgroundComboBox.BackColor = "White"
[void]$backgroundComboBox.items.add("Black")
[void]$backgroundComboBox.items.add("White")
[void]$backgroundComboBox.items.add("Random")         
$backgroundComboBox.SelectedIndex = 0
$mainForm.Controls.Add($backgroundComboBox)

# Background ComboBox Label
$backgroundComboBoxLabel = New-Object System.Windows.Forms.Label 
$backgroundComboBoxLabel.Location = "20,200"
$backgroundComboBoxLabel.Size = "100,23"
$backgroundComboBoxLabel.ForeColor = "MediumBlue"
$backgroundComboBoxLabel.Text = "Background Color"
$mainForm.Controls.Add($backgroundComboBoxLabel)

 
To complete the Form a couple buttons for the Draw and Exit actions are provided.
 

# Draw Button
$drawButton = New-Object System.Windows.Forms.Button 
$drawButton.Location = "320,200"
$drawButton.Size = "75,23"
$drawButton.ForeColor = "SeaGreen"
$drawButton.BackColor = "White"
$drawButton.Text = "Draw"
$drawButton.add_Click({DrawIt})
$mainForm.Controls.Add($drawButton)

# Exit Button
$exitButton = New-Object System.Windows.Forms.Button
$exitButton.Location = "405,200"
$exitButton.Size = "75,23"
$exitButton.ForeColor = "Red"
$exitButton.BackColor = "White"
$exitButton.Text = "Exit"
$exitButton.add_Click({$mainForm.close()})
$mainForm.Controls.Add($exitButton)

 
The Draw button calls the main drawing routine. I setup the output image and configure the background color depending on the selection made.
 

# Draw the Object
Function DrawIt {
$bitmap = New-Object System.Drawing.Bitmap(1024, 768)
$bitmapGraphics = [System.Drawing.Graphics]::FromImage($bitmap) 
$pen = New-Object Drawing.Pen "white"
# Image Background Color
If ($backgroundComboBox.Text -eq "Transparent") {
    $bitmap.MakeTransparent()
} Else {
    If ($backgroundComboBox.Text -eq "Random") {
        $red   = (Get-Random -minimum 0 -maximum 255)
        $green = (Get-Random -minimum 0 -maximum 255)
        $blue  = (Get-Random -minimum 0 -maximum 255)
        $backColor = [System.Drawing.Color]::FromArgb($red, $green, $blue)
        $bitmapGraphics.Clear($backColor)
    } Else {
        $bitmapGraphics.Clear($backgroundComboBox.Text)
    }
}

 
First the head is drawn. I set the Brush to a random color and draw it positioned relative to the outside edge. The individual objects are then drawn in the head. The selected value is replaced with the divisor used to position the object.
 

Function DrawHead {
    $red = (Get-Random -minimum 0 -maximum 255)
    $green = (Get-Random -minimum 0 -maximum 255)
    $blue = (Get-Random -minimum 0 -maximum 255)
    $brushColor = [System.Drawing.Color]::FromArgb($red, $green, $blue)
    $brush = New-Object System.Drawing.SolidBrush $brushColor
    $x = 1024/10
    $y = 768/10
    $width = 1024 - (1024/4)
    $height = 768 - (768/7)
    $bitmapGraphics.FillEllipse($brush, $x, $y, $width, $height)
}

Function DrawPupil {
    If ([int]$global:pupilValue -eq 1) {$value = 90}
    If ([int]$global:pupilValue -eq 2) {$value = 80}
    If ([int]$global:pupilValue -eq 3) {$value = 70}
    If ([int]$global:pupilValue -eq 4) {$value = 60}
    If ([int]$global:pupilValue -eq 5) {$value = 50}
    If ([int]$global:pupilValue -eq 6) {$value = 40}
    If ([int]$global:pupilValue -eq 7) {$value = 30}
    If ([int]$global:pupilValue -eq 8) {$value = 20}
    $red = (Get-Random -minimum 0 -maximum 255)
    $green = (Get-Random -minimum 0 -maximum 255)
    $blue = (Get-Random -minimum 0 -maximum 255)
    $brushColor = [System.Drawing.Color]::FromArgb($red, $green, $blue)
    $brush = New-Object System.Drawing.SolidBrush $brushColor
    $x1 = 1024/2.9
    $x2 = 1024/1.7
    $y = 768/2.7
    $width = 1024/$value
    $height = 768/$value
    $bitmapGraphics.FillEllipse($brush, $x1, $y, $width, $height)
    $bitmapGraphics.FillEllipse($brush, $x2, $y, $width, $height)
}

Function DrawNose {
    If ([int]$global:noseValue -eq 1)  {$value = 1.9}
    If ([int]$global:noseValue -eq 2)  {$value = 1.85}
    If ([int]$global:noseValue -eq 3)  {$value = 1.8}
    If ([int]$global:noseValue -eq 4)  {$value = 1.75}
    If ([int]$global:noseValue -eq 5)  {$value = 1.7}
    If ([int]$global:noseValue -eq 6)  {$value = 1.65}
    If ([int]$global:noseValue -eq 7)  {$value = 1.6}
    If ([int]$global:noseValue -eq 8)  {$value = 1.55}
    If ([int]$global:noseValue -eq 9)  {$value = 1.5}
    $red = (Get-Random -minimum 0 -maximum 255)
    $green = (Get-Random -minimum 0 -maximum 255)
    $blue = (Get-Random -minimum 0 -maximum 255)
    $penColor = [System.Drawing.Color]::FromArgb($red, $green, $blue)
    $pen = New-Object Drawing.Pen $penColor
    $pen.Width = 6
    $nose = @()
    $x1 = 1024/2.1
    $y1 = 768/2.3
    $x2 = 1024/$value
    $y2 = 768/1.6
    $x3 = 1024/2.2
    $y3 = 768/1.5
    $noseNext = New-Object System.Drawing.Point($x1, $y1)
    $nose += $noseNext
    $noseNext = New-Object System.Drawing.Point($x2, $y2)
    $nose += $noseNext
    $noseNext = New-Object System.Drawing.Point($x3, $y3)
    $nose += $noseNext
    $bitmapGraphics.DrawCurve($pen, $nose)

 
Finally the image is written to disk and displayed with the default image viewer.
 

$outFile = $scriptPath + "\"  + "CartoonFace_" + (Get-Date -UFormat %Y%m%d_%H%M%S) + ".bmp"
$bitmap.Save($outFile)
Invoke-Item $outFile

 
The objects are positioned within the Form relative placement to the Form edge. After working with this method I think greater flexibility and control would be derived by positioning the objects relative to the head boundaries. Maybe following the arc of the head ellipse and using a position along a radius. The head could then be repositioned or resized and keep the other objects relative to it. The current method allows the image to be resized but not the head individually.
 
CartoonFace
 
Download the complete script here – CartoonFace.ps1

Older Entries

%d bloggers like this: