Rendering with the canvasRenderer
The previous chapter analyzed Sprite rendering on the default webgl renderer, so let's focus on the canvasRenderer in this chapter.
A demo of an image rendered using the canvas renderer.
To use canvas as a renderer, we need to refer to the
/bundles/-legacy/dist/
Build a simple demo like the one below for testing:
<script src="/bundles/-legacy/dist/"></script>
<script type="text/javascript">
const app = new ({ width: 800, height: 600 , forceCanvas: true});
();
const rectangle = ('');
= 100;
= 100;
(0.5);
= / 4;
(rectangle);
</script>
Also create a simple demo that loads and displays the logo
Running it should see in the first chapter The exact same logo artwork as the one in the web page is rendered on the web page.
Inside the constructor function at line 78, add the();
Export the current renderer to see
constructor(options?: Partial<IApplicationOptions>)
{
// The default options
options = ({
forceCanvas: false,
}, options);
= autoDetectRenderer<VIEW>(options);
();
// install plugins here
Application._plugins.forEach((plugin) =>
{
(this, options);
});
}
Figure 3-1
In Figure 3-1, you can see that a _CanvasRenderer2 is output instead of a CanvasRenderer instance because the one loaded in the demo is actually rollup compiled.
demo /willian12345/blogpost/tree/main/analysis/PixiJS/pixijs-dev/examples/
Sprite Class
Sprite is shared by the webgl renderer and the canvas renderer.
take note of The class itself doesn't do the rendering, it holds the basic information about the Sprite.
Here the final rendering to the canvas is done using theCanvasSpriteRenderer
rendering class
When we draw an image directly using html's canvas, we directly call the method and passes an "image source"
Instead of a direct image or canvas, this image source is encapsulated into a texture, which is a texture object that is managed by the
Find lines 37 - 40 of /packages/.
static extension: ExtensionMetadata = {
name: 'sprite',
type: ,
};
You can see that CanvasSpriteRenderer is a renderer plugin that is called when a sprite needs to be rendered.
The sprite rendering method is ultimately called, i.e., the image or path is drawn to the canvas.
render(sprite: Sprite): void
{
const texture = sprite._texture;
const renderer = ;
const context = ;
const activeResolution = ;
if (!)
{
return;
}
const sourceWidth = texture._frame.width;
const sourceHeight = texture._frame.height;
let destWidth = texture._frame.width;
let destHeight = texture._frame.height;
if ()
{
destWidth = ;
destHeight = ;
}
let wt = ;
let dx = 0;
let dy = 0;
const source = ();
if ( <= 0 || <= 0 || ! || !source)
{
return;
}
(, true);
= ;
// If smoothingEnabled is supported and we need to change the smoothing property for sprite texture
const smoothingEnabled = === SCALE_MODES.LINEAR;
const smoothProperty = ;
if (smoothProperty
&& context[smoothProperty] !== smoothingEnabled)
{
context[smoothProperty] = smoothingEnabled;
}
if ()
{
dx = ( / 2) + - ( * );
dy = ( / 2) + - ( * );
}
else
{
dx = (0.5 - ) * ;
dy = (0.5 - ) * ;
}
if ()
{
(canvasRenderWorldTransform);
wt = canvasRenderWorldTransform;
(wt, , dx, dy);
// the anchor has already been applied above, so lets set it to zero
dx = 0;
dy = 0;
}
dx -= destWidth / 2;
dy -= destHeight / 2;
(wt, , 1);
// Allow for pixel rounding
if ()
{
dx = dx | 0;
dy = dy | 0;
}
const resolution = ;
const outerBlend = ._outerBlend;
if (outerBlend)
{
();
();
(
dx * activeResolution,
dy * activeResolution,
destWidth * activeResolution,
destHeight * activeResolution
);
();
}
if ( !== 0xFFFFFF)
{
if (sprite._cachedTint !== || sprite._tintedCanvas.tintId !== sprite._texture._updateID)
{
sprite._cachedTint = ;
// TODO clean up caching - how to clean up the caches?
sprite._tintedCanvas = (sprite, );
}
(
sprite._tintedCanvas,
0,
0,
(sourceWidth * resolution),
(sourceHeight * resolution),
(dx * activeResolution),
(dy * activeResolution),
(destWidth * activeResolution),
(destHeight * activeResolution)
);
}
else
{
(
source,
texture._frame.x * resolution,
texture._frame.y * resolution,
(sourceWidth * resolution),
(sourceHeight * resolution),
(dx * activeResolution),
(dy * activeResolution),
(destWidth * activeResolution),
(destHeight * activeResolution)
);
}
if (outerBlend)
{
();
}
// just in case, leaking outer blend here will be catastrophic!
(BLEND_MODES.NORMAL);
}
I think the sprite render method is probably the most used method when working with pixi's.
After typing a debugger inside this render method:
Figure 3-2
Looking at the method call stack, you can see from the red up-arrow in Figure 3-2 that the _tick function makes one level of calls to the render method.
What the render function does
The render method does the following:
-
Accepts an instance of a sprite object and gets the current "active canvas2d context" activeContext of the sprite.
The currently active context is not a fixed "rootContext" but a variable one, since it is possible and permissible to create multiple canvas such as "off-screen rendering, caching images with new canvas", etc.
exist
/packages/canvas-render/
Line 79 of the documentinit()
The initialization method can be seen inside the= ;
The default is "root context." -
The next step is to determine the rendering mode of the current canvas context
(, true);
This is to determine the rendering mode of the current canvas context based on the blendMode of the passed-in sprite, which is an enumerated value
The blendMode counterpart can be viewed
/packages/canvas-render/src/utils/
The value generated and stored in theThe effects corresponding to specific values can be viewed/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
In addition to special effects, the most common use of blendMode is in some html5 lottery effects.
-
Generate a transform matrix for the current context.
Determine the size of the "graphic" to be drawn based on the texture of the incoming sprite, rotate it, and transform it into the current context's transform matrix.
The 'wt' variable in the render method (word transform), is this line
(wt, , 1);
-
Determines if context is needed for clip trimming based on outerBlend
It's really about whether or not you want to use the mask effect. For more information see/en-US/docs/Web/API/Canvas_API/Tutorial/Compositing
-
Calling the context's canvas-native method drawImage starts the actual drawing, where the tint value is interesting and will be described in more detail later.
Before drawing, determine whether the graph has been cached before, and if it has been drawn before, draw the cached graph directly to improve performance.
Here the author also annotates a sentence that
// TODO clean up caching - how to clean up the caches?
It just goes to show you that you can't write a program in one fell swoop._!
Coloring a DisplayObject with tint
The tint property is used to change the color of the displayed object.
It gives the display an object by mixing the original color with the specified color.
Here are a few key points to understand how the tint property works:
-
Color mixing: tint accepts a hexadecimal color value which is used to mix with the original color of the object. The mixing operation is not simply replacing colors, but is based on color theory, so different visual effects can be obtained.
-
Transparency effects: The tint operation affects both color and alpha (transparency). This means that a change in color may affect how visually transparent an object is, even if it does not directly change its transparency.
-
All-white or all-transparent unaffected: if a pixel is completely white (#FFFFFF) or completely transparent, then tint will not change it. This is because fully white pixels can absorb any mix of colors, while fully transparent pixels show no color change.
-
Polygons and textures: For display objects that contain a texture (such as Sprite), tint affects the color of the entire texture. And for vector graphics (such as shapes drawn through Graphics), color blending is applied directly to the line or fill color.
-
Performance considerations: Using tint is in many cases more efficient than replacing textures or colors directly, because it avoids reloading or creating new texture resources.
It's interesting to see if you need to deal with the tint cache here. If you export the rendered contents with the value16777215
particle signaling a pause for emphasis(0xFFFFFF)
The output is also16777215
, are converted to decimal.
In our case For example, it is not cached, so it will go straight to the bottom of the direct drawing logic
If we modify our code by adding a line = 'red';
Below:
<script type="text/javascript">
const app = new ({ width: 800, height: 600 , forceCanvas: true});
();
const rectangle = ('');
= 100;
= 100;
(0.5);
= / 4;
= 'red';
(rectangle);
</script>
As you can see, the entire PixiJS logo has turned into a stunning color!
Figure 3-3
More accurately, I'd say it's mixed with red.
within the render function.sprite._tintedCanvas = (sprite, );
exist/packages/canvas-render/src/
Found at line 50 of the source filegetTintedCanvas
methodologies
Inside this method, (texture, color, canvas) is called.
= ? : ;
Finally, depending on whether you can use multiply or not, you can determine which tint method to use, giving priority to the tintWithMultiply method.
/packages/canvas-render/src/
Source document 110 - 159 lines.
tintWithMultiply: (texture: Texture, color: number, canvas: ICanvas): void =>
{
const context = ('2d');
const crop = texture._frame.clone();
const resolution = ;
*= resolution;
*= resolution;
*= resolution;
*= resolution;
= ();
= ();
();
= (color).toHex();
(0, 0, , );
= 'multiply';
const source = ();
(
source,
,
,
,
,
0,
0,
,
);
= 'destination-atop';
(
source,
,
,
,
,
0,
0,
,
);
();
},
In the tintWithMultiply method, we set the context context's fillStyle in conjunction with globalCompositeOperation to draw a rectangular box to be superimposed on the source to achieve the color change, but of course, we are using a canvas that is independent of the rootContext.
= (color).toHex();
(0, 0, , );
= 'multiply';
If Multiply is not supported, the more performance intensive tintWithPerPixel method is called.
tintWithPerPixel: (texture: Texture, color: number, canvas: ICanvas): void =>
{
const context = ('2d');
const crop = texture._frame.clone();
const resolution = ;
*= resolution;
*= resolution;
*= resolution;
*= resolution;
= ();
= ();
();
= 'copy';
(
(),
,
,
,
,
0,
0,
,
);
();
const [r, g, b] = (color).toArray();
const pixelData = (0, 0, , );
const pixels = ;
for (let i = 0; i < ; i += 4)
{
pixels[i + 0] *= r;
pixels[i + 1] *= g;
pixels[i + 2] *= b;
}
(pixelData, 0, 0);
},
Note that the tintWithPerPixel method draws the source image first and then uses getImageData and putImageData pixel-level operations to achieve the color change effect, so it's traditionally more performance intensive.
In the last sentence of the render method(BLEND_MODES.NORMAL);
Restore the context's rendering mode to its normal value so as not to affect global rendering
The canvas-sprite rendering process is now complete.
subsection (of a chapter)
As expected, canvas rendering is a bit easier to understand than webgl rendering. Although both are sequential command lines, the webgl rendering model requires many more steps to gather commands before drawing to the GPU than canvas rendering does
In the next chapter we will focus on the most important event interactions, how PixiJS implements interactive events on canvas, how to handle the most typical mouse clicks and how to respond to them.
Also, if you still haven't been able to get a tuning project up and running in your local area by now, then first refer to the first chapter of this series of articles, and then just download my this/willian12345/blogpost/tree/main/analysis/PixiJS/pixijs-dev Tuning program