SimpleBinPackingSpriteProcessor::find()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.027

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 10
cts 11
cp 0.9091
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 11
nc 5
nop 3
crap 6.027
1
<?php
2
3
namespace CSSPrites\SpriteProcessor;
4
5
/**
6
 * Based on https://github.com/jakesgordon/bin-packing/blob/master/js/packer.js.
7
 */
8
class SimpleBinPackingSpriteProcessor extends AbstractSpriteProcessor
9
{
10
    protected $root = null;
11
12
    protected $working = null;
13
14 3
    public function process()
15
    {
16
        // Prepare working array
17 3
        $this->prepare();
18
19 3
        $maxW = 0;
20 3
        $maxH = 0;
21 3
        foreach ($this->working as $working) {
22 3
            $maxW += $working->w;
23 3
            $maxH += $working->h;
24 3
        }
25 3
        $sum = (int) (min($maxW, $maxH) / 8);
26
27 3
        while (!$this->checkAllFits()) {
28 3
            $sum = (int) ($sum * 1.05);
29 3
            $this->rePrepare($sum)->tryToFit();
30 3
        }
31
32
        // Intert into the final Image
33 3
        $this->insert();
34
35 3
        return $this;
36
    }
37
38 3
    public function prepare()
39
    {
40 3
        $this->images->sort('biggest');
41
42 3
        foreach ($this->images->get() as $idx => $image) {
43 3
            $this->working[$idx] = [
44 3
                'used' => false,
45 3
                'fit'  => false,
46 3
                'x'    => 0,
47 3
                'y'    => 0,
48 3
                'w'    => $image->getWidth() + $this->spaces,
49 3
                'h'    => $image->getHeight() + $this->spaces,
50
            ];
51 3
            $this->working[$idx] = (object) $this->working[$idx];
52 3
        }
53
54 3
        return $this;
55
    }
56
57 3
    public function rePrepare($size)
58
    {
59 3
        $this->root = [
60 3
            'used' => false,
61 3
            'x'    => 0,
62 3
            'y'    => 0,
63 3
            'w'    => $size,
64 3
            'h'    => $size,
65
        ];
66
67 3
        $this->root = (object) $this->root;
68
69 3
        foreach ($this->working as $idx => $working) {
70 3
            $this->working[$idx] = [
71 3
                'used' => false,
72 3
                'fit'  => false,
73 3
                'x'    => 0,
74 3
                'y'    => 0,
75 3
                'w'    => $working->w,
76 3
                'h'    => $working->h,
77
            ];
78 3
            $this->working[$idx] = (object) $this->working[$idx];
79 3
        }
80
81 3
        return $this;
82
    }
83
84 3
    public function tryToFit()
85
    {
86 3
        foreach ($this->working as $idx => $working) {
87 3
            $node = $this->find($this->root, $working->w, $working->h);
88 3
            if (!is_null($node)) {
89 3
                $fit                 = $this->split($node, $working->w, $working->h);
90 3
                $working->used       = true;
91 3
                $working->fit        = true;
92 3
                $working->x          = $fit->x;
93 3
                $working->y          = $fit->y;
94 3
                $this->working[$idx] = $working;
95 3
                unset($fit);
96 3
            }
97 3
        }
98
99 3
        return $this;
100
    }
101
102 3
    public function find($node, $w, $h)
103
    {
104 3
        if ($node->used === true) {
105 3
            $right = $this->find($node->right, $w, $h);
106 3
            $down  = $this->find($node->down, $w, $h);
107 3
            if (!is_null($right)) {
108 3
                return $right;
109 3
            } elseif (!is_null($down)) {
110
                return $down;
111
            }
112 3
        } elseif ($w <= $node->w && $h <= $node->h) {
113 3
            return $node;
114
        }
115
116 3
        return;
117
    }
118
119 3
    public function split($node, $w, $h)
120
    {
121 3
        $node->used  = true;
122
123 3
        $node->down = [
124 3
            'used' => false,
125 3
            'x'    => $node->x,
126 3
            'y'    => $node->y + $h,
127 3
            'w'    => $node->w,
128 3
            'h'    => $node->h - $h,
129
        ];
130
131 3
        $node->right = [
132 3
            'used' => false,
133 3
            'x'    => $node->x + $w,
134 3
            'y'    => $node->y,
135 3
            'w'    => $node->w - $w,
136 3
            'h'    => $h,
137
        ];
138
139 3
        $node->down  = (object) $node->down;
140 3
        $node->right = (object) $node->right;
141
142 3
        return $node;
143
    }
144
145
    // Check if all images fits
146 3
    public function checkAllFits()
147
    {
148 3
        foreach ($this->working as $working) {
149 3
            if ($working->fit === false) {
150 3
                return false;
151
            }
152 3
        }
153
154 3
        return true;
155
    }
156
157
    // Reduce the final image size
158 3
    public function reduce()
159
    {
160 3
        $width  = 0;
161 3
        $height = 0;
162 3
        foreach ($this->working as $working) {
163 3
            $width  = max($width, $working->x + $working->w);
164 3
            $height = max($height, $working->y + $working->h);
165 3
        }
166 3
        $this->root->w = $width - $this->spaces;
167 3
        $this->root->h = $height - $this->spaces;
168
169 3
        return $this;
170
    }
171
172
    // Create the final image
173 3
    public function insert()
174
    {
175 3
        $this->reduce();
176
177 3
        $this->image = $this->imageProcessor->create($this->root->w, $this->root->h, $this->background);
178 3
        foreach ($this->working as $idx => $working) {
179 3
            $image = $this->images->get($idx);
180 3
            $image->setX($working->x)->setY($working->y);
181
182 3
            $img = $this->imageProcessor->load($image->getFilepath());
183 3
            $this->image->insert($img, $image->getX(), $image->getY());
184 3
        }
185
186 3
        return $this;
187
    }
188
}
189