Created
April 29, 2018 21:01
-
-
Save LOZORD/cd30f2e1885fb22f431b0dae4349f8fe to your computer and use it in GitHub Desktop.
Palette-clamping (PNG) image translator. I call it petanque.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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