| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.royale.compiler.internal.fxg.swf; |
| |
| import org.apache.royale.compiler.fxg.FXGVersion; |
| import org.apache.royale.compiler.internal.fxg.dom.IScalableGradientNode; |
| import org.apache.royale.compiler.internal.fxg.dom.fills.BitmapFillNode; |
| import org.apache.royale.compiler.internal.fxg.dom.transforms.MatrixNode; |
| import org.apache.royale.compiler.internal.fxg.dom.types.FillMode; |
| import org.apache.royale.compiler.internal.fxg.types.FXGMatrix; |
| |
| import org.apache.royale.swf.ISWFConstants; |
| import org.apache.royale.swf.types.CXFormWithAlpha; |
| import org.apache.royale.swf.types.Matrix; |
| import org.apache.royale.swf.types.RGBA; |
| import org.apache.royale.swf.types.Rect; |
| |
| |
| /** |
| * Utilities to help create basic SWF data types. |
| */ |
| public class TypeHelper |
| { |
| |
| public static final double GRADIENT_DIMENSION = 1638.4; |
| |
| /** |
| * Creates a SWF Rect from double precision coordinate pairs (in pixels) |
| * that specify the top left corner (minX, minY) and the bottom right corner |
| * (maxX, maxY). The values are converted into twips (1/20th of pixel) and |
| * rounded to an integer as required by the SWF format. |
| * |
| * @param minX The x-coordinate of the top left corner of the rectangle. |
| * @param minY The y-coordinate of the top left corner of the rectangle. |
| * @param maxX The x-coordinate of the bottom right corner of the rectangle. |
| * @param maxY The y-coordinate of the bottom right corner of the rectangle. |
| * @return A SWF Rect type representing the implied rectangle. |
| */ |
| public static Rect rect(double minX, double minY, double maxX, double maxY) |
| { |
| int xMin = (int)(minX * ISWFConstants.TWIPS_PER_PIXEL); |
| int yMin = (int)(minY * ISWFConstants.TWIPS_PER_PIXEL); |
| int xMax = (int)(maxX * ISWFConstants.TWIPS_PER_PIXEL); |
| int yMax = (int)(maxY * ISWFConstants.TWIPS_PER_PIXEL); |
| Rect rect = new Rect(xMin, xMax, yMin, yMax); |
| |
| return rect; |
| } |
| |
| /** |
| * Creates a SWF Rect from double precision width and height (in pixels) |
| * that imply the top left corner (0.0, 0.0) and the bottom right corner |
| * (width, height). The values are converted into twips (1/20th of pixel) |
| * and rounded to an integer as required by the SWF format. |
| * |
| * @param width The width of the rectangle in pixels. |
| * @param height The height of the rectangle in pixels. |
| * @return A SWF Rect type representing the implied rectangle. |
| */ |
| public static Rect rect(double width, double height) |
| { |
| int xMax = (int)(width * ISWFConstants.TWIPS_PER_PIXEL); |
| int yMax = (int)(height * ISWFConstants.TWIPS_PER_PIXEL); |
| Rect rect = new Rect(0, xMax, 0, yMax); |
| return rect; |
| } |
| |
| /** |
| * Method to generate a transform matrix for the radial gradient strokes and |
| * fills. First the translation and scaling is applied and later the |
| * rotation is applied. The scaling applied is the user specified value or |
| * in its absence the dimensions of the geometry. This is multiplied by the |
| * quotient of the number or twips per pixel and the number of twips on the |
| * screen in a given orientation to get the correct scale fraction. |
| * |
| * @return a SWF Matrix to apply for a radial gradient. |
| */ |
| public static Matrix radialGradientMatrix(IScalableGradientNode gradient, Rect pathBounds) |
| { |
| //support for node matrix |
| MatrixNode mtxNode = gradient.getMatrixNode(); |
| if (mtxNode != null) |
| { |
| double tx = mtxNode.tx; |
| double ty = mtxNode.ty; |
| FXGMatrix fxgMtx = new FXGMatrix(mtxNode.a, mtxNode.b, mtxNode.c, mtxNode.d, 0, 0); |
| fxgMtx.scale(ISWFConstants.TWIPS_PER_PIXEL/(float)ISWFConstants.GRADIENT_SQUARE, ISWFConstants.TWIPS_PER_PIXEL/(float)ISWFConstants.GRADIENT_SQUARE); |
| fxgMtx.translate(tx, ty); |
| return fxgMtx.toSWFMatrix(); |
| } |
| |
| double w = !Double.isNaN(gradient.getScaleX()) ? gradient.getScaleX()*ISWFConstants.TWIPS_PER_PIXEL : pathBounds.getWidth(); |
| double h = !Double.isNaN(gradient.getScaleY()) ? gradient.getScaleY()*ISWFConstants.TWIPS_PER_PIXEL: pathBounds.getHeight(); |
| double tx = (!Double.isNaN(gradient.getX()) ? gradient.getX() : (pathBounds.xMax() + pathBounds.xMin()) / (2.0*ISWFConstants.TWIPS_PER_PIXEL)); |
| double ty = (!Double.isNaN(gradient.getY()) ? gradient.getY() : (pathBounds.yMax() + pathBounds.yMin()) / (2.0*ISWFConstants.TWIPS_PER_PIXEL)); |
| |
| FXGMatrix matrix = new FXGMatrix(); |
| matrix.scale(w/ISWFConstants.GRADIENT_SQUARE, h/ISWFConstants.GRADIENT_SQUARE); |
| if (!Double.isNaN(gradient.getRotation()) && (gradient.getRotation() != 0)) |
| matrix.rotate(gradient.getRotation()); |
| matrix.translate(tx, ty); |
| |
| return matrix.toSWFMatrix(); |
| } |
| |
| /** |
| * Method to generate a transform matrix for the linear gradient strokes and |
| * fills. First the translation and scaling is applied and later the |
| * rotation is applied. The scaling applied is the user specified value or |
| * in its absence the dimensions of the geometry. This is multiplied by the |
| * quotient of the number or twips per pixel and the number of twips on the |
| * screen in a given orientation to get the correct scale fraction. |
| * |
| * @return a SWF Matrix to apply for a linear gradient. |
| */ |
| public static Matrix linearGradientMatrix(IScalableGradientNode gradient, Rect pathBounds) |
| { |
| |
| FXGMatrix matrix = new FXGMatrix(); |
| |
| //support for node matrix |
| MatrixNode mtxNode = gradient.getMatrixNode(); |
| if (mtxNode != null) |
| { |
| matrix.translate(GRADIENT_DIMENSION/2.0, GRADIENT_DIMENSION/2.0); |
| matrix.scale(1.0/GRADIENT_DIMENSION, 1.0/GRADIENT_DIMENSION); |
| FXGMatrix nodeMatrix = new FXGMatrix(mtxNode); |
| matrix.concat(nodeMatrix); |
| return matrix.toSWFMatrix(); |
| } |
| |
| double width = (pathBounds.xMax() - pathBounds.xMin()) / (double)ISWFConstants.TWIPS_PER_PIXEL; |
| double height = (pathBounds.yMax() - pathBounds.yMin()) / (double)ISWFConstants.TWIPS_PER_PIXEL; |
| double scaleX = gradient.getScaleX(); |
| double rotation = gradient.getRotation(); |
| double tx = gradient.getX(); |
| double ty = gradient.getY(); |
| |
| if (Double.isNaN(scaleX)) |
| { |
| // Figure out the two sides |
| if (rotation % 90 != 0) |
| { |
| // Normalize angles with absolute value > 360 |
| double normalizedAngle = rotation % 360; |
| // Normalize negative angles |
| if (normalizedAngle < 0) |
| normalizedAngle += 360; |
| |
| // Angles wrap at 180 |
| normalizedAngle %= 180; |
| |
| // Angles > 90 get mirrored |
| if (normalizedAngle > 90) |
| normalizedAngle = 180 - normalizedAngle; |
| |
| double side = width; |
| // Get the hypotenuse of the largest triangle that can fit in the bounds |
| double hypotenuse = Math.sqrt(width * width + height * height); |
| // Get the angle of that largest triangle |
| double hypotenuseAngle = Math.acos(width / hypotenuse) * 180 / Math.PI; |
| |
| // If the angle is larger than the hypotenuse angle, then use the height |
| // as the adjacent side of the triangle |
| if (normalizedAngle > hypotenuseAngle) |
| { |
| normalizedAngle = 90 - normalizedAngle; |
| side = height; |
| } |
| |
| // Solve for the hypotenuse given an adjacent side and an angle. |
| scaleX = side / Math.cos(normalizedAngle / 180 * Math.PI); |
| } |
| else |
| { |
| // Use either width or height based on the rotation |
| scaleX = (rotation % 180) == 0 ? width : height; |
| } |
| } |
| |
| // If only x or y is defined, force the other to be set to 0 |
| if (!Double.isNaN(tx) && Double.isNaN(ty)) |
| ty = 0; |
| if (Double.isNaN(tx) && !Double.isNaN(ty)) |
| tx = 0; |
| |
| // If x and y are specified, then move the gradient so that the |
| // top left corner is at 0,0 |
| if (!Double.isNaN(tx) && !Double.isNaN(ty)) |
| matrix.translate( ISWFConstants.GRADIENT_SQUARE/(2.0*ISWFConstants.TWIPS_PER_PIXEL), ISWFConstants.GRADIENT_SQUARE/(2.0*ISWFConstants.TWIPS_PER_PIXEL)); |
| |
| // Force the scaleX to a minimum of 2. Values of 0 or 1 have undesired behavior |
| if (Math.abs(scaleX) < 2) |
| scaleX = (scaleX < 0) ? -2 : 2; |
| |
| // Scale the gradient in the x direction. The natural size is 1638.4px. No need |
| // to scale the y direction because it is infinite |
| scaleX = (scaleX*ISWFConstants.TWIPS_PER_PIXEL)/ ISWFConstants.GRADIENT_SQUARE; |
| matrix.scale(scaleX, 1); |
| |
| if (!Double.isNaN(rotation)) |
| matrix.rotate(rotation); |
| |
| if (Double.isNaN(tx)) |
| tx = width / 2.0 + pathBounds.xMin()/(double)ISWFConstants.TWIPS_PER_PIXEL; |
| if (Double.isNaN(ty)) |
| ty = height / 2.0 + + pathBounds.yMin()/(double)ISWFConstants.TWIPS_PER_PIXEL; |
| matrix.translate(tx, ty); |
| |
| return matrix.toSWFMatrix(); |
| |
| } |
| |
| |
| public static FXGMatrix bitmapFillMatrix(BitmapFillNode fill, DefineImage img, Rect pathBounds) |
| { |
| |
| MatrixNode mtxNode = fill.matrix; |
| if (mtxNode != null) |
| { |
| double tx = mtxNode.tx; |
| double ty = mtxNode.ty; |
| FXGMatrix fxgMtx = new FXGMatrix(mtxNode.a, mtxNode.b, mtxNode.c, mtxNode.d, 0, 0); |
| fxgMtx.scale(ISWFConstants.TWIPS_PER_PIXEL, ISWFConstants.TWIPS_PER_PIXEL); |
| fxgMtx.translate(tx, ty); |
| return fxgMtx; |
| } |
| |
| FXGMatrix matrix = new FXGMatrix(); |
| double tx; |
| double ty; |
| double scaleX; |
| double scaleY; |
| if ((fill.getFileVersion() != FXGVersion.v1_0) && (fill.fillMode.equals(FillMode.SCALE))) |
| { |
| tx = (Double.isNaN(fill.x)) ? pathBounds.xMin()/(double)ISWFConstants.TWIPS_PER_PIXEL : fill.x; |
| ty = (Double.isNaN(fill.y)) ? pathBounds.yMin()/(double)ISWFConstants.TWIPS_PER_PIXEL : fill.y; |
| scaleX = (Double.isNaN(fill.scaleX)) ? (pathBounds.getWidth()/(double) img.getWidth()) : |
| ISWFConstants.TWIPS_PER_PIXEL * fill.scaleX; |
| scaleY = (Double.isNaN(fill.scaleY)) ? (pathBounds.getHeight()/(double) img.getHeight()) : |
| ISWFConstants.TWIPS_PER_PIXEL * fill.scaleY; |
| } |
| else |
| { |
| tx = (Double.isNaN(fill.x)) ? pathBounds.xMin()/(double)ISWFConstants.TWIPS_PER_PIXEL : fill.x; |
| ty = (Double.isNaN(fill.y)) ? pathBounds.yMin()/(double)ISWFConstants.TWIPS_PER_PIXEL : fill.y; |
| scaleX = (Double.isNaN(fill.scaleX)) ? ISWFConstants.TWIPS_PER_PIXEL : ISWFConstants.TWIPS_PER_PIXEL * fill.scaleX; |
| scaleY = (Double.isNaN(fill.scaleY)) ? ISWFConstants.TWIPS_PER_PIXEL : ISWFConstants.TWIPS_PER_PIXEL * fill.scaleY; |
| } |
| double angle = fill.rotation; |
| while (angle < 0) |
| angle += 360; |
| angle %= 360; |
| matrix.scale(scaleX, scaleY); |
| matrix.rotate(angle); |
| matrix.translate(tx, ty); |
| |
| return matrix; |
| |
| } |
| |
| /** |
| * Creates a SWF CXFormWithAlpha type for the common scenario where only an |
| * alpha multiplier has been specified. The double value is converted into |
| * an 8.8 fixed integer as required by the SWF format. |
| * |
| * @param alphaMultiplier The alpha multiplier value specified as a double |
| * in the range 0.0 to 1.0 (inclusive). |
| * @return a SWF CXFormWithAlpha value for the specified alpha multiplier. |
| */ |
| public static CXFormWithAlpha cxFormWithAlpha(double alphaMultiplier) |
| { |
| CXFormWithAlpha c = new CXFormWithAlpha(); |
| c.setMultTerm(fixed8(1.0), fixed8(1.0), fixed8(1.0), fixed8(alphaMultiplier)); |
| return c; |
| } |
| |
| /** |
| * Creates a SWF CXFormWithAlpha type for the given double precision values |
| * for ARGB multipliers and offsets. The multiplier values are converted |
| * into 8.8 fixed integers as required by the SWF format. The offset values |
| * are converted into integers in the range 0 to 255. |
| * |
| * @param alphaMultiplier - alpha channel multiplier |
| * @param redMultiplier - red channel multiplier |
| * @param greenMultiplier - green channel multiplier |
| * @param blueMultiplier - blue channel multiplier |
| * @param alphaOffset - alpha channel offset value in the range -255.0 to 255.0. |
| * @param redOffset - red channel offset value in the range -255.0 to 255.0. |
| * @param greenOffset - green channel offset value in the range -255.0 to 255.0. |
| * @param blueOffset - blue channel offset value in the range -255.0 to 255.0. |
| * @return a SWF CXFormWithAlpha value for the specified multipliers and |
| * offsets. |
| */ |
| public static CXFormWithAlpha cxFormWithAlpha(double alphaMultiplier, |
| double redMultiplier, double greenMultiplier, |
| double blueMultiplier, double alphaOffset, double redOffset, |
| double greenOffset, double blueOffset) |
| { |
| CXFormWithAlpha c = new CXFormWithAlpha(); |
| int alphaMultTerm = fixed8(alphaMultiplier); |
| int redMultTerm = fixed8(redMultiplier); |
| int greenMultTerm = fixed8(greenMultiplier); |
| int blueMultTerm = fixed8(blueMultiplier); |
| int alphaAddTerm = (int)(alphaOffset); |
| int redAddTerm = (int)(redOffset); |
| int greenAddTerm = (int)(greenOffset); |
| int blueAddTerm = (int)(blueOffset); |
| |
| if (redAddTerm > 0 || greenAddTerm > 0 || blueAddTerm > 0 || alphaAddTerm > 0) |
| c.setAddTerm(redAddTerm, greenAddTerm, blueAddTerm, alphaAddTerm); |
| if (alphaMultTerm > 0 || redMultTerm > 0 || greenMultTerm > 0 || blueMultTerm > 0) |
| c.setMultTerm(redMultTerm, greenMultTerm, blueMultTerm, alphaMultTerm); |
| |
| return c; |
| } |
| |
| /** |
| * Converts a gradient ratio specified as a double in the range 0.0 to 1.0 |
| * to an integer in the range required by the SWF format between 0 and 255. |
| * |
| * @param ratio A gradient entry ratio between 0.0 and 1.0. |
| * @return A SWF gradient ratio ration between 0 and 255. |
| */ |
| public static int gradientRatio(double ratio) |
| { |
| return (int)StrictMath.rint(ratio * 255); |
| } |
| |
| /** |
| * Adds alpha channel information to an RGB integer (in the highest 8 bits |
| * of the 32 bit integer) to create an ARGB integer as required by the SWF |
| * format. |
| * |
| * @param color An RGB color value specified as an int. |
| * @param alpha The alpha channel specified as a double in the range 0.0 to |
| * 1.0. |
| * @return An ARGB color value as an int. |
| */ |
| public static int colorARGB(int color, double alpha) |
| { |
| int rgb = color & 0x00FFFFFF; |
| int a = (int)StrictMath.rint(alpha * 255); |
| int argb = rgb | (a << 24); |
| return argb; |
| } |
| |
| public static RGBA splitColor(int alphacolor) { |
| return new RGBA((alphacolor & 0xFF0000) >> 16, (alphacolor & 0xFF00) >> 8, alphacolor & 0xFF, (alphacolor & 0xFF000000) >> 24); |
| } |
| |
| /** |
| * Converts a double value into a 16.16 fixed integer required by some types |
| * in the SWF format. |
| |
| */ |
| public static int fixed(double value) |
| { |
| return (int)(value * ISWFConstants.FIXED_POINT_MULTIPLE); |
| } |
| |
| /** |
| * Converts a double value into a 8.8 fixed integer required by some types |
| * in the SWF format. |
| */ |
| public static int fixed8(double value) |
| { |
| return (int)(value * ISWFConstants.FIXED_POINT_MULTIPLE_8) & 0xFFFF; |
| } |
| } |