Photoshop 애니메이션 레이어를 SVG로 자동 변환하기
Photoshop에서 작업한 애니메이션 레이어를 SVG 파일로 자동 변환하는 방법을 소개합니다. 이 방법을 통해 웹 애니메이션 제작 workflow를 개선할 수 있습니다.
// @target photoshop
// 현재 열린 PSD 파일의 레이어를 애니메이션 가능한 SVG로 변환
// SVG 헤더와 푸터 템플릿
var SVG_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ';
function main() {
if (!app.documents.length) {
alert("열린 문서가 없습니다.");
return;
}
var doc = app.activeDocument;
var docPath = doc.path;
var docName = doc.name.split('.')[0];
// SVG 저장할 폴더 생성
var svgFolder = new Folder(docPath + "/" + docName + "_svg");
if (!svgFolder.exists) {
svgFolder.create();
}
// 문서 크기 정보
var width = doc.width.value;
var height = doc.height.value;
// 모든 레이어의 원래 상태 저장
var originalStates = saveLayerStates(doc.layers);
try {
// 개별 레이어 SVG 생성
var layerInfos = processLayers(doc, doc.layers, svgFolder, width, height);
// 애니메이션 SVG 생성
createAnimatedSVG(layerInfos, svgFolder, width, height, docName);
alert("변환이 완료되었습니다.\n저장 위치: " + svgFolder.fsName);
} finally {
// 레이어 상태 복원
restoreLayerStates(doc.layers, originalStates);
}
}
function saveLayerStates(layers) {
var states = [];
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
if (layer.typename === "LayerSet") {
states = states.concat(saveLayerStates(layer.layers));
} else {
states.push({
layer: layer,
visible: layer.visible,
locked: layer.allLocked
});
}
}
return states;
}
function restoreLayerStates(layers, states) {
for (var i = 0; i < states.length; i++) {
var state = states[i];
state.layer.visible = state.visible;
state.layer.allLocked = state.locked;
}
}
function processLayers(doc, layers, outputFolder, width, height) {
var layerInfos = [];
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
// 레이어 그룹이면 재귀적으로 처리
if (layer.typename === "LayerSet") {
layerInfos = layerInfos.concat(processLayers(doc, layer.layers, outputFolder, width, height));
continue;
}
// 모든 레이어 숨기기
hideAllLayers(doc.layers);
// 현재 레이어 잠금 해제 및 표시
var wasLocked = layer.allLocked;
layer.allLocked = false;
layer.visible = true;
// 임시 파일로 PNG 저장
var tempFile = new File(outputFolder + "/temp.png");
// PNG로 저장
var pngOpts = new ExportOptionsSaveForWeb();
pngOpts.format = SaveDocumentType.PNG;
pngOpts.PNG8 = false;
pngOpts.transparency = true;
doc.exportDocument(tempFile, ExportType.SAVEFORWEB, pngOpts);
// PNG를 Base64로 인코딩
var base64 = pngToBase64(tempFile);
// 개별 레이어 SVG 저장
var svgContent = SVG_HEADER +
'width="' + width + '" height="' + height + '">\n' +
'<image width="' + width + '" height="' + height + '" ' +
'xlink:href="data:image/png;base64,' + base64 + '"/>\n' +
'</svg>';
var svgFile = new File(outputFolder + "/" + layer.name + ".svg");
svgFile.open('w');
svgFile.write(svgContent);
svgFile.close();
// 임시 파일 삭제
tempFile.remove();
// 레이어 정보 저장
layerInfos.push({
name: layer.name,
base64: base64
});
// 레이어 상태 복원
layer.allLocked = wasLocked;
}
return layerInfos;
}
function createAnimatedSVG(layerInfos, outputFolder, width, height, docName) {
var duration = 0.5; // 각 레이어당 표시 시간(초)
var totalDuration = duration * layerInfos.length;
var svgContent = SVG_HEADER +
'width="' + width + '" height="' + height + '">\n' +
'<style>\n' +
'@keyframes fadeIn {\n' +
' from { opacity: 0; }\n' +
' to { opacity: 1; }\n' +
'}\n' +
'</style>\n';
// 각 레이어 이미지 추가
for (var i = 0; i < layerInfos.length; i++) {
var layer = layerInfos[i];
var delay = i * duration;
svgContent += '<image width="' + width + '" height="' + height + '" ' +
'xlink:href="data:image/png;base64,' + layer.base64 + '" ' +
'style="animation: fadeIn ' + duration + 's ease forwards; ' +
'animation-delay: ' + delay + 's; ' +
'opacity: 0;"/>\n';
}
svgContent += '</svg>';
// 애니메이션 SVG 저장
var animFile = new File(outputFolder + "/" + docName + "_animated.svg");
animFile.open('w');
animFile.write(svgContent);
animFile.close();
}
function hideAllLayers(layers) {
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
if (layer.typename === "LayerSet") {
hideAllLayers(layer.layers);
} else {
layer.visible = false;
}
}
}
function pngToBase64(file) {
// Base64 인코딩 테이블
var base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// 파일을 바이너리로 읽기
file.open('r');
file.encoding = 'BINARY';
var data = file.read();
file.close();
var result = '';
var padding = '';
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
// 3바이트씩 처리
while (i < data.length) {
chr1 = data.charCodeAt(i++);
chr2 = i < data.length ? data.charCodeAt(i++) : NaN;
chr3 = i < data.length ? data.charCodeAt(i++) : NaN;
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
padding = '==';
} else if (isNaN(chr3)) {
enc4 = 64;
padding = '=';
}
result += base64chars.charAt(enc1) +
base64chars.charAt(enc2) +
base64chars.charAt(enc3) +
base64chars.charAt(enc4);
}
return result;
}
// 스크립트 실행
main();
주요 기능
- 레이어 자동 추출
- 모든 레이어를 개별 SVG 파일로 변환
- 숨겨진 레이어와 잠긴 레이어도 처리 가능
- 원본 레이어 이름 유지
- 애니메이션 SVG 생성
- 모든 레이어를 하나의 애니메이션 SVG로 결합
- 페이드인 효과로 순차적 표시
- 타이밍 조절 가능
사용 방법
- 스크립트 설치
- JSX 파일을 Photoshop 스크립트 폴더에 복사
- Windows:
C:\Program Files\Adobe\Adobe Photoshop [버전]\Presets\Scripts
- Mac:
/Applications/Adobe Photoshop [버전]/Presets/Scripts
- 실행하기
- Photoshop에서 PSD 파일을 엽니다
- File > Scripts > ‘PSD to SVG’ 선택
- 원본 PSD와 같은 위치에 ‘_svg’ 폴더가 생성됨
활용 사례
- 웹 애니메이션
- SVG 애니메이션을 웹사이트에 바로 적용
- CSS 애니메이션으로 추가 커스터마이징 가능
- 인터랙티브 콘텐츠
- 프레젠테이션 자료 제작
- 인포그래픽 애니메이션
- 모션 그래픽
- 배너 광고 제작
- SNS 모션 콘텐츠
장점
- 시간 절약: 수동으로 레이어를 추출하고 변환하는 시간 단축
- 일관성: 모든 레이어가 동일한 설정으로 변환
- 유연성: SVG 형식으로 변환되어 웹 환경에서 활용도 높음
- 품질 유지: 벡터 기반으로 화질 저하 없음