• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/engine/Graphics/Raster.ts
1
import { Graphic, GraphicOptions } from './Graphic';
2
import { ExcaliburGraphicsContext } from './Context/ExcaliburGraphicsContext';
3
import { Color } from '../Color';
4
import { Vector } from '../Math/vector';
5
import { BoundingBox } from '../Collision/BoundingBox';
6
import { watch } from '../Util/Watch';
7
import { ImageFiltering } from './Filtering';
8
import { omit } from '../Util/Util';
9

10
export interface RasterOptions extends GraphicOptions {
11
  /**
12
   * Optionally specify a quality number, which is how much to scale the internal Raster. Default is 1.
13
   *
14
   * For example if the quality is set to 2, it doubles the internal raster bitmap in memory.
15
   *
16
   * Adjusting this value can be useful if you are working with small rasters.
17
   */
18
  quality?: number;
19
  /**
20
   * Optionally specify "smoothing" if you want antialiasing to apply to the raster's bitmap context, by default `false`
21
   */
22
  smoothing?: boolean;
23

24
  /**
25
   * Optionally specify the color of the raster's bitmap context, by default {@apilink Color.Black}
26
   */
27
  color?: Color;
28

29
  /**
30
   * Optionally specify the stroke color of the raster's bitmap context, by default undefined
31
   */
32
  strokeColor?: Color;
33

34
  /**
35
   * Optionally specify the line width of the raster's bitmap, by default 1 pixel
36
   */
37
  lineWidth?: number;
38

39
  /**
40
   * Optionally specify the line dash of the raster's bitmap, by default `[]` which means none
41
   */
42
  lineDash?: number[];
43

44
  /**
45
   * Optionally specify the line end style, default is "butt".
46
   */
47
  lineCap?: 'butt' | 'round' | 'square';
48

49
  /**
50
   * Optionally specify the padding to apply to the bitmap
51
   */
52
  padding?: number;
53

54
  /**
55
   * Optionally specify what image filtering mode should be used, {@apilink ImageFiltering.Pixel} for pixel art,
56
   * {@apilink ImageFiltering.Blended} for hi-res art
57
   *
58
   * By default unset, rasters defer to the engine antialiasing setting
59
   */
60
  filtering?: ImageFiltering;
61
}
62

63
/**
64
 * A Raster is a Graphic that needs to be first painted to a HTMLCanvasElement before it can be drawn to the
65
 * {@apilink ExcaliburGraphicsContext}. This is useful for generating custom images using the 2D canvas api.
66
 *
67
 * Implementors must implement the {@apilink Raster.execute} method to rasterize their drawing.
68
 */
