# ============================================================
# File: wallpaper_text.ps1
# Fully universal version (PS5 + PS7 + .NET 9 safe)
# ============================================================
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
Write-Host "⚙️ Starting wallpaper generator..." -ForegroundColor Cyan
# ------------------------------------------------------------
# LOAD SYSTEM.DRAWING
# ------------------------------------------------------------
try {
Add-Type -AssemblyName System.Drawing -ErrorAction Stop
}
catch {
Add-Type -AssemblyName System.Drawing.Common
}
# ------------------------------------------------------------
# PARAMETERS
# ------------------------------------------------------------
$MARGIN_RATIO = 0.90
$BASE_FONT_SIZE = 150
$ANGLE = -7
$TEXT_FILE = "wallpaper.txt"
$FONT_PATH = "C:\Windows\Fonts\segoeui.ttf"
$LINE_SPACING = 1.0
$BgColorRaw = "10,70,90"
$TextColorRaw = "100,150,165"
function Convert-RGB([string]$rgb) {
$p = $rgb.Split(",")
[System.Drawing.Color]::FromArgb(255, [int]$p[0], [int]$p[1], [int]$p[2])
}
$BG_COLOR = Convert-RGB $BgColorRaw
$TEXT_COLOR = Convert-RGB $TextColorRaw
# ------------------------------------------------------------
# CREATE DEFAULT TEXT FILE IF MISSING
# ------------------------------------------------------------
if (-not (Test-Path -LiteralPath $TEXT_FILE)) {
"terminal-new" | Set-Content -LiteralPath $TEXT_FILE -Encoding UTF8
}
# ------------------------------------------------------------
# READ + NORMALIZE
# ------------------------------------------------------------
function Read-NormalizedUtf8([string]$path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
$txt = [System.Text.Encoding]::UTF8.GetString($bytes)
$txt = $txt.Replace("`r`n","`n").Replace("`r","`n")
[System.IO.File]::WriteAllText($path, $txt, [System.Text.UTF8Encoding]::new($false))
return $txt
}
$textContent = Read-NormalizedUtf8 $TEXT_FILE
$lines = $textContent.Split("`n") |
ForEach-Object { $_.Trim() } |
Where-Object { $_ -ne "" }
$TEXT_MODE = $true
if (-not $lines) {
Write-Host "ℹ️ No text found — background only mode." -ForegroundColor Yellow
$TEXT_MODE = $false
}
# ------------------------------------------------------------
# SAFE OUTPUT NAME
# ------------------------------------------------------------
$joined = $lines -join " "
$safe = ($joined -replace '[<>:"/\\|?*]', '').Trim()
if ($safe.Length -gt 80) { $safe = $safe.Substring(0,80) }
if (-not $safe) { $safe = "background" }
# ------------------------------------------------------------
# FAST BOUNDING BOX (LOCKBITS VERSION)
# ------------------------------------------------------------
function Get-BoundingBoxAlpha($bmp) {
$rect = New-Object System.Drawing.Rectangle(0,0,$bmp.Width,$bmp.Height)
$data = $bmp.LockBits(
$rect,
[System.Drawing.Imaging.ImageLockMode]::ReadOnly,
[System.Drawing.Imaging.PixelFormat]::Format32bppArgb
)
$minX = [int]::MaxValue
$minY = [int]::MaxValue
$maxX = -1
$maxY = -1
$hasContent = $false
try {
$bytes = New-Object byte[] ($data.Stride * $bmp.Height)
[System.Runtime.InteropServices.Marshal]::Copy(
$data.Scan0,
$bytes,
0,
$bytes.Length
)
for ($y = 0; $y -lt $bmp.Height; $y++) {
for ($x = 0; $x -lt $bmp.Width; $x++) {
$index = $y * $data.Stride + $x * 4
$alpha = $bytes[$index + 3]
if ($alpha -ne 0) {
if ($x -lt $minX) { $minX = $x }
if ($x -gt $maxX) { $maxX = $x }
if ($y -lt $minY) { $minY = $y }
if ($y -gt $maxY) { $maxY = $y }
$hasContent = $true
}
}
}
}
finally {
$bmp.UnlockBits($data)
}
return [PSCustomObject]@{
MinX = $minX
MinY = $minY
MaxX = $maxX
MaxY = $maxY
HasContent = $hasContent
}
}
# ------------------------------------------------------------
# ORIENTATIONS
# ------------------------------------------------------------
$orientations = @(
@{ Name = "landscape"; Width = 1920; Height = 1080 },
@{ Name = "portrait"; Width = 1080; Height = 1920 }
)
# ------------------------------------------------------------
# MAIN LOOP
# ------------------------------------------------------------
foreach ($o in $orientations) {
Write-Host ""
Write-Host "===============================" -ForegroundColor Yellow
Write-Host " Generating $($o.Name)..."
Write-Host "==============================="
$WIDTH = $o.Width
$HEIGHT = $o.Height
if (-not $TEXT_MODE) {
$bitmap = New-Object System.Drawing.Bitmap(
$WIDTH,$HEIGHT,[System.Drawing.Imaging.PixelFormat]::Format24bppRgb
)
$gfx = [System.Drawing.Graphics]::FromImage($bitmap)
$gfx.Clear($BG_COLOR)
$OUTPUT_FILE = "${safe}_$($o.Name).png"
$bitmap.Save($OUTPUT_FILE,[System.Drawing.Imaging.ImageFormat]::Png)
Write-Host "✔ Generated (background only): $OUTPUT_FILE" -ForegroundColor Green
$gfx.Dispose()
$bitmap.Dispose()
continue
}
$SAFE_WIDTH = if ($o.Name -eq "landscape") { 1440 } else { $WIDTH }
Write-Host "SAFE_WIDTH = $SAFE_WIDTH"
function New-ItalicFont($size) {
New-Object System.Drawing.Font($FONT_PATH,$size,[System.Drawing.FontStyle]::Italic)
}
# PASS 1
$fontSize = $BASE_FONT_SIZE
$tmpBmp = New-Object System.Drawing.Bitmap(100,100)
$tmpGfx = [System.Drawing.Graphics]::FromImage($tmpBmp)
while ($fontSize -ge 20) {
$f = New-ItalicFont $fontSize
$maxW = ($lines | ForEach-Object { $tmpGfx.MeasureString($_,$f).Width } |
Measure-Object -Maximum).Maximum
if ($maxW -le ($SAFE_WIDTH * $MARGIN_RATIO)) { $f.Dispose(); break }
$f.Dispose()
$fontSize -= 5
}
$tmpGfx.Dispose()
$tmpBmp.Dispose()
Write-Host "Pre-rotation font guess: $fontSize px"
# PASS 2
while ($true) {
$font = New-ItalicFont $fontSize
$textLayer = New-Object System.Drawing.Bitmap(
$WIDTH,$HEIGHT,[System.Drawing.Imaging.PixelFormat]::Format32bppArgb
)
$tg = [System.Drawing.Graphics]::FromImage($textLayer)
$tg.SmoothingMode = "AntiAlias"
$tg.TextRenderingHint = "ClearTypeGridFit"
$tg.Clear([System.Drawing.Color]::Transparent)
$lineHeight = [int]($tg.MeasureString("A",$font).Height)
$totalHeight = [int]($lineHeight * $lines.Count * $LINE_SPACING)
$y = ($HEIGHT - $totalHeight)/2
$brush = New-Object System.Drawing.SolidBrush($TEXT_COLOR)
foreach ($line in $lines) {
$sz = $tg.MeasureString($line,$font)
$x = ($WIDTH - $sz.Width)/2
$tg.DrawString($line,$font,$brush,$x,$y)
$y += [int]($lineHeight * $LINE_SPACING)
}
$rotated = New-Object System.Drawing.Bitmap(
$WIDTH,$HEIGHT,[System.Drawing.Imaging.PixelFormat]::Format32bppArgb
)
$rg = [System.Drawing.Graphics]::FromImage($rotated)
$mx = New-Object System.Drawing.Drawing2D.Matrix
$mx.RotateAt($ANGLE,[System.Drawing.PointF]::new($WIDTH/2,$HEIGHT/2))
$rg.Transform = $mx
$rg.DrawImage($textLayer,0,0)
$rg.ResetTransform()
$box = Get-BoundingBoxAlpha $rotated
if ($box.HasContent) {
$rotW = $box.MaxX - $box.MinX
$rotH = $box.MaxY - $box.MinY
if (
$rotW -le ($WIDTH * $MARGIN_RATIO) -and
$rotH -le ($HEIGHT * $MARGIN_RATIO)
) {
Write-Host "Final font size: $fontSize px"
break
}
}
$brush.Dispose()
$font.Dispose()
$tg.Dispose()
$textLayer.Dispose()
$rg.Dispose()
$rotated.Dispose()
$fontSize -= 5
if ($fontSize -lt 20) {
Write-Host "❌ Cannot scale text to fit!" -ForegroundColor Red
break
}
}
# FINAL DRAW
$bitmap = New-Object System.Drawing.Bitmap(
$WIDTH,$HEIGHT,[System.Drawing.Imaging.PixelFormat]::Format24bppRgb
)
$gfx = [System.Drawing.Graphics]::FromImage($bitmap)
$gfx.Clear($BG_COLOR)
$offX = ($WIDTH - ($box.MaxX - $box.MinX)) / 2 - $box.MinX
$offY = ($HEIGHT - ($box.MaxY - $box.MinY)) / 2 - $box.MinY
$gfx.DrawImage($rotated,$offX,$offY)
$OUTPUT_FILE = "${safe}_$($o.Name).png"
$bitmap.Save($OUTPUT_FILE,[System.Drawing.Imaging.ImageFormat]::Png)
Write-Host "✔ Generated: $OUTPUT_FILE" -ForegroundColor Green
$brush.Dispose()
$font.Dispose()
$gfx.Dispose()
$bitmap.Dispose()
$tg.Dispose()
$textLayer.Dispose()
$rg.Dispose()
$rotated.Dispose()
}
Write-Host "🎉 All wallpapers generated!"