Created
October 14, 2017 09:53
-
-
Save oiehot/995ff20353bbe7b210a41360c86294e4 to your computer and use it in GitHub Desktop.
Photoshop Sprite Packer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SPRITE_MARGIN = 2 | |
function rect_str(r) { | |
return "x: " + r.x + ", y: " + r.y + ", w: " + r.w + ", h: " + r.h | |
} | |
function rects_str(rects) { | |
str = "" | |
for(var i=0; i<rects.length; i++) { | |
str += "["+i+"]" + rect_str(rects[i]) + "\n" | |
} | |
return str | |
} | |
function Space(x, y, w, h) { | |
return { | |
x: x, | |
y: y, | |
w: w, | |
h: h | |
} | |
} | |
function Sprite(doc, layer) { | |
var bounds = [ | |
layer.bounds[0].value, | |
layer.bounds[1].value, | |
layer.bounds[2].value, | |
layer.bounds[3].value | |
] | |
bounds[0] -= SPRITE_MARGIN | |
bounds[1] -= SPRITE_MARGIN | |
bounds[2] += SPRITE_MARGIN | |
bounds[3] += SPRITE_MARGIN | |
return { | |
layer_idx: -1, | |
name: layer.name, | |
x: bounds[0], | |
y: bounds[1], | |
w: bounds[2] - bounds[0], | |
h: bounds[3] - bounds[1], | |
background: layer.isBackgroundLayer | |
} | |
} | |
function Sprites(doc) { | |
var sprites = [] | |
for(var i=0; i<doc.layers.length; i++) { | |
var layer = doc.layers[i] | |
sprite = Sprite(doc, layer) | |
sprite.layer_idx = i | |
sprites.push(sprite) | |
} | |
return sprites | |
} | |
// 스프라이트를 배치할 수 있는 공간의 인덱스를 리턴한다. | |
function get_placementable_space_idx(sprite, spaces) { | |
for (var i=0; i<spaces.length; i++) { | |
if ( sprite.w <= spaces[i].w && sprite.h <= spaces[i].h ) { | |
return i | |
} | |
} | |
return -1 | |
} | |
// width, height가 0보다 작은 공간은 제외한다. | |
function remove_negative_spaces(spaces) { | |
var result = [] | |
for (var i=0; i<spaces.length; i++) { | |
if ( spaces[i].w > 0 && spaces[i].h > 0 ) { | |
result.push(spaces[i]) | |
} | |
} | |
return result | |
} | |
// 두 rect가 교차하는가? | |
function is_intersect(r1, r2) { | |
r1.left = r1.x | |
r1.right = r1.x + r1.w | |
r1.top = r1.y | |
r1.bottom = r1.y + r1.h | |
r2.left = r2.x | |
r2.right = r2.x + r2.w | |
r2.top = r2.y | |
r2.bottom = r2.y + r2.h | |
if (r1.left < r2.right && r1.right > r2.left && | |
r1.top < r2.bottom && r1.bottom > r2.top ) return true | |
else return false | |
} | |
function is_include(rect, spaces) { | |
var a = rect | |
for (var i=0; i<spaces.length; i++) { | |
b = spaces[i] | |
if (a.x >= b.x && | |
a.y >= b.y && | |
a.x + a.w <= b.x + b.w && | |
a.y + a.h <= b.y + b.h) { | |
return true | |
} | |
} | |
return false | |
} | |
// 포함되는 공간을 필터링한다. | |
function remove_include_spaces(rects, spaces) { | |
var result = [] | |
for (var i=0; i<rects.length; i++) { | |
rect = rects[i] | |
if( is_include(rect, spaces) == false ) { | |
result.push(rect) | |
} | |
} | |
return result | |
} | |
// 스프라이트와 겹치는 공간들을 분할한다. | |
function intersect_divide(sprite, spaces) { | |
for (var i=0; i<spaces.length; i++) { | |
var space = spaces[i] | |
// 겹치는 경우: 배치한 영역 외의 잉여공간을 큼지막하게 나눠 재사용한다. | |
if (is_intersect(sprite, space)) { | |
top = Space(space.x, space.y, space.w, sprite.y-space.y) | |
left = Space(space.x, space.y, sprite.x-space.x, space.h) | |
right = Space(sprite.x+sprite.w, space.y, space.x+space.w-(sprite.x+sprite.w), space.h) | |
bottom = Space(space.x, sprite.y+sprite.h, space.w, space.y+space.h-(sprite.y+sprite.h) ) | |
surplus = [top, left, right, bottom] | |
spaces.splice(i, 1); i--; // 분할 전(root) 공간을 제거한다. | |
surplus = remove_negative_spaces(surplus) // 음수인 공간은 존재하지 않으므로 제거한다. | |
surplus = remove_include_spaces(surplus, spaces) // 다른 공간에 포함되는 공간은 제거한다. | |
spaces = spaces.concat(surplus) // 새로 생긴 공간들을 끝에 추가한다. | |
} | |
} | |
return spaces | |
} | |
// MaxRect 알고리즘으로 공간을 분할하며 스프라이트의 좌표를 변경한다. | |
function calc_placement(sprites, spaces) { | |
// 배치순서를 정한다. 그림 크기가 큰것 부터 배치하도록 한다. | |
sprites = sprites.sort(function(a, b){ return a.w - b.w }) | |
// 모든 스프라이트를 차례대로 탐색한다. | |
for(var i=0; i<sprites.length; i++) { | |
sprite = sprites[i] | |
if (sprite.background) continue; // 배경은 생략한다. | |
// 스프라이트를 배치할 수 있는 공간을 찾는다. | |
root_space_idx = get_placementable_space_idx(sprite, spaces) | |
if (root_space_idx == -1) { | |
sprite.placement = false | |
continue // 배치 가능한 공간이 없으면 다음 스프라이트로 넘어간다. | |
} | |
// 일단 스프라이트를 root공간의 좌상단에 배치한다. | |
space = spaces[root_space_idx] | |
sprite.x = space.x | |
sprite.y = space.y | |
spaces = intersect_divide(sprite, spaces) // 모든 공간에 대해서 겹침(intersect)여부를 확인하고, 겹치면 공간을 분할한다. | |
sprite.placement = true // 배치 성공. | |
} | |
return sprites | |
} | |
// 배치 결과에 따라, 실제 레이어들을 이동시킨다. | |
function apply_placement(doc, sprites) { | |
var fails = [] | |
for (var i=0; i<sprites.length; i++) { | |
sprite = sprites[i] | |
layer = doc.layers[sprite.layer_idx] | |
if (sprite.background) continue; // 배경인 경우 생략. | |
if (sprite.placement) { // 배치된 경우: | |
prev_x = layer.bounds[0].value | |
prev_y = layer.bounds[1].value | |
new_x = sprite.x | |
new_y = sprite.y | |
delta_x = new_x - prev_x | |
delta_y = new_y - prev_y | |
layer.translate(delta_x, delta_y) | |
} else { // 배치되지 않은 경우: | |
fails.push(layer) | |
layer.visible = false | |
} | |
} | |
// 배치에 실패한 레이어가 있다면 목록을 출력한다. | |
if (fails.length > 0) { | |
fails_str = "Placement failed:\n\n" | |
for (var i=0; i<fails.length; i++) { | |
fails_str += fails[i].name + "\n" | |
} | |
alert(fails_str) | |
} | |
} | |
function main() { | |
var doc = app.activeDocument; | |
var old_unit = app.preferences.rulerUnits; // 문서 단위를 기억해둔다. | |
app.preferences.rulerUnits = Units.PIXELS; // 픽셀 단위로 변경한다. | |
sprites = Sprites(doc) // 문서에 있는 레이어들을 스프라이트 객체화한다. | |
spaces = [Space(SPRITE_MARGIN, SPRITE_MARGIN, doc.width-SPRITE_MARGIN, doc.height-SPRITE_MARGIN)] | |
sprites = calc_placement(sprites, spaces) | |
apply_placement(doc, sprites) | |
app.preferences.rulerUnits = old_unit; // 기존 문서 단위로 복귀한다. | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment