Skip to content
This repository has been archived by the owner on May 29, 2023. It is now read-only.

Commit

Permalink
Merge dbef106 into f36c58d
Browse files Browse the repository at this point in the history
  • Loading branch information
waterplea committed Apr 19, 2020
2 parents f36c58d + dbef106 commit 7067747
Show file tree
Hide file tree
Showing 19 changed files with 551 additions and 243 deletions.
27 changes: 24 additions & 3 deletions projects/canvas/src/directives/canvas-2d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {Directive, ElementRef} from '@angular/core';
import {Directive, ElementRef, Inject, OnDestroy} from '@angular/core';
import {ANIMATION_FRAME} from '@ng-web-apis/common';
import {Observable, Subscription} from 'rxjs';
import {CANVAS_RENDERING_CONTEXT} from '../tokens/canvas-rendering-context';

export function canvasContextFactory({
Expand All @@ -7,12 +9,13 @@ export function canvasContextFactory({
const context = nativeElement.getContext('2d');

if (!context) {
throw new Error('Context was already requested');
throw new Error('Context of different type was already requested');
}

return context;
}

// @dynamic
@Directive({
selector: 'canvas[waCanvas2d]',
providers: [
Expand All @@ -23,4 +26,22 @@ export function canvasContextFactory({
},
],
})
export class Canvas2dDirective {}
export class Canvas2dDirective implements OnDestroy {
private readonly subscription: Subscription;

constructor(
@Inject(CANVAS_RENDERING_CONTEXT) context: CanvasRenderingContext2D,
@Inject(ANIMATION_FRAME) animationFrame$: Observable<number>,
) {
this.subscription = animationFrame$.subscribe(() => {
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.restore();
});
}

ngOnDestroy() {
this.subscription.unsubscribe();
}
}
74 changes: 74 additions & 0 deletions projects/canvas/src/directives/draw-image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {Directive, Inject, Input} from '@angular/core';
import {DrawService} from '../services/draw.service';

@Directive({
selector: '[waCanvasDrawImage]',
providers: [DrawService],
})
export class DrawImageDirective {
@Input()
waCanvasDrawImage?: CanvasImageSource;

@Input()
dX = 0;

@Input()
dY = 0;

@Input()
dWidth?: number;

@Input()
dHeight?: number;

@Input()
sX?: number;

@Input()
sY?: number;

@Input()
sWidth?: number;

@Input()
sHeight?: number;

constructor(@Inject(DrawService) drawService: DrawService) {
drawService.init(context => {
if (!this.waCanvasDrawImage) {
return;
}

if (
this.sWidth !== undefined &&
this.sHeight !== undefined &&
this.sX !== undefined &&
this.sY !== undefined &&
this.dWidth !== undefined &&
this.dHeight !== undefined
) {
context.drawImage(
this.waCanvasDrawImage,
this.sX,
this.sY,
this.sWidth,
this.sHeight,
this.dX,
this.dY,
this.dWidth,
this.dHeight,
);
} else if (this.dWidth !== undefined && this.dHeight !== undefined) {
context.drawImage(
this.waCanvasDrawImage,
this.dX,
this.dY,
this.dWidth,
this.dHeight,
);
} else {
context.drawImage(this.waCanvasDrawImage, this.dX, this.dY);
}
});
}
}
26 changes: 26 additions & 0 deletions projects/canvas/src/directives/fill-rect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Directive, Inject, Input} from '@angular/core';
import {DrawService} from '../services/draw.service';

@Directive({
selector: '[waCanvasFillRect]',
providers: [DrawService],
})
export class FillRectDirective {
@Input()
x = 0;

@Input()
y = 0;

@Input()
width = 0;

@Input()
height = 0;

constructor(@Inject(DrawService) drawService: DrawService) {
drawService.init(context => {
context.fillRect(this.x, this.y, this.width, this.height);
});
}
}
36 changes: 34 additions & 2 deletions projects/canvas/src/directives/tests/canvas-2d.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ import {CANVAS_RENDERING_CONTEXT} from '../../tokens/canvas-rendering-context';
describe('Canvas2dDirective', () => {
@Component({
template: `
<canvas #canvas waCanvas2d></canvas>
<canvas #canvas waCanvas2d width="100" height="100">
<ng-container
waCanvasFillRect
waCanvasFillStyle="red"
waCanvasFilter="hue-rotate(180deg)"
[x]="10"
[y]="10"
[height]="20"
[width]="20"
></ng-container>
</canvas>
`,
})
class TestComponent {
@ViewChild('canvas', {read: CANVAS_RENDERING_CONTEXT})
context!: CanvasRenderingContext2D;
readonly context!: CanvasRenderingContext2D;
}

let fixture: ComponentFixture<TestComponent>;
Expand All @@ -30,7 +40,29 @@ describe('Canvas2dDirective', () => {
fixture.detectChanges();
});

afterAll(() => {
fixture.destroy();
});

it('creates context', () => {
expect(testComponent.context instanceof CanvasRenderingContext2D).toBe(true);
});

it('draws a rectangle at given coordinates of given color with applied filter', done => {
setTimeout(() => {
expect([...testComponent.context.getImageData(5, 5, 1, 1).data]).toEqual([
0,
0,
0,
0,
]);
expect([...testComponent.context.getImageData(25, 25, 1, 1).data]).toEqual([
0,
109,
109,
255,
]);
done();
}, 50);
});
});
94 changes: 94 additions & 0 deletions projects/canvas/src/directives/tests/draw-image.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {Component, ViewChild} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {CanvasModule} from '../../module';
import {CANVAS_RENDERING_CONTEXT} from '../../tokens/canvas-rendering-context';

describe('Canvas2dDirective', () => {
@Component({
template: `
<canvas #canvas waCanvas2d width="100" height="100">
<ng-container [waCanvasDrawImage]="image"></ng-container>
<ng-container
[waCanvasDrawImage]="image"
[dX]="10"
[dY]="10"
[dWidth]="10"
[dHeight]="10"
></ng-container>
</canvas>
`,
})
class TestComponent {
@ViewChild('canvas', {read: CANVAS_RENDERING_CONTEXT})
readonly context!: CanvasRenderingContext2D;

readonly image = new Image();

constructor() {
this.image.src =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==';
}
}

let fixture: ComponentFixture<TestComponent>;
let testComponent: TestComponent;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [CanvasModule],
declarations: [TestComponent],
});
});

beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
testComponent = fixture.componentInstance;
fixture.detectChanges();
});

