Skip to content

Instantly share code, notes, and snippets.

@Colouratura
Last active October 16, 2017 04:27
Show Gist options
  • Save Colouratura/4d92c91cdb947554c90cbd79b032b1e7 to your computer and use it in GitHub Desktop.
Save Colouratura/4d92c91cdb947554c90cbd79b032b1e7 to your computer and use it in GitHub Desktop.
A thing
/*
NAME
sketch - sketch an image
SYNOPSIS
sketch [-l -p -save -stat] [file]
DESCRIPTION
Sketch approximates the input image using randomly placed lines.
Sketch periodically saves results to sequentially named files in the
working directory, starting at "000.png". The interval between saves can
be adjusted using the -save option.
The -p flag removes duplicate colours from the palette, which means a more
uniformly random selection of colours is used when drawing lines. Some
images, like line art, converge faster with the -p flag enabled.
-l length
line length limit (default 40)
-p
remove duplicate colours from palette
-save interval
save interval, in seconds (default 5)
-stat interval
statistics reporting interval, in seconds (default 1)
*/
package main
import (
"flag"
"fmt"
"github.com/StephaneBunel/bresenham"
"image"
"image/color"
"image/draw"
_ "image/gif"
_ "image/jpeg"
"image/png"
"log"
"math"
"math/rand"
"os"
"time"
)
func bdiff(a, b image.Image, x1, y1, x2, y2 int) float64 {
var dx, dy, e, slope int
var dif float64
if x1 > x2 {
x1, y1, x2, y2 = x2, y2, x1, y1
}
dx, dy = x2 - x1, y2 - y1
if dy < 0 {
dy = -dy
}
switch {
case x1 == x2 && y1 == y2:
dif += calcdiff(a, b, x1, y1)
case y1 == y2:
for;
dx != 0;
dx--{
dif += calcdiff(a, b, x1, y1)
x1++
}
dif += calcdiff(a, b, x1, y1)
case x1 == x2:
if y1 > y2 {
y1, y2 = y2, y1
}
for;
dy != 0;
dy--{
dif += calcdiff(a, b, x1, y1)
y1++
}
dif += calcdiff(a, b, x1, y1)
case dx == dy:
if y1 < y2 {
for;
dx != 0;
dx--{
dif += calcdiff(a, b, x1, y1)
x1++
y1++
}
} else {
for;
dx != 0;
dx--{
dif += calcdiff(a, b, x1, y1)
x1++
y1--
}
}
dif += calcdiff(a, b, x1, y1)
case dx > dy:
if y1 < y2 {
dy, e, slope = 2 * dy, dx, 2 * dx
for;
dx != 0;
dx--{
dif += calcdiff(a, b, x1, y1)
x1++
e -= dy
if e < 0 {
y1++
e += slope
}
}
} else {
dy, e, slope = 2 * dy, dx, 2 * dx
for;
dx != 0;
dx--{
dif += calcdiff(a, b, x1, y1)
x1++
e -= dy
if e < 0 {
y1--
e += slope
}
}
}
dif += calcdiff(a, b, x2, y2)
default:
if y1 < y2 {
dx, e, slope = 2 * dx, dy, 2 * dy
for;
dy != 0;
dy--{
dif += calcdiff(a, b, x1, y1)
y1++
e -= dx
if e < 0 {
x1++
e += slope
}
}
} else {
dx, e, slope = 2 * dx, dy, 2 * dy
for;
dy != 0;
dy--{
dif += calcdiff(a, b, x1, y1)
y1--
e -= dx
if e < 0 {
x1++
e += slope
}
}
}
dif += calcdiff(a, b, x2, y2)
}
return dif
}
func calcdiff(a, b image.Image, x, y int) float64 {
aR, aG, aB, aA: = a.At(x, y).RGBA()
bR, bG, bB, bA: = b.At(x, y).RGBA()
ra: = float64(aR)
rb: = float64(bR)
ga: = float64(aG)
gb: = float64(bG)
ba: = float64(aB)
bb: = float64(bB)
aa: = float64(aA)
ab: = float64(bA)
R: = (rb - ra) * (rb - ra)
G: = (gb - ga) * (gb - ga)
B: = (bb - ba) * (bb - ba)
A: = (ab - aa) * (ab - aa)
return math.Sqrt(R + G + B + A)
}
func bcopy(img, src * image.RGBA, x1, y1, x2, y2 int) {
var dx, dy, e, slope int
if x1 > x2 {
x1, y1, x2, y2 = x2, y2, x1, y1
}
dx, dy = x2 - x1, y2 - y1
if dy < 0 {
dy = -dy
}
switch {
case x1 == x2 && y1 == y2:
img.Set(x1, y1, src.At(x1, y1))
case y1 == y2:
for;
dx != 0;
dx--{
img.Set(x1, y1, src.At(x1, y1))
x1++
}
img.Set(x1, y1, src.At(x1, y1))
case x1 == x2:
if y1 > y2 {
y1, y2 = y2, y1
}
for;
dy != 0;
dy--{
img.Set(x1, y1, src.At(x1, y1))
y1++
}
img.Set(x1, y1, src.At(x1, y1))
case dx == dy:
if y1 < y2 {
for;
dx != 0;
dx--{
img.Set(x1, y1, src.At(x1, y1))
x1++
y1++
}
} else {
for;
dx != 0;
dx--{
img.Set(x1, y1, src.At(x1, y1))
x1++
y1--
}
}
img.Set(x1, y1, src.At(x1, y1))
case dx > dy:
if y1 < y2 {
dy, e, slope = 2 * dy, dx, 2 * dx
for;
dx != 0;
dx--{
img.Set(x1, y1, src.At(x1, y1))
x1++
e -= dy
if e < 0 {
y1++
e += slope
}
}
} else {
dy, e, slope = 2 * dy, dx, 2 * dx
for;
dx != 0;
dx--{
img.Set(x1, y1, src.At(x1, y1))
x1++
e -= dy
if e < 0 {
y1--
e += slope
}
}
}
img.Set(x2, y2, src.At(x2, y2))
default:
if y1 < y2 {
dx, e, slope = 2 * dx, dy, 2 * dy
for;
dy != 0;
dy--{
img.Set(x1, y1, src.At(x1, y1))
y1++
e -= dx
if e < 0 {
x1++
e += slope
}
}
} else {
dx, e, slope = 2 * dx, dy, 2 * dy
for;
dy != 0;
dy--{
img.Set(x1, y1, src.At(x1, y1))
y1--
e -= dx
if e < 0 {
x1++
e += slope
}
}
}
img.Set(x2, y2, src.At(x2, y2))
}
}
var saveNum int
func save(img image.Image) {
outf, err: = os.Create(fmt.Sprintf("%03d.png", saveNum))
if err != nil {
log.Fatalln(err)
}
defer outf.Close()
png.Encode(outf, img)
saveNum++
}
var lineLen int
var palletize bool
var saveInterval float64
var statInterval float64
func init() {
flag.IntVar( & lineLen, "l", 40, "line `length` limit")
flag.BoolVar( & palletize, "p", false, "remove duplicate colours from palette")
flag.Float64Var( & saveInterval, "save", 1.5, "save `interval`, in seconds")
flag.Float64Var( & statInterval, "stat", 1.0, "statistics reporting `interval`, in seconds")
}
func main() {
log.SetFlags(0)
rand.Seed(123456789)
flag.Parse()
if flag.NArg() != 1 {
log.Fatalln("usage: sketch [-l -p -save -stat] [file]")
}
f, err: = os.Open(flag.Arg(0))
if err != nil {
log.Fatalln(err)
}
src, _, err: = image.Decode(f)
if err != nil {
log.Fatalln(err)
}
f.Close()
w: = src.Bounds().Dx()
h: = src.Bounds().Dy()
img: = image.NewRGBA(src.Bounds())
for y: = 0;
y < h;
y++{
for x: = 0;
x < w;
x++{
clr: = src.At(x, y)
clr = color.RGBAModel.Convert(clr)
img.Set(x, y, clr)
}
}
palette: = make([] color.Color, 0, 600000)
palettemap: = make(map[color.Color] bool, 600000)
for y: = 0;
y < h;
y++{
for x: = 0;
x < w;
x++{
if palletize {
if _, ok: = palettemap[img.At(x, y)];
!ok {
palette = append(palette, img.At(x, y))
palettemap[img.At(x, y)] = true
}
} else {
palette = append(palette, img.At(x, y))
}
}
}
log.Printf("%d colours in palette\n", len(palette))
img1: = image.NewRGBA(img.Bounds())
img2: = image.NewRGBA(img.Bounds())
bg: = color.RGBA {
0, 0, 0, 255
}
draw.Draw(img1, img1.Bounds(), & image.Uniform {
bg
}, image.ZP, draw.Src)
draw.Draw(img2, img2.Bounds(), & image.Uniform {
bg
}, image.ZP, draw.Src)
var lastSaveTime = time.Now()
var lastStatTime = time.Now()
var stati int
var statc int
for i: = 0;;
i++{
stati++
x1: = rand.Intn(w)
y1: = rand.Intn(h)
x2: = -lineLen / 2 + x1 + rand.Intn(lineLen)
y2: = -lineLen / 2 + y1 + rand.Intn(lineLen)
clr: = palette[rand.Intn(len(palette))]
bresenham.Bresenham(img1, x1, y1, x2, y2, clr)
if bdiff(img, img1, x1, y1, x2, y2) < bdiff(img, img2, x1, y1, x2, y2) {
// converges
bcopy(img2, img1, x1, y1, x2, y2)
statc++
} else {
// diverges
bcopy(img1, img2, x1, y1, x2, y2)
}
if i % 50 == 0 { // don't smash that time.Now()
now: = time.Now()
dur: = now.Sub(lastSaveTime)
if dur >= time.Duration(saveInterval) * time.Second {
save(img2)
lastSaveTime = now
}
dur = now.Sub(lastStatTime)
if dur >= time.Duration(statInterval) * time.Second {
ips: = float64(stati) / dur.Seconds()
cps: = float64(statc) / dur.Seconds()
log.Printf("%8d iters %10.2f iter/s %9.2f converg/s %6.2f%% c/i\n", i, ips, cps, 100 * cps / ips)
stati = 0
statc = 0
lastStatTime = now
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment