├── .gitignore ├── README.md ├── after_effects_spritesheet_exporter ├── (support) │ ├── Sheetifier.jsx │ ├── SheetifyDialog.jsx │ ├── SheetifyDialogContents.jsx │ └── SheetifyExporter.jsx ├── README.md ├── Sheetify.jsx └── spritesheet_test.aep ├── photoshop_spritesheet_exporter ├── README.md ├── Sprite Sheet from Photoshop Layers.jsx └── sample_images │ ├── SheetTest00000.psd │ ├── SheetTest00001.psd │ ├── SheetTest00002.psd │ ├── SheetTest00003.psd │ ├── SheetTest00004.psd │ ├── SheetTest00005.psd │ ├── SheetTest00006.psd │ ├── SheetTest00007.psd │ ├── SheetTest00008.psd │ ├── SheetTest00009.psd │ ├── SheetTest00010.psd │ ├── SheetTest00011.psd │ ├── SheetTest00012.psd │ ├── SheetTest00013.psd │ ├── SheetTest00014.psd │ └── SheetTest00015.psd ├── screenshots └── after_effects_spritesheet_exporter.gif └── spritesheet_splitter ├── README.md ├── Sprite Sheet Splitter.jsx └── TestSheet.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/Adobe\ After\ Effects\ Auto-Save/ 3 | **/*Logs/ 4 | **/SpriteSheets/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sprite Sheet Scripts for Adobe Creative Suite 2 | 3 | A set of handy scripts for automating the manipulation of tiled sprite sheets within Adobe Creative Suite. The scripts have been written with Unreal Engine content creators in mind, but they may prove useful for other purposes. 4 | 5 | ## Script 1: Sprite Sheet from After Effects Composition 6 | 7 | This script enables the user to create a sprite sheet directly from an After Effects composition and have it saved automatically in multiple different sizes. 8 | 9 | ![After Effects Spritesheet Exporter](screenshots/after_effects_spritesheet_exporter.gif) 10 | 11 | ## Script 2: Sprite Sheet from Photoshop Layers 12 | 13 | This script works much the same as the first one, except it is intended to be used from within Photoshop rather than After Effects. This may be useful in cases where the source images already exist or weren't created in After Effects. The user can either import a set of image files from the file system, or use the layers from the current document. 14 | 15 | ## Script 3: Sprite Sheet Splitter 16 | 17 | This script is designed to split an existing sprite sheet back into individual frames so that they can be manipulated and rearranged easily. This may be useful when the source files are not available for a particular sprite sheet. 18 | -------------------------------------------------------------------------------- /after_effects_spritesheet_exporter/(support)/Sheetifier.jsx: -------------------------------------------------------------------------------- 1 | #include "SheetifyExporter.jsx" 2 | 3 | /** 4 | * Sheetifier: Generates sprite sheets from After Effects compositions. 5 | */ 6 | function Sheetifier(sourceComp, config) 7 | { 8 | this.config = config; 9 | this.sourceComp = sourceComp; 10 | this.destComp = null; 11 | 12 | /** 13 | * Creates a sprite sheet according to the supplied configuration and exports the desired output files. 14 | */ 15 | this.sheetify = function() 16 | { 17 | var currentFrame = 1; 18 | var frameWidth = this.config["frameSize"]["width"]; 19 | var frameHeight = this.config["frameSize"]["height"]; 20 | 21 | // Create destination composition. 22 | this.createDestComp(); 23 | this.destComp.openInViewer(); 24 | 25 | // Import the source composition once per frame and shift its start time back one frame. 26 | for(var row = this.config["numFrames"]["rows"]-1; row >= 0; --row) 27 | { 28 | for(var col = this.config["numFrames"]["cols"]-1; col >= 0; --col) 29 | { 30 | var frame = this.destComp.layers.add(this.sourceComp); 31 | 32 | // Move frame into position. 33 | frame.property("Transform").property("Anchor Point").setValue([0,0]); 34 | frame.property("Transform").property("Position").setValue([col * frameWidth, row * frameHeight]); 35 | 36 | // Shift start time so all sub-frames appear at destComp's first frame. 37 | frame.startTime = this.timeAtFrame(currentFrame) - this.timeAtFrame(this.config["numFrames"]["total"]); 38 | 39 | ++currentFrame; 40 | } 41 | } 42 | 43 | this.export(); 44 | this.deleteDestComp(); 45 | }; 46 | 47 | /** 48 | * Creates the composition to which the sprite sheet will be drawn. 49 | * Settings such as pixel aspect and frame rate will match those of the source composition. 50 | */ 51 | this.createDestComp = function() 52 | { 53 | this.destComp = app.project.items.addComp( 54 | config["sheetName"] + "_spritesheet", 55 | config["sheetPixelSize"]["width"], 56 | config["sheetPixelSize"]["height"], 57 | this.sourceComp.pixelAspect, 58 | this.timeAtFrame(1), 59 | this.sourceComp.frameRate 60 | ); 61 | } 62 | 63 | this.deleteDestComp = function() 64 | { 65 | this.destComp.remove(); 66 | this.destComp = null; 67 | } 68 | 69 | /** 70 | * Returns the time of the given frame in seconds. 71 | */ 72 | this.timeAtFrame = function(frame) 73 | { 74 | return frame / this.sourceComp.frameRate; 75 | } 76 | 77 | this.export = function() 78 | { 79 | var exporter = new SheetifyExporter(this.destComp, this.config); 80 | exporter.export(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /after_effects_spritesheet_exporter/(support)/SheetifyDialog.jsx: -------------------------------------------------------------------------------- 1 | #include "SheetifyDialogContents.jsx" 2 | 3 | /** 4 | * Allows the value of 'this' to be explicity set in event listeners. 5 | */ 6 | if(!('bind' in Function.prototype)) 7 | { 8 | Function.prototype.bind = function() 9 | { 10 | var fn = this, 11 | context = arguments[0], 12 | args = Array.prototype.slice.call(arguments, 1); 13 | return function() 14 | { 15 | return fn.apply(context, args.concat([].slice.call(arguments))); 16 | } 17 | } 18 | } 19 | 20 | /** 21 | * SheetifyDialog: A dialog box that captures various options for exporting a sprite sheet. 22 | */ 23 | function SheetifyDialog(sheetName, numSourceFrames, sourceFrameWidth, sourceFrameHeight) 24 | { 25 | /** Name under which the sprite sheet will be saved. */ 26 | this.sheetName = sheetName; 27 | 28 | /** Number of cols/rows the user wishes the sheet to have. */ 29 | this.numDesiredCols = 4; 30 | this.numDesiredRows = 4; 31 | 32 | /** Width/height of source frames. */ 33 | this.sourceFrameWidth = sourceFrameWidth; 34 | this.sourceFrameHeight = sourceFrameHeight; 35 | 36 | /** Number of source frames available. */ 37 | this.numSourceFrames = numSourceFrames; 38 | 39 | /** Dialog box object. */ 40 | this.dialog = new SheetifyDialogContents(); 41 | 42 | /** If true, the user has cancelled the dialog. */ 43 | this.cancelled = false; 44 | 45 | /** 46 | * Returns a string for debugging purposes. 47 | */ 48 | this.toString = function() 49 | { 50 | return JSON.stringify(this.config()); 51 | }; 52 | 53 | /** 54 | * Returns a hash containing the configuration the user has entered. 55 | */ 56 | this.config = function() 57 | { 58 | return { 59 | sheetName: this.sheetName, 60 | frameSize: { 61 | width: this.sourceFrameWidth, 62 | height: this.sourceFrameHeight 63 | }, 64 | numFrames: { 65 | cols: this.numDesiredCols, 66 | rows: this.numDesiredRows, 67 | total: this.numDestinationFrames() 68 | }, 69 | sheetPixelSize: this.sheetPixelSize(), 70 | outputSizes: this.outputSizes() 71 | }; 72 | } 73 | 74 | /** 75 | * Returns the number of frames that will be rendered given the number of desired cols/rows. 76 | */ 77 | this.numDestinationFrames = function() 78 | { 79 | return this.numDesiredCols * this.numDesiredRows; 80 | }; 81 | 82 | /** 83 | * Returns the 'disparity' in the number of destination frames versus the number of source frames available. 84 | * If negative: Not all source frames will appear in the sprite sheet. 85 | * If zero: All source frames will be used. 86 | * If positive: The sprite sheet will contain blank frames. 87 | */ 88 | this.disparity = function() 89 | { 90 | return this.numDestinationFrames() - this.numSourceFrames; 91 | }; 92 | 93 | /** 94 | * Returns a string describing how the sprite sheet will look. See disparity() for details. 95 | */ 96 | this.disparityString = function() 97 | { 98 | var disparity = this.disparity(); 99 | if(disparity == 0) 100 | return "Sheet will be filled perfectly."; 101 | else if(disparity < 0) 102 | return -disparity + " " + (disparity === -1 ? "frame" : "frames") + " will be left out."; 103 | else if(disparity > 0) 104 | return "Sheet will contain " + disparity + " empty " + (disparity == 1 ? "frame." : "frames."); 105 | else 106 | return "Invalid cols/rows input."; 107 | }; 108 | 109 | /** 110 | * Returns the colour to use for the disparity string. 111 | */ 112 | this.disparityColour = function() 113 | { 114 | if(this.disparity() == 0) 115 | return this.dialog.greenPen; 116 | 117 | return this.dialog.redPen; 118 | } 119 | 120 | /** 121 | * Returns true if the resultant sprite sheet will be perfectly square. 122 | */ 123 | this.isSquare = function() 124 | { 125 | return ( 126 | this.numDesiredCols == this.numDesiredRows && 127 | this.sourceFrameWidth == this.sourceFrameHeight 128 | ); 129 | }; 130 | 131 | /** 132 | * Returns the pixel size of the resultant sprite sheet encapsulated in a JS object. 133 | */ 134 | this.sheetPixelSize = function() { 135 | return { 136 | width: this.sourceFrameWidth * this.numDesiredCols, 137 | height: this.sourceFrameHeight * this.numDesiredRows 138 | }; 139 | }; 140 | 141 | /** 142 | * Returns a string representing the retultant sprite sheet's size and whether or not it will be square. 143 | */ 144 | this.sheetPixelSizeString = function() 145 | { 146 | var size = this.sheetPixelSize(); 147 | return size.width + " x " + size.height + (this.isSquare() ? "" : " (not square)"); 148 | } 149 | 150 | /** 151 | * Returns a set of dimensions that is most likely to produce a square sprite sheet. Used for populating the dialog with initial values. 152 | * TODO: At present, this just returns the nearest perfect square root. Should probably take into account the source frame size to better support rectangular frames. 153 | */ 154 | this.bestCellConfiguration = function() 155 | { 156 | var x = Math.round(Math.sqrt(this.numSourceFrames)); 157 | return { 158 | rows: x, 159 | cols: x 160 | }; 161 | } 162 | 163 | /** 164 | * Returns an array containing each size to be exported. 165 | */ 166 | this.outputSizes = function() 167 | { 168 | var sizes = []; 169 | if(this.dialog.sizeSquareGroup.enabled) 170 | { 171 | // Add all selected square sizes 172 | for(var i = 0; i < this.dialog.squareOutputSizeElements.length; ++i) 173 | { 174 | var item = this.dialog.squareOutputSizeElements[i]; 175 | if(item["checkbox"].value === true) 176 | sizes.push(item["size"]); 177 | } 178 | } 179 | else 180 | { 181 | // Add original size 182 | var pixelSize = this.sheetPixelSize(); 183 | sizes.push({ 184 | width: pixelSize.width, 185 | height: pixelSize.height 186 | }) 187 | } 188 | 189 | return sizes; 190 | }; 191 | 192 | /** 193 | * Synchronises internal configuration with UI input values. 194 | */ 195 | this.updateConfig = function() 196 | { 197 | this.sheetName = this.dialog.filenameText.text; 198 | this.numDesiredCols = this.dialog.numColsText.text; 199 | this.numDesiredRows = this.dialog.numRowsText.text; 200 | }; 201 | 202 | /** 203 | * Recalculates dimensions area text. 204 | */ 205 | this.updateDimensions = function() 206 | { 207 | // Calculate dimensions. 208 | var total = this.numDesiredCols + " x " + this.numDesiredRows + " (" + (this.numDesiredCols * this.numDesiredRows) + ")"; 209 | this.dialog.totalLabel.text = "Frame dimensions: " + total; 210 | this.dialog.squareLabel.text = "Pixel dimensions: " + this.sheetPixelSizeString(); 211 | this.dialog.disparityLabel.text = this.disparityString(); 212 | 213 | // Determine colours to be used. 214 | this.dialog.totalLabel.graphics.foregroundColor = (this.isSquare() ? this.dialog.greenPen : this.dialog.yellowPen); 215 | this.dialog.squareLabel.graphics.foregroundColor = (this.isSquare() ? this.dialog.greenPen : this.dialog.yellowPen); 216 | this.dialog.disparityLabel.graphics.foregroundColor = this.disparityColour(); 217 | }; 218 | 219 | /** 220 | * Updates the comments next to each checkbox. 221 | */ 222 | this.updateSquareSizeComments = function() 223 | { 224 | var largestSensibleSize = { 225 | width: this.numDesiredCols * this.sourceFrameWidth, 226 | height: this.numDesiredRows * this.sourceFrameHeight 227 | } 228 | 229 | for(var i = 0; i < this.dialog.squareOutputSizeElements.length; ++i) 230 | { 231 | var label = this.dialog.squareOutputSizeElements[i]; 232 | if(largestSensibleSize.width >= label.size.width || largestSensibleSize.height >= label.size.height) 233 | this.dialog.setOK(label.comment, label.checkbox); 234 | else 235 | this.dialog.setNotRecommended(label.comment, label.checkbox); 236 | } 237 | } 238 | 239 | /** 240 | * Enables the square sizes group, disabling the non-square group. 241 | */ 242 | this.activateSquareSizesGroup = function() 243 | { 244 | this.dialog.sizeSquareGroup.enabled = true; 245 | this.dialog.sizeOriginalGroup.enabled = false; 246 | this.dialog.sizeOriginalComment.graphics.foregroundColor = this.dialog.greyPen; 247 | this.dialog.sizeOriginalComment.text = "Unavailable"; 248 | } 249 | 250 | /** 251 | * Enables the non-square group, disabling the square sizes group. 252 | */ 253 | this.activateOriginalSizesGroup = function() 254 | { 255 | this.dialog.sizeOriginalGroup.enabled = true; 256 | this.dialog.sizeSquareGroup.enabled = false; 257 | this.dialog.sizeOriginalComment.text = "OK"; 258 | 259 | if(this.dialog.sizeOriginalCheckbox.value === true) 260 | this.dialog.sizeOriginalComment.graphics.foregroundColor = this.dialog.greenPen; 261 | else 262 | this.dialog.sizeOriginalComment.graphics.foregroundColor = this.dialog.greyPen; 263 | } 264 | 265 | /** 266 | * Updates the dialog contents according to the desired number of cols/rows. 267 | */ 268 | this.update = function() 269 | { 270 | this.updateConfig(); 271 | this.updateDimensions(); 272 | 273 | // Activate appropriate checkbox group. 274 | if(this.isSquare()) 275 | { 276 | this.activateSquareSizesGroup(); 277 | this.updateSquareSizeComments(); 278 | } 279 | else 280 | { 281 | this.activateOriginalSizesGroup(); 282 | } 283 | }; 284 | 285 | /** 286 | * Handler for cancel button. 287 | */ 288 | this.cancel = function() 289 | { 290 | this.cancelled = true; 291 | this.dialog.window.close(); 292 | }; 293 | 294 | /** 295 | * Handler for OK button. 296 | */ 297 | this.ok = function() 298 | { 299 | // TODO: Tidy this up 300 | var boxChecked = false; 301 | for(var i = 0; i < this.dialog.squareOutputSizeElements.length; ++i) 302 | { 303 | if(this.dialog.squareOutputSizeElements[i].checkbox.value === true) 304 | boxChecked = true; 305 | } 306 | 307 | if(this.dialog.sizeOriginalCheckbox.value === true) 308 | boxChecked = true; 309 | 310 | // Ensure at least one output checkbox is checked. 311 | if(boxChecked) 312 | this.dialog.window.close(); 313 | else 314 | alert("At least one output size must be checked."); 315 | } 316 | 317 | /** 318 | * Adds event listeners to dialog box buttons. 319 | */ 320 | this.bindButtons = function() 321 | { 322 | // Close the dialog when the cancel button is pressed. 323 | this.dialog.cancelButton.onClick = this.cancel.bind(this); 324 | this.dialog.okButton.onClick = this.ok.bind(this); 325 | } 326 | 327 | /** 328 | * Adds event listeners to text inputs. 329 | */ 330 | this.bindTextInputs = function() 331 | { 332 | // Update the dialog when the desired rows/cols is changed. 333 | this.dialog.numColsText.addEventListener('changing', this.update.bind(this), false); 334 | this.dialog.numRowsText.addEventListener('changing', this.update.bind(this), false); 335 | 336 | // Update when the sheet name changes. 337 | this.dialog.filenameText.addEventListener('changing', this.update.bind(this), false); 338 | } 339 | 340 | /** 341 | * Adds event listeners to checkboxes. 342 | */ 343 | this.bindCheckboxes = function() 344 | { 345 | // Update when checkboxes are clicked. 346 | this.dialog.sizeOriginalCheckbox.onClick = this.update.bind(this); 347 | for(var i = 0; i < this.dialog.squareOutputSizeElements.length; ++i) 348 | var checkbox = this.dialog.squareOutputSizeElements[i].checkbox.onClick = this.update.bind(this); 349 | } 350 | 351 | /** 352 | * Populates the dialog box with initial values. 353 | */ 354 | this.populateDialog = function() 355 | { 356 | var bestCellConfiguration = this.bestCellConfiguration(); 357 | 358 | this.dialog.filenameText.text = this.sheetName; 359 | this.dialog.numColsText.text = bestCellConfiguration.cols; 360 | this.dialog.numRowsText.text = bestCellConfiguration.rows; 361 | this.dialog.numFramesLabel.text = this.numSourceFrames + " frames detected."; 362 | this.dialog.numFramesLabelPen = this.dialog.whitePen; 363 | } 364 | 365 | /** 366 | * Populates and shows the dialog box. 367 | */ 368 | this.show = function() 369 | { 370 | // Bind UI actions. 371 | this.bindButtons(); 372 | this.bindTextInputs(); 373 | this.bindCheckboxes(); 374 | 375 | // Add initial values. 376 | this.populateDialog(); 377 | 378 | // Make sure everything is up to date, then show the dialog. 379 | this.update(); 380 | this.dialog.window.show(); 381 | }; 382 | } 383 | -------------------------------------------------------------------------------- /after_effects_spritesheet_exporter/(support)/SheetifyDialogContents.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * SheetifyDialogContents: Defines the contents of a SheetifyDialog, separating presentation from logic. 3 | */ 4 | function SheetifyDialogContents() 5 | { 6 | /** Definition of the dialog box. */ 7 | this.contents = "dialog { \ 8 | text: 'Sheetify', \ 9 | alignChildren: 'fill', \ 10 | closeButton: 'true', \ 11 | \ 12 | filenamePanel: Panel { \ 13 | text: 'Output File', \ 14 | alignChildren: 'fill', \ 15 | orientation: 'row', \ 16 | margins: [15,15,15,15], \ 17 | \ 18 | filenameLabel: StaticText { text: 'Name' }, \ 19 | filenameText: EditText { characters: 30 }, \ 20 | } \ 21 | \ 22 | sheetOptionsPanel: Panel { \ 23 | text: 'Sprite Sheet Options', \ 24 | alignChildren: 'left', \ 25 | orientation: 'column', \ 26 | margins: [15,15,15,15], \ 27 | \ 28 | numFramesLabel: StaticText {}, \ 29 | \ 30 | dimensionsGroup: Group { \ 31 | alignChildren: 'left', \ 32 | orientation: 'column', \ 33 | \ 34 | colsGroup: Group { \ 35 | orientation: 'row', \ 36 | numColsLabel: StaticText { text: 'Cols:', characters: 5 }, \ 37 | numColsText: EditText { characters: 3 }, \ 38 | } \ 39 | \ 40 | rowsGroup: Group { \ 41 | orientation: 'row', \ 42 | numRowsLabel: StaticText { text: 'Rows:', characters: 5 }, \ 43 | numRowsText: EditText { characters: 3 }, \ 44 | } \ 45 | \ 46 | totalLabel: StaticText { \ 47 | characters: 35 \ 48 | } \ 49 | \ 50 | squareLabel: StaticText { \ 51 | characters: 35 \ 52 | } \ 53 | \ 54 | disparityLabel: StaticText { \ 55 | characters: 35 \ 56 | } \ 57 | } \ 58 | } \ 59 | \ 60 | saveOptionsPanel: Panel { \ 61 | text: 'Output Size Options', \ 62 | alignChildren: 'left', \ 63 | orientation: 'row', \ 64 | margins: [15,15,15,15], \ 65 | \ 66 | sizesGroup: Group { \ 67 | orientation: 'column', \ 68 | \ 69 | sizeOriginalGroup: Group { \ 70 | orientation: 'row', \ 71 | sizeOriginalLabel: StaticText { text: 'Original', characters: 10 }, \ 72 | sizeOriginalCheckbox: Checkbox { value: false }, \ 73 | sizeOriginalComment: StaticText { text: '', characters: 15 }, \ 74 | } \ 75 | \ 76 | sizeSquareGroup: Group { \ 77 | orientation: 'column', \ 78 | size8192Group: Group { \ 79 | orientation: 'row', \ 80 | size8192Label: StaticText { text: '8192x8192', characters: 10 }, \ 81 | size8192Checkbox: Checkbox { value: false }, \ 82 | size8192Comment: StaticText { text: '', characters: 15 }, \ 83 | } \ 84 | \ 85 | size4096Group: Group { \ 86 | orientation: 'row', \ 87 | size4096Label: StaticText { text: '4096x4096', characters: 10 }, \ 88 | size4096Checkbox: Checkbox { value: false }, \ 89 | size4096Comment: StaticText { text: '', characters: 15 }, \ 90 | } \ 91 | \ 92 | size2048Group: Group { \ 93 | orientation: 'row', \ 94 | size2048Label: StaticText { text: '2048x2048', characters: 10 }, \ 95 | size2048Checkbox: Checkbox { value: false }, \ 96 | size2048Comment: StaticText { text: '', characters: 15 }, \ 97 | } \ 98 | \ 99 | size1024Group: Group { \ 100 | orientation: 'row', \ 101 | size1024Label: StaticText { text: '1024x1024', characters: 10 }, \ 102 | size1024Checkbox: Checkbox { value: false }, \ 103 | size1024Comment: StaticText { text: '', characters: 15 }, \ 104 | } \ 105 | \ 106 | size512Group: Group { \ 107 | orientation: 'row', \ 108 | size512Label: StaticText { text: '512x512', characters: 10 }, \ 109 | size512Checkbox: Checkbox { value: false }, \ 110 | size512Comment: StaticText { text: '', characters: 15 }, \ 111 | } \ 112 | \ 113 | size256Group: Group { \ 114 | orientation: 'row', \ 115 | size256Label: StaticText { text: '256x256', characters: 10 }, \ 116 | size256Checkbox: Checkbox { value: false }, \ 117 | size256Comment: StaticText { text: '', characters: 15 }, \ 118 | } \ 119 | \ 120 | size128Group: Group { \ 121 | orientation: 'row', \ 122 | size128Label: StaticText { text: '128x128', characters: 10 }, \ 123 | size128Checkbox: Checkbox { value: false }, \ 124 | size128Comment: StaticText { text: '', characters: 15 }, \ 125 | } \ 126 | } \ 127 | } \ 128 | } \ 129 | \ 130 | buttonGroup: Group { \ 131 | orientation: 'row', \ 132 | alignment: 'right', \ 133 | \ 134 | cancelButton: Button { \ 135 | text: 'Cancel' \ 136 | } \ 137 | \ 138 | okButton: Button { \ 139 | text: 'OK' \ 140 | } \ 141 | } \ 142 | }"; 143 | 144 | this.window = new Window(this.contents); 145 | 146 | // References to UI elements to prevent us from going dotty 147 | this.filenameText = this.window.filenamePanel.filenameText; 148 | this.sheetOptionsPanel = this.window.sheetOptionsPanel; 149 | this.numFramesLabel = this.sheetOptionsPanel.numFramesLabel; 150 | this.numColsText = this.sheetOptionsPanel.dimensionsGroup.colsGroup.numColsText; 151 | this.numRowsText = this.sheetOptionsPanel.dimensionsGroup.rowsGroup.numRowsText; 152 | this.totalLabel = this.sheetOptionsPanel.dimensionsGroup.totalLabel; 153 | this.squareLabel = this.sheetOptionsPanel.dimensionsGroup.squareLabel; 154 | this.disparityLabel = this.sheetOptionsPanel.dimensionsGroup.disparityLabel; 155 | this.saveOptionsPanel = this.window.saveOptionsPanel; 156 | this.sizeOriginalGroup = this.saveOptionsPanel.sizesGroup.sizeOriginalGroup; 157 | this.sizeOriginalCheckbox = this.sizeOriginalGroup.sizeOriginalCheckbox; 158 | this.sizeOriginalComment = this.sizeOriginalGroup.sizeOriginalComment; 159 | this.sizeSquareGroup = this.window.saveOptionsPanel.sizesGroup.sizeSquareGroup; 160 | this.size8192Checkbox = this.sizeSquareGroup.size8192Group.size8192Checkbox; 161 | this.size8192Comment = this.sizeSquareGroup.size8192Group.size8192Comment; 162 | this.size4096Checkbox = this.sizeSquareGroup.size4096Group.size4096Checkbox; 163 | this.size4096Comment = this.sizeSquareGroup.size4096Group.size4096Comment; 164 | this.size2048Checkbox = this.sizeSquareGroup.size2048Group.size2048Checkbox; 165 | this.size2048Comment = this.sizeSquareGroup.size2048Group.size2048Comment; 166 | this.size1024Checkbox = this.sizeSquareGroup.size1024Group.size1024Checkbox; 167 | this.size1024Comment = this.sizeSquareGroup.size1024Group.size1024Comment; 168 | this.size512Checkbox = this.sizeSquareGroup.size512Group.size512Checkbox; 169 | this.size512Comment = this.sizeSquareGroup.size512Group.size512Comment; 170 | this.size256Checkbox = this.sizeSquareGroup.size256Group.size256Checkbox; 171 | this.size256Comment = this.sizeSquareGroup.size256Group.size256Comment; 172 | this.size128Checkbox = this.sizeSquareGroup.size128Group.size128Checkbox; 173 | this.size128Comment = this.sizeSquareGroup.size128Group.size128Comment; 174 | this.cancelButton = this.window.buttonGroup.cancelButton; 175 | this.okButton = this.window.buttonGroup.okButton; 176 | 177 | /** Predefined text colours. */ 178 | this.greyPen = this.window.graphics.newPen(this.window.graphics.PenType.SOLID_COLOR, [0.55, 0.55, 0.55], 1); 179 | this.whitePen = this.window.graphics.newPen(this.window.graphics.PenType.SOLID_COLOR, [1.0, 1.0, 1.0], 1); 180 | this.redPen = this.window.graphics.newPen(this.window.graphics.PenType.SOLID_COLOR, [1.0, 0.0, 0.0], 1); 181 | this.greenPen = this.window.graphics.newPen(this.window.graphics.PenType.SOLID_COLOR, [0.0, 1.0, 0.0], 1); 182 | this.yellowPen = this.window.graphics.newPen(this.window.graphics.PenType.SOLID_COLOR, [1.0, 1.0, 0.0], 1); 183 | 184 | /** Cached UI labels/checkboxes for output sizes. */ 185 | this.squareOutputSizeElements = [ 186 | { size: { width: 128, height: 128 }, comment: this.size128Comment, checkbox: this.size128Checkbox }, 187 | { size: { width: 256, height: 256 }, comment: this.size256Comment, checkbox: this.size256Checkbox }, 188 | { size: { width: 512, height: 512 }, comment: this.size512Comment, checkbox: this.size512Checkbox }, 189 | { size: { width: 1024, height: 1024 }, comment: this.size1024Comment, checkbox: this.size1024Checkbox }, 190 | { size: { width: 2048, height: 2048 }, comment: this.size2048Comment, checkbox: this.size2048Checkbox }, 191 | { size: { width: 4096, height: 4096 }, comment: this.size4096Comment, checkbox: this.size4096Checkbox }, 192 | { size: { width: 8192, height: 8192 }, comment: this.size8192Comment, checkbox: this.size8192Checkbox } 193 | ] 194 | 195 | /** 196 | * Changes the given size label to "OK", indicating that the output size is sensible. 197 | */ 198 | this.setOK = function(sizeElementLabel, sizeElementCheckbox) 199 | { 200 | sizeElementLabel.text = "OK"; 201 | if(sizeElementCheckbox.value === true) 202 | sizeElementLabel.graphics.foregroundColor = this.greenPen; 203 | else 204 | sizeElementLabel.graphics.foregroundColor = this.greyPen; 205 | }; 206 | 207 | /** 208 | * Changes the given size label to "Not recommended", indicating that the output size will be unnecessarily large. 209 | */ 210 | this.setNotRecommended = function(sizeElementLabel, sizeElementCheckbox) 211 | { 212 | sizeElementLabel.text = "Not recommended"; 213 | if(sizeElementCheckbox.value === true) 214 | sizeElementLabel.graphics.foregroundColor = this.yellowPen; 215 | else 216 | sizeElementLabel.graphics.foregroundColor = this.greyPen; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /after_effects_spritesheet_exporter/(support)/SheetifyExporter.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * SheetifyExporter: Exports a composition to file/s. 3 | */ 4 | function SheetifyExporter(sourceComp, config) 5 | { 6 | /** Name of the output folder. TODO: Use a File dialog to pick output location. */ 7 | this.outputFolderName = "SpriteSheets" 8 | 9 | /** Composition containing the spritesheet to be exported. */ 10 | this.sourceComp = sourceComp; 11 | 12 | /** Configuration options specified by the user. */ 13 | this.config = config; 14 | 15 | /** Scaled destination compositions. */ 16 | this.scaledComps = []; 17 | 18 | /** 19 | * Exports the spritesheet to whatever file sizes/formats the user specified. 20 | */ 21 | this.export = function() 22 | { 23 | var queue = app.project.renderQueue; 24 | this.createOutputFolder(); 25 | 26 | // Create scaled destination composition for each output size. 27 | for(var i = 0; i < this.config.outputSizes.length; ++i) 28 | { 29 | var scaledComp = this.createScaledComp(this.config.outputSizes[i]); 30 | var renderItem = queue.items.add(scaledComp); 31 | var renderFile = new File(this.outputFolderName + "/" + this.config.sheetName + "_" + this.config.outputSizes[i].width + "x" + this.config.outputSizes[i].height + ".psd"); 32 | 33 | renderItem.outputModules[1].file = renderFile; 34 | renderItem.outputModules[1].applyTemplate("Photoshop"); // TODO: Support custom templates 35 | } 36 | 37 | // Render and remove all compositions. 38 | queue.render(); 39 | this.removeScaledComps(); 40 | } 41 | 42 | /** 43 | * Creates the output folder if it doesn't exist. 44 | */ 45 | this.createOutputFolder = function() 46 | { 47 | var outputFolder = new Folder(this.outputFolderName); 48 | if(!outputFolder.exists) 49 | { 50 | if(!outputFolder.create()) 51 | throw Error("Could not create output folder. Make sure \"Allow Scripts to Write Files and Access Network\" is enabled under Edit->Preferences->General."); 52 | } 53 | }; 54 | 55 | /** 56 | * Returns a new composition containing the source composition scaled to reach the given size. 57 | */ 58 | this.createScaledComp = function(size) 59 | { 60 | // Add new composition with the desired size. 61 | var scaledComp = app.project.items.addComp( 62 | config.sheetName + "_spritesheet_" + size.width + "x" + size.height, 63 | size.width, 64 | size.height, 65 | this.sourceComp.pixelAspect, 66 | 1 / sourceComp.frameRate, 67 | this.sourceComp.frameRate 68 | ); 69 | 70 | // Copy source composition into the new one. 71 | var frame = scaledComp.layers.add(this.sourceComp); 72 | 73 | // Scale layer to fill new composition. 74 | var xScale = size.width / this.sourceComp.width * 100; 75 | var yScale = size.height / this.sourceComp.height * 100; 76 | 77 | frame.property("Transform").property("Anchor Point").setValue([0,0]); 78 | frame.property("Transform").property("Position").setValue([0,0]); 79 | frame.property("Transform").property("Scale").setValue([xScale, yScale]); 80 | 81 | this.scaledComps.push(scaledComp); 82 | return scaledComp; 83 | } 84 | 85 | /** 86 | * Removes all temporary compositions that were used for resizing. 87 | */ 88 | this.removeScaledComps = function() 89 | { 90 | while(this.scaledComps.length > 0) 91 | this.scaledComps.pop().remove(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /after_effects_spritesheet_exporter/README.md: -------------------------------------------------------------------------------- 1 | # Sprite Sheet from AE Composition 2 | 3 | This script enables the user to export an After Effects composition to a sprite sheet with uniformly-sized cells. If the resultant spritesheet will be a square image, the user is also given the option of automatically resizing and saving the sprite sheet in multiple different common sizes/resolutions. 4 | 5 | ![After Effects Spritesheet Exporter](../screenshots/after_effects_spritesheet_exporter.gif) 6 | 7 | ## Installation 8 | 9 | 1. Copy the contents of this directory to the following location: 10 | 11 | * **Windows:** `\Support Files\Scripts` 12 | * **OSX:** `~/Applications/Adobe After Effects /Scripts` 13 | 14 | ## Usage 15 | 16 | 1. With a project open and your composition selected, run the script via the *File->Scripts* menu. For testing, a sample After Effects project called `spritesheet_test.aep` has been provided. 17 | 18 | 1. In the dialog box: 19 | 20 | 1. Enter a name in the top section. This will be used as the filename prefix for your output files. For instance, if you enter `T_MySpriteSheet`, the output filename will be called `T_MySpriteSheet_2048.psd`, or similar. 21 | 1. Enter the number of rows and columns you desire. This should add up to the number of frames in the work area to avoid extraneous or missing frames. 22 | 1. Select the image sizes you wish to save. 23 | 1. Click OK. 24 | 25 | 1. The exported sprite sheet images will be exported into the same directory as the After Effects project (ie. `\SpriteSheets\`). 26 | -------------------------------------------------------------------------------- /after_effects_spritesheet_exporter/Sheetify.jsx: -------------------------------------------------------------------------------- 1 | #script "Sheetify" 2 | #target aftereffects 3 | #include "(support)/SheetifyDialog.jsx" 4 | #include "(support)/Sheetifier.jsx" 5 | 6 | /** 7 | * Finds the active composition, prompts the user for options, and renders the spritesheet. 8 | */ 9 | function main() 10 | { 11 | // Get project. 12 | var project = app.project; 13 | if(!project) 14 | { 15 | alert("No project open. Please select a project before running this script."); 16 | return; 17 | } 18 | 19 | // Get source composition. 20 | var sourceComp; 21 | if(project.items.length == 1) 22 | { 23 | // Only one composition to choose from, so it doesn't matter whether it's active or not. 24 | sourceComp = project.items[1]; 25 | } 26 | else if(project.items.length > 1 && project.activeItem instanceof CompItem) 27 | { 28 | // If a composition is currently active, assume we're rendering that one. 29 | sourceComp = project.activeItem; 30 | } 31 | else 32 | { 33 | alert("No active composition found. Please select the composition you would like to convert from the Project pane."); 34 | return; 35 | } 36 | 37 | // Get composition properties. 38 | var numSourceFrames = Math.floor(sourceComp.workAreaDuration * sourceComp.frameRate) + 1; 39 | 40 | // Show options dialog. 41 | var sheetifyDialog = new SheetifyDialog(sourceComp.name, numSourceFrames, sourceComp.width, sourceComp.height); 42 | sheetifyDialog.show(); 43 | 44 | if(!sheetifyDialog.cancelled) 45 | { 46 | // Sheetify! 47 | var sheetifier = new Sheetifier(sourceComp, sheetifyDialog.config()); 48 | sheetifier.sheetify(); 49 | 50 | alert("Spritesheets have been saved to the following directory:\n\n" + app.project.file.absoluteURI.split(".aep")[0] + "/SpriteSheets/") 51 | } 52 | } 53 | 54 | main(); 55 | -------------------------------------------------------------------------------- /after_effects_spritesheet_exporter/spritesheet_test.aep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/after_effects_spritesheet_exporter/spritesheet_test.aep -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/README.md: -------------------------------------------------------------------------------- 1 | # Sprite Sheet from Photoshop Layers Intended for Unreal Engine content creators, this script enables the user to import a set of image files into Photoshop and have them automatically arranged into a grid-like sprite sheet. The user is also given the option of automatically resizing and saving the sprite sheet in multiple different sizes/resolutions. Feel free to distribute and mangle this script as you like, but please give credit where it is due. If you find a bug or add any cool new features, drop me a line at [rohanliston.com](http://www.rohanliston.com) or submit a pull request. ## Installation Place this script in the following directory: * **Windows:** `\Presets\Scripts` * **Mac:** `~/Applications/Adobe Photoshop CS#/Presets/Scripts` This will make it accessible from the *File->Scripts* menu in Photoshop. ## Usage 1. Run the script via the *File->Scripts* menu. A prompt will appear asking whether you wish to import a set of source images, or to use the layers in the current document. Select the appropriate option. 2. In the next prompt: 1. If you are auto-saving the resultant sheet, enter a name in the top section. This will be used as the prefix for your output files (eg. `T_MySpriteSheet_2048.png`). 2. Enter the number of rows and columns you desire. **This must add up to the number of layers in the document**. 3. If you wish to auto-save, select the image sizes you wish to save, as well as the output file format. 4. Click OK. 3. If you chose to auto-save, select the output folder. That's it, you're done! -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/Sprite Sheet from Photoshop Layers.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MakeSpriteSheet_Photoshop.jsx 3 | * 4 | * Intended for UDK/UE3 content creators, this script enables the user to import a set of image files into 5 | * Photoshop and have them automatically arranged into a grid-like sprite sheet. The user is also given 6 | * the option of automatically resizing and saving the sprite sheet in multiple different sizes/resolutions. 7 | * 8 | * @author Rohan Liston 9 | * @version 1.0 10 | * @date 14-Feb-2012 11 | * 12 | * Feel free to distribute and mangle this script as you like, but please give credit where it is due. 13 | * If you find a bug or come up with any cool new features, drop me a line at http://www.rohanliston.com ! 14 | */ 15 | 16 | #target photoshop 17 | #script "Make Sprite Sheet" 18 | 19 | var acceptableSheetSizes = [2048, 1024, 512, 256, 128, 64]; // List of acceptable square sheet sizes (in pixels) 20 | 21 | // Note: any values assigned to the variables below will appear in the showDialog() box by default. 22 | var spriteSheetDoc; // The document that will contain the sprite sheet 23 | var userCancelled = false; // Whether the user has cancelled from any of the dialog boxes 24 | var numCols = 4, numRows = 4; // Number of rows/columns in the sheet 25 | var sheetSizes = [2048, 1024]; // Array of sheet sizes we wish to export 26 | var autoSave = true; // Whether we want to resize and save the sheet automatically 27 | var fileFormat = "PNG"; // Which file format we wish to export as 28 | var bitDepth = "32"; // Bit depth (ie. 24 or 32-bit) 29 | var exportFolder; // Where the document will be saved 30 | var sheetFilename = "T_Untitled"; // Export filename for the sprite sheet 31 | 32 | /** 33 | * Extension of Array prototype - Array.contains(obj) 34 | */ 35 | Array.prototype.contains = function(obj) 36 | { 37 | var i = this.length; 38 | while(i--) 39 | { 40 | if(this[i] == obj) 41 | return true; 42 | } 43 | return false; 44 | } 45 | 46 | main(); 47 | 48 | function main() 49 | { 50 | try 51 | { 52 | // Prompt the user for a list of files to import to layers 53 | if(confirm("Do you wish to import image files into the layer stack now? Choose No if you have already imported images into the layer stack.")) 54 | { 55 | importFiles(); 56 | } 57 | else 58 | { 59 | if(app.documents.length == 0) 60 | throw Error("Please create or open a document first."); 61 | 62 | spriteSheetDoc = app.activeDocument; 63 | } 64 | 65 | if(spriteSheetDoc.artLayers.length <= 1) 66 | throw Error("Not enough layers to make a sprite sheet."); 67 | 68 | // If the user's settings are valid, make the sprite sheet 69 | getValidSettings(); 70 | makeSpriteSheet(); 71 | 72 | if(autoSave) 73 | saveFiles(); 74 | 75 | } 76 | catch(e) 77 | { 78 | if(e.toString() != "exit") 79 | alert(e.toString()); 80 | } 81 | } 82 | 83 | /** 84 | * Continually shows the user settings dialog until the user has input valid data or cancelled. 85 | */ 86 | function getValidSettings() 87 | { 88 | // Show the sprite sheet settings dialog 89 | showDialog(); 90 | if(userCancelled) 91 | throw "exit"; 92 | 93 | if(!validateSheet()) 94 | getValidSettings(); 95 | } 96 | 97 | 98 | /** 99 | * Prompts the user for a list of files to import. 100 | */ 101 | function importFiles() 102 | { 103 | // Open the file select dialog 104 | var fileList = File.openDialog("Select the images you wish to convert into a sprite sheet.", "Image files:*.psd;*.bmp;*.jpg;*.jpeg;*.jpe;*.png;*.tga;*.tif;*.tiff", true); 105 | 106 | if(fileList != null) 107 | { 108 | // Create a new document with the same dimensions as the first image in the sequence 109 | var tempDoc = app.open(fileList[0]); 110 | spriteSheetDoc = app.documents.add(tempDoc.width, tempDoc.height); 111 | tempDoc.close(); 112 | 113 | // Import each image into the new sprite sheet document 114 | for(i=0; i < fileList.length; i++) 115 | { 116 | tempDoc = app.open(fileList[i]); 117 | tempDoc.selection.selectAll(); 118 | tempDoc.selection.copy(); 119 | app.activeDocument = spriteSheetDoc; 120 | spriteSheetDoc.paste(); 121 | tempDoc.close(); 122 | } 123 | 124 | // Get rid of the background layer 125 | spriteSheetDoc.artLayers["Background"].remove(); 126 | } 127 | else 128 | { 129 | throw "exit"; // User cancelled 130 | } 131 | } 132 | 133 | /** 134 | * Checks whether the resulting sheet will be square and a power of two (eg. 1024x1024) 135 | */ 136 | function validateSheet() 137 | { 138 | var finalWidth = numCols * spriteSheetDoc.width; 139 | var finalHeight = numRows * spriteSheetDoc.height; 140 | var squareSheet = finalWidth == finalHeight; 141 | var powerOfTwo = acceptableSheetSizes.contains(Math.sqrt(finalWidth * finalHeight)); 142 | 143 | // Make sure the sheet has an appropriate name 144 | if(autoSave == true && (sheetFilename == "" || sheetFilename == "T_Untitled")) 145 | { 146 | alert("Please give the sprite sheet an acceptable name."); 147 | return false; 148 | } 149 | 150 | // Make sure the layer count matches the number of frames in the sheet 151 | if(numCols*numRows != spriteSheetDoc.artLayers.length) 152 | { 153 | alert("The number of frames in the document (" + spriteSheetDoc.artLayers.length + ") does not match the number of frames in the desired sprite sheet (" + numCols*numRows + ")."); 154 | return false; 155 | } 156 | 157 | // Only warn if not square AND not a power of two - if it's square we can just resize it 158 | if(!squareSheet && !powerOfTwo) 159 | { 160 | if(!confirm("WARNING: Resulting sheet size will be " + finalWidth + " by " + finalHeight + ", which is not optimal for UE3/UDK. If you proceed, your sprite sheet will not be auto-saved. Proceed?")) 161 | throw "exit"; 162 | else 163 | autoSave = false; 164 | } 165 | 166 | return true; 167 | } 168 | 169 | /** 170 | * Arranges the document layers into a sprite sheet. 171 | */ 172 | function makeSpriteSheet() 173 | { 174 | // Get the document/frame properties 175 | var activeLayer = spriteSheetDoc.activeLayer; 176 | var numLayers = spriteSheetDoc.artLayers.length; 177 | var spriteSizeX = spriteSheetDoc.width; 178 | var spriteSizeY = spriteSheetDoc.height; 179 | 180 | // Resize the canvas 181 | spriteSheetDoc.resizeCanvas(spriteSizeX*numCols, spriteSizeY*numRows, AnchorPosition.TOPLEFT ); 182 | 183 | var currentRow = 0; 184 | var currentCol = 0; 185 | 186 | // Move each layer into the correct spot 187 | for(i=numLayers-1; i>=0; i--) 188 | { 189 | spriteSheetDoc.artLayers[i].visible = 1; 190 | spriteSheetDoc.artLayers[i].translate(spriteSizeX*currentCol, spriteSizeY*currentRow); 191 | 192 | currentCol++; 193 | if(currentCol > numCols-1) 194 | { 195 | currentRow++; 196 | currentCol = 0; 197 | } 198 | } 199 | 200 | // Merge everything together 201 | spriteSheetDoc.mergeVisibleLayers(); 202 | } 203 | 204 | /** 205 | * Saves the files in whatever sizes were specified in the After Effects dialog. 206 | */ 207 | function saveFiles() 208 | { 209 | app.activeDocument = spriteSheetDoc; 210 | 211 | exportFolder = Folder.selectDialog("Choose the folder in which you wish to save the sprite sheet.") 212 | if(exportFolder == null) 213 | throw "exit"; // User cancelled 214 | 215 | for(i=0; i < sheetSizes.length; i++) 216 | { 217 | saveFile(parseInt(sheetSizes[i])); 218 | } 219 | } 220 | 221 | /** 222 | * Saves the document with the (square) dimensions specified. 223 | * @param size - The size of the image in pixels (eg. 2048, 1024, etc) 224 | */ 225 | function saveFile(size) 226 | { 227 | // Save the current state so we can revert back after resizing/saving 228 | var savedState = spriteSheetDoc.activeHistoryState; 229 | var sheetFile = new File(exportFolder + "/" + sheetFilename + "_" + size); 230 | var saveOptions; 231 | 232 | // Set the file save options 233 | if(fileFormat == "PNG") 234 | { 235 | saveOptions = new PNGSaveOptions(); 236 | saveOptions.interlaced = false; 237 | } 238 | else 239 | { 240 | saveOptions = new TargaSaveOptions(); 241 | saveOptions.resolution = bitDepth == "32" ? TargaBitsPerPixels.THIRTYTWO : TargaBitsPerPixels.TWENTYFOUR; 242 | saveOptions.alphaChannels = bitDepth == "32" ? true : false; 243 | saveOptions.rleCompression = true; 244 | } 245 | 246 | // Resize, save, then revert back to original state 247 | spriteSheetDoc.resizeImage(size, size); 248 | spriteSheetDoc.saveAs(sheetFile, saveOptions, true, Extension.LOWERCASE); 249 | spriteSheetDoc.activeHistoryState = savedState; 250 | } 251 | 252 | /** 253 | * Shows a dialog box with various sprite sheet options for the user to select 254 | */ 255 | function showDialog() 256 | { 257 | var dialog = new Window("dialog {text:'Make Sprite Sheet', alignChildren:['fill','center']}"); 258 | 259 | var filenameOptions = dialog.add("panel {text:'Sheet Name', alignChildren:'left', orientation:'row', margins:[15,15,15,15]}"); 260 | var filenameGroup = filenameOptions.add("group {alignChildren:'left', orientation:'row'}"); 261 | var filenameLabel = filenameGroup.add("statictext {text:'Name:'}"); 262 | var filenameText = filenameGroup.add("edittext {text:'" + sheetFilename + "', characters:20}"); 263 | 264 | var sheetOptions = dialog.add("panel {text:'Sprite Sheet Options', alignChildren:'left', orientation:'row', margins:[15,15,15,15]}"); 265 | 266 | var rowsColsGroup = sheetOptions.add("group {alignChildren:'left', orientation:'column'}"); 267 | 268 | var colsGroup = rowsColsGroup.add("group"); 269 | var numColsText = colsGroup.add("edittext {text:'" + numCols + "', characters:3}"); 270 | var numColsLabel = colsGroup.add("statictext {text:'Columns'}"); 271 | 272 | var rowsGroup = rowsColsGroup.add("group"); 273 | var numRowsText = rowsGroup.add("edittext {text:'" + numRows + "', characters:3, enabled:" + (numCols == numRows ? "false" : "true") + "}"); 274 | var numRowsLabel = rowsGroup.add("statictext {text:'Rows', enabled:" + (numCols == numRows ? "false" : "true") + "}"); 275 | 276 | var separator1 = sheetOptions.add("panel {alignment:['center','fill']}"); 277 | var equalRowsGroup = sheetOptions.add("group"); 278 | var equalRowsBox = equalRowsGroup.add("checkbox {text:'Rows = Cols', value:" + (numCols == numRows ? "true" : "false") + "}"); 279 | 280 | var fileOptions = dialog.add("panel {text:'File Save Options', margins:[15,15,15,15]}"); 281 | var autoSaveBox = fileOptions.add("checkbox {text:'Automatically save output files', value:" + autoSave.toString() + ", orientation:'row'}"); 282 | var fileOptionsInner = fileOptions.add("group {orientation:'row', spacing:25}"); 283 | var resOptions = fileOptionsInner.add("group {orientation:'column'}"); 284 | var formatOptions = fileOptionsInner.add("group {orientation:'column'}"); 285 | 286 | var group2048 = resOptions.add("group"); 287 | var label2048 = group2048.add("statictext {text:'2048x2048', characters:9, justify:'right', name:'2048'}"); 288 | var box2048 = group2048.add("checkbox {value:" + sheetSizes.contains(2048) + "}"); 289 | 290 | var group1024 = resOptions.add("group"); 291 | var label1024 = group1024.add("statictext {text:'1024x1024', characters:9, justify:'right', name:'1024'}"); 292 | var box1024 = group1024.add("checkbox {value:" + sheetSizes.contains(1024) + "}"); 293 | 294 | var group512 = resOptions.add("group"); 295 | var label512 = group512.add("statictext {text:'512x512', characters:9, justify:'right', name:'512'}"); 296 | var box512 = group512.add("checkbox {value:" + sheetSizes.contains(512) + "}"); 297 | 298 | var group256 = resOptions.add("group"); 299 | var label256 = group256.add("statictext {text:'256x256', characters:9, justify:'right', name:'256'}"); 300 | var box256 = group256.add("checkbox {value:" + sheetSizes.contains(256) + "}"); 301 | 302 | var group128 = resOptions.add("group"); 303 | var label128 = group128.add("statictext {text:'128x128', characters:9, justify:'right', name:'128'}"); 304 | var box128 = group128.add("checkbox {value:" + sheetSizes.contains(128) + "}"); 305 | 306 | var group64 = resOptions.add("group"); 307 | var label64 = group64.add("statictext {text:'64x64', characters:9, justify:'right', name:'64'}"); 308 | var box64 = group64.add("checkbox {value:" + sheetSizes.contains(64) + "}"); 309 | 310 | var formatMenu = formatOptions.add("dropdownlist {title:'Format:', characters:9, justify:'right'}"); 311 | formatMenu.add("item","PNG"); 312 | formatMenu.add("item","TGA"); 313 | formatMenu.selection = fileFormat == "PNG" ? formatMenu.items[0] : formatMenu.items[1]; 314 | 315 | var radio24Bit = formatOptions.add("radiobutton {text:'24-bit', enabled:" + (fileFormat == "PNG" ? "false" : "true") + ", value:" + (bitDepth == "24" ? "true" : "false") + "}"); 316 | var radio32Bit = formatOptions.add("radiobutton {text:'32-bit', value:" + (bitDepth == "32" ? "true" : "false") + "}"); 317 | fileOptionsInner.enabled = autoSave; 318 | 319 | var buttonGroup = dialog.add("group {alignChildren:['fill','center']}"); 320 | var OKButton = buttonGroup.add("button", undefined, "OK"); 321 | var cancelButton = buttonGroup.add("button", undefined, "Cancel"); 322 | 323 | // Make the number of rows match the number of columns if necessary 324 | numColsText.onChanging = function() 325 | { 326 | if(equalRowsBox.value == true) 327 | numRowsText.text = numColsText.text; 328 | } 329 | 330 | // When the checkbox is clicked, enable/disable the second input box 331 | equalRowsBox.onClick = function() 332 | { 333 | numRowsLabel.enabled = !numRowsLabel.enabled; 334 | numRowsText.enabled = !numRowsText.enabled; 335 | 336 | if(equalRowsBox.value == true) 337 | numRowsText.text = numColsText.text; 338 | } 339 | 340 | // When the autosave checkbox is clicked, enable/disable the file save options 341 | autoSaveBox.onClick = function() 342 | { 343 | fileOptionsInner.enabled = autoSaveBox.value; 344 | } 345 | 346 | // Event handler for OK button - writes info to variables to be saved in writeOutputFile() 347 | OKButton.onClick = function() 348 | { 349 | var index = 0; 350 | sheetFilename = filenameText.text.toString(); 351 | numCols = parseInt(numColsText.text); 352 | numRows = parseInt(numRowsText.text); 353 | 354 | sheetSizes = new Array(); 355 | // Read the sheet size checkboxes and add the active ones to the sheetSizes array 356 | for(i=0; i < resOptions.children.length; i++) 357 | { 358 | if(resOptions.children[i].children[1].value == true) 359 | { 360 | sheetSizes[index] = resOptions.children[i].children[0].name; 361 | index++; 362 | } 363 | } 364 | 365 | autoSave = (autoSaveBox.value == true && sheetSizes.length > 0); 366 | 367 | fileFormat = formatMenu.selection.toString(); 368 | bitDepth = radio32Bit.value == true ? "32" : "24"; 369 | 370 | dialog.close(); 371 | } 372 | 373 | // Event handler for Cancel button 374 | cancelButton.onClick = function() 375 | { 376 | userCancelled = true; 377 | dialog.close(); 378 | } 379 | 380 | // Event handler for changing formats drop-down 381 | formatMenu.onChange = function() 382 | { 383 | if(formatMenu.selection.toString() == "PNG") 384 | { 385 | radio24Bit.value = false; 386 | radio32Bit.value = true; 387 | radio24Bit.enabled = false; 388 | } 389 | else if(formatMenu.selection.toString() == "TGA") 390 | { 391 | radio24Bit.enabled = true; 392 | } 393 | } 394 | 395 | dialog.center(); 396 | dialog.show(); 397 | } -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00000.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00000.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00001.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00001.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00002.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00002.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00003.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00003.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00004.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00004.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00005.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00005.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00006.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00006.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00007.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00007.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00008.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00008.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00009.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00009.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00010.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00010.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00011.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00011.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00012.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00012.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00013.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00013.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00014.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00014.psd -------------------------------------------------------------------------------- /photoshop_spritesheet_exporter/sample_images/SheetTest00015.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/photoshop_spritesheet_exporter/sample_images/SheetTest00015.psd -------------------------------------------------------------------------------- /screenshots/after_effects_spritesheet_exporter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/screenshots/after_effects_spritesheet_exporter.gif -------------------------------------------------------------------------------- /spritesheet_splitter/README.md: -------------------------------------------------------------------------------- 1 | # Sprite Sheet Splitter 2 | 3 | Intended for Unreal Engine content creators, this script enables the user to split a grid-like sprite sheet 4 | into Photoshop layers. After editing, they can be re-assembled using the "Sprite Sheet from Photoshop Layers" script. 5 | 6 | Feel free to distribute and mangle this script as you like, but please give credit where it is due. 7 | If you find a bug or add any cool new features, drop me a line at [rohanliston.com](http://www.rohanliston.com) or submit a pull request. 8 | 9 | ## Installation 10 | 11 | 1. Place this script in the following directory: 12 | 13 | * **Windows:** `\Presets\Scripts` 14 | * **Mac:** `~/Adobe Photoshop CS#/Presets/Scripts` 15 | 16 | This will make it accessible from the *File->Scripts* menu in Photoshop. 17 | 18 | ## Usage 19 | 20 | 1. Run the script via the *File->Scripts* menu. 21 | 2. In the next prompt, input the number of rows and columns in the sprite sheet. 22 | 3. The image will be broken up into however many Photoshop layers you specified. -------------------------------------------------------------------------------- /spritesheet_splitter/Sprite Sheet Splitter.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * SpriteSheetSplitter.jsx 3 | * 4 | * Splits a grid-based sprite sheet into individual images. 5 | * 6 | * @author Rohan Liston 7 | * @version 1.0 8 | * @date 14-Feb-2012 9 | * 10 | * Feel free to distribute and mangle this script as you like, but please give credit where it is due. 11 | * If you find a bug or come up with any cool new features, drop me a line at http://www.rohanliston.com ! 12 | */ 13 | 14 | var numCols, numRows; 15 | var userCancelled = false; 16 | main(); 17 | 18 | function main() 19 | { 20 | if (documents.length > 0) 21 | { 22 | // Use pixels for all document measurements 23 | app.preferences.rulerUnits = Units.PIXELS; 24 | 25 | showDialog(); 26 | if(userCancelled) 27 | return; 28 | 29 | app.bringToFront(); 30 | 31 | if(numCols > 0 && numRows > 0) 32 | splitSheet(); 33 | else 34 | alert("Error: Bad input."); 35 | } 36 | else 37 | { 38 | alert("Error: There are no open documents."); 39 | } 40 | } 41 | 42 | /** 43 | * Splits the sheet up into (numCols*numRows) images 44 | * and pastes them as separate layers in a new document. 45 | */ 46 | function splitSheet() 47 | { 48 | var originalDoc = activeDocument; 49 | 50 | // Calculate number of sub-images and their dimensions 51 | var numLayers = numCols * numRows; 52 | var spriteWidth = originalDoc.width / numCols; 53 | var spriteHeight = originalDoc.height / numRows; 54 | var currentRow = 0; 55 | var currentCol = 0; 56 | 57 | // Create a new document 58 | var newDoc = app.documents.add(spriteWidth, spriteHeight); 59 | 60 | // Iterate through the sub-images and copy them into a new document 61 | // This is done last->first so that no re-ordering of layers has to be done in the new document 62 | for(i=0; i < numLayers; i++) 63 | { 64 | // Bring the original document to the front 65 | app.activeDocument = originalDoc; 66 | 67 | // Work out coordinates of the selection box 68 | var topLeftX = currentCol * spriteWidth; 69 | var topLeftY = currentRow * spriteHeight; 70 | var topRightX = topLeftX + spriteWidth; 71 | var topRightY = topLeftY; 72 | var bottomLeftX = topLeftX; 73 | var bottomLeftY = topLeftY + spriteHeight; 74 | var bottomRightX = topLeftX + spriteWidth; 75 | var bottomRightY = bottomLeftY; 76 | 77 | var shapeRef = [[topLeftX, topLeftY], 78 | [topRightX, topRightY], 79 | [bottomRightX, bottomRightY], 80 | [bottomLeftX, bottomLeftY]]; 81 | 82 | // Copy the selection and paste it into the new document 83 | originalDoc.selection.select(shapeRef); 84 | originalDoc.selection.copy(); 85 | app.activeDocument = newDoc; 86 | newDoc.paste(); 87 | 88 | // Go to the next col/row in the sequence 89 | currentCol++; 90 | if(currentCol > numCols-1) 91 | { 92 | currentRow++; 93 | currentCol = 0; 94 | } 95 | } 96 | 97 | // Remove the background layer in the new document 98 | newDoc.artLayers["Background"].remove(); 99 | } 100 | 101 | /** 102 | * Shows/validates the UI dialog box. 103 | */ 104 | function showDialog() 105 | { 106 | // Create a new dialog box with a single panel 107 | var dialog = new Window("dialog", "Sprite Sheet Splitter"); 108 | var sizePanel = dialog.add("panel", [0,0,215,180], "Sprite Sheet Size"); 109 | 110 | // Number of columns 111 | var numColsLabel = sizePanel.add("statictext", [25,25,150,35], "Number of columns:"); 112 | var numColsText = sizePanel.add("edittext", [145,24,185,43], 4); 113 | 114 | // Number of rows 115 | var numRowsLabel = sizePanel.add("statictext", [25,55,150,65], "Number of rows:"); 116 | var numRowsText = sizePanel.add("edittext", [145,54,185,73], 4); 117 | numRowsLabel.enabled = false; 118 | numRowsText.enabled = false; 119 | 120 | // Checkbox for making the number of cols/rows the same 121 | var equalRowsLabel = sizePanel.add("statictext", [25,85,150,95], "Equal cols/rows:"); 122 | var equalRowsBox = sizePanel.add("checkbox", [145,85,175,105]); 123 | equalRowsBox.value = true; 124 | 125 | // When the checkbox is clicked, enable/disable the second input box 126 | equalRowsBox.onClick = function() 127 | { 128 | numRowsLabel.enabled = !numRowsLabel.enabled; 129 | numRowsText.enabled = !numRowsText.enabled; 130 | 131 | if(equalRowsBox.value == true) 132 | numRowsText.text = numColsText.text; 133 | } 134 | 135 | // Make the number of rows match the number of columns if necessary 136 | numColsText.onChanging = function() 137 | { 138 | if(equalRowsBox.value == true) 139 | numRowsText.text = numColsText.text; 140 | } 141 | 142 | // Buttons for OK/Cancel 143 | var okButton = sizePanel.add("button", [25,125,100,150], "OK", {name:'ok'}); 144 | var cancelButton = sizePanel.add("button", [110,125,185,150], "Cancel", {name:'cancel'}); 145 | 146 | // Event handler for OK button 147 | okButton.onClick = function() 148 | { 149 | numCols = parseInt(numColsText.text); 150 | numRows = parseInt(numRowsText.text); 151 | dialog.close(0); 152 | } 153 | 154 | // Event handler for Cancel button 155 | cancelButton.onClick = function() 156 | { 157 | dialog.close(); 158 | userCancelled = true; 159 | } 160 | 161 | dialog.center(); 162 | dialog.show(); 163 | } -------------------------------------------------------------------------------- /spritesheet_splitter/TestSheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohanliston/adobe-spritesheet-scripts/0fac17d51d25b27ddf413ae2f7f832d8a6f0139b/spritesheet_splitter/TestSheet.png --------------------------------------------------------------------------------