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() {
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) {

View File

@ -18,6 +18,7 @@ package packing
import (
"errors"
"fmt"
"image"
)
type Page struct {
@ -65,10 +66,7 @@ func (p *Page) IsEmpty() bool {
}
type Node struct {
x int
y int
width int
height int
region image.Rectangle
used bool
parent *Node
@ -86,8 +84,8 @@ func (n *Node) canFree() bool {
return n.child0.canFree() && n.child1.canFree()
}
func (n *Node) Region() (x, y, width, height int) {
return n.x, n.y, n.width, n.height
func (n *Node) Region() image.Rectangle {
return n.region
}
// 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 {
if n.width < width || n.height < height {
if n.region.Dx() < width || n.region.Dy() < height {
return nil
}
if n.used {
return 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
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
n.child0 = &Node{
x: n.x,
y: n.y,
width: width,
height: n.height,
region: image.Rect(n.region.Min.X, n.region.Min.Y, n.region.Min.X+width, n.region.Max.Y),
parent: n,
}
n.child1 = &Node{
x: n.x + width,
y: n.y,
width: n.width - width,
height: n.height,
region: image.Rect(n.region.Min.X+width, n.region.Min.Y, n.region.Max.X, n.region.Max.Y),
parent: n,
}
} else {
// Split horizontally
n.child0 = &Node{
x: n.x,
y: n.y,
width: n.width,
height: height,
region: image.Rect(n.region.Min.X, n.region.Min.Y, n.region.Max.X, n.region.Min.Y+height),
parent: n,
}
n.child1 = &Node{
x: n.x,
y: n.y + height,
width: n.width,
height: n.height - height,
region: image.Rect(n.region.Min.X, n.region.Min.Y+height, n.region.Max.X, n.region.Max.Y),
parent: n,
}
}
// Note: it now MUST fit, due to above preconditions (repeated here).
if n.child0.width < width || n.child0.height < 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))
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.region.Dx(), n.child0.region.Dy(), width, height))
}
// 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).
node := alloc(n.child0, width, height)
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
}
@ -183,8 +169,7 @@ func (p *Page) Alloc(width, height int) *Node {
if p.root == nil {
p.root = &Node{
width: p.width,
height: p.height,
region: image.Rect(0, 0, p.width, p.height),
}
}
return p.extendForAndAlloc(width, height)
@ -273,7 +258,7 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
aborted := false
if p.root != nil {
_ = 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
}
if n.used {
@ -295,16 +280,10 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
if newHeight-p.height > 0 {
upper := p.root
lower := &Node{
x: 0,
y: p.height,
width: p.width,
height: newHeight - p.height,
region: image.Rect(0, p.height, p.width, newHeight),
}
p.root = &Node{
x: 0,
y: 0,
width: p.width,
height: newHeight,
region: image.Rect(0, 0, p.width, newHeight),
child0: upper,
child1: lower,
}
@ -316,16 +295,10 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
if newWidth-p.width > 0 {
left := p.root
right := &Node{
x: p.width,
y: 0,
width: newWidth - p.width,
height: newHeight,
region: image.Rect(p.width, 0, newWidth, newHeight),
}
p.root = &Node{
x: 0,
y: 0,
width: newWidth,
height: newHeight,
region: image.Rect(0, 0, newWidth, newHeight),
child0: left,
child1: right,
}
@ -344,28 +317,28 @@ func (p *Page) extend(newWidth int, newHeight int) func() {
}
} else {
origWidth, origHeight := p.width, p.height
origWidths := map[*Node]int{}
origHeights := map[*Node]int{}
origMaxXs := map[*Node]int{}
origMaxYs := map[*Node]int{}
for _, n := range edgeNodes {
if n.x+n.width == p.width {
origWidths[n] = n.width
n.width += newWidth - p.width
if n.region.Max.X == p.width {
origMaxXs[n] = n.region.Max.X
n.region.Max.X = newWidth
}
if n.y+n.height == p.height {
origHeights[n] = n.height
n.height += newHeight - p.height
if n.region.Max.Y == p.height {
origMaxYs[n] = n.region.Max.Y
n.region.Max.Y = newHeight
}
}
rollback = func() {
p.width = origWidth
p.height = origHeight
for n, w := range origWidths {
n.width = w
for n, x := range origMaxXs {
n.region.Max.X = x
}
for n, h := range origHeights {
n.height = h
for n, y := range origMaxYs {
n.region.Max.Y = y
}
}
}

View File

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