Created
February 10, 2022 16:50
-
-
Save cormullion/47ed283082595706a7575be4db10e00c to your computer and use it in GitHub Desktop.
painting boxes
This file contains hidden or 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
using Colors, Images, Luxor, Statistics | |
# tile of an image | |
struct ImageTile | |
image::Array | |
imagewidth::Real | |
imageheight::Real | |
tilewidth::Real | |
tileheight::Real | |
centerpos::Point | |
function ImageTile(img, imgwidth, imgheight, tilewidth, tileheight, centerpos) | |
new(img, imgwidth, imgheight, tilewidth, tileheight, centerpos) | |
end | |
end | |
function loadsourceimage(image) | |
img1 = Images.load(image) | |
# first dim is vertical!? | |
pageheight, pagewidth = size(img1) | |
# convert to x-across y-down view | |
pimg1 = permutedims(img1, (2, 1)) | |
return (pimg1, pagewidth, pageheight) | |
end | |
""" | |
return value of pixel at position pos, converting pos to location on image | |
""" | |
function getpixel(img, pos, imagewidth, imageheight) | |
x = convert(Int, round(pos.x + imagewidth/2, RoundUp)) | |
y = convert(Int, round(pos.y + imageheight/2, RoundUp)) | |
return img[x, y] | |
end | |
function getimageview(imagetile::ImageTile, pos, w, h) | |
# get an image section, centered at pos, such that the section has width w and height h | |
# convert pos (Luxor canvas coordinates centered at 0/0 in the middle) | |
# to image coordinates which are top left 0/0 | |
lx = (pos.x + imagetile.imagewidth/2) - (w/2) | |
rx = (pos.x + imagetile.imagewidth/2) + (w/2) | |
ty = (pos.y + imagetile.imageheight/2) - (h/2) | |
by = (pos.y + imagetile.imageheight/2) + (h/2) | |
leftx = convert(Int, floor(clamp(lx, 1, imagetile.imagewidth))) | |
rightx = convert(Int, floor(clamp(rx, 1, imagetile.imagewidth))) | |
topy = convert(Int, floor(clamp(ty, 1, imagetile.imageheight))) | |
bottomy = convert(Int, floor(clamp(by, 1, imagetile.imageheight))) | |
return view(imagetile.image, leftx:rightx, topy:bottomy) | |
end | |
function drawtile(thistile::ImageTile) | |
global counter | |
pixvalue = getpixel(thistile.image, thistile.centerpos, thistile.imagewidth, thistile.imageheight) | |
aslice = getimageview(thistile, thistile.centerpos, thistile.tilewidth, thistile.tileheight) | |
sethue(mean(aslice)) | |
# while subdividing, draw white outline | |
if thistile.tilewidth > 15 | |
gsave() | |
sethue("white") | |
setline(1) | |
box(thistile.centerpos, thistile.tilewidth, thistile.tileheight, :stroke) | |
# sethue(pixvalue) | |
sethue(mean(aslice)) | |
box(thistile.centerpos, thistile.tilewidth-2, thistile.tileheight-2, :fill) | |
grestore() | |
else | |
box(thistile.centerpos, thistile.tilewidth, thistile.tileheight, :fillstroke) | |
end | |
counter += 1 | |
end | |
function needssubdividing(thistile::ImageTile) | |
# don't go too small | |
if (thistile.tilewidth < settings["smallestwidth"]) && (thistile.tileheight < settings["smallestheight"]) | |
return false | |
end | |
aslice = getimageview(thistile, thistile.centerpos, thistile.tilewidth, thistile.tileheight) | |
standarddeviation = std(Float64.(Gray.(aslice))) | |
# return true if this tile could do with some subdivision | |
if standarddeviation[1] > settings["threshold"] | |
return true | |
else | |
return false | |
end | |
end | |
function subdividetile(imgtile::ImageTile) | |
# returns an array of imagetiles for each quadrant | |
# TODO rewrite this | |
cpos = Point(imgtile.centerpos.x - imgtile.tilewidth/4, imgtile.centerpos.y - imgtile.tileheight/4) | |
tl = ImageTile(imgtile.image, imgtile.imagewidth, imgtile.imageheight, imgtile.tilewidth/2, imgtile.tileheight/2, cpos) | |
cpos = Point(imgtile.centerpos.x + imgtile.tilewidth/4, imgtile.centerpos.y - imgtile.tileheight/4) | |
tr = ImageTile(imgtile.image, imgtile.imagewidth, imgtile.imageheight, imgtile.tilewidth/2, imgtile.tileheight/2, cpos) | |
cpos = Point(imgtile.centerpos.x - imgtile.tilewidth/4, imgtile.centerpos.y + imgtile.tileheight/4) | |
bl = ImageTile(imgtile.image, imgtile.imagewidth, imgtile.imageheight, imgtile.tilewidth/2, imgtile.tileheight/2, cpos) | |
cpos = Point(imgtile.centerpos.x + imgtile.tilewidth/4, imgtile.centerpos.y + imgtile.tileheight/4) | |
br = ImageTile(imgtile.image, imgtile.imagewidth, imgtile.imageheight, imgtile.tilewidth/2, imgtile.tileheight/2, cpos) | |
return ImageTile[tl, tr, bl, br] | |
end | |
function processtile(thistile::ImageTile) | |
if ! needssubdividing(thistile) | |
drawtile(thistile) | |
else | |
map(x -> processtile(x), subdividetile(thistile)) | |
end | |
end | |
function tileimage(img, imgwidth, imgheight, cellwidth, cellheight) | |
maintiles = Partition(imgwidth, imgheight, cellwidth, cellheight) | |
for (pos, n) in maintiles | |
newtile = ImageTile(img, imgwidth, imgheight, maintiles.tilewidth, maintiles.tileheight, pos) | |
processtile(newtile) | |
end | |
end | |
makeeven(n) = n - (n % 2) | |
function boximage(scene, framenumber; imfile=imfile) | |
global counter | |
counter = 0 | |
img, imgwidth, imgheight = loadsourceimage(imfile) | |
imgwidth = makeeven(imgwidth) | |
imgheight = makeeven(imgheight) | |
background("white") | |
settings["threshold"] = rescale(framenumber, 1, scene.framerange.stop, 0.24, 0.005) | |
tileimage(img, imgwidth, imgheight, imgwidth, imgheight) | |
sethue("cornsilk") | |
fontsize(20) | |
fontface("Menlo") | |
t = string("boxes: $counter, standard deviation: $(round(settings["threshold"], digits=3))") | |
text(t, -(imgwidth/2) + 50, -(imgheight/2) + 50, halign=:left) | |
end | |
function makeanimation(fname) | |
img, imgwidth, imgheight = loadsourceimage(fname) | |
# should be even... ? | |
imgwidth = makeeven(imgwidth) | |
imgheight = makeeven(imgheight) | |
m = Movie(imgwidth, imgheight, "movieboxes") | |
animate(m, Scene(m, | |
(s, f) -> boximage(s, f, imfile=fname), 1:50), | |
creategif=true) | |
end | |
# default settings | |
settings = Dict( | |
"threshold" => 0.1, | |
"smallestwidth" => 12, | |
"smallestheight" => 12, | |
) | |
makeanimation("holbein.jpg") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment