internal/packing: refactoring: use image.Rectangle

This commit is contained in:
Hajime Hoshi 2023-04-27 22:34:42 +09:00
parent 6c5477adea
commit 82c7436be5
3 changed files with 91 additions and 118 deletions

View File

@ -375,7 +375,9 @@ func (i *Image) regionWithPadding() (x, y, width, height int) {
if !i.isOnAtlas() { if !i.isOnAtlas() {
return 0, 0, i.width + i.paddingSize(), i.height + i.paddingSize() return 0, 0, i.width + i.paddingSize(), i.height + i.paddingSize()
} }
return i.node.Region() // TODO: Use image.Rectangle as it is.
r := i.node.Region()
return r.Min.X, r.Min.Y, r.Dx(), r.Dy()
} }
func (i *Image) processSrc(src *Image) { func (i *Image) processSrc(src *Image) {

View File

@ -18,6 +18,7 @@ package packing
import ( import (
"errors" "errors"
"fmt" "fmt"
"image"
) )
type Page struct { type Page struct {
@ -65,10 +66,7 @@ func (p *Page) IsEmpty() bool {
} }
type Node struct { type Node struct {
x int region image.Rectangle
y int
width int
height int
used bool used bool
parent *Node parent *Node
@ -86,8 +84,8 @@ func (n *Node) canFree() bool {
return n.child0.canFree() && n.child1.canFree() return n.child0.canFree() && n.child1.canFree()
} }
func (n *Node) Region() (x, y, width, height int) { func (n *Node) Region() image.Rectangle {
return n.x, n.y, n.width, n.height return n.region
} }
// square returns a float value indicating how much the given rectangle is close to a square. // square returns a float value indicating how much the given rectangle is close to a square.
@ -104,59 +102,47 @@ func square(width, height int) float64 {
} }
func alloc(n *Node, width, height int) *Node { func alloc(n *Node, width, height int) *Node {
if n.width < width || n.height < height { if n.region.Dx() < width || n.region.Dy() < height {
return nil return nil
} }
if n.used { if n.used {
return nil return nil
} }
if n.child0 == nil && n.child1 == nil { if n.child0 == nil && n.child1 == nil {
if n.width == width && n.height == height { if n.region.Dx() == width && n.region.Dy() == height {
n.used = true n.used = true
return n return n
} }
if square(n.width-width, n.height) >= square(n.width, n.height-height) { if square(n.region.Dx()-width, n.region.Dy()) >= square(n.region.Dx(), n.region.Dy()-height) {
// Split vertically // Split vertically
n.child0 = &Node{ n.child0 = &Node{
x: n.x, region: image.Rect(n.region.Min.X, n.region.Min.Y, n.region.Min.X+width, n.region.Max.Y),
y: n.y,
width: width,
height: n.height,
parent: n, parent: n,
} }
n.child1 = &Node{ n.child1 = &Node{
x: n.x + width, region: image.Rect(n.region.Min.X+width, n.region.Min.Y, n.region.Max.X, n.region.Max.Y),
y: n.y,
width: n.width - width,
height: n.height,
parent: n, parent: n,
} }
} else { } else {
// Split horizontally // Split horizontally
n.child0 = &Node{ n.child0 = &Node{
x: n.x, region: image.Rect(n.region.Min.X, n.region.Min.Y, n.region.Max.X, n.region.Min.Y+height),
y: n.y,
width: n.width,
height: height,
parent: n, parent: n,
} }
n.child1 = &Node{ n.child1 = &Node{
x: n.x, region: image.Rect(n.region.Min.X, n.region.Min.Y+height, n.region.Max.X, n.region.Max.Y),
y: n.y + height,
width: n.width,
height: n.height - height,
parent: n, parent: n,
} }
} }
// Note: it now MUST fit, due to above preconditions (repeated here). // Note: it now MUST fit, due to above preconditions (repeated here).
if n.child0.width < width || n.child0.height < height { if n.child0.region.Dx() < width || n.child0.region.Dy() < height {
panic(fmt.Sprintf("packing: the newly created child node (%d, %d) unexpectedly does not contain the requested size (%d, %d)", n.child0.width, n.child0.height, width, height)) panic(fmt.Sprintf("packing: the newly created child node (%d, %d) unexpectedly does not contain the requested size (%d, %d)", n.child0.region.Dx(), n.child0.region.Dy(), width, height))
} }
// Thus, alloc can't return nil, but it may do another split along the other dimension // Thus, alloc can't return nil, but it may do another split along the other dimension
// to get a node with the exact size (width, height). // to get a node with the exact size (width, height).
node := alloc(n.child0, width, height) node := alloc(n.child0, width, height)
if node == nil { if node == nil {
panic(fmt.Sprintf("packing: could not allocate the requested size (%d, %d) in the newly created child node (%d, %d)", width, height, n.child0.width, n.child0.height)) panic(fmt.Sprintf("packing: could not allocate the requested size (%d, %d) in the newly created child node (%d, %d)", width, height, n.child0.region.Dx(), n.child0.region.Dy()))
} }
return node return node
} }
@ -183,8 +169,7 @@ func (p *Page) Alloc(width, height int) *Node {
if p.root == nil { if p.root == nil {
p.root = &Node{ p.root = &Node{
width: p.width, region: image.Rect(0, 0, p.width, p.height),
height: p.height,
} }
} }
return p.extendForAndAlloc(width, height) return p.extendForAndAlloc(width, height)
@ -273,7 +258,7 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
aborted := false aborted := false
if p.root != nil { if p.root != nil {
_ = walk(p.root, func(n *Node) error { _ = walk(p.root, func(n *Node) error {
if n.x+n.width < p.width && n.y+n.height < p.height { if n.region.Max.X < p.width && n.region.Max.Y < p.height {
return nil return nil
} }
if n.used { if n.used {
@ -295,16 +280,10 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
if newHeight-p.height > 0 { if newHeight-p.height > 0 {
upper := p.root upper := p.root
lower := &Node{ lower := &Node{
x: 0, region: image.Rect(0, p.height, p.width, newHeight),
y: p.height,
width: p.width,
height: newHeight - p.height,
} }
p.root = &Node{ p.root = &Node{
x: 0, region: image.Rect(0, 0, p.width, newHeight),
y: 0,
width: p.width,
height: newHeight,
child0: upper, child0: upper,
child1: lower, child1: lower,
} }
@ -316,16 +295,10 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
if newWidth-p.width > 0 { if newWidth-p.width > 0 {
left := p.root left := p.root
right := &Node{ right := &Node{
x: p.width, region: image.Rect(p.width, 0, newWidth, newHeight),
y: 0,
width: newWidth - p.width,
height: newHeight,
} }
p.root = &Node{ p.root = &Node{
x: 0, region: image.Rect(0, 0, newWidth, newHeight),
y: 0,
width: newWidth,
height: newHeight,
child0: left, child0: left,
child1: right, child1: right,
} }
@ -344,28 +317,28 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
} }
} else { } else {
origWidth, origHeight := p.width, p.height origWidth, origHeight := p.width, p.height
origWidths := map[*Node]int{} origMaxXs := map[*Node]int{}
origHeights := map[*Node]int{} origMaxYs := map[*Node]int{}
for _, n := range edgeNodes { for _, n := range edgeNodes {
if n.x+n.width == p.width { if n.region.Max.X == p.width {
origWidths[n] = n.width origMaxXs[n] = n.region.Max.X
n.width += newWidth - p.width n.region.Max.X = newWidth
} }
if n.y+n.height == p.height { if n.region.Max.Y == p.height {
origHeights[n] = n.height origMaxYs[n] = n.region.Max.Y
n.height += newHeight - p.height n.region.Max.Y = newHeight
} }
} }
rollback = func() { rollback = func() {
p.width = origWidth p.width = origWidth
p.height = origHeight p.height = origHeight
for n, w := range origWidths { for n, x := range origMaxXs {
n.width = w n.region.Max.X = x
} }
for n, h := range origHeights { for n, y := range origMaxYs {
n.height = h n.region.Max.Y = y
} }
} }
} }

View File

@ -15,17 +15,16 @@
package packing_test package packing_test
import ( import (
"image"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2/internal/packing" "github.com/hajimehoshi/ebiten/v2/internal/packing"
) )
func TestPage(t *testing.T) { func TestPage(t *testing.T) {
type Rect struct { rect := func(x0, y0, x1, y1 int) *image.Rectangle {
X int r := image.Rect(x0, y0, x1, y1)
Y int return &r
Width int
Height int
} }
type Op struct { type Op struct {
@ -37,7 +36,7 @@ func TestPage(t *testing.T) {
cases := []struct { cases := []struct {
Name string Name string
In []Op In []Op
Out []*Rect Out []*image.Rectangle
}{ }{
{ {
Name: "alloc and random free", Name: "alloc and random free",
@ -56,20 +55,20 @@ func TestPage(t *testing.T) {
{0, 0, 4}, {0, 0, 4},
{1024, 1024, -1}, {1024, 1024, -1},
}, },
Out: []*Rect{ Out: []*image.Rectangle{
{0, 0, 100, 100}, rect(0, 0, 100, 100),
{0, 100, 100, 100}, rect(0, 100, 100, 200),
{0, 200, 100, 100}, rect(0, 200, 100, 300),
{0, 300, 100, 100}, rect(0, 300, 100, 400),
{0, 400, 100, 100}, rect(0, 400, 100, 500),
{0, 500, 100, 100}, rect(0, 500, 100, 600),
nil, nil,
nil, nil,
nil, nil,
nil, nil,
nil, nil,
nil, nil,
{0, 0, 1024, 1024}, rect(0, 0, 1024, 1024),
}, },
}, },
{ {
@ -88,13 +87,13 @@ func TestPage(t *testing.T) {
{0, 0, 4}, {0, 0, 4},
{0, 0, 5}, {0, 0, 5},
}, },
Out: []*Rect{ Out: []*image.Rectangle{
{0, 0, 31, 41}, rect(0, 0, 31, 41),
{31, 0, 59, 26}, rect(31, 0, 31+59, 26),
{31, 26, 53, 58}, rect(31, 26, 31+53, 26+58),
{31, 84, 97, 93}, rect(31, 84, 31+97, 84+93),
{0, 41, 28, 84}, rect(0, 41, 28, 41+84),
{31, 177, 62, 64}, rect(31, 177, 31+62, 177+64),
nil, nil,
nil, nil,
nil, nil,
@ -118,18 +117,18 @@ func TestPage(t *testing.T) {
{0, 0, 2}, {0, 0, 2},
{100, 200, -1}, {100, 200, -1},
}, },
Out: []*Rect{ Out: []*image.Rectangle{
{0, 0, 100, 200}, rect(0, 0, 100, 200),
nil, nil,
{0, 200, 100, 200}, rect(0, 200, 100, 400),
{0, 400, 50, 50}, rect(0, 400, 50, 450),
{100, 0, 200, 200}, rect(100, 0, 300, 200),
nil, nil,
{100, 200, 500, 500}, rect(100, 200, 600, 700),
nil, nil,
{0, 450, 100, 100}, rect(0, 450, 100, 550),
nil, nil,
{0, 200, 100, 200}, rect(0, 200, 100, 400),
}, },
}, },
{ {
@ -157,26 +156,26 @@ func TestPage(t *testing.T) {
{256, 256, -1}, {256, 256, -1},
}, },
Out: []*Rect{ Out: []*image.Rectangle{
{0, 0, 256, 256}, rect(0, 0, 256, 256),
{0, 256, 256, 256}, rect(0, 256, 256, 512),
{0, 512, 256, 256}, rect(0, 512, 256, 768),
{0, 768, 256, 256}, rect(0, 768, 256, 1024),
{256, 0, 256, 256}, rect(256, 0, 512, 256),
{512, 0, 256, 256}, rect(512, 0, 768, 256),
{768, 0, 256, 256}, rect(768, 0, 1024, 256),
{256, 256, 256, 256}, rect(256, 256, 512, 512),
{256, 512, 256, 256}, rect(256, 512, 512, 768),
{256, 768, 256, 256}, rect(256, 768, 512, 1024),
{512, 256, 256, 256}, rect(512, 256, 768, 512),
{768, 256, 256, 256}, rect(768, 256, 1024, 512),
{512, 512, 256, 256}, rect(512, 512, 768, 768),
{512, 768, 256, 256}, rect(512, 768, 768, 1024),
{768, 512, 256, 256}, rect(768, 512, 1024, 768),
{768, 768, 256, 256}, rect(768, 768, 1024, 1024),
nil, nil,
}, },
@ -195,16 +194,16 @@ func TestPage(t *testing.T) {
{300, 300, -1}, {300, 300, -1},
{300, 300, -1}, {300, 300, -1},
}, },
Out: []*Rect{ Out: []*image.Rectangle{
{0, 0, 300, 300}, rect(0, 0, 300, 300),
{0, 300, 300, 300}, rect(0, 300, 300, 600),
{0, 600, 300, 300}, rect(0, 600, 300, 900),
{300, 0, 300, 300}, rect(300, 0, 600, 300),
{600, 0, 300, 300}, rect(600, 0, 900, 300),
{300, 300, 300, 300}, rect(300, 300, 600, 600),
{300, 600, 300, 300}, rect(300, 600, 600, 900),
{600, 300, 300, 300}, rect(600, 300, 900, 600),
{600, 600, 300, 300}, rect(600, 600, 900, 900),
nil, nil,
}, },
}, },
@ -239,8 +238,7 @@ func TestPage(t *testing.T) {
} }
continue continue
} }
x, y, width, height := nodes[i].Region() got := nodes[i].Region()
got := Rect{x, y, width, height}
if out == nil { if out == nil {
t.Errorf("%s: nodes[%d]: got: %v, want: %v", c.Name, i, got, nil) t.Errorf("%s: nodes[%d]: got: %v, want: %v", c.Name, i, got, nil)
continue continue