Browse Source

sample: extract a palette from the first input image

pull/15/head
Lukas Schwab 3 years ago
parent
commit
fdd5b6a01c
  1. 1
      go.mod
  2. 3
      go.sum
  3. 34
      subcommand_helpers.go
  4. 67
      subcommands.go

1
go.mod

@ -5,6 +5,7 @@ go 1.16
require ( require (
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/makeworld-the-better-one/dither/v2 v2.3.0 github.com/makeworld-the-better-one/dither/v2 v2.3.0
github.com/mccutchen/palettor v1.0.0 // indirect
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
) )

3
go.sum

@ -7,6 +7,9 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/makeworld-the-better-one/dither/v2 v2.3.0 h1:s9wgm88KFZSzvZh9gL79tPayp5sDUGIku/1aJewxlB4= github.com/makeworld-the-better-one/dither/v2 v2.3.0 h1:s9wgm88KFZSzvZh9gL79tPayp5sDUGIku/1aJewxlB4=
github.com/makeworld-the-better-one/dither/v2 v2.3.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc= github.com/makeworld-the-better-one/dither/v2 v2.3.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
github.com/mccutchen/palettor v1.0.0 h1:YRNAzEZlRBnu8qP/9siuNTJiAj7VhL0dEQ1AQmu9jew=
github.com/mccutchen/palettor v1.0.0/go.mod h1:5ZFq9YwI0o5zRpmAuEsm+0B7divaVds1dvTAznEnd6g=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=

34
subcommand_helpers.go

@ -9,6 +9,7 @@ import (
"image/gif" "image/gif"
"image/png" "image/png"
"io" "io"
"log"
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
@ -17,6 +18,7 @@ import (
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/makeworld-the-better-one/dither/v2" "github.com/makeworld-the-better-one/dither/v2"
"github.com/mccutchen/palettor"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/image/colornames" "golang.org/x/image/colornames"
) )
@ -49,7 +51,9 @@ func parsePercentArg(arg string, maxOne bool) (float64, error) {
// globalFlag returns the value of flag at the top level of the command. // globalFlag returns the value of flag at the top level of the command.
// For example, with the command: // For example, with the command:
// dither --threads 1 edm -s Simple2D //
// dither --threads 1 edm -s Simple2D
//
// "threads" is a global flag, and "s" is a flag local to the edm subcommand. // "threads" is a global flag, and "s" is a flag local to the edm subcommand.
func globalFlag(flag string, c *cli.Context) interface{} { func globalFlag(flag string, c *cli.Context) interface{} {
ancestor := c.Lineage()[len(c.Lineage())-1] ancestor := c.Lineage()[len(c.Lineage())-1]
@ -132,10 +136,38 @@ func rgbaToColor(s string) (color.NRGBA, error) {
return color.NRGBA{r, g, b, a}, nil return color.NRGBA{r, g, b, a}, nil
} }
// extractInputPalette extracts a 5-color palette from the first input image
// using palettor.
func extractInputPalette(flag string, c *cli.Context) ([]color.Color, error) {
img, err := getInputImage(inputImages[0], c)
if err != nil {
return nil, fmt.Errorf("error loading image for palette extraction '%v': %w", inputImages, err)
}
// Resize: keep palettor.Extract fast. See the palettor CLI source:
// https://github.com/mccutchen/palettor/blob/3eaed180/cmd/palettor/palettor.go#L57
thumbnail := imaging.Resize(img, 200, 200, imaging.NearestNeighbor)
// TODO: make these settings configurable, particularly the number of colors
// in the palette. That means threading the argument through the CLI.
palette, err := palettor.Extract(5, 500, thumbnail)
if err != nil {
return nil, fmt.Errorf("error extracting image palette: %w", err)
}
log.Printf("Extracted palette: %v", palette.Colors())
return palette.Colors(), nil
}
// parseColors takes args and turns them into a color slice. All returned // parseColors takes args and turns them into a color slice. All returned
// colors are guaranteed to only be color.NRGBA. // colors are guaranteed to only be color.NRGBA.
func parseColors(flag string, c *cli.Context) ([]color.Color, error) { func parseColors(flag string, c *cli.Context) ([]color.Color, error) {
args := parseArgs([]string{globalFlag(flag, c).(string)}, " ") args := parseArgs([]string{globalFlag(flag, c).(string)}, " ")
if len(args) == 1 && args[0] == "sample" {
return extractInputPalette(flag, c)
}
colors := make([]color.Color, len(args)) colors := make([]color.Color, len(args))
for i, arg := range args { for i, arg := range args {

67
subcommands.go

@ -71,39 +71,6 @@ func preProcess(c *cli.Context) error {
runtime.GOMAXPROCS(int(c.Uint("threads"))) runtime.GOMAXPROCS(int(c.Uint("threads")))
var err error var err error
palette, err = parseColors("palette", c)
if err != nil {
return err
}
if len(palette) < 2 {
return errors.New("the palette must have at least two colors")
}
if c.String("recolor") != "" {
recolorPalette, err = parseColors("recolor", c)
if err != nil {
return err
}
if len(recolorPalette) != len(palette) {
return errors.New("recolor palette must have the same number of colors as the initial palette")
}
}
// Check if palette is grayscale and make image grayscale
// Or if the user forces it
grayscale = true
if !c.Bool("grayscale") {
// Grayscale isn't specified by the user
// So check to see if palette is grayscale
for _, c := range palette {
r, g, b, _ := c.RGBA()
if r != g || g != b {
grayscale = false
break
}
}
}
saturation, err = parsePercentArg(c.String("saturation"), false) saturation, err = parsePercentArg(c.String("saturation"), false)
if err != nil { if err != nil {
@ -138,6 +105,40 @@ func preProcess(c *cli.Context) error {
} }
} }
palette, err = parseColors("palette", c)
if err != nil {
return err
}
if len(palette) < 2 {
return errors.New("the palette must have at least two colors")
}
if c.String("recolor") != "" {
recolorPalette, err = parseColors("recolor", c)
if err != nil {
return err
}
if len(recolorPalette) != len(palette) {
return errors.New("recolor palette must have the same number of colors as the initial palette")
}
}
// Check if palette is grayscale and make image grayscale
// Or if the user forces it
grayscale = true
if !c.Bool("grayscale") {
// Grayscale isn't specified by the user
// So check to see if palette is grayscale
for _, c := range palette {
r, g, b, _ := c.RGBA()
if r != g || g != b {
grayscale = false
break
}
}
}
formatVal := c.String("format") formatVal := c.String("format")
if formatVal != "png" && formatVal != "gif" { if formatVal != "png" && formatVal != "gif" {
return fmt.Errorf(unsupportedFormat, formatVal) return fmt.Errorf(unsupportedFormat, formatVal)

Loading…
Cancel
Save