param( [string]$Root = (Split-Path -Parent $PSScriptRoot) ) $ErrorActionPreference = "Stop" Add-Type -AssemblyName System.Drawing $sourceDir = Join-Path $Root "screenshots" $outputDir = Join-Path $Root "store-assets\ready-to-upload" New-Item -ItemType Directory -Force -Path $outputDir | Out-Null $purpleDark = [System.Drawing.ColorTranslator]::FromHtml("#281052") $purple = [System.Drawing.ColorTranslator]::FromHtml("#6D35D8") $purpleBright = [System.Drawing.ColorTranslator]::FromHtml("#8250E8") $white = [System.Drawing.Color]::White $ink = [System.Drawing.ColorTranslator]::FromHtml("#171429") function New-Canvas([int]$Width, [int]$Height) { $bitmap = New-Object System.Drawing.Bitmap($Width, $Height, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb) $bitmap.SetResolution(96, 96) return $bitmap } function Save-Png($Bitmap, [string]$Name) { $path = Join-Path $outputDir $Name $Bitmap.Save($path, [System.Drawing.Imaging.ImageFormat]::Png) return $path } function Set-Quality($Graphics) { $Graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality $Graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic $Graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality $Graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::ClearTypeGridFit } function Draw-Cover($Graphics, $Image, [System.Drawing.Rectangle]$Destination, [System.Drawing.Rectangle]$Source) { $sourceRatio = $Source.Width / $Source.Height $destinationRatio = $Destination.Width / $Destination.Height if ($sourceRatio -gt $destinationRatio) { $cropWidth = [int]($Source.Height * $destinationRatio) $Source.X += [int](($Source.Width - $cropWidth) / 2) $Source.Width = $cropWidth } else { $cropHeight = [int]($Source.Width / $destinationRatio) $Source.Y += [int](($Source.Height - $cropHeight) / 2) $Source.Height = $cropHeight } $Graphics.DrawImage($Image, $Destination, $Source, [System.Drawing.GraphicsUnit]::Pixel) } function Draw-Header($Graphics, [string]$Title, [string]$Subtitle) { $brush = New-Object System.Drawing.Drawing2D.LinearGradientBrush( (New-Object System.Drawing.Rectangle(0, 0, 1280, 112)), $purpleBright, $purpleDark, 0 ) $titleFont = New-Object System.Drawing.Font("Segoe UI", 27, [System.Drawing.FontStyle]::Bold) $subtitleFont = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Regular) try { $Graphics.FillRectangle($brush, 0, 0, 1280, 112) $Graphics.DrawString($Title, $titleFont, [System.Drawing.Brushes]::White, 38, 20) $Graphics.DrawString($Subtitle, $subtitleFont, [System.Drawing.Brushes]::White, 40, 67) } finally { $brush.Dispose(); $titleFont.Dispose(); $subtitleFont.Dispose() } } function Redact-SandboxName($Graphics) { $cover = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White) try { $Graphics.FillRectangle($cover, 128, 124, 150, 34) } finally { $cover.Dispose() } } function Build-Screenshot([string]$Name, [string]$SourceName, [string]$Title, [string]$Subtitle, [System.Drawing.Rectangle]$Crop, [bool]$Redact) { $source = [System.Drawing.Image]::FromFile((Join-Path $sourceDir $SourceName)) $canvas = New-Canvas 1280 800 $graphics = [System.Drawing.Graphics]::FromImage($canvas) try { Set-Quality $graphics Draw-Header $graphics $Title $Subtitle Draw-Cover $graphics $source (New-Object System.Drawing.Rectangle(0, 112, 1280, 688)) $Crop if ($Redact) { Redact-SandboxName $graphics } Save-Png $canvas $Name | Out-Null } finally { $graphics.Dispose(); $canvas.Dispose(); $source.Dispose() } } $viewportCrop = New-Object System.Drawing.Rectangle(0, 100, 1920, 1040) Build-Screenshot "01-original-canvas-page.png" "original.png" "A normal Canvas lesson page" "Global and course navigation surround the lesson." $viewportCrop $true Build-Screenshot "02-lildra-focused-reading.png" "lildra.png" "A calmer reading view" "Lildra hides surrounding navigation and centres the lesson." $viewportCrop $false Build-Screenshot "03-sticky-dynamic-title.png" "lildra.png" "The lesson title stays visible" "A compact title bar follows the current Canvas page title." (New-Object System.Drawing.Rectangle(170, 100, 1580, 850)) $false Build-Screenshot "04-original-content-preserved.png" "lildra.png" "Original lesson content remains" "Headings, text and images remain in the original page." (New-Object System.Drawing.Rectangle(270, 240, 1500, 850)) $false $before = [System.Drawing.Image]::FromFile((Join-Path $sourceDir "original.png")) $after = [System.Drawing.Image]::FromFile((Join-Path $sourceDir "lildra.png")) $comparison = New-Canvas 1280 800 $graphics = [System.Drawing.Graphics]::FromImage($comparison) try { Set-Quality $graphics Draw-Header $graphics "Before and after" "The same lesson, with surrounding Canvas navigation hidden." Draw-Cover $graphics $before (New-Object System.Drawing.Rectangle(0, 112, 632, 688)) (New-Object System.Drawing.Rectangle(0, 100, 1050, 1040)) Draw-Cover $graphics $after (New-Object System.Drawing.Rectangle(648, 112, 632, 688)) (New-Object System.Drawing.Rectangle(250, 100, 1050, 1040)) $divider = New-Object System.Drawing.SolidBrush($purple) $labelFont = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Bold) try { $graphics.FillRectangle($divider, 632, 112, 16, 688) $graphics.FillRectangle([System.Drawing.Brushes]::White, 18, 130, 92, 34) $graphics.FillRectangle([System.Drawing.Brushes]::White, 666, 130, 92, 34) $graphics.DrawString("Before", $labelFont, (New-Object System.Drawing.SolidBrush($ink)), 28, 134) $graphics.DrawString("Lildra", $labelFont, (New-Object System.Drawing.SolidBrush($purple)), 676, 134) # Remove the private sandbox course name from the before panel. $graphics.FillRectangle([System.Drawing.Brushes]::White, 102, 124, 145, 36) } finally { $divider.Dispose(); $labelFont.Dispose() } Save-Png $comparison "05-before-and-after.png" | Out-Null } finally { $graphics.Dispose(); $comparison.Dispose(); $before.Dispose(); $after.Dispose() } # Create a store icon candidate from the established logo. Only edge-connected # near-white canvas pixels are removed; the white L inside the purple mark remains. $originalIcon = [System.Drawing.Image]::FromFile((Join-Path $Root "icons\LildraLogoHiRes.png")) $sourceIcon = New-Canvas $originalIcon.Width $originalIcon.Height $sourceGraphics = [System.Drawing.Graphics]::FromImage($sourceIcon) try { $sourceGraphics.DrawImageUnscaled($originalIcon, 0, 0) } finally { $sourceGraphics.Dispose(); $originalIcon.Dispose() } $visited = New-Object 'bool[,]' $sourceIcon.Width, $sourceIcon.Height $queue = New-Object 'System.Collections.Generic.Queue[System.Drawing.Point]' for ($x = 0; $x -lt $sourceIcon.Width; $x++) { $queue.Enqueue([System.Drawing.Point]::new($x, 0)) $queue.Enqueue([System.Drawing.Point]::new($x, ($sourceIcon.Height - 1))) } for ($y = 0; $y -lt $sourceIcon.Height; $y++) { $queue.Enqueue([System.Drawing.Point]::new(0, $y)) $queue.Enqueue([System.Drawing.Point]::new(($sourceIcon.Width - 1), $y)) } while ($queue.Count -gt 0) { $point = $queue.Dequeue() if ($point.X -lt 0 -or $point.Y -lt 0 -or $point.X -ge $sourceIcon.Width -or $point.Y -ge $sourceIcon.Height -or $visited[$point.X, $point.Y]) { continue } $visited[$point.X, $point.Y] = $true $pixel = $sourceIcon.GetPixel($point.X, $point.Y) if ($pixel.R -lt 215 -or $pixel.G -lt 215 -or $pixel.B -lt 215) { continue } $sourceIcon.SetPixel($point.X, $point.Y, [System.Drawing.Color]::Transparent) $queue.Enqueue([System.Drawing.Point]::new(($point.X + 1), $point.Y)) $queue.Enqueue([System.Drawing.Point]::new(($point.X - 1), $point.Y)) $queue.Enqueue([System.Drawing.Point]::new($point.X, ($point.Y + 1))) $queue.Enqueue([System.Drawing.Point]::new($point.X, ($point.Y - 1))) } $storeIcon = New-Canvas 128 128 $graphics = [System.Drawing.Graphics]::FromImage($storeIcon) try { Set-Quality $graphics $graphics.Clear([System.Drawing.Color]::Transparent) $graphics.DrawImage($sourceIcon, (New-Object System.Drawing.Rectangle(16, 16, 96, 96))) Save-Png $storeIcon "store-icon-128.png" | Out-Null } finally { $graphics.Dispose(); $storeIcon.Dispose(); $sourceIcon.Dispose() } $icon = [System.Drawing.Image]::FromFile((Join-Path $outputDir "store-icon-128.png")) $focused = [System.Drawing.Image]::FromFile((Join-Path $sourceDir "lildra.png")) foreach ($promo in @( @{Name="small-promo-440x280.png"; Width=440; Height=280; Icon=112; Screenshot=$false}, @{Name="marquee-promo-1400x560.png"; Width=1400; Height=560; Icon=170; Screenshot=$true} )) { $canvas = New-Canvas $promo.Width $promo.Height $graphics = [System.Drawing.Graphics]::FromImage($canvas) $gradient = New-Object System.Drawing.Drawing2D.LinearGradientBrush( (New-Object System.Drawing.Rectangle(0, 0, $promo.Width, $promo.Height)), $purpleBright, $purpleDark, 12 ) $nameFont = New-Object System.Drawing.Font("Segoe UI", $(if($promo.Width -gt 500){58}else{34}), [System.Drawing.FontStyle]::Bold) $tagFont = New-Object System.Drawing.Font("Segoe UI", $(if($promo.Width -gt 500){24}else{15}), [System.Drawing.FontStyle]::Regular) try { Set-Quality $graphics $graphics.FillRectangle($gradient, 0, 0, $promo.Width, $promo.Height) $iconX = $(if($promo.Width -gt 500){80}else{34}) $iconY = [int](($promo.Height - $promo.Icon) / 2) $graphics.DrawImage($icon, $iconX, $iconY, $promo.Icon, $promo.Icon) $textX = $iconX + $promo.Icon + $(if($promo.Width -gt 500){42}else{22}) $graphics.DrawString("Lildra", $nameFont, [System.Drawing.Brushes]::White, $textX, $(if($promo.Width -gt 500){170}else{105})) if ($promo.Width -gt 500) { $graphics.DrawString("Distraction-free lesson reading", $tagFont, [System.Drawing.Brushes]::White, $textX + 2, 250) } if ($promo.Screenshot) { $panel = New-Object System.Drawing.Rectangle(785, 55, 570, 450) Draw-Cover $graphics $focused $panel $viewportCrop } Save-Png $canvas $promo.Name | Out-Null } finally { $gradient.Dispose(); $nameFont.Dispose(); $tagFont.Dispose(); $graphics.Dispose(); $canvas.Dispose() } } $icon.Dispose(); $focused.Dispose() Get-ChildItem -LiteralPath $outputDir -File | Sort-Object Name | Select-Object Name, Length