Last active
October 16, 2017 04:27
-
-
Save Colouratura/4d92c91cdb947554c90cbd79b032b1e7 to your computer and use it in GitHub Desktop.
A thing
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
/* | |
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