Example #1
0
define(function (require, exports, module) {
    "use strict";

    var Immutable = require("immutable");

    var unit = require("js/util/unit"),
        objUtil = require("js/util/object"),
        contentLib = require("adapter").lib.contentLayer;

    /**
     * Model for a bounds rectangle, we extract it from the layer descriptor
     * for the bounds without effects
     * 
     * @constructor
     * @param {object} model
     */
    var Radii = Immutable.Record({
        /**
         * Radius of the top-left border
         * @type {number} 
         */
        topLeft: 0,

        /**
         * Radius of the top-right border
         * @type {number} 
         */
        topRight: 0,

        /**
         * Radius of the bottom-right border
         * @type {number} 
         */
        bottomRight: 0,

        /**
         * Radius of the bottom-left border
         * @type {number} 
         */
        bottomLeft: 0
    });

    Object.defineProperties(Radii.prototype, objUtil.cachedGetSpecs({
        /**
         * Convert the set of border radii to a single scalar, or null of the radii
         * are disequal.
         *
         * @return {?number}
         */
        "scalar": function () {
            if (this.topLeft === this.topRight &&
                this.topRight === this.bottomRight &&
                this.bottomRight === this.bottomLeft) {
                return this.topLeft;
            } else {
                return null;
            }
        }
    }));

    /**
     * Updates the radii object with new properties
     *
     * @param {object} descriptor Photoshop layer descriptor
     * @return {Radii}
     */
    Radii.prototype.resetFromDescriptor = function (descriptor) {
        var newRadiiObject = Radii.fromLayerDescriptor(descriptor);

        return this.merge(newRadiiObject);
    };

    /**
     * Construct a Radii object from the given Photoshop layer descriptor.
     *
     * @param {object} descriptor
     * @return {?Radii}
     */
    Radii.fromLayerDescriptor = function (descriptor) {
        if (!descriptor.keyOriginType || descriptor.keyOriginType.length === 0) {
            return null;
        }

        // There is a shape, but it has been deformed and the radii are invalid
        var value = descriptor.keyOriginType[0];
        if (value.keyShapeInvalidated) {
            return null;
        }

        var model = {},
            type = value.keyOriginType;

        switch (type) {
        case contentLib.originTypes.ORIGIN_RECT:
            model.topLeft = 0;
            model.topRight = 0;
            model.bottomRight = 0;
            model.bottomLeft = 0;
            break;
        case contentLib.originTypes.ORIGIN_ROUNDED_RECT:
            var radii = value.keyOriginRRectRadii,
                resolution = objUtil.getPath(descriptor, "AGMStrokeStyleInfo.strokeStyleResolution");

            if (resolution === undefined) {
                resolution = 300;
            }

            model.topLeft = unit.toPixels(radii.topLeft, resolution);
            model.topRight = unit.toPixels(radii.topRight, resolution);
            model.bottomRight = unit.toPixels(radii.bottomRight, resolution);
            model.bottomLeft = unit.toPixels(radii.bottomLeft, resolution);
            break;
        default:
            return null;
        }

        return new Radii(model);
    };
    
    module.exports = Radii;
});
Example #2
0
define(function (require, exports, module) {
    "use strict";

    var Immutable = require("immutable");

    var objUtil = require("js/util/object"),
        Layer = require("./layer");

    /**
     * Model document bounds or layer bounds without effects.
     * 
     * @constructor
     * @param {object} model
     */
    var Bounds = Immutable.Record({
        /**
         * @type {number}
         */
        top: null,

        /**
         * @type {number}
         */
        bottom: null,

        /**
         * @type {number}
         */
        left: null,

        /**
         * @type {number}
         */
        right: null
    });

    Object.defineProperties(Bounds.prototype, objUtil.cachedGetSpecs({
        /**
         * Width of the bounding box.
         * @type {number}
         */
        "width": function () {
            return this.right - this.left;
        },
        /**
         * Height of the bounding box.
         * @type {number}
         */
        "height": function () {
            return this.bottom - this.top;
        },
        /**
         * Horizontal center of the bounding box.
         * @type {number}
         */
        "xCenter": function () {
            return this.left + (this.width / 2);
        },
        /**
         * Vertical center of the bounding box.
         * @type {number}
         */
        "yCenter": function () {
            return this.top + (this.height / 2);
        },
        /**
         * Area of the bounding box
         * @type {number}
         */
        "area": function () {
            return this.width * this.height;
        },
        /**
         * Whether the bounds are empty.
         * @type {boolean}
         */
        "empty": function () {
            return this.area === 0;
        }
    }));

    /**
     * Create a new Bounds object from the given document descriptor.
     * 
     * @param {object} descriptor Photoshop document descriptor
     * @return {Bounds}
     */
    Bounds.fromDocumentDescriptor = function (descriptor) {
        var resolution = descriptor.resolution._value,
            multiplier = resolution / 72,
            model = {};

        var height = descriptor.height._value * multiplier,
            width = descriptor.width._value * multiplier;

        model.top = 0;
        model.left = 0;
        model.bottom = height;
        model.right = width;

        return new Bounds(model);
    };

    /**
     * Create a new Bounds object from the given layer descriptor.
     * 
     * @param {object} descriptor Photoshop layer descriptor
     * @return {Bounds}
     */
    Bounds.fromLayerDescriptor = function (descriptor) {
        var boundsObject = this.parseLayerDescriptor(descriptor);
            
        return new Bounds(boundsObject);
    };

    /**
     * Parses given layer descriptor to a constructor usable object
     *
     * @param {object} descriptor
     * @param {number} descriptor.layerKind
     * @param {LayerKind} descriptor.layerKindName
     * @param {?boolean} descriptor.artboardEnabled If set, will parse artboard value
     * @param {?object} descriptor.artboard Contains the artboard bounds descriptor
     * @param {?object} descriptor.pathBounds If available, will be parsed as shape layer
     * @param {?object} descriptor.boundsNoEffects Bounds object available for all layers
     * @param {?object} descriptor.boundsNoMask Bounds object available for all layers
     *
     * @return {?{top: number, left: number, bottom: number, right: number}}
     */
    Bounds.parseLayerDescriptor = function (descriptor) {
        var boundsObject,
            layerKind = descriptor.layerKindName || Layer.KIND_TO_NAME[descriptor.layerKind];

        // artboards are also groups. so we handle them separately 
        if (descriptor.artboardEnabled) {
            boundsObject = objUtil.getPath(descriptor, "artboard.artboardRect");
        } else if (layerKind === Layer.KINDS.VECTOR &&
                descriptor.hasOwnProperty("pathBounds")) {
            boundsObject = objUtil.getPath(descriptor, "pathBounds.pathBounds");
        } else {
            switch (layerKind) {
                // Photoshop's group / adjustment bounds are not useful, so ignore them.
            case Layer.KINDS.GROUP:
                if (descriptor.vectorMaskEnabled) {
                    boundsObject = descriptor.bounds;
                } else {
                    return null;
                }
                break;
            case Layer.KINDS.GROUPEND:
            case Layer.KINDS.ADJUSTMENT:
                return null;
            case Layer.KINDS.TEXT:
                boundsObject = descriptor.boundingBox;
                break;
            case Layer.KINDS.VECTOR:
                boundsObject = descriptor.boundsNoEffects;
                break;
            default:
                boundsObject = descriptor.boundsNoMask;
                break;
            }

            var model = {};

            model.top = boundsObject.top._value;
            model.left = boundsObject.left._value;
            model.bottom = boundsObject.bottom._value;
            model.right = boundsObject.right._value;

            boundsObject = model;
        }

        delete boundsObject._obj;

        return boundsObject;
    };

    /**
     * Updates the bound object with new properties
     *
     * @param {object} descriptor Photoshop layer descriptor
     * @return {Bounds} [description]
     */
    Bounds.prototype.resetFromDescriptor = function (descriptor) {
        var newBoundObject = Bounds.parseLayerDescriptor(descriptor);

        return this.merge(newBoundObject);
    };

    /**
     * Create a new bounds object from the union of the given bounds objects.
     * Returns null if no bounds objects are supplied.
     * 
     * @param {Array.<Bounds>} childBounds
     * @return {?Bounds}
     */
    Bounds.union = function (childBounds) {
        if (childBounds.isEmpty()) {
            return null;
        }

        var startBounds = childBounds.first(),
            nextBounds = startBounds.withMutations(function (model) {
                childBounds.rest().forEach(function (child) {
                    model.top = Math.min(model.top, child.top);
                    model.left = Math.min(model.left, child.left);
                    model.bottom = Math.max(model.bottom, child.bottom);
                    model.right = Math.max(model.right, child.right);
                });
            });

        return nextBounds;
    };

    /**
     * Creates a new bounds object from the intersection of the given bounds objects.
     * Returns null if they don't intersect 
     *
     * @param {Bounds} boundsOne
     * @param {Bounds} boundsTwo
     * @return {?Bounds} Intersection of boundsOne and boundsTwo
     */
    Bounds.intersection = function (boundsOne, boundsTwo) {
        if (!boundsOne || !boundsTwo) {
            return null;
        }
        
        var model = {
            top: boundsTwo.top < boundsOne.top ? boundsOne.top : boundsTwo.top,
            left: boundsTwo.left < boundsOne.left ? boundsOne.left : boundsTwo.left,
            bottom: boundsTwo.bottom < boundsOne.bottom ? boundsTwo.bottom : boundsOne.bottom,
            right: boundsTwo.right < boundsOne.right ? boundsTwo.right : boundsOne.right
        };

        if (model.bottom < model.top || model.right < model.left) {
            return null;
        }

        return new Bounds(model);
    };

    /**
     * Indicates whether the given point is contained in the bounding box.
     *
     * @param {number} x
     * @param {number} y
     * @return {boolean}
     */
    Bounds.prototype.contains = function (x, y) {
        return this.top <= y && y <= this.bottom &&
            this.left <= x && x <= this.right;
    };

    /**
     * If the width or height are negative, will swap the adjacent edges
     *
     * @return {Bounds} Updated bounds object
     */
    Bounds.prototype.normalize = function () {
        var newBounds = {};

        if (this.right < this.left) {
            newBounds.left = this.right;
            newBounds.right = this.left;
        }

        if (this.bottom < this.top) {
            newBounds.top = this.bottom;
            newBounds.bottom = this.top;
        }

        return this.merge(newBounds);
    };

    /**
     * Clones this bounds object with an updated position.
     *
     * @protected
     * @param {number=} x New X position
     * @param {number=} y New Y position
     * @return {Bounds} The updated bounds object
     */
    Bounds.prototype.updatePosition = function (x, y) {
        var width = this.width,
            height = this.height,
            newBounds = {};

        if (typeof x === "number") {
            newBounds.left = x;
            newBounds.right = x + width;
        }

        if (typeof y === "number") {
            newBounds.top = y;
            newBounds.bottom = y + height;
        }

        return this.merge(newBounds);
    };

    /**
     * Clones this bounds object with an updated size.
     *
     * @protected
     * @param {number=} w New width
     * @param {number=} h New height
     * @param {boolean=} proportional size change  
     * @return {Bounds} The updated bounds object
     */
    Bounds.prototype.updateSize = function (w, h, proportional) {
        var newBounds = {};

        if (typeof w === "number") {
            var oldWidth = this.width;
            newBounds.right = this.left + w;
            if (proportional) {
                var newHeight = this.height / oldWidth * w;
                newBounds.bottom = this.top + newHeight;
            }
        }

        if (typeof h === "number") {
            var oldHeight = this.height;
            newBounds.bottom = this.top + h;
            if (proportional) {
                var newWidth = this.width / oldHeight * h;
                newBounds.right = this.left + newWidth;
            }
        }

        return this.merge(newBounds);
    };

    /**
     * Clones this bounds object with an updated position and size.
     *
     * @protected
     * @param {number=} x New X position
     * @param {number=} y New Y position
     * @param {number=} width New width
     * @param {number=} height New height
     * @return {Bounds} The updated bounds object
     */
    Bounds.prototype.updateSizeAndPosition = function (x, y, width, height) {
        var model = {};

        if (typeof x === "number") {
            model.left = x;
            if (typeof width === "number") {
                model.right = x + width;
            }
        }

        if (typeof y === "number") {
            model.top = y;
            if (typeof height === "number") {
                model.bottom = y + height;
            }
        }

        return this.merge(model);
    };

    /**
     * Checks to see if these bounds intersects with other bounds
     *
     * @param {Bounds} otherBounds
     * @return {boolean}
     */
    Bounds.prototype.intersects = function (otherBounds) {
        return this.left < otherBounds.right && this.right > otherBounds.left &&
            this.top < otherBounds.bottom && this.bottom > otherBounds.top;
    };

    /**
     * Clones this bounds object returning it relative to the position 
     * of the given bounds' top left value
     *
     * @param {Bounds} otherBounds Comparison bounds
     * @return {Bounds} Updated bounds where location is in relation to otherBounds
     */
    Bounds.prototype.relativeTo = function (otherBounds) {
        var x = otherBounds.left,
            y = otherBounds.top,
            model = {
                left: this.left - x,
                top: this.top - y,
                right: this.right - x,
                bottom: this.bottom - y
            };

        return this.merge(model);
    };

    module.exports = Bounds;
});
Example #3
0
define(function (require, exports, module) {
    "use strict";

    var Immutable = require("immutable"),
        mathjs = require("mathjs"),
        tinycolor = require("tinycolor2");

    var objUtil = require("js/util/object"),
        mathUtil = require("js/util/math");

    /**
     * @constructor
     * @param {object} model
     */
    var Color = Immutable.Record({
        /**
         * [0, 255]
         * @type {number}
         */
        r: 0,

        /**
         * [0, 255]
         * @type {number}
         */
        g: 0,

        /**
         * [0, 255]
         * @type {number}
         */
        b: 0,

        /**
         * [0, 1]
         * @type {number}
         */
        a: 1
    });

    /**
     * @type {Color}
     */
    Color.DEFAULT = new Color();

    /**
     * Test that a color object supplied by Ps is a valid RGB color object
     *
     * @param {object} obj
     * @return {boolean} true if the supplied object is of type RGBColor
     */
    Color.isValidPhotoshopColorObj = function (obj) {
        return obj && (typeof obj === "object") && (obj._obj === "RGBColor");
    };

    /**
     * Construct a new Color object from a Photoshop color descriptor and
     * opacity percentage.
     * 
     * @param {object} rgb Photoshop color descriptor
     * @param {number} opacityPercentage In [0, 100]
     * @return {Color}
     */
    Color.fromPhotoshopColorObj = function (rgb, opacityPercentage) {
        var green;

        if (rgb.hasOwnProperty("grain")) {
            green = rgb.grain;
        } else if (rgb.hasOwnProperty("green")) {
            green = rgb.green;
        } else {
            throw new Error("Unable to parse Photoshop color object");
        }

        return new Color({
            "r": mathjs.round(rgb.red),
            "g": mathjs.round(green),
            "b": mathjs.round(rgb.blue),
            "a": mathjs.round(opacityPercentage / 100, 4)
        });
    };

    /**
     * Construct a new Color object from a TinyColor instance.
     * 
     * @param {TinyColor} tiny
     * @return {Color}
     */
    Color.fromTinycolor = function (tiny) {
        return new Color(tiny.toRgb());
    };

    Object.defineProperties(Color.prototype, objUtil.cachedGetSpecs({
        /**
         * The opacity percentage of this color object
         * In [0, 100] 
         * @type {number} 
         */
        "opacity": function () {
            return mathjs.round(this.a * 100, 0);
        }
    }));

    /**
     * Set the alpha value of this color, but not its RGB value.
     *
     * @param {number|Color} alpha Number in [0, 1], or a Color object
     * @return {Color}
     */
    Color.prototype.setAlpha = function (alpha) {
        if (alpha instanceof Color) {
            alpha = alpha.a;
        }

        return this.set("a", alpha);
    };

    /**
     * Set the opaque value of this color, but not its alpha value.
     *
     * @param {Color} color
     * @return {Color}
     */
    Color.prototype.setOpaque = function (color) {
        return this.merge({
            r: color.r,
            g: color.g,
            b: color.b
        });
    };

    /**
     * Normalize the alpha value of this color w.r.t. 255.
     *
     * @return {Color}
     */
    Color.prototype.normalizeAlpha = function () {
        return this.set("a", mathUtil.normalize(this.a, 255));
    };

    /**
     * Set the opacity value of this color. 
     * 
     * @param {number} opacityPercentage In [0, 100]
     * @return {Color}
     */
    Color.prototype.setOpacity = function (opacityPercentage) {
        return this.setAlpha(mathjs.round(opacityPercentage / 100, 4));
    };

    /**
     * Get an opaque version of this color.
     *
     * @return {Color}
     */
    Color.prototype.opaque = function () {
        return this.delete("a");
    };
    
    /**
     * Return instance of TinyColor. Provide convenicen for accessing TinyColor functions
     * @return {TinyColor} 
     */
    Color.prototype.toTinyColor = function () {
        return tinycolor(this.toJS());
    };

    module.exports = Color;
});