@awayjs/graphics
Version:
AwayJS graphics classes
472 lines (432 loc) • 16.7 kB
text/typescript
import { DataBuffer } from './DataBuffer';
import { assert, ensureTypedArrayCapacity } from './utilities';
/**
* Copyright 2014 Mozilla Foundation
*
* Licensed 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.
*/
/**
* Serialization format for shape data:
* (canonical, update this instead of anything else!)
*
* Shape data is serialized into a set of three buffers:
* - `commands`: a Uint8Array for commands
* - valid values: [1-11] (i.e. one of the PathCommand enum values)
* - `coordinates`: an Int32Array for path coordinates*
* OR uint8 thickness iff the current command is PathCommand.LineStyleSolid
* - valid values: the full range of 32bit numbers, representing x,y coordinates in twips
* - `styles`: a DataBuffer for style definitions
* - valid values: structs for the various style definitions as described below
*
* (*: with one exception: to make various things faster, stroke widths are stored in the
* coordinates buffer, too.)
*
* All entries always contain all fields, default values aren't omitted.
*
* the various commands write the following sets of values into the various buffers:
*
* moveTo:
* commands: PathCommand.MoveTo
* coordinates: target x coordinate, in twips
* target y coordinate, in twips
* styles: n/a
*
* lineTo:
* commands: PathCommand.LineTo
* coordinates: target x coordinate, in twips
* target y coordinate, in twips
* styles: n/a
*
* curveTo:
* commands: PathCommand.CurveTo
* coordinates: control point x coordinate, in twips
* control point y coordinate, in twips
* target x coordinate, in twips
* target y coordinate, in twips
* styles: n/a
*
* cubicCurveTo:
* commands: PathCommand.CubicCurveTo
* coordinates: control point 1 x coordinate, in twips
* control point 1 y coordinate, in twips
* control point 2 x coordinate, in twips
* control point 2 y coordinate, in twips
* target x coordinate, in twips
* target y coordinate, in twips
* styles: n/a
*
* beginFill:
* commands: PathCommand.BeginSolidFill
* coordinates: n/a
* styles: uint32 - RGBA color
*
* beginGradientFill:
* commands: PathCommand.BeginGradientFill
* coordinates: n/a
* Note: the style fields are ordered this way to optimize performance in the rendering backend
* Note: the style record has a variable length depending on the number of color stops
* styles: uint8 - GradientType.{LINEAR,RADIAL}
* fix8 - focalPoint [-128.0xff,127.0xff]
* matrix - transform (see Matrix#writeExternal for details)
* uint8 - colorStops (Number of color stop records that follow)
* list of uint8,uint32 pairs:
* uint8 - ratio [0-0xff]
* uint32 - RGBA color
* uint8 - SpreadMethod.{PAD,REFLECT,REPEAT}
* uint8 - InterpolationMethod.{RGB,LINEAR_RGB}
*
* beginBitmapFill:
* commands: PathCommand.BeginBitmapFill
* coordinates: n/a
* styles: uint32 - Index of the bitmapData object in the Graphics object's `textures`
* array
* matrix - transform (see Matrix#writeExternal for details)
* bool - repeat
* bool - smooth
*
* lineStyle:
* commands: PathCommand.LineStyleSolid
* coordinates: uint32 - thickness (!)
* style: uint32 - RGBA color
* bool - pixelHinting
* uint8 - LineScaleMode, [0-3] see LineScaleMode.fromNumber for meaning
* uint8 - CapsStyle, [0-2] see CapsStyle.fromNumber for meaning
* uint8 - JointStyle, [0-2] see JointStyle.fromNumber for meaning
* uint8 - miterLimit
*
* lineGradientStyle:
* commands: PathCommand.LineStyleGradient
* coordinates: n/a
* Note: the style fields are ordered this way to optimize performance in the rendering backend
* Note: the style record has a variable length depending on the number of color stops
* styles: uint8 - GradientType.{LINEAR,RADIAL}
* int8 - focalPoint [-128,127]
* matrix - transform (see Matrix#writeExternal for details)
* uint8 - colorStops (Number of color stop records that follow)
* list of uint8,uint32 pairs:
* uint8 - ratio [0-0xff]
* uint32 - RGBA color
* uint8 - SpreadMethod.{PAD,REFLECT,REPEAT}
* uint8 - InterpolationMethod.{RGB,LINEAR_RGB}
*
* lineBitmapStyle:
* commands: PathCommand.LineBitmapStyle
* coordinates: n/a
* styles: uint32 - Index of the bitmapData object in the Graphics object's `textures`
* array
* matrix - transform (see Matrix#writeExternal for details)
* bool - repeat
* bool - smooth
*
* lineEnd:
* Note: emitted for invalid `lineStyle` calls
* commands: PathCommand.LineEnd
* coordinates: n/a
* styles: n/a
*
*/
/**
* Used for (de-)serializing Graphics path data in defineShape, flash.display.Graphics
* and the renderer.
*/
export const enum PathCommand {
BeginSolidFill = 1,
BeginGradientFill = 2,
BeginBitmapFill = 3,
EndFill = 4,
LineStyleSolid = 5,
LineStyleGradient = 6,
LineStyleBitmap = 7,
LineEnd = 8,
MoveTo = 9,
LineTo = 10,
CurveTo = 11,
CubicCurveTo = 12,
}
export const enum GradientType {
Linear = 0x10,
Radial = 0x12
}
export const enum GradientSpreadMethod {
Pad = 0,
Reflect = 1,
Repeat = 2
}
export const enum GradientInterpolationMethod {
RGB = 0,
LinearRGB = 1
}
export const enum LineScaleMode {
None = 0,
Normal = 1,
Vertical = 2,
Horizontal = 3
}
export interface ShapeMatrix {
a: number;
b: number;
c: number;
d: number;
tx: number;
ty: number;
}
export class PlainObjectShapeData {
constructor(public commands: Uint8Array, public commandsPosition: number,
public coordinates: Int32Array, public morphCoordinates: Int32Array,
public coordinatesPosition: number,
public styles: ArrayBuffer, public stylesLength: number,
public morphStyles: ArrayBuffer, public morphStylesLength: number,
public hasFills: boolean, public hasLines: boolean) {}
}
enum DefaultSize {
Commands = 32,
Coordinates = 128,
Styles = 16
}
export class ShapeData {
commands: Uint8Array;
commandsPosition: number;
coordinates: Int32Array;
// Note: creation and capacity-ensurance have to happen from the outside for this field.
morphCoordinates: Int32Array;
coordinatesPosition: number;
styles: DataBuffer;
morphStyles: DataBuffer;
hasFills: boolean;
hasLines: boolean;
constructor(initialize: boolean = true) {
if (initialize) {
this.clear();
}
}
static FromPlainObject(source: PlainObjectShapeData): ShapeData {
const data = new ShapeData(false);
data.commands = source.commands;
data.coordinates = source.coordinates;
data.morphCoordinates = source.morphCoordinates;
data.commandsPosition = source.commandsPosition;
data.coordinatesPosition = source.coordinatesPosition;
data.styles = DataBuffer.FromArrayBuffer(source.styles, source.stylesLength);
data.styles.endian = 'auto';
if (source.morphStyles) {
data.morphStyles = DataBuffer.FromArrayBuffer(
source.morphStyles, source.morphStylesLength);
data.morphStyles.endian = 'auto';
}
data.hasFills = source.hasFills;
data.hasLines = source.hasLines;
return data;
}
moveTo(x: number, y: number): void {
this.ensurePathCapacities(1, 2);
this.commands[this.commandsPosition++] = PathCommand.MoveTo;
this.coordinates[this.coordinatesPosition++] = x;
this.coordinates[this.coordinatesPosition++] = y;
}
lineTo(x: number, y: number): void {
this.ensurePathCapacities(1, 2);
this.commands[this.commandsPosition++] = PathCommand.LineTo;
this.coordinates[this.coordinatesPosition++] = x;
this.coordinates[this.coordinatesPosition++] = y;
}
curveTo(controlX: number, controlY: number, anchorX: number, anchorY: number): void {
this.ensurePathCapacities(1, 4);
this.commands[this.commandsPosition++] = PathCommand.CurveTo;
this.coordinates[this.coordinatesPosition++] = controlX;
this.coordinates[this.coordinatesPosition++] = controlY;
this.coordinates[this.coordinatesPosition++] = anchorX;
this.coordinates[this.coordinatesPosition++] = anchorY;
}
cubicCurveTo(controlX1: number, controlY1: number, controlX2: number, controlY2: number,
anchorX: number, anchorY: number): void {
this.ensurePathCapacities(1, 6);
this.commands[this.commandsPosition++] = PathCommand.CubicCurveTo;
this.coordinates[this.coordinatesPosition++] = controlX1;
this.coordinates[this.coordinatesPosition++] = controlY1;
this.coordinates[this.coordinatesPosition++] = controlX2;
this.coordinates[this.coordinatesPosition++] = controlY2;
this.coordinates[this.coordinatesPosition++] = anchorX;
this.coordinates[this.coordinatesPosition++] = anchorY;
}
beginFill(color: number): void {
this.ensurePathCapacities(1, 0);
this.commands[this.commandsPosition++] = PathCommand.BeginSolidFill;
this.styles.writeUnsignedInt(color);
this.hasFills = true;
}
writeMorphFill(color: number) {
this.morphStyles.writeUnsignedInt(color);
}
endFill() {
this.ensurePathCapacities(1, 0);
this.commands[this.commandsPosition++] = PathCommand.EndFill;
}
endLine() {
this.ensurePathCapacities(1, 0);
this.commands[this.commandsPosition++] = PathCommand.LineEnd;
}
lineStyle(thickness: number, color: number, pixelHinting: boolean,
scaleMode: LineScaleMode, caps: number, joints: number, miterLimit: number): void {
assert(thickness === (thickness|0), thickness >= 0 && thickness <= 0xff * 20);
this.ensurePathCapacities(2, 0);
this.commands[this.commandsPosition++] = PathCommand.LineStyleSolid;
this.coordinates[this.coordinatesPosition++] = thickness;
const styles: DataBuffer = this.styles;
styles.writeUnsignedInt(color);
styles.writeBoolean(pixelHinting);
styles.writeUnsignedByte(scaleMode);
styles.writeUnsignedByte(caps);
styles.writeUnsignedByte(joints);
styles.writeUnsignedByte(miterLimit);
this.hasLines = true;
}
writeMorphLineStyle(thickness: number, color: number) {
this.morphCoordinates[this.coordinatesPosition - 1] = thickness;
this.morphStyles.writeUnsignedInt(color);
}
/**
* Bitmaps are specified the same for fills and strokes, so we only need to serialize them
* once. The Parameter `pathCommand` is treated as the actual command to serialize, and must
* be one of BeginBitmapFill and LineStyleBitmap.
*/
beginBitmap(pathCommand: PathCommand, bitmapId: number, matrix: ShapeMatrix,
repeat: boolean, smooth: boolean): void {
assert(pathCommand === PathCommand.BeginBitmapFill ||
pathCommand === PathCommand.LineStyleBitmap);
this.ensurePathCapacities(1, 0);
this.commands[this.commandsPosition++] = pathCommand;
const styles: DataBuffer = this.styles;
styles.writeUnsignedInt(bitmapId);
this._writeStyleMatrix(matrix, false);
styles.writeBoolean(repeat);
styles.writeBoolean(smooth);
this.hasFills = true;
}
writeMorphBitmap(matrix: ShapeMatrix) {
this._writeStyleMatrix(matrix, true);
}
/**
* Gradients are specified the same for fills and strokes, so we only need to serialize them
* once. The Parameter `pathCommand` is treated as the actual command to serialize, and must
* be one of BeginGradientFill and LineStyleGradient.
*/
beginGradient(pathCommand: PathCommand, colors: number[], ratios: number[],
gradientType: number, matrix: ShapeMatrix,
spread: number, interpolation: number, focalPointRatio: number) {
assert(pathCommand === PathCommand.BeginGradientFill ||
pathCommand === PathCommand.LineStyleGradient);
this.ensurePathCapacities(1, 0);
this.commands[this.commandsPosition++] = pathCommand;
const styles: DataBuffer = this.styles;
styles.writeUnsignedByte(gradientType);
assert(focalPointRatio === (focalPointRatio|0));
styles.writeShort(focalPointRatio);
this._writeStyleMatrix(matrix, false);
const colorStops = colors.length;
styles.writeByte(colorStops);
for (let i = 0; i < colorStops; i++) {
// Ratio must be valid, otherwise we'd have bailed above.
styles.writeUnsignedByte(ratios[i]);
// Colors are coerced to uint32, with the highest byte stripped.
styles.writeUnsignedInt(colors[i]);
}
styles.writeUnsignedByte(spread);
styles.writeUnsignedByte(interpolation);
this.hasFills = true;
}
writeMorphGradient(colors: number[], ratios: number[], matrix: ShapeMatrix) {
this._writeStyleMatrix(matrix, true);
const styles: DataBuffer = this.morphStyles;
for (let i = 0; i < colors.length; i++) {
// Ratio must be valid, otherwise we'd have bailed above.
styles.writeUnsignedByte(ratios[i]);
// Colors are coerced to uint32, with the highest byte stripped.
styles.writeUnsignedInt(colors[i]);
}
}
writeCommandAndCoordinates(command: PathCommand, x: number, y: number) {
this.ensurePathCapacities(1, 2);
this.commands[this.commandsPosition++] = command;
this.coordinates[this.coordinatesPosition++] = x;
this.coordinates[this.coordinatesPosition++] = y;
}
writeCoordinates(x: number, y: number) {
this.ensurePathCapacities(0, 2);
this.coordinates[this.coordinatesPosition++] = x;
this.coordinates[this.coordinatesPosition++] = y;
}
writeMorphCoordinates(x: number, y: number) {
this.morphCoordinates = ensureTypedArrayCapacity(this.morphCoordinates,
this.coordinatesPosition);
this.morphCoordinates[this.coordinatesPosition - 2] = x;
this.morphCoordinates[this.coordinatesPosition - 1] = y;
}
clear() {
this.commandsPosition = this.coordinatesPosition = 0;
this.commands = new Uint8Array(DefaultSize.Commands);
this.coordinates = new Int32Array(DefaultSize.Coordinates);
this.styles = new DataBuffer(DefaultSize.Styles);
this.styles.endian = 'auto';
this.hasFills = this.hasLines = false;
}
isEmpty() {
return this.commandsPosition === 0;
}
clone(): ShapeData {
const copy = new ShapeData(false);
copy.commands = new Uint8Array(this.commands);
copy.commandsPosition = this.commandsPosition;
copy.coordinates = new Int32Array(this.coordinates);
copy.coordinatesPosition = this.coordinatesPosition;
copy.styles = new DataBuffer(this.styles.length);
copy.styles.writeRawBytes(this.styles.bytes.subarray(0, this.styles.length));
if (this.morphStyles) {
copy.morphStyles = new DataBuffer(this.morphStyles.length);
copy.morphStyles.writeRawBytes(
this.morphStyles.bytes.subarray(0, this.morphStyles.length));
}
copy.hasFills = this.hasFills;
copy.hasLines = this.hasLines;
return copy;
}
toPlainObject(): PlainObjectShapeData {
return new PlainObjectShapeData(this.commands, this.commandsPosition,
this.coordinates, this.morphCoordinates,
this.coordinatesPosition,
this.styles.buffer, this.styles.length,
this.morphStyles && this.morphStyles.buffer,
this.morphStyles ? this.morphStyles.length : 0,
this.hasFills, this.hasLines);
}
public get buffers(): ArrayBuffer[] {
const buffers = [this.commands.buffer, this.coordinates.buffer, this.styles.buffer];
if (this.morphCoordinates) {
buffers.push(this.morphCoordinates.buffer);
}
if (this.morphStyles) {
buffers.push(this.morphStyles.buffer);
}
return buffers;
}
private _writeStyleMatrix(matrix: ShapeMatrix, isMorph: boolean) {
const styles: DataBuffer = isMorph ? this.morphStyles : this.styles;
styles.write6Floats(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
}
private ensurePathCapacities(numCommands: number, numCoordinates: number) {
// ensureTypedArrayCapacity will hopefully be inlined, in which case the field writes
// will be optimized out.
this.commands = ensureTypedArrayCapacity(this.commands, this.commandsPosition + numCommands);
this.coordinates = ensureTypedArrayCapacity(this.coordinates,
this.coordinatesPosition + numCoordinates);
}
}