Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Photoshop Sprite Packer
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