Skip to content

Commit

Permalink
Fully fledged gif and compression tool
Browse files Browse the repository at this point in the history
  • Loading branch information
sevagh committed Feb 5, 2019
1 parent a59615d commit 4738c04
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 259 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ all: build

test:
go test -v ./...
@make clean

fmt:
go fmt ./...
Expand Down
81 changes: 0 additions & 81 deletions color_distance.go

This file was deleted.

40 changes: 18 additions & 22 deletions convert.go → compress.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import (
"os"
)

func (q *QuadTree) ToImage(level int) image.Image {
img := image.NewNRGBA(image.Rect(0, 0, q.Width, q.Height))

for y := 0; y < q.Height; y++ {
for x := 0; x < q.Width; x++ {
img.Set(x, y, DeinterleaveZOrderRGB(q.getPixel(x, y, level)))
}
}

return img
}

func LoadImage(path string) (*image.Image, error) {
imgF, err := os.Open(path)
if err != nil {
Expand Down Expand Up @@ -36,33 +48,17 @@ func WriteImage(img image.Image, imgPath string) error {
return nil
}

func (q *QuadTree) ToImage(level int) (image.Image, bool) {
img := image.NewNRGBA(image.Rect(0, 0, q.Width, q.Height))

var maxQualityAchieved bool
var pixColor uint64

for y := 0; y < q.Height; y++ {
for x := 0; x < q.Width; x++ {
pixColor, maxQualityAchieved = q.getPixel(x, y, level)
img.Set(x, y, DeinterleaveZOrderRGB(pixColor))
}
}

return img, maxQualityAchieved
}

func (q *QuadTree) getPixel(x, y, level int) (uint64, bool) {
func (q *QuadTree) getPixel(x, y, level int) uint64 {
if q.Root != nil {
return q.Root.getPixel(x, y, q.Width, q.Height, 0, level)
}
return 0, false
return 0
}

func (qn *QuadTreeNode) getPixel(x, y, xCoord, yCoord, level, maxLevel int) (uint64, bool) {
func (qn *QuadTreeNode) getPixel(x, y, xCoord, yCoord, level, maxLevel int) uint64 {
level += 1
if maxLevel != -1 && level >= maxLevel {
return qn.Color, false
if maxLevel > 0 && level >= maxLevel {
return qn.Color
}
if qn.NW != nil && x < xCoord/2 && y < yCoord/2 {
return qn.NW.getPixel(x, y, xCoord/2, yCoord/2, level, maxLevel)
Expand All @@ -76,5 +72,5 @@ func (qn *QuadTreeNode) getPixel(x, y, xCoord, yCoord, level, maxLevel int) (uin
if qn.SE != nil && x >= xCoord/2 && y >= yCoord/2 {
return qn.SE.getPixel(x-xCoord/2, y-yCoord/2, xCoord/2, yCoord/2, level, maxLevel)
}
return qn.Color, true
return qn.Color
}
22 changes: 19 additions & 3 deletions convert_test.go → compress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestCreateImageRoundTripThroughQuadTree(t *testing.T) {
t.Fatalf("Error when creating quadtree from image '%s': %+v", path, err)
}

img, _ := qt.ToImage(-1)
img, _ := qt.ToImage(&ArtOpts{})
outErr := WriteImage(img, outPath)
if outErr != nil {
t.Fatalf("Error when converting quadtree to image: %+v", err)
Expand Down Expand Up @@ -72,7 +72,7 @@ func TestCreateFakeImage(t *testing.T) {
qt.Height = 10
qt.Width = 10

img, _ := qt.ToImage(-1)
img, _ := qt.ToImage(&ArtOpts{})
err := WriteImage(img, outPath)
if err != nil {
t.Fatalf("Error creating fake image '%s': %+v", outPath, err)
Expand All @@ -89,7 +89,7 @@ func TestCreateImageProgressiveQuality(t *testing.T) {
}

for i := 1; i < 16; i++ {
img, maxAchieved := qt.ToImage(i)
img, maxAchieved := qt.ToImage(&ArtOpts{Level: i})
outErr := WriteImage(img, fmt.Sprintf(outPathFmt, i))
if outErr != nil {
t.Fatalf("Error when converting quadtree to image: %+v", err)
Expand All @@ -100,3 +100,19 @@ func TestCreateImageProgressiveQuality(t *testing.T) {
}
}
}

func TestCreateImageInverted(t *testing.T) {
path := "./samples/jungle.png"
outPath := "./out_inverted.png"

qt, err := BuildQuadTree(path)
if err != nil {
t.Fatalf("Error when creating quadtree from image '%s': %+v", path, err)
}

img, _ := qt.ToImage(&ArtOpts{Invert: true})
outErr := WriteImage(img, outPath)
if outErr != nil {
t.Fatalf("Error when converting quadtree to image: %+v", err)
}
}
Empty file removed go.sum
Empty file.
52 changes: 29 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,55 @@
package main

import (
//"flag"
"flag"
"fmt"
"image"
"image/color/palette"
"image/draw"
"image/gif"
"os"
"strconv"
)

func main() {
if len(os.Args) != 4 {
fmt.Fprintf(os.Stderr, "Usage: %s <path-to-image> <levels of quality> <path-to-out-gif>\n", os.Args[0])
os.Exit(1)
}
qualityFlag := flag.Int("quality", 10, "quadtree depth (more is better quality)")
gifFlag := flag.Bool("gif", false, "generate gif")
compressFlag := flag.Bool("compress", false, "compress image")
delayFlag := flag.Int("delayMS", 500, "frame delay in ms")

qt, err := BuildQuadTree(os.Args[1])
if err != nil {
panic(err)
flag.Parse()

if flag.NArg() != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] <inpath> <outpath>\n", os.Args[0])
os.Exit(0)
}

levelsOfQuality, err := strconv.Atoi(os.Args[2])
inPath := flag.Args()[0]
outPath := flag.Args()[1]

qt, err := BuildQuadTree(inPath)
if err != nil {
panic(err)
}

outGif := &gif.GIF{}

for i := 1; i < levelsOfQuality; i++ {
img, maxAchieved := qt.ToImage(i)
if *gifFlag {
outGif := &gif.GIF{}
for i := 1; i < *qualityFlag; i++ {
img := qt.ToImage(i)

pImg := image.NewPaletted(img.Bounds(), palette.Plan9)
draw.Draw(pImg, pImg.Rect, img, img.Bounds().Min, draw.Over)
pImg := image.NewPaletted(img.Bounds(), palette.Plan9)
draw.Draw(pImg, pImg.Rect, img, img.Bounds().Min, draw.Over)

outGif.Image = append(outGif.Image, pImg)
outGif.Delay = append(outGif.Delay, 50)

if maxAchieved {
break
outGif.Image = append(outGif.Image, pImg)
outGif.Delay = append(outGif.Delay, *delayFlag/10)
}
createGif(outPath, outGif)
} else if *compressFlag {
img := qt.ToImage(*qualityFlag)
WriteImage(img, outPath)
} else {
fmt.Fprintf(os.Stderr, "Usage: -h - specify one of -gif or -compress\n")
os.Exit(0)
}

createGif(os.Args[3], outGif)
}

func createGif(outPath string, outGif *gif.GIF) {
Expand Down
28 changes: 0 additions & 28 deletions quadtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func BuildQuadTree(imageSource string) (*QuadTree, error) {
qt.Height = (*img).Bounds().Max.Y - 1 - (*img).Bounds().Min.Y

qt.Root = buildQuadTree(img, (*img).Bounds().Min.X, (*img).Bounds().Min.Y, qt.Width, qt.Height)
qt.Root.prune()

return &qt, nil
}
Expand Down Expand Up @@ -73,30 +72,3 @@ func buildQuadTree(img *image.Image, x, y, w, h int) *QuadTreeNode {

return &qn
}

func (q *QuadTreeNode) prune() {
if q.NE == nil && q.NW == nil && q.SE == nil && q.SW == nil {
return
}

if q.NE.canPrune(q) && q.NW.canPrune(q) && q.SE.canPrune(q) && q.SW.canPrune(q) {
q.NE = nil
q.NW = nil
q.SE = nil
q.SW = nil
} else {
q.NE.prune()
q.NW.prune()
q.SE.prune()
q.SW.prune()
}
}

// stack recursion - essentially a DFS of sorts
func (q *QuadTreeNode) canPrune(parent *QuadTreeNode) bool {
if q.NE == nil { // leaf node
return CIE76(parent.Color, q.Color) <= 2.3
}

return q.NE.canPrune(parent) && q.NW.canPrune(parent) && q.SE.canPrune(parent) && q.SW.canPrune(parent)
}
Loading

0 comments on commit 4738c04

Please sign in to comment.