From e7a825dced3ed2c227f9c58831fd949e66c444c5 Mon Sep 17 00:00:00 2001
From: tso <tso@teknik.io>
Date: Sun, 11 Jun 2023 23:19:51 -0400
Subject: [PATCH] Add CSS4() and JSON() methods to Palette.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - coming back to this project after 7 (SEVEN) years! what a rush!
 - some background: I originally made this port from Java so I could use
   it in the production of another project that has been one false start
   after the next since 2009. I'm back on my BS and trying it one more timeة
   anyway after having to use this library in my own code I realized it
   wasnt' outputting what I want/needed to use it. I needed JSON to
   store it in the DB, CSS4 vars to use in the UI and 16 bit colors as
   integers to do image fills with the image package in stdlib
 - this comes up short of an ideal solution however and isn't as
 flexible as it should be yet but it's one small step in that direction
 - feedback and contributions welcome!!!
 - happy hacking, -Dave, from NJ Sun 11 Jun 2023 11:24:03 PM EDT
---
 palette.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 webapp.go  |  22 +++++++-----
 2 files changed, 117 insertions(+), 8 deletions(-)

diff --git a/palette.go b/palette.go
index 2d0c9a0..e6bf568 100644
--- a/palette.go
+++ b/palette.go
@@ -1,9 +1,11 @@
 package vibrant
 
 import (
+	"encoding/json"
 	"errors"
 	"image"
 	"math"
+	"strings"
 )
 
 // These constants are taken directly from the Android Palette source code,
@@ -36,6 +38,107 @@ type Palette struct {
 	selected          []*Swatch
 }
 
