@@ -4,6 +4,7 @@ import type { HTMLCanvasElement } from "./HTMLCanvasElement.js";
4
4
5
5
import { CANVAS_DATA } from "./HTMLCanvasElement.js" ;
6
6
import { ImageData } from "./ImageData.js" ;
7
+ import { resizeImage } from "./WasmResize.js"
7
8
8
9
// Partial types via https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts
9
10
export type RenderingContext = CanvasRenderingContext2D | ImageBitmapRenderingContext
@@ -37,19 +38,52 @@ interface RGBAColor {
37
38
a ?: number
38
39
}
39
40
40
- const FILL_STYLE : unique symbol = Symbol ( "fill-style" ) ;
41
+ interface Context2DState {
42
+ fillStyle : string
43
+ scaleX : number
44
+ scaleY : number
45
+ translateX : number
46
+ translateY : number
47
+ }
48
+
49
+ const STATE : unique symbol = Symbol ( "context2d-state" ) ;
41
50
42
51
export class CanvasRenderingContext2D implements CanvasRect , CanvasDrawImage , CanvasImageData {
43
52
readonly canvas : HTMLCanvasElement ;
44
53
45
- private [ FILL_STYLE ] : string ;
54
+ private [ STATE ] : Context2DState ;
55
+
56
+ reset ( ) {
57
+ this [ STATE ] = {
58
+ fillStyle : "#000" ,
59
+ scaleX : 1 ,
60
+ scaleY : 1 ,
61
+ translateX : 0 ,
62
+ translateY : 0 ,
63
+ } ;
64
+ }
46
65
47
66
get fillStyle ( ) : string {
48
- return this [ FILL_STYLE ] ;
67
+ return this [ STATE ] . fillStyle ;
49
68
}
50
69
set fillStyle ( newStyle : string ) {
51
70
console . log ( `${ this } →fillStyle = ${ newStyle } ` ) ;
52
- this [ FILL_STYLE ] = newStyle ;
71
+ this [ STATE ] . fillStyle = newStyle ;
72
+ }
73
+
74
+ get transformActive ( ) : boolean {
75
+ const active = this [ STATE ] . scaleX !== 1 || this [ STATE ] . scaleY !== 1 || this [ STATE ] . translateX !== 0 || this [ STATE ] . translateY !== 0 ;
76
+
77
+ if ( active ) {
78
+ const activeTransforms = [ ] ;
79
+ if ( this [ STATE ] . scaleX !== 1 ) activeTransforms . push ( `scaleX: ${ this [ STATE ] . scaleX } ` ) ;
80
+ if ( this [ STATE ] . scaleY !== 1 ) activeTransforms . push ( `scaleY: ${ this [ STATE ] . scaleY } ` ) ;
81
+ if ( this [ STATE ] . translateX !== 0 ) activeTransforms . push ( `translateX: ${ this [ STATE ] . translateX } ` ) ;
82
+ if ( this [ STATE ] . translateY !== 0 ) activeTransforms . push ( `translateY: ${ this [ STATE ] . translateY } ` ) ;
83
+ console . log ( `${ this } : context has active matrix transforms: ${ activeTransforms . join ( ', ' ) } ` ) ;
84
+ }
85
+
86
+ return active ;
53
87
}
54
88
55
89
// CanvasRect
@@ -58,6 +92,10 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
58
92
}
59
93
60
94
fillRect ( x : number , y : number , w : number , h : number ) : void {
95
+ if ( this [ STATE ] . scaleX !== 1 || this [ STATE ] . scaleY !== 1 || this [ STATE ] . translateX !== 0 || this [ STATE ] . translateY !== 0 ) {
96
+ console . log ( `Warning: ${ this } →fillRect( ${ Array . from ( arguments ) . join ( ', ' ) } ) canvas transform matrix not supported: ${ Object . values ( this [ STATE ] ) . map ( ( [ k , v ] ) => k + ': ' + v ) . join ( ', ' ) } ` ) ;
97
+ }
98
+
61
99
const { r, g, b, a } = this . fillStyleRGBA ;
62
100
const alpha = a * 255 | 0 ;
63
101
@@ -98,7 +136,7 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
98
136
this . canvas = parentCanvas ;
99
137
100
138
// defaults
101
- this . fillStyle = "#000" ;
139
+ this . reset ( ) ;
102
140
}
103
141
104
142
// CanvasDrawImage
@@ -109,29 +147,61 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
109
147
if ( image instanceof globalThis . HTMLCanvasElement ) {
110
148
w1 = w1 ?? image . width ;
111
149
h1 = h1 ?? image . height ;
150
+ x2 = x2 ?? 0 ;
151
+ y2 = y2 ?? 0 ;
112
152
113
153
if ( w1 !== w2 || h1 !== h2 ) {
114
154
console . log ( `${ this } Not implemented: image scaling in drawImage( <${ image . constructor . name } > ${ Array . from ( arguments ) . join ( ', ' ) } )` ) ;
115
155
return ;
116
156
}
117
157
118
- const srcImage = image . getContext ( "2d" ) . getImageData ( x1 , y1 , w1 , h1 ) ;
158
+ let srcImage = image . getContext ( "2d" ) . getImageData ( x1 , y1 , w1 , h1 ) ;
159
+
160
+ // Scaling/translation needed
161
+ if ( this . transformActive ) {
162
+ // This is slightly inaccurate but we don't do subpixel drawing
163
+ const targetWidth = this [ STATE ] . scaleX * w1 | 0 ;
164
+ const targetHeight = this [ STATE ] . scaleY * h1 | 0 ;
165
+
166
+ x2 = x2 + this [ STATE ] . translateX | 0 ;
167
+ y2 = y2 + this [ STATE ] . translateY | 0 ;
168
+
169
+ srcImage = resizeImage ( srcImage , targetWidth , targetHeight ) ;
170
+ w1 = srcImage . width ;
171
+ h1 = srcImage . height ;
172
+
173
+ console . log ( `${ this } →drawImage(): source image resized to: ${ w1 } x${ h1 } (${ srcImage . data . length / 4 } pixels)` ) ;
174
+ console . log ( `${ this } →drawImage(): drawing to translated coordinates: ( ${ x2 } , ${ y2 } )` ) ;
175
+ }
176
+
119
177
const srcPixels = srcImage . data ;
120
178
const dstPixels = this . canvas [ CANVAS_DATA ] ;
179
+ const canvasW = this . canvas . width ;
180
+ const canvasH = this . canvas . height ;
121
181
const rows = h1 ;
122
182
const cols = w1 ;
123
183
184
+ let ntp = 0 ;
185
+ let oob = 0 ;
124
186
for ( let row = 0 ; row < rows ; ++ row ) {
125
187
for ( let col = 0 ; col < cols ; ++ col ) {
188
+ // Index of the destination canvas pixel should be within bounds
189
+ const di = ( ( y2 + row ) * canvasW + x2 + col ) * 4 ;
190
+
191
+ if ( di < 0 || di >= dstPixels . length ) {
192
+ ++ oob ;
193
+ continue ;
194
+ }
195
+
126
196
// source pixel
127
197
const si = ( ( y1 + row ) * srcImage . width + x1 + col ) * 4 ;
128
198
const sr = srcPixels [ si ] ;
129
199
const sg = srcPixels [ si + 1 ] ;
130
200
const sb = srcPixels [ si + 2 ] ;
131
201
const sa = srcPixels [ si + 3 ] ;
202
+ if ( sa > 0 ) ++ ntp ;
132
203
133
204
// destination pixel
134
- const di = ( ( y2 + row ) * srcImage . width + x2 + col ) * 4 ;
135
205
const dr = dstPixels [ di ] ;
136
206
const dg = dstPixels [ di + 1 ] ;
137
207
const db = dstPixels [ di + 2 ] ;
@@ -148,6 +218,8 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
148
218
}
149
219
}
150
220
console . log ( `${ this } →drawImage( <${ image . constructor . name } > ${ Array . from ( arguments ) . join ( ', ' ) } )` ) ;
221
+ console . log ( `${ this } →drawImage(): number of non-transparent source pixels drawn: ${ ntp } (${ ntp / ( srcPixels . length / 4 ) * 100 | 0 } %)` ) ;
222
+ console . log ( `${ this } →drawImage(): skipped drawing of ${ oob } out-of-bounds pixels on the canvas` ) ;
151
223
return ;
152
224
}
153
225
@@ -233,14 +305,46 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
233
305
setTransform ( a : number , b : number , c : number , d : number , e : number , f : number ) : void ;
234
306
setTransform ( transform ?: DOMMatrix2DInit ) : void ;
235
307
setTransform ( matrixOrA ?: any , b ?, c ?, d ?, e ?, f ?) {
236
- console . log ( `${ this } Not implemented: context2d.setTransform( ${ Array . from ( arguments ) . join ( ', ' ) } )` ) ;
308
+ // Expand calls using a DOMMatrix2D object
309
+ if ( typeof matrixOrA === 'object' ) {
310
+ if ( 'a' in matrixOrA || 'b' in matrixOrA || 'c' in matrixOrA || 'd' in matrixOrA || 'e' in matrixOrA || 'f' in matrixOrA ||
311
+ 'm11' in matrixOrA || 'm12' in matrixOrA || 'm21' in matrixOrA || 'm22' in matrixOrA || 'm31' in matrixOrA || 'm32' in matrixOrA ) {
312
+ return this . setTransform (
313
+ matrixOrA . a ?? matrixOrA . m11 , matrixOrA . b ?? matrixOrA . m12 , matrixOrA . c ?? matrixOrA . m21 ,
314
+ matrixOrA . dx ?? matrixOrA . m22 , matrixOrA . e ?? matrixOrA . m31 , matrixOrA . f ?? matrixOrA . m32
315
+ ) ;
316
+ }
317
+ } else {
318
+ const a = matrixOrA ;
319
+
320
+ if ( b !== 0 || c !== 0 ) {
321
+ console . log ( `${ this } Not implemented: context2d.setTransform( ${ Array . from ( arguments ) . join ( ', ' ) } ) skew/rotate transforms` ) ;
322
+ }
323
+
324
+ this . scale ( a , d ) ;
325
+ this . translate ( e , f ) ;
326
+
327
+ console . log ( `${ this } →setTransform( ${ Array . from ( arguments ) . join ( ', ' ) } )` ) ;
328
+ }
329
+ }
330
+ scale ( xScale : number , yScale : number ) {
331
+ this [ STATE ] . scaleX = xScale ;
332
+ this [ STATE ] . scaleY = yScale ;
333
+ }
334
+ translate ( x : number , y : number ) {
335
+ this [ STATE ] . translateX = x ;
336
+ this [ STATE ] . translateY = y ;
237
337
}
238
338
239
339
// Stringifies the context object with its canvas & unique ID to ease debugging
240
340
get [ Symbol . toStringTag ] ( ) {
241
341
return `${ this . canvas [ Symbol . toStringTag ] } ::context2d` ;
242
342
}
243
343
344
+ private setPixel ( x , y , r , g , b , a ) {
345
+
346
+ }
347
+
244
348
// https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
245
349
private get fillStyleRGBA ( ) : RGBAColor {
246
350
let c ;
0 commit comments