Skip to content

Commit

Permalink
Optimal packer added
Browse files Browse the repository at this point in the history
  • Loading branch information
odrick committed Dec 13, 2019
1 parent bb59de0 commit 63722d5
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 40 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
{
"name": "Querijn Heijmans aka Querijn",
"homepage": "https://github.com/Querijn"
},
{
"name": "Timo Kämäräinen aka qtiki",
"homepage": "https://github.com/qtiki"
}
],
"scripts": {
Expand Down
95 changes: 77 additions & 18 deletions src/client/PackProcessor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import MaxRectsBinPack from './packers/MaxRectsBin';
import OptimalPacker from './packers/OptimalPacker';
import allPackers from './packers';
import Trimmer from './utils/Trimmer';
import TextureRenderer from './utils/TextureRenderer';

import I18 from './utils/I18';

Expand All @@ -13,7 +16,7 @@ class PackProcessor {
let rect1 = rects[i];
for(let n=i+1; n<rects.length; n++) {
let rect2 = rects[n];
if(rect1.image._base64 == rect2.image._base64 && identical.indexOf(rect2) < 0) {
if(rect1.image._base64 === rect2.image._base64 && identical.indexOf(rect2) < 0) {
rect2.identical = rect1;
identical.push(rect2);
}
Expand Down Expand Up @@ -76,7 +79,7 @@ class PackProcessor {
let alphaThreshold = options.alphaThreshold || 0;
if(alphaThreshold > 255) alphaThreshold = 255;

let names = Object.keys(images);
let names = Object.keys(images).sort();

for(let key of names) {
let img = images[key];
Expand Down Expand Up @@ -146,35 +149,91 @@ class PackProcessor {
identical = res.identical;
}

let getAllPackers = () => {
let methods = [];
for (let packerClass of allPackers) {
if (packerClass !== OptimalPacker) {
for (let method in packerClass.methods) {
methods.push({ packerClass, packerMethod: packerClass.methods[method] });
}
}
}
return methods;
};

let packerClass = options.packer || MaxRectsBinPack;
let packerMethod = options.packerMethod || MaxRectsBinPack.methods.BestShortSideFit;
let packerCombos = (packerClass === OptimalPacker) ? getAllPackers() : [{ packerClass, packerMethod }];

let res = [];
let optimalRes;
let optimalSheets = Infinity;
let optimalEfficiency = 0;

while(rects.length) {
let packer = new packerClass(width, height, options.allowRotation);
let result = packer.pack(rects, packerMethod);
let sourceArea = 0;
for (let rect of rects) {
sourceArea += rect.sourceSize.w * rect.sourceSize.h;
}

for(let item of result) {
item.frame.x += padding + extrude;
item.frame.y += padding + extrude;
item.frame.w -= padding*2 + extrude*2;
item.frame.h -= padding*2 + extrude*2;
}
for (let combo of packerCombos) {
let res = [];
let sheetArea = 0;

// duplicate rects if more than 1 combo since the array is mutated in pack()
let _rects = packerCombos.length > 1 ? rects.map(rect => {
return Object.assign({}, rect, {
frame: Object.assign({}, rect.frame),
spriteSourceSize: Object.assign({}, rect.spriteSourceSize),
sourceSize: Object.assign({}, rect.sourceSize)
});
}) : rects;

// duplicate identical if more than 1 combo and fix references to point to the
// cloned rects since the array is mutated in applyIdentical()
let _identical = packerCombos.length > 1 ? identical.map(rect => {
for (let rect2 of _rects) {
if (rect.identical.image._base64 == rect2.image._base64) {
return Object.assign({}, rect, { identical: rect2 });
}
}
}) : identical;

while(_rects.length) {
let packer = new combo.packerClass(width, height, options.allowRotation);
let result = packer.pack(_rects, combo.packerMethod);

for(let item of result) {
item.frame.x += padding + extrude;
item.frame.y += padding + extrude;
item.frame.w -= padding*2 + extrude*2;
item.frame.h -= padding*2 + extrude*2;
}

if(options.detectIdentical) {
result = PackProcessor.applyIdentical(result, _identical);
}

res.push(result);

for(let item of result) {
this.removeRect(_rects, item.name);
}

if(options.detectIdentical) {
result = PackProcessor.applyIdentical(result, identical);
let { width: sheetWidth, height: sheetHeight } = TextureRenderer.getSize(result, options);
sheetArea += sheetWidth * sheetHeight;
}

res.push(result);
let sheets = res.length;
let efficiency = sourceArea / sheetArea;

for(let item of result) {
this.removeRect(rects, item.name);
if (sheets < optimalSheets || (sheets === optimalSheets && efficiency > optimalEfficiency)) {
optimalRes = res;
optimalSheets = sheets;
optimalEfficiency = efficiency;
}
}

if(onComplete) {
onComplete(res);
onComplete(optimalRes);
}
}

Expand Down
34 changes: 34 additions & 0 deletions src/client/packers/OptimalPacker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Packer from "./Packer";

const METHOD = {
Automatic: "Automatic"
};

class OptimalPacker extends Packer {
constructor(width, height, allowRotate=false) {
super();
}

pack(data, method) {
throw new Error('OptimalPacker is a dummy and cannot be used directly');
}

static get type() {
return "OptimalPacker";
}

static get methods() {
return METHOD;
}

static getMethodProps(id='') {
switch(id) {
case METHOD.Automatic:
return {name: "Automatic", description: ""};
default:
throw Error("Unknown method " + id);
}
}
}

export default OptimalPacker;
6 changes: 4 additions & 2 deletions src/client/packers/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import MaxRectsPacker from "./MaxRectsPacker";
import MaxRectsBin from "./MaxRectsBin";
import OptimalPacker from "./OptimalPacker";

const list = [
MaxRectsBin,
MaxRectsPacker
MaxRectsPacker,
OptimalPacker
];

function getPackerByType(type) {
for(let item of list) {
if(item.type == type) {
if(item.type === type) {
return item;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/client/resources/static/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ html, body {

.about-content {
width: 490px;
height: 340px;
height: 360px;
background: #fff;
margin: auto;
margin-top: 86px;
Expand Down
14 changes: 7 additions & 7 deletions src/client/ui/About.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class About extends React.Component {
renderDownload() {
return (
<tr>
<td>{I18.f("ABOUT_APPS")}</td>
<td><b>{I18.f("ABOUT_APPS")}</b></td>
<td><a href={appInfo.download} target="_blank" className="color-800">{appInfo.download}</a></td>
</tr>
)
Expand All @@ -27,7 +27,7 @@ class About extends React.Component {
renderWebVersion() {
return (
<tr>
<td>{I18.f("ABOUT_WEB")}</td>
<td><b>{I18.f("ABOUT_WEB")}</b></td>
<td><a href={appInfo.webApp} target="_blank" className="color-800">{appInfo.webApp}</a></td>
</tr>
)
Expand All @@ -49,24 +49,24 @@ class About extends React.Component {
<table>
<tbody>
<tr>
<td>{I18.f("ABOUT_HOMEPAGE")}</td>
<td><b>{I18.f("ABOUT_HOMEPAGE")}</b></td>
<td><a href={appInfo.url} target="_blank" className="color-800">{appInfo.url}</a></td>
</tr>

<tr>
<td>{I18.f("ABOUT_SOURCES")}</td>
<td><b>{I18.f("ABOUT_SOURCES")}</b></td>
<td><a href={appInfo.homepage} target="_blank" className="color-800">{appInfo.homepage}</a></td>
</tr>

<tr>
<td>{I18.f("ABOUT_BUGS")}</td>
<td><b>{I18.f("ABOUT_BUGS")}</b></td>
<td><a href={appInfo.bugs.url} target="_blank" className="color-800">{appInfo.bugs.url}</a></td>
</tr>

{PLATFORM === "web" ? this.renderDownload() : this.renderWebVersion()}

<tr>
<td>{I18.f("ABOUT_LIBS")}</td>
<td><b>{I18.f("ABOUT_LIBS")}</b></td>
<td>
<div>
<a href="https://facebook.github.io/react" target="_blank" className="color-800">React</a>
Expand All @@ -90,7 +90,7 @@ class About extends React.Component {
</tr>

<tr>
<td>{I18.f("ABOUT_CONTRIBUTORS")}</td>
<td><b>{I18.f("ABOUT_CONTRIBUTORS")}</b></td>
<td>
{
appInfo.contributors.map(contributor => {
Expand Down
30 changes: 18 additions & 12 deletions src/client/utils/TextureRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ class TextureRenderer {
this.render(data, options);
}

render(data, options={}) {
let ctx = this.buffer.getContext("2d");

static getSize(data, options={}) {
let width = options.width || 0;
let height = options.height || 0;

let padding = options.padding || 0;
let extrude = options.extrude || 0;

Expand Down Expand Up @@ -48,16 +46,24 @@ class TextureRenderer {
if (options.powerOfTwo) {
let sw = Math.round(Math.log(width)/Math.log(2));
let sh = Math.round(Math.log(height)/Math.log(2));
let pw = Math.pow(2, sw);

let pw = Math.pow(2, sw);
let ph = Math.pow(2, sh);
if(pw < width) pw = Math.pow(2, sw + 1);
if(ph < height) ph = Math.pow(2, sh + 1);
width = pw;
height = ph;

if(pw < width) pw = Math.pow(2, sw + 1);
if(ph < height) ph = Math.pow(2, sh + 1);

width = pw;
height = ph;
}

return {width, height};
}

render(data, options={}) {
let ctx = this.buffer.getContext("2d");

let { width, height } = TextureRenderer.getSize(data, options);

this.width = width;
this.height = height;
Expand Down

0 comments on commit 63722d5

Please sign in to comment.