-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c36d035
commit e13f338
Showing
14 changed files
with
610 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
|
||
# Gopkg.toml example | ||
# | ||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md | ||
# for detailed Gopkg.toml documentation. | ||
# | ||
# required = ["github.com/user/thing/cmd/thing"] | ||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] | ||
# | ||
# [[constraint]] | ||
# name = "github.com/user/project" | ||
# version = "1.0.0" | ||
# | ||
# [[constraint]] | ||
# name = "github.com/user/project2" | ||
# branch = "dev" | ||
# source = "github.com/myfork/project2" | ||
# | ||
# [[override]] | ||
# name = "github.com/x/y" | ||
# version = "2.4.0" | ||
|
||
|
||
[[constraint]] | ||
branch = "master" | ||
name = "github.com/fogleman/primitive" | ||
|
||
[[constraint]] | ||
name = "github.com/tdewolff/minify" | ||
version = "2.3.3" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"log" | ||
"math/rand" | ||
"os" | ||
"runtime" | ||
"time" | ||
|
||
"github.com/denisbrodbeck/sqip" | ||
) | ||
|
||
var ( | ||
successExitCode = 0 | ||
errorExitCode = 1 | ||
errorParseExitCode = 2 | ||
) | ||
|
||
func usage() { | ||
log.Println(usageStr) | ||
os.Exit(errorParseExitCode) | ||
} | ||
|
||
func failOnErr(err error) { | ||
if err != nil { | ||
log.Println(err) | ||
os.Exit(errorExitCode) | ||
} | ||
} | ||
|
||
func main() { | ||
var outFile string | ||
var count int | ||
var help bool | ||
var mode int | ||
var alpha int | ||
var background string | ||
flag.StringVar(&outFile, "o", "", "") | ||
flag.StringVar(&outFile, "out", "", "") | ||
flag.IntVar(&count, "n", 8, "") | ||
flag.BoolVar(&help, "h", false, "") | ||
flag.BoolVar(&help, "help", false, "") | ||
flag.IntVar(&mode, "mode", 0, "") | ||
flag.IntVar(&alpha, "alpha", 128, "") | ||
flag.StringVar(&background, "bg", "", "") | ||
|
||
log.SetFlags(0) | ||
flag.Usage = usage | ||
flag.Parse() | ||
|
||
if help { | ||
usage() | ||
} | ||
|
||
if flag.NArg() != 1 { | ||
log.Println("Missing input file") | ||
usage() | ||
} | ||
|
||
inFile := flag.Arg(0) | ||
workers := runtime.NumCPU() | ||
workSize := 256 | ||
repeat := 0 | ||
|
||
// seed random number generator for primitive | ||
rand.Seed(time.Now().UTC().UnixNano()) | ||
|
||
svg, w, h, err := sqip.Run(inFile, workSize, count, mode, alpha, repeat, workers, background) | ||
failOnErr(err) | ||
|
||
if outFile == "" { | ||
fmt.Println(sqip.ImageTag(outFile, sqip.Base64(svg), w, h)) | ||
} else { | ||
err = sqip.SaveFile(outFile, svg) | ||
failOnErr(err) | ||
} | ||
} | ||
|
||
const usageStr = `sqip is a tool for SVG-based LQIP image creation | ||
Usage: sqip [-n <int>] [-o <path>] [options...] <file> | ||
Flags: | ||
-n <int> number of primitive SVG shapes (default: 8) | ||
-o <path> save the placeholder SVG to a file (default: empty) | ||
Options: | ||
-mode <int> shape type (default: 0) | ||
-alpha <int> color alpha (use 0 to let the algorithm choose alpha for each shape) (default: 128) | ||
-bg <hex> background color as hex (default: avg) | ||
If no output path is provided, an example <img> tag will be printed to stdout. | ||
Available shape types: 0=combo 1=triangle 2=rect 3=ellipse 4=circle 5=rotatedrect 6=beziers 7=rotatedellipse 8=polygon | ||
Try: | ||
sqip -n 12 path/to/image.jpg | ||
sqip -mode 8 -o ./image.svg path/to/image.jpg` |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package sqip | ||
|
||
import ( | ||
"bytes" | ||
"strings" | ||
|
||
"github.com/tdewolff/minify" | ||
"github.com/tdewolff/minify/svg" | ||
) | ||
|
||
// Minify takes a svg and returns a minified version of the input. | ||
func Minify(in string) (out string, err error) { | ||
reader := strings.NewReader(in) | ||
writer := bytes.NewBuffer(nil) | ||
|
||
min := minify.New() | ||
min.AddFunc("image/svg+xml", svg.Minify) | ||
|
||
if err := min.Minify("image/svg+xml", writer, reader); err != nil { | ||
return "", err | ||
} | ||
return writer.String(), nil | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package sqip | ||
|
||
import "testing" | ||
|
||
const rawSVG = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1920" height="1275"> | ||
<rect x="0" y="0" width="1920" height="1275" fill="#e8d800" /> | ||
<g transform="scale(7.500000) translate(0.5 0.5)"> | ||
<g transform="translate(216.415586 67.041899) rotate(264.185032) scale(12.544812 122.824888)"><ellipse fill="#594600" fill-opacity="0.501961" cx="0" cy="0" rx="1" ry="1" /></g> | ||
<ellipse fill="#7b6d00" fill-opacity="0.501961" cx="106" cy="45" rx="16" ry="41" /> | ||
<g transform="translate(40 110) rotate(239) scale(245 130)"><rect fill="#ffff00" fill-opacity="0.501961" x="-0.5" y="-0.5" width="1" height="1" /></g> | ||
<g transform="translate(248.773440 54.467579) rotate(169.943097) scale(47.964587 24.404783)"><ellipse fill="#7c6500" fill-opacity="0.501961" cx="0" cy="0" rx="1" ry="1" /></g> | ||
<polygon fill="#cfba00" fill-opacity="0.501961" points="123.498216,-16.000000,100.592494,58.210557,104.297909,77.905916,256.369922,49.227811" /> | ||
<g transform="translate(117 49) rotate(189) scale(68 2)"><rect fill="#000000" fill-opacity="0.501961" x="-0.5" y="-0.5" width="1" height="1" /></g> | ||
<polygon fill="#000000" fill-opacity="0.501961" points="265,76 246,67 211,78" /> | ||
<ellipse fill="#fffd00" fill-opacity="0.501961" cx="150" cy="150" rx="255" ry="26" /> | ||
</g> | ||
</svg>` | ||
|
||
func TestMinify(t *testing.T) { | ||
input := rawSVG | ||
output, err := Minify(input) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
// We can only test on shorter outputs, not on individual output comparison. | ||
// Package minify might make improvements to svg minification, | ||
// which in turn would change expected output. | ||
// Thus we only care, whether the minified output is shorter than the input. | ||
if len(output) >= len(input) { | ||
t.Errorf("Minify() result is not shorter than input.\noutput = %v\ninput = %v", output, input) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package sqip | ||
|
||
import ( | ||
"image" | ||
"log" | ||
|
||
"github.com/fogleman/primitive/primitive" | ||
"github.com/nfnt/resize" | ||
) | ||
|
||
func Primitive(input image.Image, workSize, outputSize, count, mode, alpha, repeat, workers int, background string) (svg string, err error) { | ||
// scale down input image if needed | ||
if workSize > 0 { | ||
input = resize.Thumbnail(uint(workSize), uint(workSize), input, resize.Bilinear) | ||
} | ||
|
||
// determine background color | ||
log.Println(background) | ||
var bg primitive.Color | ||
if background == "" { | ||
bg = primitive.MakeColor(primitive.AverageImageColor(input)) | ||
} else { | ||
bg = primitive.MakeHexColor(background) | ||
} | ||
|
||
// run algorithm | ||
model := primitive.NewModel(input, bg, outputSize, workers) | ||
for i := 1; i <= count; i++ { | ||
// find optimal shape and add it to the model | ||
model.Step(primitive.ShapeType(mode), alpha, repeat) | ||
} | ||
return model.SVG(), nil | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package sqip | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestPrimitive(t *testing.T) { | ||
img, err := LoadImage(testImage) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
w, h := ImageWidthAndHeight(img) | ||
outputSize := largerOne(w, h) | ||
svg, err := Primitive(img, 64, outputSize, 8, 0, 128, 0, 1) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if svg == "" { | ||
t.Error("Primitive() output is empty") | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package sqip | ||
|
||
// Run takes a file and primitve related config properties and creates a SVG-based LQIP image. | ||
func Run(file string, workSize, count, mode, alpha, repeat, workers int, background string) (out string, width, height int, err error) { | ||
// Load image | ||
image, err := LoadImage(file) | ||
if err != nil { | ||
return "", 0, 0, err | ||
} | ||
// Use image-size to retrieve the width and height dimensions of the input image | ||
// We need these sizes to pass to Primitive and to write the SVG viewbox | ||
w, h := ImageWidthAndHeight(image) | ||
// Since Primitive is only interested in the larger dimension of the input image, let's find it | ||
outputSize := largerOne(w, h) | ||
|
||
// create primitive | ||
svg, err := Primitive(image, workSize, outputSize, count, mode, alpha, repeat, workers, background) | ||
if err != nil { | ||
return "", 0, 0, err | ||
} | ||
|
||
// minify svg | ||
svg, err = Minify(svg) | ||
if err != nil { | ||
return "", 0, 0, err | ||
} | ||
|
||
// blur the svg | ||
svg, err = Blur(svg, w, h) | ||
if err != nil { | ||
return "", 0, 0, err | ||
} | ||
return svg, w, h, nil | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package sqip | ||
|
||
import "testing" | ||
|
||
func TestRun(t *testing.T) { | ||
svg, w, h, err := Run(testImage, 64, 4, 0, 128, 0, 1) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if w != 512 { | ||
t.Errorf("Run() width = %v, want = %v", w, 512) | ||
} | ||
if h != 341 { | ||
t.Errorf("Run() height = %v, want = %v", h, 341) | ||
} | ||
if svg == "" { | ||
t.Error("Run() svg is empty") | ||
} | ||
} |
Oops, something went wrong.