+// CSS4
+//
+// Export as CSS4 variables. Use in your CSS with var()
+// e.g. html { background-color: var(--vibrant); }
+//
+// FIXME(day): variable names not final
+// FIXME(day): variable names not final
+// FIXME(day): variable names not final
+// --vibrant:#123456;
+// --vibrantText:#ffffff;
+// --darkvibrant:#123456;
+// --darkvibrantText:#ffffff;
+// --lightvibrant:#123456;
+// --lightvibrantText:#ffffff;
+// --muted:#123456;
+// --mutedText:#ffffff;
+// --darkmuted:#123456;
+// --darkmutedText:#ffffff;
+// --lightmuted:#123456;
+// --lightmutedText:#ffffff;
+func (p *Palette) CSS4() string {
+	out := ""
+	for k, sw := range p.ExtractAwesome() {
+		// @consistency these variable names should match JSON output
+		// @consistency these variable names should match JSON output
+		// @consistency these variable names should match JSON output
+		out += "--" + strings.ToLower(k) + ":" + sw.Color.String() + ";"
+		out += "--" + strings.ToLower(k) + "Text:" + sw.Color.TitleTextColor().String() + ";"
+	}
+	return out
+}
+
+// JSON
+//
+// err should always be nil unless something catastrophic is happening.
+//
+// JSON structure:
+//
+//	{
+//		"vibrant": 0x123456,
+//		"dark-vibrant": 0x123456,
+//		"light-vibrant": 0x123456,
+//		"muted": 0x123456,
+//		"dark-muted": 0x123456,
+//		"light-muted": 0x123456,
+//		"vibrant-text": 0x123456,
+//		"dark-vibrant-text": 0x123456,
+//		"light-vibrant-text": 0x123456,
+//		"muted-text": 0x123456,
+//		"dark-muted-text": 0x123456,
+//		"light-muted-text": 0x123456
+//	}
+//
+// Please note that encoding/json encodes hex numbers as decimal integers
+// Please note that encoding/json encodes hex numbers as decimal integers
+// Please note that encoding/json encodes hex numbers as decimal integers
+func (p *Palette) JSON() ([]byte, error) {
+	// NOTE(day): As I type this I realize a more elegant data structure might look like
+	// {
+	//		"vibrant": {"color": 0x99ccff, "text": 0xffffff},
+	//		"dark-vibrant": {"color": 0x00007f, "text": 0xffffff},
+	//		...
+	// }
+	// also using PascalCase camelCase or snake_case instead of kebab-case
+	// might yield a more pleasurable user experience.
+	// this library was designed for CSS webdev stuff (that's why vibrant.Color.String() returns #abcdef oh. Oh. I just realized something...
+	// -day Sun 11 Jun 2023 10:28:06 PM EDT
+	// email me@dayvonjersen if you disagree =)
+	// -day Sun 11 Jun 2023 09:57:57 PM EDT
+	type vibrantJSON struct {
+		Vibrant          int `json:"vibrant"`
+		DarkVibrant      int `json:"dark-vibrant"`
+		LightVibrant     int `json:"light-vibrant"`
+		Muted            int `json:"muted"`
+		DarkMuted        int `json:"dark-muted"`
+		LightMuted       int `json:"light-muted"`
+		VibrantText      int `json:"vibrant-text"`
+		DarkVibrantText  int `json:"dark-vibrant-text"`
+		LightVibrantText int `json:"light-vibrant-text"`
+		MutedText        int `json:"muted-text"`
+		DarkMutedText    int `json:"dark-muted-text"`
+		LightMutedText   int `json:"light-muted-text"`
+	}
+	this := p.ExtractAwesome()
+	choose := func(sw *Swatch, defaultColor int, defaultText int) (primary, text int) {
+		if sw == nil {
+			return defaultColor, defaultText
+		}
+		return int(sw.Color), int(sw.Color.TitleTextColor())
+	}
+	// NOTE(day): sensible fallback colors (greyscale)
+	data := &vibrantJSON{}
+	data.Vibrant, data.VibrantText = choose(this["Vibrant"], 0xacaaaa, 0x0)
+	data.LightVibrant, data.LightVibrantText = choose(this["LightVibrant"], 0xffffff, 0x0)
+	data.DarkVibrant, data.DarkVibrantText = choose(this["DarkVibrant"], 0x2b2b2b, 0xffffff)
+	data.LightMuted, data.LightMutedText = choose(this["LightMuted"], 0xdad5d5, 0x0)
+	data.DarkMuted, data.DarkMutedText = choose(this["DarkMuted"], 0x32312f, 0xffffff)
+	data.Muted, data.MutedText = choose(this["Muted"], 0x6d6a6a, 0xffffff)
+	return json.Marshal(data)
+}
+
 // Calls NewPalette with DEFAULT_CALCULATE_NUMBER_COLORS as a default value for numColors.
 func NewPaletteFromImage(img image.Image) (Palette, error) {
 	return NewPalette(img, DEFAULT_CALCULATE_NUMBER_COLORS)
diff --git a/webapp.go b/webapp.go
index 4a5fe67..2b1fc0e 100644
--- a/webapp.go
+++ b/webapp.go
@@ -1,12 +1,13 @@
+//go:build ignore
 // +build ignore
 
 /*
-	sample webapp to demo the functionality of github.com/dayvonjersen/vibrant
+sample webapp to demo the functionality of github.com/dayvonjersen/vibrant
 
-	base64 encoding the images provides the benefit of not having to manage
-	uploaded files or do any javascript fanciness but comes with a performance penalty
+base64 encoding the images provides the benefit of not having to manage
+uploaded files or do any javascript fanciness but comes with a performance penalty
 
-	DO NOT USE THIS IN A PRODUCTION ENVIRONMENT
+DO NOT USE THIS IN A PRODUCTION ENVIRONMENT
 */
 package main
 
@@ -17,6 +18,7 @@ import (
 	"io"
 	"log"
 	"net/http"
+	"sorbet/vibrant"
 	"strconv"
 	"time"
 
@@ -25,8 +27,6 @@ import (
 	_ "image/png"
 
 	"text/template"
-
-	"github.com/dayvonjersen/vibrant"
 )
 
 func index(w http.ResponseWriter, r *http.Request) {
@@ -56,6 +56,7 @@ func index(w http.ResponseWriter, r *http.Request) {
 		Benchmark        time.Duration
 		DefaultMaxColors int
 		MaxColors        int
+		JSON, CSS4       string
 	}{DefaultMaxColors: vibrant.DEFAULT_CALCULATE_NUMBER_COLORS,
 		MaxColors: vibrant.DEFAULT_CALCULATE_NUMBER_COLORS}
 
@@ -133,6 +134,9 @@ func index(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	data.Stylesheet = stylesheet
+	j, _ := palette.JSON()
+	data.JSON = string(j)
+	data.CSS4 = palette.CSS4()
 	data.Missing = len(awesome) != 6
 	setStatus(200)
 	data.Response = true
@@ -141,8 +145,8 @@ func index(w http.ResponseWriter, r *http.Request) {
 
 func main() {
 	http.HandleFunc("/", index)
-	log.Println("Listening on :8080...")
-	log.Fatalln(http.ListenAndServe(":8080", nil))
+	log.Println("Listening on :9999...")
+	log.Fatalln(http.ListenAndServe(":9999", nil))
 }
 
 const tpl = `
@@ -264,6 +268,8 @@ h2 {
             </figcaption>
         </figure>
         <textarea readonly onclick='this.select()'>{{.Stylesheet}}</textarea>
+        <textarea readonly onclick='this.select()'>{{.JSON}}</textarea>
+        <textarea readonly onclick='this.select()'>{{.CSS4}}</textarea>
         {{ if .Missing }}
         <h3>If color swatches are missing, try increasing <code>maxColors</code> in the text field above.</h3>
         {{ end }}