afterAll(() => {
fixture.destroy();
});

it('draws an image', done => {
setTimeout(() => {
expect([...testComponent.context.getImageData(1, 1, 1, 1).data]).toEqual([
0,
0,
0,
0,
]);
expect([...testComponent.context.getImageData(0, 0, 1, 1).data]).toEqual([
255,
0,
0,
255,
]);
done();
}, 50);
});

it('draws an image with offset and scale', done => {
setTimeout(() => {
expect([...testComponent.context.getImageData(10, 10, 1, 1).data]).toEqual([
255,
0,
0,
255,
]);
expect([...testComponent.context.getImageData(19, 19, 1, 1).data]).toEqual([
255,
0,
0,
255,
]);
expect([...testComponent.context.getImageData(20, 20, 1, 1).data]).toEqual([
0,
0,
0,
0,
]);
done();
}, 50);
});
});
6 changes: 6 additions & 0 deletions projects/canvas/src/interfaces/canvas-draw-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {Context2dProcessor} from '../types/context-processor';

export interface CanvasDrawStep {
readonly beforeHook: Context2dProcessor;
readonly afterHook: Context2dProcessor;
}
20 changes: 18 additions & 2 deletions projects/canvas/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import {NgModule} from '@angular/core';
import {Canvas2dDirective} from './directives/canvas-2d';
import {DrawImageDirective} from './directives/draw-image';
import {FillRectDirective} from './directives/fill-rect';
import {FillStyleDirective} from './steps/fill-style';
import {FilterDirective} from './steps/filter';

@NgModule({
declarations: [Canvas2dDirective],
exports: [Canvas2dDirective],
declarations: [
Canvas2dDirective,
DrawImageDirective,
FillRectDirective,
FillStyleDirective,
FilterDirective,
],
exports: [
Canvas2dDirective,
DrawImageDirective,
FillRectDirective,
FillStyleDirective,
FilterDirective,
],
})
export class CanvasModule {}
22 changes: 22 additions & 0 deletions projects/canvas/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,27 @@
* Public API Surface of @ng-web-apis/canvas
*/

/* Directives */
export * from './directives/canvas-2d';
export * from './directives/draw-image';
export * from './directives/fill-rect';

/* Interfaces */
export * from './interfaces/canvas-draw-step';

/* Services */
export * from './services/draw.service';

/* Steps */
export * from './steps/fill-style';
export * from './steps/filter';

/* Tokens */
export * from './tokens/canvas-draw-steps';
export * from './tokens/canvas-rendering-context';

/* Types */
export * from './types/context-processor';

/* Modules */
export * from './module';
35 changes: 35 additions & 0 deletions projects/canvas/src/services/draw.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {Inject, Injectable, OnDestroy} from '@angular/core';
import {ANIMATION_FRAME} from '@ng-web-apis/common';
import {Observable, Subscription} from 'rxjs';
import {CanvasDrawStep} from '../interfaces/canvas-draw-step';
import {CANVAS_DRAW_STEPS} from '../tokens/canvas-draw-steps';
import {CANVAS_RENDERING_CONTEXT} from '../tokens/canvas-rendering-context';
import {Context2dProcessor} from '../types/context-processor';

// @dynamic
@Injectable()
export class DrawService implements OnDestroy {
private draw: Context2dProcessor = () => {};

private readonly subscription: Subscription;

constructor(
@Inject(CANVAS_DRAW_STEPS) steps: CanvasDrawStep[],
@Inject(CANVAS_RENDERING_CONTEXT) context: CanvasRenderingContext2D,
@Inject(ANIMATION_FRAME) animationFrame$: Observable<number>,
) {
this.subscription = animationFrame$.subscribe(() => {
steps.forEach(step => step.beforeHook(context));
this.draw(context);
steps.forEach(step => step.afterHook(context));
});
}

init(draw: Context2dProcessor) {
this.draw = draw;
}

ngOnDestroy() {
this.subscription.unsubscribe();
}
}

0 comments on commit 7067747

Please sign in to comment.