icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

oracle_windows_test.go (4818B)


      1 //go:build windows
      2 
      3 package ico
      4 
      5 // Windows decides what an ico file may contain, so these tests hand it files
      6 // this package wrote and read back a file it wrote itself. They run wherever
      7 // Windows PowerShell is present, which covers the Windows CI runner.
      8 
      9 import (
     10 	"image"
     11 	"image/color"
     12 	"os"
     13 	"os/exec"
     14 	"path/filepath"
     15 	"strconv"
     16 	"strings"
     17 	"testing"
     18 )
     19 
     20 // readFrames lists every frame the Windows Imaging Component finds, as
     21 // "width height r g b a". This is the decoder behind Explorer's preview.
     22 const readFrames = `
     23 param([string]$Path)
     24 Add-Type -AssemblyName PresentationCore
     25 $stream = [System.IO.File]::OpenRead($Path)
     26 $decoder = [System.Windows.Media.Imaging.BitmapDecoder]::Create($stream, 'None', 'OnLoad')
     27 foreach ($frame in $decoder.Frames) {
     28     $converted = New-Object System.Windows.Media.Imaging.FormatConvertedBitmap($frame, [System.Windows.Media.PixelFormats]::Bgra32, $null, 0)
     29     $pixel = New-Object byte[] 4
     30     $at = New-Object System.Windows.Int32Rect ([int]($converted.PixelWidth/2)), ([int]($converted.PixelHeight/2)), 1, 1
     31     $converted.CopyPixels($at, $pixel, 4, 0)
     32     "{0} {1} {2} {3} {4} {5}" -f $converted.PixelWidth, $converted.PixelHeight, $pixel[2], $pixel[1], $pixel[0], $pixel[3]
     33 }
     34 $stream.Close()
     35 `
     36 
     37 // writeIcon has Windows write an ico file of one solid colour.
     38 const writeIcon = `
     39 param([string]$Path, [int]$Size, [int]$R, [int]$G, [int]$B)
     40 Add-Type -AssemblyName System.Drawing
     41 $bitmap = New-Object System.Drawing.Bitmap($Size, $Size, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
     42 $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
     43 $graphics.Clear([System.Drawing.Color]::FromArgb(255, $R, $G, $B))
     44 $graphics.Dispose()
     45 $icon = [System.Drawing.Icon]::FromHandle($bitmap.GetHicon())
     46 $stream = [System.IO.File]::Create($Path)
     47 $icon.Save($stream)
     48 $stream.Close()
     49 `
     50 
     51 // powershell runs a script and returns what it printed, failing the test
     52 // with whatever it wrote if it did not run.
     53 func powershell(t *testing.T, script string, args ...string) string {
     54 	t.Helper()
     55 	path := filepath.Join(t.TempDir(), "oracle.ps1")
     56 	if err := os.WriteFile(path, []byte(script), 0o600); err != nil {
     57 		t.Fatal(err)
     58 	}
     59 	args = append([]string{"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path}, args...)
     60 	out, err := exec.Command("powershell.exe", args...).CombinedOutput()
     61 	if err != nil {
     62 		t.Fatalf("powershell %v: %v\n%s", args, err, out)
     63 	}
     64 	return string(out)
     65 }
     66 
     67 // TestOracleWindowsReads checks that Windows finds every icon this package
     68 // writes, at the size and colour it was given.
     69 func TestOracleWindowsReads(t *testing.T) {
     70 	var (
     71 		images = map[uint]image.Image{}
     72 		want   = map[int]color.NRGBA{}
     73 	)
     74 	for i, size := range Sizes() {
     75 		c := color.NRGBA{R: uint8(20 + i*30), G: uint8(200 - i*20), B: 0x40, A: 0xFF}
     76 		images[size] = solid(int(size), c)
     77 		want[int(size)] = c
     78 	}
     79 	path := filepath.Join(t.TempDir(), "icon.ico")
     80 	f, err := os.Create(path)
     81 	if err != nil {
     82 		t.Fatal(err)
     83 	}
     84 	if err := NewEncoder(f).EncodeSizes(images); err != nil {
     85 		f.Close()
     86 		t.Fatal(err)
     87 	}
     88 	if err := f.Close(); err != nil {
     89 		t.Fatal(err)
     90 	}
     91 
     92 	out := powershell(t, readFrames, "-Path", path)
     93 	seen := map[int]bool{}
     94 	for _, line := range strings.Split(out, "\n") {
     95 		fields := strings.Fields(line)
     96 		if len(fields) != 6 {
     97 			continue
     98 		}
     99 		n := make([]int, 6)
    100 		for i, field := range fields {
    101 			if n[i], err = strconv.Atoi(field); err != nil {
    102 				t.Fatalf("the oracle printed %q", line)
    103 			}
    104 		}
    105 		width, height := n[0], n[1]
    106 		if width != height {
    107 			t.Errorf("Windows read a %dx%d icon, which is not square", width, height)
    108 		}
    109 		got := color.NRGBA{R: uint8(n[2]), G: uint8(n[3]), B: uint8(n[4]), A: uint8(n[5])}
    110 		if got != want[width] {
    111 			t.Errorf("Windows read the %d pixel icon as %v, want %v", width, got, want[width])
    112 		}
    113 		seen[width] = true
    114 	}
    115 	for size := range want {
    116 		if !seen[size] {
    117 			t.Errorf("Windows did not read the %d pixel icon at all:\n%s", size, out)
    118 		}
    119 	}
    120 }
    121 
    122 // TestOracleWindowsWrites decodes an icon Windows wrote. It writes them
    123 // indexed, so this covers the colour table against a real writer.
    124 func TestOracleWindowsWrites(t *testing.T) {
    125 	const size = 48
    126 	// Red is in the palette Windows picks, so the colour survives exactly.
    127 	want := color.NRGBA{R: 0xFF, A: 0xFF}
    128 	path := filepath.Join(t.TempDir(), "windows.ico")
    129 	powershell(t, writeIcon,
    130 		"-Path", path,
    131 		"-Size", strconv.Itoa(size),
    132 		"-R", strconv.Itoa(int(want.R)),
    133 		"-G", strconv.Itoa(int(want.G)),
    134 		"-B", strconv.Itoa(int(want.B)),
    135 	)
    136 	f, err := os.Open(path)
    137 	if err != nil {
    138 		t.Fatal(err)
    139 	}
    140 	defer f.Close()
    141 	img, err := Decode(f)
    142 	if err != nil {
    143 		t.Fatal(err)
    144 	}
    145 	if got := img.Bounds().Dx(); got != size {
    146 		t.Errorf("decoded a %d pixel icon, want %d", got, size)
    147 	}
    148 	if got := centre(img); got != want {
    149 		t.Errorf("centre = %v, want %v", got, want)
    150 	}
    151 }