Skip to content

Commit

Permalink
feat: Implement Gradients
Browse files Browse the repository at this point in the history
  • Loading branch information
mbasaglia committed Oct 11, 2021
1 parent be30ed4 commit ca898bd
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 6 deletions.
6 changes: 3 additions & 3 deletions src/constants/gradient-fill-type.ts
@@ -1,7 +1,7 @@
export enum GradientFillType {
NONE = 1,
LINEAR = 2,
RADIAL = 3,
NONE = 0,
LINEAR = 1,
RADIAL = 2,
ANGULAR = 4,
REFLECTED = 5,
DIAMOND = 6,
Expand Down
130 changes: 130 additions & 0 deletions src/properties/gradient.ts
@@ -0,0 +1,130 @@
import { PropertyType } from '../constants';
import { KeyFrame } from '../timeline/key-frame';
import { Property } from './property';

export class GradientStop {
public offset: number;
public color: number[];

constructor(offset = 0, color: number[] = []) {
this.offset = offset;
this.color = color;
}

get hasAlpha(): boolean {
return this.color.length > 3;
}

get red(): number {
return this.color[0]!;
}

get green(): number {
return this.color[1]!;
}

get blue(): number {
return this.color[2]!;
}

get alpha(): number | undefined {
return this.color[3];
}
}

class GradientColorsProperty extends Property {
public colorCount = 0;

private keyframeValue(index: number): number[] {
if (index >= this.values.length) return [];
return this.values[index].value as number[];
}

public keyframeHasAlpha(index: number): boolean {
return this.keyframeValue(index).length == this.colorCount * 6;
}

public keframeStops(index: number): GradientStop[] {
const values = this.keyframeValue(index);
const stops: GradientStop[] = [];
const hasAlpha = this.keyframeHasAlpha(index);
for (let i = 0; i < this.colorCount; i++) {
const color = values.slice(i, 3);
if (hasAlpha) color.push(values[this.colorCount * 4 + i * 2]);
stops.push(new GradientStop(values[i * 4], color));
}
return stops;
}

public setKeyframeStops(index: number, stops: GradientStop[]) {
if (index >= this.values.length) return;
if (stops.length > this.colorCount) this.colorCount = stops.length;
this.values[index].value = this.stopsToArray(stops);
}

public addKeyframe(frame: number, stops: GradientStop[]) {
const keyframe: KeyFrame = new KeyFrame(frame, this.stopsToArray(stops));
if (stops.length > this.colorCount) this.colorCount = stops.length;
this.values.push(keyframe);
return keyframe;
}

private stopsToArray(stops: GradientStop[]): number[] {
let hasAlpha = false;
const result: number[] = [];
for (const color of stops) {
result.push(color.offset);
result.push(color.red);
result.push(color.green);
result.push(color.blue);
if (color.hasAlpha) hasAlpha = true;
}

if (hasAlpha) {
for (const color of stops) {
result.push(color.offset);
result.push(color.hasAlpha ? color.alpha! : 1);
}
}

return result;
}
}

export class Gradient {
public gradientColors: GradientColorsProperty = new GradientColorsProperty(this, PropertyType.COLOR);

public get colorCount(): number {
return this.gradientColors.colorCount;
}

public set colorCount(count: number) {
this.gradientColors.colorCount = count;
}

/**
* Convert the class instance to Lottie JSON object.
*
* Called by Javascript when serializing object with JSON.stringify()
*
* @returns JSON object
*/
public toJSON(): Record<string, any> {
return {
p: this.colorCount,
k: this.gradientColors,
};
}

/**
* Convert the Lottie JSON object to class instance.
*
* @param json JSON object
* @returns Gradient instance
*/
public fromJSON(json: Record<string, any>): Gradient {
this.gradientColors.fromJSON(json.k);
this.colorCount = json.p;
return this;
}
}
1 change: 1 addition & 0 deletions src/properties/index.ts
@@ -1 +1,2 @@
export * from './property';
export * from './gradient';
6 changes: 3 additions & 3 deletions src/shapes/gradient-fill-shape.ts
@@ -1,21 +1,21 @@
import { BlendMode, FillRuleType, GradientFillType, PropertyType, ShapeType } from '../constants';
import { Property } from '../properties';
import { Gradient, Property } from '../properties';
import { Shape } from './shape';

/**
* Gradient fill shape type.
*/
export class GradientFillShape extends Shape {
/**
* Gradient shape type: fl
* Gradient shape type: gf
*/
public readonly type = ShapeType.GRADIENT_FILL;

public blendMode: BlendMode = BlendMode.NORMAL;

public endPoint: Property = new Property(this, PropertyType.POSITION);

public gradientColors: Property = new Property(this, PropertyType.COLOR);
public gradientColors: Gradient = new Gradient();

public gradientType: GradientFillType = GradientFillType.LINEAR;

Expand Down
128 changes: 128 additions & 0 deletions src/shapes/gradient-stroke-shape.ts
@@ -0,0 +1,128 @@
import { BlendMode, GradientFillType, LineCapType, LineJoinType, PropertyType, ShapeType } from '../constants';
import { Gradient, Property } from '../properties';
import { Shape } from './shape';

/**
* Gradient stroke shape type.
*/
export class GradientStrokeShape extends Shape {
/**
* Gradient shape type: gs
*/
public readonly type = ShapeType.GRADIENT_STROKE;

public blendMode: BlendMode = BlendMode.NORMAL;

public endPoint: Property = new Property(this, PropertyType.POSITION);

public gradientColors: Gradient = new Gradient();

public gradientType: GradientFillType = GradientFillType.LINEAR;

public highlightAngle: Property = new Property(this, PropertyType.NUMBER);

public highlightLength: Property = new Property(this, PropertyType.NUMBER);

public opacity: Property = new Property(this, PropertyType.OPACITY);

public startPoint: Property = new Property(this, PropertyType.POSITION);

/**
* Line cap.
*
* Mapped field: lc
*/
public lineCapType: LineCapType = LineCapType.ROUND;

/**
* Line join.
*
* Mapped field: lj
*/
public lineJoinType: LineJoinType = LineJoinType.ROUND;

/**
* Miter limit.
*
* Mapped field: ml
*/
public miterLimit!: number;

public width: Property = new Property(this, PropertyType.STROKE_WIDTH);

/**
* Convert the Lottie JSON object to class instance.
*
* @param json JSON object
* @returns GradientFillShape instance
*/
public fromJSON(json: Record<string, any>): GradientStrokeShape {
// Base shape
this.classNames = json.cl;
this.id = json.ln;
this.isHidden = json.hd;
this.matchName = json.mn;
this.name = json.nm;

// This shape
this.blendMode = json.bm;
this.opacity.fromJSON(json.o);

// Gradient
this.endPoint.fromJSON(json.e);
this.gradientColors.fromJSON(json.g);
this.gradientType = json.t;
this.startPoint.fromJSON(json.s);

if (this.gradientType === GradientFillType.LINEAR) {
this.highlightAngle.fromJSON(json.a);
this.highlightLength.fromJSON(json.h);
}

// Stroke
this.lineCapType = json.lc in LineCapType ? json.lc : LineCapType.ROUND;
this.lineJoinType = json.lj in LineJoinType ? json.lj : LineJoinType.ROUND;
this.miterLimit = json.ml;
this.width.fromJSON(json.w);

return this;
}

/**
* Convert the class instance to Lottie JSON object.
*
* Called by Javascript when serializing object with JSON.stringify()
*
* @returns JSON object
*/
public toJSON(): Record<string, any> {
return {
ty: this.type,

// Base shape
cl: this.classNames,
hd: this.isHidden,
ln: this.id,
mn: this.matchName,
nm: this.name,

// This shape
bm: this.blendMode,
o: this.opacity,

// Gradient
e: this.endPoint,
g: this.gradientColors,
t: this.gradientType,
a: this.highlightAngle,
h: this.highlightLength,
s: this.startPoint,

// Stroke
lc: this.lineCapType,
lj: this.lineJoinType,
ml: this.miterLimit,
w: this.width,
};
}
}
1 change: 1 addition & 0 deletions src/shapes/index.ts
Expand Up @@ -7,3 +7,4 @@ export * from './rectangle-shape';
export * from './shape';
export * from './stroke-shape';
export * from './trim-shape';
export * from './gradient-stroke-shape';

0 comments on commit ca898bd

Please sign in to comment.