Skip to content

Instantly share code, notes, and snippets.

@LOZORD
Created April 29, 2018 21:01
Show Gist options
  • Save LOZORD/cd30f2e1885fb22f431b0dae4349f8fe to your computer and use it in GitHub Desktop.
Save LOZORD/cd30f2e1885fb22f431b0dae4349f8fe to your computer and use it in GitHub Desktop.
Palette-clamping (PNG) image translator. I call it petanque.
package main
// Usage:
// ./petanque -img=/path/to/input.png -colors="ABCDEF,123456,00F001"
import (
"flag"
"fmt"
"image"
"image/color"
"image/png"
_ "image/png"
"log"
"math"
"os"
"path/filepath"
"strconv"
"strings"
)
const (
opaque = math.MaxUint8
)
var (
imageFlag = flag.String("img", "", "The filepath of original PNG image.")
colorsFlag = flag.String("colors", "",
"A comma separated list of CSS hex values. Each must be six digits long. No pound or 0x prefix. Example: 'ABC123,DEF456,FF0077'")
)
func main() {
flag.Parse()
if err := doMain(*imageFlag, *colorsFlag); err != nil {
log.Fatalf("fatal error: %v", err)
}
}
func doMain(origPath, colors string) error {
origFile, err := os.Open(origPath)
if err != nil {
return fmt.Errorf("failed to open %q: %v", origPath, err)
}
defer origFile.Close()
origImg, format, err := image.Decode(origFile)
if err != nil {
return fmt.Errorf("failed to decode %q: %v", origPath, err)
}
if strings.ToLower(format) != "png" {
return fmt.Errorf("expected format to be png, was %q", format)
}
outImg := image.NewRGBA64(origImg.Bounds())
palette, err := parsePalette(colors)
if err != nil {
return fmt.Errorf("failed to parse palette: %v", err)
}
translate(origImg, outImg, palette)
base := filepath.Base(origPath)
outPath := fmt.Sprintf("%s_petanque.png", strings.TrimSuffix(base, filepath.Ext(base)))
log.Printf("writing image to %q", outPath)
outFile, err := os.Create(outPath)
if err != nil {
return fmt.Errorf("failed to open %q: %v", outPath, err)
}
defer outFile.Close()
if err := png.Encode(outFile, outImg); err != nil {
return fmt.Errorf("failed to encode png image to %q: %v", outPath, err)
}
log.Print("done")
return nil
}
func parsePalette(colors string) (color.Palette, error) {
var cs []color.Color
for _, c := range strings.Split(colors, ",") {
c = strings.TrimSpace(c)
if len(c) != 6 {
return nil, fmt.Errorf("expected %q to have a length of 6", c)
}
r, err := strconv.ParseUint(c[0:2], 16, 8)
if err != nil {
return nil, fmt.Errorf("failed to parse R component %q from %q: %v", c[0:2], c, err)
}
g, err := strconv.ParseUint(c[2:4], 16, 8)
if err != nil {
return nil, fmt.Errorf("failed to parse R component %q from %q: %v", c[2:4], c, err)
}
b, err := strconv.ParseUint(c[4:6], 16, 8)
if err != nil {
return nil, fmt.Errorf("failed to parse R component %q from %q: %v", c[4:6], c, err)
}
cs = append(cs, color.NRGBA{
R: uint8(r),
G: uint8(g),
B: uint8(b),
A: opaque,
})
}
if len(cs) < 2 {
return nil, fmt.Errorf("need at least two colors in the palette, got %d", len(cs))
}
log.Printf("got palette: %v", cs)
return color.Palette(cs), nil
}
func translate(in image.Image, out *image.RGBA64, p color.Palette) {
for x := in.Bounds().Min.X; x < in.Bounds().Max.X; x++ {
for y := in.Bounds().Min.Y; y < in.Bounds().Max.Y; y++ {
// TODO: Make the input pixel fully opaque.
c := p.Convert(in.At(x, y))
out.Set(x, y, c)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment