Saturday, August 29, 2026

Learning dog 2d animation blocking

 


Thanks for looking


Inspired by,
Frank Thomas,
Ollie Johnston,
Glen Keane,
Milt Kahl
Alan Becker’s online YouTube tutorial on ‘12 principles of animation’
And Hopkins


Thursday, June 4, 2026

Using big brush to help sketch poses

 


















Thanks for looking



Thursday, May 21, 2026

scripting a storyboard template tool in photoshop javascript

this tool allows placing an image in a grid layout in a new photoshop document.  it has some examples for creating a simple ui in photoshop using javascript. it also has some examples of writing methods/creating variables in photoshop javascript. it also is an example of storing a photoshop ui file in separate file than the core of the tool.  there may be bugs or better ways to implement so please modify/use at your own risk.

here is a video of the tool in action:



//ui.jsx
//a simple tool to create story panels from an input image. example from a blank panel
//tested in Photoshop 10.0.1
//please modify/use at your own risk

#include "core.jsx"

/*check if input path is a valid image path
Args:
    path (str): path to check ex: Users/Nathaniel/Desktop/photoshopTools/storyPanels/test_scene.jpg
Returns:
    (bool): true if path is a valid image.
*/
function isPathValidImage(path)
{
    var fileObj = new File(path);
    var validExtensions = ["jpg", "jpeg", "png", "tif"];

    if(!fileObj.exists)
    {
        return false;
    }

    var pathExt = path.split('.').pop().toLowerCase();
    //check if path's extension is in valid extensions
    for(var i=0; i<validExtensions.length; i++)
    {
        if(pathExt == validExtensions[i])
        {
            //path is valid extension. dont need to check any more extensions
            return true;
        }
    }

    return false;
}


/*
Returns:
    (bool): true if there is a document open false otherwise
*/
function isDocumentOpen()
{
    return app.documents.length > 0
}

function showUI(){

    if(!isDocumentOpen())
    {
        alert("requires a document open to use tool.")
        return
    }

    var dlg = new Window("dialog", "Panel Importer UI");
    dlg.orientation = "column";
    dlg.preferredSize = [600, 200];
    dlg.alignChildren = "left"; //align all widgets in window to the left

    //stores options for generating panels
    var parameterGroupLabel = dlg.add("statictext", undefined, "1. choose parameters for final panel layout")
    var parameterGroup = dlg.add("group");
    parameterGroup.orientation = "row";
    var numRowsLabel = parameterGroup.add("statictext", undefined, "rows:");
    var numRowsIntField = parameterGroup.add("edittext", undefined, "2");
    numRowsIntField.characters = 4; //set width

    var numColsLabel = parameterGroup.add("statictext", undefined, "cols:");
    var numColsIntField = parameterGroup.add("edittext", undefined, "2");
    numColsIntField.characters = 4; //set width

    var borderWidthPixelsLabel = parameterGroup.add("statictext", undefined, "border width (pixels):");
    var borderWidthPixelsIntField = parameterGroup.add("edittext", undefined, "20");
    borderWidthPixelsIntField.characters = 4; //set width    
    //

    //limit int fields to numbers only
    var intFields = [numRowsIntField, numColsIntField, borderWidthPixelsIntField];    
    //limit int fields to numbers
    var sharedOnChange = function()
    {
        //alert("field changed:" + this.text);
        //remove non numeric character
        this.text = this.text.replace(/[^\d]/g, '');
    }
    //assign shared onChange function
    for(var j=0; j<intFields.length; j++)
    {
        intFields[j].onChanging = sharedOnChange;
    }
    //


    //button
    var addPathBtn = dlg.add("button", undefined, "2. add image path to use for panel")
    //stores image path
    var imagePathText = dlg.add("statictext", undefined, "No Image Selected");
    imagePathText.preferredSize = [600, 50];

    var createPanelBtn = dlg.add("button", undefined, "3. create panels from image");
    var closeBtn = dlg.add("button", undefined, "close");

    //add function to button click
    addPathBtn.onClick = function()
    {
        var fileObj = File.openDialog("Select Image");
        if(fileObj)
        {
            selectedPath = fileObj.fsName;
            if(!isPathValidImage(selectedPath))
            {
                alert("selected path not a valid image. exiting");
                return;
            }
            imagePathText.text = selectedPath;
        }

    }

    createPanelBtn.onClick = function()
    {
        var imagePath = imagePathText.text;

        //get parameters from ui
        var numRows = parseInt(numRowsIntField.text);
        var numCols = parseInt(numColsIntField.text);
        var numPanels = numRows*numCols;
        var borderWidth = parseInt(borderWidthPixelsIntField.text);

        var panelImporter = new PanelImporter(imagePath=imagePath, 
                                            numPanels=numPanels, 
                                            numRows=numRows, 
                                            numCols=numCols, 
                                            borderWidth=borderWidth);
        panelImporter.doIt();
        alert("yaay creating storyboard panels done!");
        dlg.close(); //close ui when done so active document refreshed with story panels
    }

    closeBtn.onClick = function()
    {
        dlg.close();
    }

    dlg.show();

}


