shiny/iconvg: implement suggested palettes.

Change-Id: I5bd5f0d1c0c5c83be0216eb149c87fbce595d7ab
Reviewed-on: https://go-review.googlesource.com/31374
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/iconvg/color.go b/shiny/iconvg/color.go
index f8ff941..b2fa7d3 100644
--- a/shiny/iconvg/color.go
+++ b/shiny/iconvg/color.go
@@ -84,7 +84,8 @@
 //
 // To blend a Color that is not encodable as a 1 byte color, first load that
 // Color into a CREG color register, then call CRegColor to produce a Color
-// that is encodable as a 1 byte color.
+// that is encodable as a 1 byte color. See testdata/favicon.ivg for an
+// example.
 //
 // See the "Colors" section in the package documentation for details.
 func BlendColor(t, c0, c1 uint8) Color { return Color{ColorTypeBlend, color.RGBA{R: t, G: c0, B: c1}} }
diff --git a/shiny/iconvg/decode_test.go b/shiny/iconvg/decode_test.go
index 520abb7..fae6ca7 100644
--- a/shiny/iconvg/decode_test.go
+++ b/shiny/iconvg/decode_test.go
@@ -136,7 +136,7 @@
 	{"testdata/blank", ""},
 	{"testdata/cowbell", ""},
 	{"testdata/elliptical", ""},
-	{"testdata/favicon", ""},
+	{"testdata/favicon", ";pink"},
 	{"testdata/gradient", ""},
 	{"testdata/lod-polygon", ";64"},
 	{"testdata/video-005.primitive", ""},
@@ -198,10 +198,17 @@
 				height = int(float32(length) * dy / dx)
 			}
 
+			opts := &DecodeOptions{}
+			if variant == "pink" {
+				pal := DefaultPalette
+				pal[0] = color.RGBA{0xfe, 0x76, 0xea, 0xff}
+				opts.Palette = &pal
+			}
+
 			got := image.NewRGBA(image.Rect(0, 0, width, height))
 			var z Rasterizer
 			z.SetDstImage(got, got.Bounds(), draw.Src)
-			if err := Decode(&z, ivgData, nil); err != nil {
+			if err := Decode(&z, ivgData, opts); err != nil {
 				t.Errorf("%s %q variant: Decode: %v", tc.filename, variant, err)
 				continue
 			}
diff --git a/shiny/iconvg/encode.go b/shiny/iconvg/encode.go
index ead030a..c9c3ac9 100644
--- a/shiny/iconvg/encode.go
+++ b/shiny/iconvg/encode.go
@@ -119,7 +119,53 @@
 	}
 
 	if mcSuggestedPalette {
-		panic("TODO: encode mcSuggestedPalette")
+		n := 63
+		for ; n >= 0 && m.Palette[n] == (color.RGBA{0x00, 0x00, 0x00, 0xff}); n-- {
+		}
+
+		// Find the shortest encoding that can represent all of m.Palette's n+1
+		// explicit colors.
+		enc1, enc2, enc3 := true, true, true
+		for _, c := range m.Palette[:n+1] {
+			if enc1 && (!is1(c.R) || !is1(c.G) || !is1(c.B) || !is1(c.A)) {
+				enc1 = false
+			}
+			if enc2 && (!is2(c.R) || !is2(c.G) || !is2(c.B) || !is2(c.A)) {
+				enc2 = false
+			}
+			if enc3 && (c.A != 0xff) {
+				enc3 = false
+			}
+		}
+
+		e.altBuf = e.altBuf[:0]
+		e.altBuf.encodeNatural(midSuggestedPalette)
+		if enc1 {
+			e.altBuf = append(e.altBuf, byte(n)|0x00)
+			for _, c := range m.Palette[:n+1] {
+				x, _ := encodeColor1(RGBAColor(c))
+				e.altBuf = append(e.altBuf, x)
+			}
+		} else if enc2 {
+			e.altBuf = append(e.altBuf, byte(n)|0x40)
+			for _, c := range m.Palette[:n+1] {
+				x, _ := encodeColor2(RGBAColor(c))
+				e.altBuf = append(e.altBuf, x[0], x[1])
+			}
+		} else if enc3 {
+			e.altBuf = append(e.altBuf, byte(n)|0x80)
+			for _, c := range m.Palette[:n+1] {
+				e.altBuf = append(e.altBuf, c.R, c.G, c.B)
+			}
+		} else {
+			e.altBuf = append(e.altBuf, byte(n)|0xc0)
+			for _, c := range m.Palette[:n+1] {
+				e.altBuf = append(e.altBuf, c.R, c.G, c.B, c.A)
+			}
+		}
+
+		e.buf.encodeNatural(uint32(len(e.altBuf)))
+		e.buf = append(e.buf, e.altBuf...)
 	}
 }
 
