Skip to content

Commit

Permalink
Add latest code state
Browse files Browse the repository at this point in the history
  • Loading branch information
denisbrodbeck committed Nov 23, 2017
1 parent c36d035 commit e13f338
Show file tree
Hide file tree
Showing 14 changed files with 610 additions and 0 deletions.
51 changes: 51 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions Gopkg.toml
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"
97 changes: 97 additions & 0 deletions cmd/sqip/main.go
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`
23 changes: 23 additions & 0 deletions minify.go
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
}
32 changes: 32 additions & 0 deletions minify_test.go
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)
}
}
33 changes: 33 additions & 0 deletions primitive.go
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
}
21 changes: 21 additions & 0 deletions primitive_test.go
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")
}
}
34 changes: 34 additions & 0 deletions run.go
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
}
19 changes: 19 additions & 0 deletions run_test.go
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")
}
}
Loading

0 comments on commit e13f338

Please sign in to comment.