showUI();

//core.jsx
//old style class with ability to import an image multiple times to form a panel layout
/*
Args:
    imagePath(str): path to image to use for each panel
    numPanels(int): number of panels to create
    numRows(int): number of rows for storyboard
    numCols(int): number of cols for storyboard
    borderWidth(int): how many pixels to separate panels
*/
function PanelImporter(imagePath, numPanels, numRows, numCols, borderWidth)//(imagePath, numPanels, numRows, numCols, borderWidth)
{

    this.doc = app.activeDocument; //assumes in a created document. todo make a document if there is none
    this._imagePath = imagePath;
    this._numPanels = numPanels;
    this._numRows = numRows;
    this._numCols = numCols;
    this._borderWidth = borderWidth;

    /*do any additional initializations for this class
    apparently cant run a method of this class in its init. when using old style function for class
    */
    this._init = function()
    {
        this._images = this._getImages(this._imagePath, this._numPanels);//will hold image paths to use for each panel
    }
    
    /*
    Args:
        imagePath(str): path to image to use for each panel ex: /Users/Nathaniel/Desktop/photoshopTools/storyPanels/test_scene.jpg
    */
    this.doIt = function()
    {
        this._init();
        this._createPanels();   
    }

    /*
    Args:
        imagePath(str): path to image to use for each panel ex: /Users/Nathaniel/Desktop/photoshopTools/storyPanels/test_scene.jpg
        numPanels(int): number of times to repeat imagePath
    Returns:
        list(str): the imagePath repeated numPanels many times
    */
    this._getImages = function(imagePath, numPanels)
    {
        var images = [];
        //var imagePath = "/Users/Nathaniel/Desktop/photoshopTools/storyPanels/test_scene.jpg";
        for(var i=0; i<numPanels; i++)
        {
            images.push(imagePath);
        }

        return images;
    }

    /*creates storyboard panels using a single image for each panel
    */
    this._createPanels = function()
    {
        //var doc = app.activeDocument;
        var targetDoc = this.doc;//app.activeDocument;
        var images = this._images;
        var numRows = this._numRows;
        var numCols = this._numCols;
        var borderWidth = this._borderWidth;


        var imageIndex = 0; //will be used to query images
        var numImages = images.length;
        if(numImages > numRows*numCols)
        {
            alert("too many images. make sure number of images is smaller than number of rows multiplied by number of columns");
            return;
        }


        for(var i=0; i<numRows; i++)
        {
            for(var j=0; j<numCols; j++)
            {
                if(imageIndex >= numImages)
                {
                    break;
                }

                var pathToImage = images[imageIndex];
                var fileObj = new File(pathToImage);


                if(!fileObj.exists){
                    alert("image file doesnt exist");
                    return;
                }

                //open image file
                var imgDoc = app.open(fileObj);

                //select all and copy
                imgDoc.selection.selectAll();
                imgDoc.selection.copy();

                

                //close the opened image file
                imgDoc.close(SaveOptions.DONOTSAVECHANGES);

                //paste image into active document
                var newLayer = targetDoc.paste();

                var originalRulerUnits = app.preferences.rulerUnits;
                app.preferences.rulerUnits = Units.PIXELS;

                //set goal x y pixels
                //width = right - left
                //bounds [left, top, right, bottom]
                var widthImage = newLayer.bounds[2].value - newLayer.bounds[0].value;
                var heightImage = newLayer.bounds[3].value - newLayer.bounds[1].value;

                var goalX = j*widthImage + (j+1)*borderWidth; //the columns are used for x coordinate
                var goalY = i*heightImage + (i+1)*borderWidth;//the rows are used for the y coordinate

                var curX = newLayer.bounds[0].value;
                var curY = newLayer.bounds[1].value;

                var dx = goalX - curX;
                var dy = goalY - curY;

                //move imported image to goal x, y pixels
                newLayer.translate(dx, dy);

                //increment image index
                imageIndex += 1;
            }

        }
        //restore original untis
        app.preferences.rulerUnits = originalRulerUnits;
    }


}

Thanks for looking