69
export abstract class Raster extends Graphic {
70
  public filtering?: ImageFiltering;
UNCOV
71
  public lineCap: 'butt' | 'round' | 'square' = 'butt';
×
UNCOV
72
  public quality: number = 1;
×
73

74
  public _bitmap: HTMLCanvasElement;
75
  protected _ctx: CanvasRenderingContext2D;
UNCOV
76
  private _dirty: boolean = true;
×
77

78
  constructor(options?: RasterOptions) {
UNCOV
79
    super(omit({ ...options }, ['width', 'height'])); // rasters do some special sauce with width/height
×
UNCOV
80
    if (options) {
×
UNCOV
81
      this.quality = options.quality ?? this.quality;
×
UNCOV
82
      this.color = options.color ?? Color.Black;
×
UNCOV
83
      this.strokeColor = options?.strokeColor;
×
UNCOV
84
      this.smoothing = options.smoothing ?? this.smoothing;
×
UNCOV
85
      this.lineWidth = options.lineWidth ?? this.lineWidth;
×
UNCOV
86
      this.lineDash = options.lineDash ?? this.lineDash;
×
UNCOV
87
      this.lineCap = options.lineCap ?? this.lineCap;
×
UNCOV
88
      this.padding = options.padding ?? this.padding;
×
UNCOV
89
      this.filtering = options.filtering ?? this.filtering;
×
90
    }
UNCOV
91
    this._bitmap = document.createElement('canvas');
×
92
    // get the default canvas width/height as a fallback
UNCOV
93
    const bitmapWidth = options?.width ?? this._bitmap.width;
×
UNCOV
94
    const bitmapHeight = options?.height ?? this._bitmap.height;
×
UNCOV
95
    this.width = bitmapWidth;
×
UNCOV
96
    this.height = bitmapHeight;
×
UNCOV
97
    const maybeCtx = this._bitmap.getContext('2d');
×
UNCOV
98
    if (!maybeCtx) {
×
99
      /* istanbul ignore next */
100
      throw new Error('Browser does not support 2d canvas drawing, cannot create Raster graphic');
101
    } else {
UNCOV
102
      this._ctx = maybeCtx;
×
103
    }
104
  }
105

106
  public cloneRasterOptions(): RasterOptions {
UNCOV
107
    return {
×
108
      color: this.color ? this.color.clone() : undefined,
×
109
      strokeColor: this.strokeColor ? this.strokeColor.clone() : undefined,
×
110
      smoothing: this.smoothing,
111
      lineWidth: this.lineWidth,
112
      lineDash: this.lineDash,
113
      lineCap: this.lineCap,
114
      quality: this.quality,
115
      padding: this.padding
116
    };
117
  }
118

119
  /**
120
   * Gets whether the graphic is dirty, this means there are changes that haven't been re-rasterized
121
   */
122
  public get dirty() {
UNCOV
123
    return this._dirty;
×
124
  }
125

126
  /**
127
   * Flags the graphic as dirty, meaning it must be re-rasterized before draw.
128
   * This should be called any time the graphics state changes such that it affects the outputted drawing
129
   */
130
  public flagDirty() {
UNCOV
131
    this._dirty = true;
×
132
  }
133

134
  private _originalWidth?: number;
135
  /**
136
   * Gets or sets the current width of the Raster graphic. Setting the width will cause the raster
137
   * to be flagged dirty causing a re-raster on the next draw.
138
   *
139
   * Any `padding`s or `quality` set will be factored into the width
140
   */
141
  public get width() {
UNCOV
142
    return Math.abs(this._getTotalWidth() * this.scale.x);
×
143
  }
144
  public set width(value: number) {
UNCOV
145
    value /= Math.abs(this.scale.x);
×
UNCOV
146
    this._bitmap.width = value;
×
UNCOV
147
    this._originalWidth = value;
×
UNCOV
148
    this.flagDirty();
×
149
  }
150

151
  private _originalHeight?: number;
152
  /**
153
   * Gets or sets the current height of the Raster graphic. Setting the height will cause the raster
154
   * to be flagged dirty causing a re-raster on the next draw.
155
   *
156
   * Any `padding` or `quality` set will be factored into the height
157
   */
158
  public get height() {
UNCOV
159
    return Math.abs(this._getTotalHeight() * this.scale.y);
×
160
  }
161

162
  public set height(value: number) {
UNCOV
163
    value /= Math.abs(this.scale.y);
×
UNCOV
164
    this._bitmap.height = value;
×
UNCOV
165
    this._originalHeight = value;
×
UNCOV
166
    this.flagDirty();
×
167
  }
168

169
  private _getTotalWidth() {
UNCOV
170
    return ((this._originalWidth ?? this._bitmap.width) + this.padding * 2) * 1;
×
171
  }
172

173
  private _getTotalHeight() {
UNCOV
174
    return ((this._originalHeight ?? this._bitmap.height) + this.padding * 2) * 1;
×
175
  }
176

177
  /**
178
   * Returns the local bounds of the Raster including the padding
179
   */
180
  public get localBounds() {
UNCOV
181
    return BoundingBox.fromDimension(this._getTotalWidth() * this.scale.x, this._getTotalHeight() * this.scale.y, Vector.Zero);
×
182
  }
183

UNCOV
184
  private _smoothing: boolean = false;
×
185
  /**
186
   * Gets or sets the smoothing (anti-aliasing of the graphic). Setting the height will cause the raster
187
   * to be flagged dirty causing a re-raster on the next draw.
188
   */
189
  public get smoothing() {
UNCOV
190
    return this._smoothing;
×
191
  }
192
  public set smoothing(value: boolean) {
UNCOV
193
    this._smoothing = value;
×
UNCOV
194
    this.flagDirty();
×
195
  }
196

UNCOV
197
  private _color: Color = watch(Color.Black, () => this.flagDirty());
×
198
  /**
199
   * Gets or sets the fillStyle of the Raster graphic. Setting the fillStyle will cause the raster to be
200
   * flagged dirty causing a re-raster on the next draw.
201
   */
202
  public get color() {
UNCOV
203
    return this._color;
×
204
  }
205
  public set color(value) {
UNCOV
206
    this.flagDirty();
×
UNCOV
207
    this._color = watch(value, () => this.flagDirty());
×
208
  }
209

210
  private _strokeColor: Color | undefined;
211
  /**
212
   * Gets or sets the strokeStyle of the Raster graphic. Setting the strokeStyle will cause the raster to be
213
   * flagged dirty causing a re-raster on the next draw.
214
   */
215
  public get strokeColor() {
UNCOV
216
    return this._strokeColor;
×
217
  }
218
  public set strokeColor(value: Color | undefined) {
UNCOV
219
    this.flagDirty();
×
UNCOV
220
    if (value) {
×
UNCOV
221
      this._strokeColor = watch(value, () => this.flagDirty());
×
222
    }
223
  }
224

UNCOV
225
  private _lineWidth: number = 1;
×
226
  /**
227
   * Gets or sets the line width of the Raster graphic. Setting the lineWidth will cause the raster to be
228
   * flagged dirty causing a re-raster on the next draw.
229
   */
230
  public get lineWidth() {
UNCOV
231
    return this._lineWidth;
×
232
  }
233
  public set lineWidth(value) {
UNCOV
234
    this._lineWidth = value;
×
UNCOV
235
    this.flagDirty();
×
236
  }
237

UNCOV
238
  private _lineDash: number[] = [];
×
239
  public get lineDash() {
UNCOV
240
    return this._lineDash;
×
241
  }
242

243
  public set lineDash(value) {
UNCOV
244
    this._lineDash = value;
×
UNCOV
245
    this.flagDirty();
×
246
  }
247

UNCOV
248
  private _padding: number = 0;
×
249
  public get padding() {
UNCOV
250
    return this._padding;
×
251
  }
252

253
  public set padding(value: number) {
UNCOV
254
    this._padding = value;
×
UNCOV
255
    this.flagDirty();
×
256
  }
257

258
  /**
259
   * Rasterize the graphic to a bitmap making it usable as in excalibur. Rasterize is called automatically if
260
   * the graphic is {@apilink Raster.dirty} on the next {@apilink Graphic.draw} call
261
   */
262
  public rasterize(): void {
UNCOV
263
    this._dirty = false;
×
UNCOV
264
    this._ctx.clearRect(0, 0, this._getTotalWidth(), this._getTotalHeight());
×
UNCOV
265
    this._ctx.save();
×
UNCOV
266
    this._applyRasterProperties(this._ctx);
×
UNCOV
267
    this.execute(this._ctx);
×
UNCOV
268
    this._ctx.restore();
×
269
  }
270

271
  protected _applyRasterProperties(ctx: CanvasRenderingContext2D) {
UNCOV
272
    this._bitmap.width = this._getTotalWidth() * this.quality;
×
UNCOV
273
    this._bitmap.height = this._getTotalHeight() * this.quality;
×
274
    // Do a bad thing to pass the filtering as an attribute
UNCOV
275
    this._bitmap.setAttribute('filtering', this.filtering as any);
×
UNCOV
276
    this._bitmap.setAttribute('forceUpload', 'true');
×
UNCOV
277
    ctx.scale(this.quality, this.quality);
×
UNCOV
278
    ctx.translate(this.padding, this.padding);
×
UNCOV
279
    ctx.imageSmoothingEnabled = this.smoothing;
×
UNCOV
280
    ctx.lineWidth = this.lineWidth;
×
UNCOV
281
    ctx.setLineDash(this.lineDash ?? ctx.getLineDash());
×
UNCOV
282
    ctx.lineCap = this.lineCap;
×
UNCOV
283
    ctx.strokeStyle = this.strokeColor?.toString() ?? '';
×
UNCOV
284
    ctx.fillStyle = this.color?.toString();
×
285
  }
286

287
  protected _drawImage(ex: ExcaliburGraphicsContext, x: number, y: number) {
UNCOV
288
    if (this._dirty) {
×
UNCOV
289
      this.rasterize();
×
290
    }
UNCOV
291
    ex.scale(1 / this.quality, 1 / this.quality);
×
UNCOV
292
    ex.drawImage(this._bitmap, x, y);
×
293
  }
294

295
  /**
296
   * Executes drawing implementation of the graphic, this is where the specific drawing code for the graphic
297
   * should be implemented. Once `rasterize()` the graphic can be drawn to the {@apilink ExcaliburGraphicsContext} via `draw(...)`
298
   * @param ctx Canvas to draw the graphic to
299
   */
300
  abstract execute(ctx: CanvasRenderingContext2D): void;
301
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc