Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions examples/atlas_packing.nim
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,19 @@ proc generateRandomImage(minSize, maxSize: int): Image =
else:
discard

# Add debug grid to the image
# for x in 0 ..< width:
# for y in 0 ..< height:
# if x mod 10 == 0 and y mod 10 == 0:
# image.unsafe[x, y] = color(1, 1, 1, 1).rgbx()

return image

let window = newWindow("Atlas Packing", ivec2(1000, 1000))
makeContextCurrent(window)
loadExtensions()

let ctx = newBoxy(atlasSize = 512) # Add 2 pixel margin around each image
let ctx = newBoxy(atlasSize = 128) # Add 2 pixel margin around each image

if not dirExists("tmp"):
createDir("tmp")
Expand Down Expand Up @@ -122,7 +128,7 @@ for i in 1..totalImages:
ctx.addImage(key, img)
inc imageCount

echo "Added image ", i, " (", img.width, "x", img.height, ")"
# echo "Added image ", i, " (", img.width, "x", img.height, ")"

# Save atlas periodically
if (i < 40) or (i mod saveInterval == 0) or (i == totalImages):
Expand All @@ -131,9 +137,10 @@ for i in 1..totalImages:
ctx.drawImage(key, vec2(0, 0))
ctx.endFrame()

let filename = "tmp/atlas_" & $i & ".png"
ctx.atlasTexture.writeFile(filename)
echo " Wrote ", filename
# when not defined(emscripten):
# let filename = "tmp/atlas_" & $i & ".png"
# ctx.atlasTexture.writeFile(filename)
# echo " Wrote ", filename


var frameCount = 0
Expand Down
143 changes: 109 additions & 34 deletions src/boxy.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import bitty, boxy/blends, boxy/blurs, boxy/buffers, boxy/shaders,
boxy/spreads, boxy/textures, bumpy, chroma, hashes, opengl, pixie, sets,
shady, strutils, tables, vmath
import
std/[algorithm, sequtils, sets, strutils, tables],
bitty, shady, vmath, bumpy, chroma, hashes, opengl, pixie,
boxy/[blends, blurs, buffers, shaders, spreads, textures]

export atlasVert, atlasMain, maskMain

Expand All @@ -22,7 +23,7 @@ type
of tkColor:
color: Color

ImageInfo = object
ImageInfo = ref object
size: IVec2 ## Size of the image in pixels.
tiles: seq[seq[TileInfo]] ## The tile info for this image.
oneColor: Color ## If tiles = [] then this is the image's color.
Expand Down Expand Up @@ -358,6 +359,8 @@ proc exitRawOpenGLMode*(boxy: Boxy) =

# Forward declaration
proc drawUvRect(boxy: Boxy, at, to, uvAt, uvTo: Vec2, tint: Color)
proc saveTransform*(boxy: Boxy)
proc restoreTransform*(boxy: Boxy)

proc removeImage*(boxy: Boxy, key: string) =
## Removes an image, does nothing if the image has not been added.
Expand All @@ -381,45 +384,116 @@ proc clearColor(boxy: Boxy) =
proc grow(boxy: Boxy) =
## Grows the atlas size by 2 (growing area by 4) and repositions tiles.
if boxy.atlasSize == boxy.maxAtlasSize:
var images = boxy.entries.pairs().toSeq()
images.sort(proc(a, b: (string, ImageInfo)): int = cmp(-a[1].size.x * a[1].size.y, -b[1].size.x * b[1].size.y))
var i = 0
for image in images:
echo " Image ", image[0], " size: ", image[1].size.x, "x", image[1].size.y
inc i
when not defined(emscripten):
boxy.atlasTexture.writeFile("tmp/atlas.png")
raise newException(
BoxyError,
"Can't grow boxy atlas texture, max supported size reached: " &
$boxy.maxAtlasSize
)

boxy.flush()
# Read old atlas content

let
oldAtlas = boxy.readAtlas()
oldAtlasSize = boxy.atlasSize
newAtlasSize = oldAtlasSize * 2
oldTileRun = boxy.tileRun
newTileRun = newAtlasSize div (boxy.tileSize + boxy.tileMargin)

boxy.atlasSize *= 2
if boxy.atlasSize > boxy.maxAtlasSize:
boxy.atlasSize = boxy.maxAtlasSize
# Create new atlas texture
let newAtlasTexture = boxy.createAtlasTexture(newAtlasSize)

boxy.tileRun = boxy.atlasSize div (boxy.tileSize + boxy.tileMargin)
boxy.maxTiles = boxy.tileRun * boxy.tileRun
boxy.takenTiles.setLen(boxy.maxTiles)
boxy.atlasTexture = boxy.createAtlasTexture(boxy.atlasSize)
# Create framebuffer for new atlas
var newFramebuffer: GLuint
glGenFramebuffers(1, newFramebuffer.addr)
boxy.drawToTexture(newAtlasTexture, newFramebuffer)

boxy.addWhiteTile()
# Save state
let
savedFramebuffer = if boxy.layerNum >= 0:
boxy.layerFramebuffers[boxy.layerNum]
else:
0.GLuint
savedProj = boxy.proj
savedShader = boxy.activeShader

# Setup drawing to new atlas
glBindFramebuffer(GL_FRAMEBUFFER, newFramebuffer)
glViewport(0, 0, newAtlasSize.int32, newAtlasSize.int32)

# Clear new atlas
glClearColor(0, 0, 0, 0)
glClear(GL_COLOR_BUFFER_BIT)

for y in 0 ..< oldTileRun:
for x in 0 ..< oldTileRun:
let
imageTile = oldAtlas.superImage(
x * (boxy.tileSize + boxy.tileMargin),
y * (boxy.tileSize + boxy.tileMargin),
boxy.tileSize + boxy.tileMargin,
boxy.tileSize + boxy.tileMargin
)
index = x + y * oldTileRun
updateSubImage(
boxy.atlasTexture,
(index mod boxy.tileRun) * (boxy.tileSize + boxy.tileMargin),
(index div boxy.tileRun) * (boxy.tileSize + boxy.tileMargin),
imageTile
)
# Setup projection for new atlas
boxy.proj = ortho(0.float32, newAtlasSize.float32, newAtlasSize.float32, 0, -1000, 1000)

# Use atlas shader
boxy.activeShader = boxy.atlasShader

# Disable blending for copy
glDisable(GL_BLEND)

# Draw old atlas into new atlas
boxy.saveTransform()
boxy.mat = mat3()

boxy.drawUvRect(
at = vec2(0, newAtlasSize),
to = vec2(oldAtlasSize, oldAtlasSize),
uvAt = vec2(0, 0),
uvTo = vec2(oldAtlasSize, oldAtlasSize),
tint = color(1, 1, 1, 1)
)

boxy.flush()

boxy.restoreTransform()

# Restore state
glEnable(GL_BLEND)
# If layerNum is -1, savedFramebuffer is 0.
glBindFramebuffer(GL_FRAMEBUFFER, savedFramebuffer)
glViewport(0, 0, boxy.frameSize.x, boxy.frameSize.y)
boxy.proj = savedProj
boxy.activeShader = savedShader

# Clean up temporary framebuffer
glDeleteFramebuffers(1, newFramebuffer.addr)

# Delete old atlas texture
glDeleteTextures(1, boxy.atlasTexture.textureId.addr)

# Update boxy
boxy.atlasTexture = newAtlasTexture
boxy.atlasSize = newAtlasSize
boxy.tileRun = newTileRun
boxy.maxTiles = newTileRun * newTileRun

# Rebuild takenTiles and update entries
var newTakenTiles = newBitArray(boxy.maxTiles)
newTakenTiles[0] = true # White tile

for key, imageInfo in boxy.entries.mpairs:
for level in 0 ..< imageInfo.tiles.len:
for i in 0 ..< imageInfo.tiles[level].len:
if imageInfo.tiles[level][i].kind == tkIndex:
let
oldIndex = imageInfo.tiles[level][i].index
x = oldIndex mod oldTileRun
y = oldIndex div oldTileRun
newIndex = x + y * newTileRun

imageInfo.tiles[level][i].index = newIndex
newTakenTiles[newIndex] = true

boxy.takenTiles = newTakenTiles

proc takeFreeTile(boxy: Boxy): int =
let (found, index) = boxy.takenTiles.firstFalse
Expand All @@ -429,7 +503,7 @@ proc takeFreeTile(boxy: Boxy): int =
boxy.grow()
boxy.takeFreeTile()

proc addImage*(boxy: Boxy, key: string, image: Image) =
proc addImage*(boxy: Boxy, key: string, image: Image, mipmaps: bool = true) =
if key in boxy.entriesBuffered:
raise newException(
BoxyError,
Expand All @@ -440,7 +514,8 @@ proc addImage*(boxy: Boxy, key: string, image: Image) =
boxy.removeImage(key)
boxy.entriesBuffered.incl(key)

var imageInfo: ImageInfo
var imageInfo = ImageInfo()
boxy.entries[key] = imageInfo
imageInfo.size = ivec2(image.width.int32, image.height.int32)

if image.isOneColor():
Expand Down Expand Up @@ -475,15 +550,15 @@ proc addImage*(boxy: Boxy, key: string, image: Image) =
(index div boxy.tileRun) * (boxy.tileSize + boxy.tileMargin),
tileImage
)
if not mipmaps:
break

if img.width <= 1 or img.height <= 1:
break

img = img.minifyBy2()
inc level

boxy.entries[key] = imageInfo

proc getImageSize*(boxy: Boxy, key: string): IVec2 =
## Return the size of an inserted image.
boxy.entries[key].size
Expand Down