blob: 5a7a32869c5570b0566d7465ef4893bf4d921f09 [file] [log] [blame]
/*
*
* 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;
}
}