Created
March 26, 2025 16:36
-
-
Save ThioJoe/4dcc7dd7802727f5a0e3e3211454953d to your computer and use it in GitHub Desktop.
Split an image into four quadrants and center each new image based on the non-transparent pixels
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Params block must be the very first executable statement | |
param( | |
# Made Mandatory=$false so we can prompt if not provided | |
[Parameter(Mandatory=$false, HelpMessage="Enter the path to the folder containing PNG images.")] | |
[string]$FolderPath | |
) | |
#Requires -Modules PowerShellGet | |
#Requires -Version 5.1 | |
# Ensure System.Drawing assembly is loaded (usually automatic in PS 7+, needed in WinPS 5.1) | |
try { | |
Add-Type -AssemblyName System.Drawing -ErrorAction Stop | |
} catch { | |
Write-Error "Failed to load System.Drawing assembly. Ensure .NET Framework/Desktop Runtime is installed." | |
return | |
} | |
# --- Script Configuration --- | |
$OutputSubFolderName = "outfolder" | |
# Alpha value STRICTLY GREATER than this will be considered non-transparent | |
# 63 corresponds to approx 25% opacity (255 * 0.25 = 63.75) | |
$AlphaThreshold = 63 | |
# --- Get and Validate Input Folder --- | |
# Check if FolderPath was provided, if not, prompt the user | |
if ([string]::IsNullOrWhiteSpace($FolderPath)) { | |
Write-Host "Folder path parameter was not provided." | |
while ([string]::IsNullOrWhiteSpace($FolderPath)) { | |
$FolderPath = Read-Host -Prompt "Please enter the path to the folder containing PNG images" | |
if ([string]::IsNullOrWhiteSpace($FolderPath)) { | |
Write-Warning "Path cannot be empty. Please try again." | |
} | |
} | |
} | |
# Strip leading/trailing quotes (single or double) from the path | |
$FolderPath = $FolderPath -replace '^["'']|["'']$' | |
# If path is empty after stripping quotes, it's invalid | |
if ([string]::IsNullOrWhiteSpace($FolderPath)) { | |
Write-Error "Invalid folder path provided (empty after removing quotes)." | |
return | |
} | |
# If the path is not rooted (doesn't start with drive/UNC), assume it's relative to the current directory | |
if (-not ([System.IO.Path]::IsPathRooted($FolderPath))) { | |
Write-Verbose "Path '$FolderPath' is not rooted. Assuming it's relative to the current working directory: '$($PWD.Path)'" | |
$FolderPath = Join-Path -Path $PWD.Path -ChildPath $FolderPath | |
} | |
# Resolve the path to get a full, standardized path and check existence/type | |
try { | |
$resolvedPathItem = Resolve-Path $FolderPath -ErrorAction Stop | |
if (-not (Test-Path $resolvedPathItem.ProviderPath -PathType Container)) { | |
# Test-Path is sufficient, Get-Item was redundant if Test-Path succeeds | |
Write-Error "Path exists but is not a directory: '$($resolvedPathItem.ProviderPath)'" | |
return | |
} | |
# Use the fully qualified, resolved path | |
$FolderPath = $resolvedPathItem.ProviderPath | |
Write-Verbose "Using resolved folder path: $FolderPath" | |
} catch { | |
Write-Error "Folder not found or path is invalid: '$FolderPath'. Error: $($_.Exception.Message)" | |
return | |
} | |
# --- Create Output Folder --- | |
$OutFolderPath = Join-Path -Path $FolderPath -ChildPath $OutputSubFolderName | |
if (-not (Test-Path $OutFolderPath -PathType Container)) { # Check if it exists and is a directory | |
try { | |
New-Item -Path $OutFolderPath -ItemType Directory -ErrorAction Stop | Out-Null | |
Write-Host "Created output directory: $OutFolderPath" | |
} catch { | |
Write-Error "Failed to create output directory: '$OutFolderPath'. Check permissions. Error: $($_.Exception.Message)" | |
return | |
} | |
} else { | |
Write-Host "Output directory already exists: $OutFolderPath" | |
} | |
# --- Find PNG Files --- | |
$pngFiles = Get-ChildItem -Path $FolderPath -Filter "*.png" -File -ErrorAction SilentlyContinue | |
if (-not $pngFiles) { | |
Write-Warning "No PNG files found in '$FolderPath'." | |
return | |
} | |
Write-Host "Found $($pngFiles.Count) PNG files. Starting processing..." | |
# --- Process Each PNG File --- | |
foreach ($file in $pngFiles) { | |
Write-Host "Processing $($file.FullName)..." | |
# Pre-declare variables for finally block | |
$sourceImage = $null | |
$quadrantBitmap = $null | |
$graphicsQ = $null # Graphics for drawing quadrant | |
$finalQuadrantImage = $null | |
$graphicsF = $null # Graphics for drawing final centered image | |
try { | |
# Load the source image | |
$sourceImage = [System.Drawing.Bitmap]::new($file.FullName) | |
# Calculate dimensions for quadrants | |
$width = $sourceImage.Width | |
$height = $sourceImage.Height | |
$q1_Width = [Math]::Ceiling($width / 2) | |
$q1_Height = [Math]::Ceiling($height / 2) | |
$q2_Width = $width - $q1_Width | |
$q4_Height = $height - $q1_Height | |
# Define the source rectangles for the four quadrants | |
$quadrantRects = @{ | |
'q1' = [System.Drawing.Rectangle]::new(0, 0, $q1_Width, $q1_Height) # Top-Left | |
'q2' = [System.Drawing.Rectangle]::new($q1_Width, 0, $q2_Width, $q1_Height) # Top-Right | |
'q3' = [System.Drawing.Rectangle]::new(0, $q1_Height, $q1_Width, $q4_Height) # Bottom-Left | |
'q4' = [System.Drawing.Rectangle]::new($q1_Width, $q1_Height, $q2_Width, $q4_Height) # Bottom-Right | |
} | |
# Process each quadrant | |
foreach ($quadKey in $quadrantRects.Keys) { | |
$quadrantSourceRect = $quadrantRects[$quadKey] # Area from original image | |
$quadrantWidth = $quadrantSourceRect.Width | |
$quadrantHeight = $quadrantSourceRect.Height | |
if ($quadrantWidth -le 0 -or $quadrantHeight -le 0) { | |
Write-Host " Skipping $quadKey - Zero dimension calculated." | |
continue | |
} | |
# 1. Create a temporary bitmap holding only the quadrant's pixels | |
$quadrantBitmap = [System.Drawing.Bitmap]::new($quadrantWidth, $quadrantHeight, $sourceImage.PixelFormat) | |
$graphicsQ = [System.Drawing.Graphics]::FromImage($quadrantBitmap) | |
$graphicsQ.Clear([System.Drawing.Color]::Transparent) | |
$graphicsQ.DrawImage( | |
$sourceImage, | |
[System.Drawing.Rectangle]::new(0, 0, $quadrantWidth, $quadrantHeight), | |
$quadrantSourceRect, | |
[System.Drawing.GraphicsUnit]::Pixel | |
) | |
$graphicsQ.Dispose(); $graphicsQ = $null | |
# 2. Find the bounds of the non-transparent pixels (Alpha > $AlphaThreshold) | |
$minX = $quadrantWidth | |
$minY = $quadrantHeight | |
$maxX = -1 | |
$maxY = -1 | |
$foundContent = $false | |
# Consider using LockBits for performance on large images if needed | |
for ($y = 0; $y -lt $quadrantHeight; $y++) { | |
for ($x = 0; $x -lt $quadrantWidth; $x++) { | |
if ($quadrantBitmap.GetPixel($x, $y).A -gt $AlphaThreshold) { | |
$minX = [Math]::Min($minX, $x) | |
$minY = [Math]::Min($minY, $y) | |
$maxX = [Math]::Max($maxX, $x) | |
$maxY = [Math]::Max($maxY, $y) | |
$foundContent = $true | |
} | |
} | |
} | |
# 3. If non-transparent content was found, prepare the final centered image | |
if ($foundContent) { | |
# Calculate dimensions of the actual content based on the threshold | |
$contentWidth = $maxX - $minX + 1 | |
$contentHeight = $maxY - $minY + 1 | |
# Define the rectangle covering the content within the quadrantBitmap | |
$contentSourceRect = [System.Drawing.Rectangle]::new($minX, $minY, $contentWidth, $contentHeight) | |
# Create the final output image, same size as the quadrant, initially transparent | |
$finalQuadrantImage = [System.Drawing.Bitmap]::new($quadrantWidth, $quadrantHeight, $quadrantBitmap.PixelFormat) | |
$graphicsF = [System.Drawing.Graphics]::FromImage($finalQuadrantImage) | |
$graphicsF.Clear([System.Drawing.Color]::Transparent) | |
# Calculate top-left position to draw the content so it's centered | |
$targetX = [Math]::Floor(($quadrantWidth - $contentWidth) / 2) | |
$targetY = [Math]::Floor(($quadrantHeight - $contentHeight) / 2) | |
# Define the destination rectangle where content will be drawn in the final image | |
$contentDestRect = [System.Drawing.Rectangle]::new($targetX, $targetY, $contentWidth, $contentHeight) | |
# Draw the identified content from the quadrant bitmap onto the final image, centered | |
$graphicsF.DrawImage( | |
$quadrantBitmap, # Source Image (the temp quadrant bitmap) | |
$contentDestRect, # Destination rectangle (centered position in final image) | |
$contentSourceRect, # Source rectangle (the content bounds in temp bitmap) | |
[System.Drawing.GraphicsUnit]::Pixel | |
) | |
$graphicsF.Dispose(); $graphicsF = $null | |
# 4. Save the final centered image | |
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) | |
$outFileName = "{0}_{1}.png" -f $baseName, $quadKey | |
$outFilePath = Join-Path -Path $OutFolderPath -ChildPath $outFileName | |
try { | |
$finalQuadrantImage.Save($outFilePath, [System.Drawing.Imaging.ImageFormat]::Png) | |
Write-Host " Saved: $outFileName ($($quadrantWidth)x$($quadrantHeight))" | |
} catch { | |
Write-Warning " Failed to save '$outFileName'. Error: $($_.Exception.Message)" | |
} | |
if ($finalQuadrantImage -ne $null) { $finalQuadrantImage.Dispose(); $finalQuadrantImage = $null } | |
} else { | |
Write-Host " Skipped $quadKey - Quadrant has no pixels above $($AlphaThreshold) alpha." | |
} | |
if ($quadrantBitmap -ne $null) { $quadrantBitmap.Dispose(); $quadrantBitmap = $null } | |
} # End foreach quadrant | |
} catch { | |
Write-Warning "Error processing file '$($file.FullName)': $($_.Exception.Message)" | |
# Uncomment the next line for detailed debugging if needed | |
# Write-Warning "Stack Trace: $($_.ScriptStackTrace)" | |
} finally { | |
# Ensure disposal of all GDI+ objects in case of errors | |
if ($graphicsQ -ne $null) { try { $graphicsQ.Dispose() } catch { Write-Warning "Non-critical error disposing graphicsQ" } } | |
if ($graphicsF -ne $null) { try { $graphicsF.Dispose() } catch { Write-Warning "Non-critical error disposing graphicsF" } } | |
if ($quadrantBitmap -ne $null) { try { $quadrantBitmap.Dispose() } catch { Write-Warning "Non-critical error disposing quadrantBitmap" } } | |
if ($finalQuadrantImage -ne $null) { try { $finalQuadrantImage.Dispose() } catch { Write-Warning "Non-critical error disposing finalQuadrantImage" } } | |
if ($sourceImage -ne $null) { try { $sourceImage.Dispose() } catch { Write-Warning "Non-critical error disposing sourceImage" } } | |
} | |
} # End foreach file | |
Write-Host "Processing finished." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment