Skip to content

Instantly share code, notes, and snippets.

@cormullion
Created February 10, 2022 16:50
Show Gist options
  • Save cormullion/47ed283082595706a7575be4db10e00c to your computer and use it in GitHub Desktop.
Save cormullion/47ed283082595706a7575be4db10e00c to your computer and use it in GitHub Desktop.
painting boxes
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