Skip to content

Instantly share code, notes, and snippets.

@ThioJoe
Created March 26, 2025 16:36
Show Gist options
  • Save ThioJoe/4dcc7dd7802727f5a0e3e3211454953d to your computer and use it in GitHub Desktop.
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
# 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