colour count of an image - image

I am trying to make an upscale texture mod for an old game "Recoil".
For this, I need to find the height, width, pixel format and colour count of a few 1000 images, so that I can feed this information to excel and find the best textures to be upscaled.
I have been able to get the height, width and pixel format via a PowerShell script, which I can then copy to excel as this script provides a table. the script works on the whole folder.
Function Get-Image{
Param(
[Parameter(ValueFromPipeline=$true)]
[System.IO.FileINfo]$file
)
begin{
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") |Out-Null
}
process{
if( $file.Exists){
$img=[System.Drawing.Image]::FromFile($file)
$image=$img.Clone()
$img.Dispose()
$image | Add-Member `
-MemberType NoteProperty `
-Name Filename `
-Value $file.FUllname `
-PassThru
}else{
Write-Host "File not found: $file" -fore yellow
}
}
end{}
}
dir C:\test\*.png | Get-Image
dir C:\test\*.png -Recurse | Get-Image | select filename, Width, Height, PixelFormat | ft -auto
the result of the above code
I need help finding a way to get a colour count of the images. I have found a manual way to do it via a Photoshop filter but it is not a viable way to do all the images.
photoshop filter example
If I can get the Colour count in a similar way to the code provided it would be the best.
edit: I need a way to get Colour count of all images in the folder.
the images themselves are small (the biggest being 512x512). I just need the number of colours, no need for the breakdown of RGB.
ps- I have literally no knowledge of programming and scripting ( even the above script someone Reddit helped me out with)
Hopefully, I have been able to explain my query clearly.
Thank you for your time and consideration.
Edit 2
so this code works but I found an issue. Is there any way to make it not count the alphas?
Issue: colour count difference in the Photoshop Filter (telegraphic- Colour Count) and the new code.
Reason: Photoshop Filter counts colours only (without alpha), and the PowerShell script counts pixels (with alpha).
Format32bppArgb - has the issue
Format24bppRgb - it counts it fine.
below is the present code
Function Get-Image{
Param(
[Parameter(ValueFromPipeline=$true)]
[System.IO.FileINfo]$file
)
begin{
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") |Out-Null
}
process {
if ($file.Exists) {
# Load image
$img = [System.Drawing.Image]::FromFile($file)
$image = $img.Clone()
$img.Dispose()
# Count colors
$colorSet = [System.Collections.Generic.HashSet[System.Drawing.Color]]::new()
foreach ($x in 0..($image.Width - 1)) {
foreach ($y in 0..($image.Height - 1)) {
[void]$colorSet.Add($image.GetPixel($x, $y))
}
}
# Add file name and color count properties to image object
$fileNameProp = #{ MemberType = 'NoteProperty'; Name = 'Filename'; Value = $file.FullName; PassThru = $true}
$colorCountProp = #{ MemberType = 'NoteProperty'; Name = 'ColorCount'; Value = $colorSet.Count; PassThru = $true}
$image | Add-Member #fileNameProp | Add-Member #colorCountProp
}else{
Write-Host "File not found: $file" -fore yellow
}
}
end{}
}
dir D:\Games\Setups\RECOIL_fixed_edition_v0.5\SourceFile\zbd\Dataset_D\Dataset_D\ammoarcgun\*.png | Get-Image
dir D:\Games\Setups\RECOIL_fixed_edition_v0.5\SourceFile\zbd\Dataset_D\Dataset_D\ammoarcgun\*.png -Recurse | Get-Image | select filename, Width, Height, PixelFormat, ColorCount | ft -auto

Use the GetPixel() method to fetch the color used for each pixel, then count the unique colors you encounter:
$hashset = [System.Collections.Generic.HashSet[System.Drawing.Color]]::new()
foreach($x in 0..($image.Width - 1)){
foreach($y in 0..($image.Height - 1)){
[void]$hashset.Add($image.GetPixel($x, $y))
}
}
Write-Host "Image has $($hashset.Count) unique colors"
You could add this routine to your existing function like so:
process {
if ($file.Exists) {
# Load image
$img = [System.Drawing.Image]::FromFile($file)
$image = $img.Clone()
$img.Dispose()
# Count colors
$colorSet = [System.Collections.Generic.HashSet[System.Drawing.Color]]::new()
foreach ($x in 0..($image.Width - 1)) {
foreach ($y in 0..($image.Height - 1)) {
[void]$colorSet.Add($image.GetPixel($x, $y))
}
}
# Add file name and color count properties to image object
$fileNameProp = #{ MemberType = 'NoteProperty'; Name = 'Filename'; Value = $file.FullName; PassThru = $true}
$colorCountProp = #{ MemberType = 'NoteProperty'; Name = 'ColorCount'; Value = $colorSet.Count; PassThru = $true}
$image | Add-Member #fileNameProp | Add-Member #colorCountProp
}
else {
Write-Host "File not found: $file" -fore yellow
}
}
And now you can do:
dir C:\test\*.png -Recurse | Get-Image | ft Filename, Width, Height, PixelFormat, ColorCount -AutoSize
If you want to ignore the alpha-channel in each pixel, change this line:
[void]$colorSet.Add($image.GetPixel($x, $y))
to
$pixel = $image.GetPixel($x, $y)
[void]$colorSet.Add([System.Drawing.Color]::FromArgb($pixel.R, $pixel.G, $pixel.B))

Related

Powershell GUI PictureBox not able to change photos

I am new to Powershell.
I am trying to create picture box that will load a lot of images in a folder.
I have a folder that contain 10 images. but i want my picturebox to display it dynamically every second or 3 seconds
here is my code so far.
####################################### Form settings ##############################################
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form = New-Object System.Windows.Forms.Form
$Form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$Form.Anchor = "Top,Bottom,Left,Right"
$Form.Size = New-Object System.Drawing.Size(1920,1600)
$Form.AutoScale = $True
$Form.StartPosition = "CenterScreen" #loads the window in the center of the screen
$Form.BackgroundImageLayout = "Zoom"
$Form.MinimizeBox = $True
$Form.MaximizeBox = $False
$Form.WindowState = "Normal"
$Form.SizeGripStyle = "Auto"
$Form.AutoSizeMode = New-Object System.Windows.Forms.AutoSizeMode
$Form.SizeGripStyle = "Show"
$Form.BackColor = "LightGray"
###################################################################################################
###################################################################################################
######################################### Image Folder ############################################
$ImagePreview = New-Object System.Windows.Forms.PictureBox
$ImagePreview.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::StretchImage
$ImagePreview.Location = New-Object System.Drawing.Size(965,110)
$ImagePreview.Size = New-Object System.Drawing.Size(295,370)
$ImagePreview.BackColor = "White"
$Form.Controls.Add($ImagePreview)
Function ImageFolder($ImagePreview)
{
$ImageItem = Get-Item "C:\Newfolder"
$ImagePreview.ImageLocation = $ImageItem
}
$TimerImageFolder = New-Object System.Windows.Forms.Timer
$TimerImageFolder.Interval = 3000
$TimerImageFolder.Add_Tick({$ImageFolder $ImagePreview})
$TimerImageFolder.Enabled = $True
$ImageGroupBox = New-Object System.Windows.Forms.GroupBox
$ImageGroupBox.Location = New-Object System.Drawing.Size(940,70)
$ImageGroupBox.size = New-Object System.Drawing.Size(350,440)
$ImageGroupBox.text = "Preview"
$ImageGroupBox.BackColor = "DimGray"
$ImageGroupBox.ForeColor = "White"
$Form.Controls.Add($ImageGroupBox)
####################################### Result ##############################################
$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()
I don't know which part is wrong.
Thank You so much for the help Guys
What you're attempting to do is a cumbersome task from PowerShell. As you may note, it requires the instantiation of a powershell instance where initialize the Forms.Timer, this is needed because we need the .Tick event to happen on a separate thread, otherwise, the form would freeze on each triggering of this event.
The code shared below is, as a minimal reproduction of what you're trying to achieve, I have tried to simplified as much as possible but, as stated before, this is no easy task from PowerShell.
Below example as well as another example using an API to load the pictures from the Internet instead of from Disk can be found on this Gist. There is also a gif demo there.
using namespace System.Windows.Forms
using namespace System.Drawing
using namespace System.Management.Automation.Runspaces
Add-Type -AssemblyName System.Drawing, System.Windows.Forms
[Application]::EnableVisualStyles()
$event = #{}
# Get the paths of all the pictures in the `pictures` folder
$pictures = (Get-ChildItem .\pictures -Filter *.png).FullName
# basic form
$form = [Form]#{
ClientSize = [Size]::new(1280, 720)
StartPosition = 'CenterScreen'
}
# basic picturebox, note it includes a `Name` so it's easier to find
# from inside the `powershell` instance
$pictureBox = [PictureBox]#{
Name = 'myPictureBox'
Anchor = 'top, left'
SizeMode = [PictureBoxSizeMode]::StretchImage
Location = [Point]::new(20, 20)
Size = [Size]::new($form.Width - 60, $form.Height - 80)
Image = [Drawing.Image]::FromFile($pictures[0])
}
$form.Controls.Add($pictureBox)
# event to resize the picturebox when the form is resized
$form.Add_Resize({
$pictureBox.Size = [Size]::new($this.Width - 60, $this.Height - 80)
})
# initialize a `powershell` instance where we can handle the the Slide Show
# in PowerShell we require to perform this action from a different thread,
# else the form will freeze every 3000 milliseconds
$ps = [powershell]::Create().AddScript({
param($pictures, $form)
[ref] $ref = 1
$timer = [Windows.Forms.Timer]#{
Interval = 3000
}
# find the pictureBox in the Form controls
$pictureBox = $form.Controls.Find('myPictureBox', $false)[0]
# this Tick Event swaps the pictures when triggered
$timer.Add_Tick({
$pictureBox.Image = [Drawing.Image]::FromFile($pictures[$ref.Value++ % $pictures.Count])
})
# Start the Timer
$timer.Enabled = $true
# this `while` loop keeps this thread running until the form is closed
while($form.DialogResult -eq 'None') {
# we perform `Application.DoEvents()` so the form is updated on each loop iteration,
# without it we wouldn't see the picturebox updated
[Windows.Forms.Application]::DoEvents()
}
$form.DialogResult
# here we pass the list of paths and the form instance itself to this thread
}).AddParameters(#{ pictures = $pictures; form = $form })
# when the form is shown
$form.Add_Shown({
# bring it to front
$this.Activate()
# and add this AsyncResult to the `$events` hashtable
$event['AsyncResult'] = $ps.BeginInvoke()
})
# display the form (this blocks the current thread)
$null = $form.ShowDialog()
# when the form is closed, stop the runspace
$ps.EndInvoke($event['AsyncResult'])
# and dispose everything
$form, $ps | ForEach-Object Dispose

Powershell gui textbox with multiple color lines

Im having trouble with powershell textbox
here is the definition of it:
$ResultsTextBox = New-Object System.Windows.Forms.TextBox
$ResultsTextBox.Location = New-Object System.Drawing.Size(780,40)
$ResultsTextBox.Size = New-Object System.Drawing.Size(450,340)
i would like to create a function which output text to this text box but i would like to generate the text either red or green
depending of my choice..
but when i do this:
function LinkFn {
$ResultsTextBox.clear()
$SWITCH = Get-ADOrganizationalUnit -filter * -Property CanonicalName | Where-Object {$_.CanonicalName -eq $listBox2.SelectedItem}
forEach ($line in $listBox1.selecteditems){
try {
$ResultsTextBox.ForeColor ='green'
New-GPlink -name $line -target $SWITCH -ErrorAction STOP | Out-null
$ResultsTextBox.AppendText("`n GPO: $line HAVE BEEN LINKED Successfully.`n")
}
catch{
$ResultsTextBox.ForeColor ='red'
$ResultsTextBox.AppendText("`n COULDN NOT LINK GPO: $line `n")
}
}
it changes all the lines, when the result i wanted could be 1 line in red 2 lines in green for example
You need to use a RichTextBox control for this instead of a regular TextBox.
For demo here's a small form that fills lines in different colors to a RichTextBox:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# helper function to write text in a given color
# to the specified RichTextBox control
function Append-ColoredLine {
param(
[Parameter(Mandatory = $true, Position = 0)]
[System.Windows.Forms.RichTextBox]$box,
[Parameter(Mandatory = $true, Position = 1)]
[System.Drawing.Color]$color,
[Parameter(Mandatory = $true, Position = 2)]
[string]$text
)
$box.SelectionStart = $box.TextLength
$box.SelectionLength = 0
$box.SelectionColor = $color
$box.AppendText($text)
$box.AppendText([Environment]::NewLine)
}
$form = New-Object System.Windows.Forms.Form
$form.Width = 400
$form.Height = 500
$richText = New-Object System.Windows.Forms.RichTextBox
$richText.Location = [System.Drawing.Point]::new(10,10)
$richText.Size = [System.Drawing.Size]::new(364,350)
$richText.Font = [System.Drawing.Font]::new('Calibri', 14)
$richText.Anchor = 'Top','Right','Bottom','Left'
$form.Controls.Add($richText)
$button = New-Object System.Windows.Forms.Button
$button.Location = [System.Drawing.Point]::new(10,400)
$button.Size = [System.Drawing.Size]::new(80,30)
$button.Text = 'Test'
$button.Anchor = 'Bottom','Left'
$button.Add_Click({
$richText.Clear()
# write green lines
Append-ColoredLine $richText Green "GPO: 'gpo_A' has been linked Successfully"
Append-ColoredLine $richText Green "GPO: 'gpo_B' has been linked Successfully"
# write red line
Append-ColoredLine $richText Red "Could not link GPO: 'gpo_C'"
# insert blank line
$richText.AppendText([Environment]::NewLine)
# write various lines in different colors
'Blue','DarkGoldenrod','DarkCyan','OliveDrab','Chocolate','Crimson' | ForEach-Object {
Append-ColoredLine $richText $_ "Some text using color '$_'"
}
})
$form.Controls.Add($button)
[void] $form.ShowDialog()
$form.Dispose()
When pressing the Test button on this form, colored lines are written
Hope that helps

Transparent background of Winform in powershell

Here I have a form with a PictureBox in it. I removed borders and now I want to make the background of the form transparent, so when I launch the script we just see the image nothing else.
I am making a splash screen kind of project with an unusual png shape. I tried the "TransperancyKey = Color" thing from .Net but it doesn't work. I want it to run in PowerShell.
# Importing Assembly for Windows Forms
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# Main form/SplashScreen Object
$SScreen = New-Object system.Windows.Forms.Form
$SScreen.BackColor = [System.Drawing.Color]::FromArgb(255,0,0,0)
#$SScreen.BackColor = Color.Lime
$SScreen.StartPosition = 1
$SScreen.FormBorderStyle = 0
$img = [System.Drawing.Image]::Fromfile('./1.png')
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $img.Size.Width
$pictureBox.Height = $img.Size.Height
$pictureBox.Image = $img
$SScreen.controls.add($pictureBox)
$SScreen.Width = $pictureBox.Width
$SScreen.Height = $pictureBox.Height
# Open the main form
Start-Process -FilePath "C:\Windows\system32\WindowsPowerShell\v1.0\powershell_ise.exe"
$SScreen.TopMost = $true
$SScreen.Show()
Start-Sleep -seconds 5
$SScreen.Close()```
You can do this without adding a picturebox to the form and simply use the forms own BackgroundImage property for this.
Make sure your image has transparency of course.
For this, I took your image and made all black transparent.
Because I copied from the web page, it won't be as crisp as yours, but it's the idea that counts.
Try:
# Importing Assembly for Windows Forms
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$img = [System.Drawing.Image]::Fromfile('./1.png')
# Main form/SplashScreen Object
$SScreen = New-Object system.Windows.Forms.Form
$SScreen.Width = $img.Width
$SScreen.Height = $img.Height
$SScreen.TopMost = $true
$SScreen.BackgroundImage = $img
$SScreen.AllowTransparency = $true
$SScreen.TransparencyKey = $SScreen.BackColor
$SScreen.StartPosition = 1
$SScreen.FormBorderStyle = 0
# Open the main form
Start-Process -FilePath "C:\Windows\system32\WindowsPowerShell\v1.0\powershell_ise.exe"
$SScreen.Show()
Start-Sleep -Seconds 5
$SScreen.Close()
# tell Windows it can be removed from memory
$SScreen.Dispose()
Result:

Powershell MultiColored text on single line AND Centered

I have a powershell function that I need to be able to change. The function centers text in the terminal however, I need to be able to output multiple colors for text on a single line. If I do -NoNewLine and do more Write-host to change the color... then it still calculates the width of the terminal and still adds as much padding as it would without me adding -NoNewLine. Essentially I want my text centered and I want to be able to use multiple colors. With what I have I can only do 1 color per line.
function WriteCentered([String] $text, $color = $null)
{
$width = [int](Get-Host).UI.RawUI.BufferSize.Width
$twidth = [int]$text.Length
$offset = ($width / 2) - ($twidth / 2)
$newText = $text.PadLeft($offset + $twidth)
if($color)
{
Write-Host $newText -ForegroundColor $color
}
else
{
Write-Host $newText
}
}
I have added more IF conditions, I have changed my padding calculations, I am having trouble with getting it just right.
The PowerShell-Module PSWriteColor already does a good job in outputting multiple colors on a single line. Either you download it from GitHub directly and import it with Import-Module <PATH-TO>\PSWriteColor.psd1 or you install it from the PowerShell Gallery directly with Install-Module -Name PSWriteColor.
The syntax in short is Write-Color -Text "GreenText","RedText","BlueText" -Color Green,Red,Blue. So we need to prepend the [String[]]$Text argument with a string containing the necessary whitespace in order to center the message on the screen and prepend a color to the [ConsoleColor[]]$Color argument accordingly.
Here's a little helper function for centering.
#Requires -Modules #{ ModuleName="PSWriteColor"; ModuleVersion="0.8.5" }
function WriteColor-Centered {
param(
[Parameter(Mandatory=$true)][string[]]$Text,
[Parameter(Mandatory=$true)][ConsoleColor[]]$Color
)
$messageLength = 0
$Text | ForEach-Object { $messageLength += $_.Length }
[String[]] $centeredText = "{0}" -f (' ' * (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Floor($messageLength / 2))))
$centeredText += $Text
[ConsoleColor[]]$OutColor = #([ConsoleColor]::White)
$OutColor += $Color
Write-Color -Text $centeredText -Color $OutColor
# Alt.: use WriteColor-Core, see below
# WriteColor-Core -Text $centeredText -Color $OutColor
}
I copied the whitespace calculation from this stackoverflow answer.
EDIT: I was being asked if it's possible to make this work without importing the module. To be honest I feel a little dirty now because I went into source code of a well-written module stripped all the functionality and error handling from it and pasted it here.
Well anyways - if you replace the invocation of Write-Color in the wrapper function above and invoke the following WriteColor-Core instead you can dispense with loading the PSWriteColor module.
function WriteColor-Core {
param(
[Parameter(Mandatory=$true)][string[]]$Text,
[Parameter(Mandatory=$true)][ConsoleColor[]]$Color
)
# Fallback defaults if one of the values isn't set
$LastForegroundColor = [console]::ForegroundColor
# The real deal coloring
for ($i = 0; $i -lt $Text.Count; $i++) {
$CurrentFGColor = if ($Color[$i]) { $Color[$i] } else { $LastForegroundColor }
$WriteParams = #{
NoNewLine = $true
ForegroundColor = $CurrentFGColor
}
Write-Host $Text[$i] #WriteParams
# Store last color set, in case next iteration doesn't have a set color
$LastForegroundColor = $CurrentFGColor
}
Write-Host
}

Windows Forms - Randomize BackColor - Powershell

I'm making a GUI using Windows Forms and I'd like to add a feature where you can randomize the background color of the form. Please be noted that I intend to keep the code fully written in Powershell.
Here's a snippet of the code:
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$choochoo = New-Object System.Windows.Forms.Button
And here is the code that I need help with:
$choochoo.Add_Click({$objForm.BackColor = Random})
Obviously 'Random' does not work in this example and is only being used as a placeholder.
I suppose I need to generate random values using the time but would appreciate a more concrete code. Thanks in advance.
Been learning Powershell recently. Playing around came up with a solution for this today if anyone wants to do all possible random colors (I know this thread is old).
Add-Type -AssemblyName System.Windows.Forms
$Form = New-Object system.Windows.Forms.Form
$Form.Text = "Sample Form"
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "This is a Form."
$Form.Controls.Add($Label)
function colorful{
$c1 = '';
$c2 = '';
foreach( $digit in (1..6 | foreach{ '{0:X}' -f (Get-Random -Max 16) })){
$c1 = "$c1"+"$digit";}
foreach( $digit in (1..6 | foreach{ '{0:X}' -f (Get-Random -Max 16) })){
$c2 = "$c2"+"$digit";}
$Form.BackColor = "#FF"+$c1;
$Form.ForeColor = "#FF"+$c2;
};
colorful;
$Form.ShowDialog();
You'll want to be using an instance of System.Random to select from the possible static properties of System.Drawing.Color.
Got it to work with this if someone needs this too:
function RandomBacklight {`
$random = New-Object System.Random
switch ($random.Next(9)) {
0 {$objForm.BackColor = "LightBlue"}
1 {$objForm.BackColor = "LightGreen"}
2 {$objForm.BackColor = "LightPink"}
3 {$objForm.BackColor = "Yellow"}
4 {$objForm.BackColor = "Orange"}
5 {$objForm.BackColor = "Brown"}
6 {$objForm.BackColor = "Magenta"}
7 {$objForm.BackColor = "White"}
8 {$objForm.BackColor = "Gray"}
}
}
$choochoo.Add_Click({RandomBacklight})

Resources