diff --git a/shiny/iconvg/encode_test.go b/shiny/iconvg/encode_test.go
index 94f3e0f..71489b3 100644
--- a/shiny/iconvg/encode_test.go
+++ b/shiny/iconvg/encode_test.go
@@ -351,11 +351,37 @@
 }}
 
 func TestEncodeFavicon(t *testing.T) {
-	var e Encoder
+	// Set up a base color for theming the favicon, gopher blue by default.
+	pal := DefaultPalette
+	pal[0] = faviconColors[0] // color.RGBA{0x76, 0xe1, 0xfe, 0xff}
 
-	for i, c := range faviconColors[:2] {
-		e.SetCReg(uint8(i), false, RGBAColor(c))
+	var e Encoder
+	e.Reset(Metadata{
+		ViewBox: DefaultViewBox,
+		Palette: pal,
+	})
+
+	// The favicon graphic also uses a dark version of that base color. blend
+	// is 75% dark (CReg[63]) and 25% the base color (pal[0]).
+	dark := color.RGBA{0x23, 0x1d, 0x1b, 0xff}
+	blend := BlendColor(0x40, 0xff, 0x80)
+
+	// First, set CReg[63] to dark, then set CReg[63] to the blend of that dark
+	// color with pal[0].
+	e.SetCReg(1, false, RGBAColor(dark))
+	e.SetCReg(1, false, blend)
+
+	// Check that, for the suggested palette, blend resolves to the
+	// (non-themable) SVG file's faviconColors[1].
+	got := blend.Resolve(&pal, &[64]color.RGBA{
+		63: dark,
+	})
+	want := faviconColors[1]
+	if got != want {
+		t.Fatalf("Blend:\ngot  %#02x\nwant %#02x", got, want)
 	}
+
+	// Set aside the remaining, non-themable colors.
 	remainingColors := faviconColors[2:]
 
 	seenFCI2 := false
diff --git a/shiny/iconvg/testdata/README b/shiny/iconvg/testdata/README
index 21c5ee6..22b9579 100644
--- a/shiny/iconvg/testdata/README
+++ b/shiny/iconvg/testdata/README
@@ -58,7 +58,7 @@
 
 favicon.ivg.disassembly is a disassembly of that IconVG file.
 
-favicon.png is a rendering of that IconVG file.
+favicon.png and favicon.pink.png are renderings of that IconVG file.
 
 
 
diff --git a/shiny/iconvg/testdata/favicon.ivg b/shiny/iconvg/testdata/favicon.ivg
index afe10eb..68ad0e9 100644
--- a/shiny/iconvg/testdata/favicon.ivg
+++ b/shiny/iconvg/testdata/favicon.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/favicon.ivg.disassembly b/shiny/iconvg/testdata/favicon.ivg.disassembly
index 28fca9d..c655193 100644
--- a/shiny/iconvg/testdata/favicon.ivg.disassembly
+++ b/shiny/iconvg/testdata/favicon.ivg.disassembly
@@ -1,9 +1,15 @@
 89 49 56 47   IconVG Magic identifier
-00            Number of metadata chunks: 0
-90            Set CREG[CSEL-0] to a 3 byte (direct) color
+02            Number of metadata chunks: 1
+0a            Metadata chunk length: 5
+02            Metadata Identifier: 1 (suggested palette)
+80                1 palette colors, 3 bytes per color
 76 e1 fe          RGBA 76e1feff
 91            Set CREG[CSEL-1] to a 3 byte (direct) color
-38 4e 54          RGBA 384e54ff
+23 1d 1b          RGBA 231d1bff
+a1            Set CREG[CSEL-1] to a 3 byte (indirect) color
+40                blend 191:64 c0:c1
+ff                    c0: CREG[63]
+80                    c1: customPalette[0]
 c1            Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
 31 80             +0.1875
 44                -30
diff --git a/shiny/iconvg/testdata/favicon.pink.png b/shiny/iconvg/testdata/favicon.pink.png
new file mode 100644
index 0000000..862c538
--- /dev/null
+++ b/shiny/iconvg/testdata/favicon.pink.png
Binary files differ