TextureCanvas
TextureCanvas builds and edits a texture at runtime: scaling, image effects, and composing several textures into one.
var renderContext = RenderContext.GetShared(game.Services);
using var canvas = new TextureCanvas(renderContext, new Size2(1024, 1024));
The third constructor parameter, pixelFormat, defaults to PixelFormat.R8G8B8A8_UNorm.
Note
Dispose the canvas. It holds several GPU textures.
Load a source texture
Most operations start from a texture loaded with the engine's API.
var directory = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!;
var filePath = Path.Combine(directory, "input.png");
using var input = File.Open(filePath, FileMode.Open);
var texture = Texture.Load(game.GraphicsDevice, input);
Load into the canvas
Load reads a file, a Stream or a byte[] straight into the canvas.
var directory = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!;
var filePath = Path.Combine(directory, "input.png");
canvas.Load(filePath);
Store
Store writes the canvas to a file or a Stream; GetData returns the bytes.
var directory = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!;
var filePath = Path.Combine(directory, "output.png");
using var output = File.Create(filePath);
canvas.Store(output, ImageFileType.Png);
ToTexture returns the result as a texture for a material or the UI.
var output = canvas.ToTexture();
Note
Store and GetData copy from the GPU to the CPU and stall the game. Do not call them every frame.
Manipulation
Resample
Resample changes the size or the pixel format and keeps the content, resized if necessary. Setting
Size or PixelFormat does the same for one of the two.
canvas.Resample(size: new Size2(512, 512), pixelFormat: PixelFormat.R8G8B8A8_UNorm);
BrightFilter
Applies a BrightFilter. The values shown are the defaults.
canvas.BrightFilter(threshold: 0.2f, steepness: 1.0f);
GaussianBlur
Applies a GaussianBlur. The values shown are the defaults.
canvas.GaussianBlur(radius: 4, sigmaRatio: 2.0f);
CoCMapBlur
Applies a CoCMapBlur. The default radius is 4.
canvas.CoCMapBlur(radius: 4);
Transform
Applies a ColorTransformGroup. preTransforms and postTransforms are
optional.
canvas.Transform(transforms: new[] { new LuminanceToChannelTransform { ColorChannel = ColorChannel.R } });
Combine
Applies a ColorCombiner. A null entry in textures is the canvas content.
canvas.Combine(
textures: new [] { sourceTexture1, sourceTexture2, null /* = Canvas Content */ },
factors: new [] { .2f, .2f, .6f }
);
Colorize
Multiplies the canvas by a colour.
canvas.Colorize(colorMultiplier: new Color4(1f, 0, 0));
Recolorize
Multiplies the red channel by a colour. Without a colour the result is greyscale.
canvas.Recolorize(colorMultiplier: new Color4(1f, 0, 0));
Apply
Applies any ImageEffect.
using var effect = new GaussianBlur();
canvas.Apply(effect);
Drawing
DrawTexture copies part of a source texture to part of the canvas, which composes several textures
into one.
Stretch
How the source rectangle is resampled into the target rectangle.
- None: The texture preserves its original size. Overflowing content is cropped.
- Stretch: The texture is resized to fill the destination dimensions. The aspect ratio is not preserved.
- Contain: The texture is resized to fit in the destination dimensions while it preserves its native aspect ratio.
- Cover: The texture is resized to fill the destination dimensions while it preserves its native aspect ratio. If the aspect ratio of the destination rectangle differs from the source, the source texture is clipped to fit in the destination dimensions.
Anchor
How the source is aligned when clipping or padding is necessary. The effect depends on the stretch mode.
- TopLeft: Adjust the position so the top-left corner of the source and target rect are aligned.
- Top: Adjust the position so the top-edge center of the source and target rect are aligned.
- TopRight: Adjust the position so the top-right corner of the source and target rect are aligned.
- Left: Adjust the position so the left-edge center of the source and target rect are aligned.
- Center: Adjust the position so the center of the source and target rect are aligned.
- Right: Adjust the position so the right-edge center of the source and target rect are aligned.
- BottomLeft: Adjust the position so the bottom-left corner of the source and target rect are aligned.
- Bottom: Adjust the position so the bottom-edge center of the source and target rect are aligned.
- BottomRight: Adjust the position so the bottom-right corner of the source and target rect are aligned.
Draw
Every overload has this shape. The rectangles are Rectangle in pixels or RectangleF relative to
the texture and the canvas; omit the source rectangle for the whole texture and both for the whole
canvas.
canvas.DrawTexture(
texture,
sourceRect,
destinationRect,
colorMultiplier: null, // Color4, multiplies the source
stretch: Stretch.Stretch,
anchor: Anchor.TopLeft,
samplingPattern: SamplingPattern.Linear);
Examples
| Stretch | Anchor | Source Rect | Target Rect | Result |
|---|---|---|---|---|
| None | TopLeft | ![]() |
![]() |
![]() |
| None | Top | ![]() |
![]() |
![]() |
| None | TopRight | ![]() |
![]() |
![]() |
| None | Left | ![]() |
![]() |
![]() |
| None | Center | ![]() |
![]() |
![]() |
| None | Right | ![]() |
![]() |
![]() |
| None | BottomLeft | ![]() |
![]() |
![]() |
| None | Bottom | ![]() |
![]() |
![]() |
| None | BottomRight | ![]() |
![]() |
![]() |
| Stretch | Any | ![]() |
![]() |
![]() |
| Stretch | Any | ![]() |
![]() |
![]() |
| Contain | AnyLeft | ![]() |
![]() |
![]() |
| Contain | AnyRight | ![]() |
![]() |
![]() |
| Contain | AnyTop | ![]() |
![]() |
![]() |
| Contain | AnyBottom | ![]() |
![]() |
![]() |
| Contain | AnyCenter | ![]() |
![]() |
![]() |
| Cover | AnyLeft | ![]() |
![]() |
![]() |
| Cover | AnyRight | ![]() |
![]() |
![]() |
| Cover | AnyTop | ![]() |
![]() |
![]() |
| Cover | AnyBottom | ![]() |
![]() |
![]() |
| Cover | AnyCenter | ![]() |
![]() |
![]() |
View on GitHub.
using Stride.CommunityToolkit.Engine;
using Stride.CommunityToolkit.Rendering;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Graphics;
using Stride.Rendering.Images;
using Stride.Rendering.Sprites;
using Stride.UI;
using Stride.UI.Controls;
using Stride.UI.Panels;
using var game = new Game();
// A tall window for the 9 by 9 grid, 1000 pixels square. This runs before the device exists;
// game.Window.SetSize(...) inside Start would resize an already-created window, which also works
// but shows a flash at the default size first. The rendering settings a Game Studio project
// carries (DefaultBackBufferWidth and Height through game.UseGameSettings) are not enough on their
// own: the engine clamps them to the window it has already created, so they can shrink a window
// but never grow it past 1280 by 720. SetWindowSize sizes the window and the back buffer together.
game.SetWindowSize(1000, 1080);
game.Run(new SceneSetup3D { Camera = CameraSetup.Perspective(), Ground = null }, start: Start);
void Start(Scene rootScene)
{
// A colour picture: sRGB and premultiplied, as the content pipeline would have imported it. Texture.Load
// with its defaults reads it as linear, and every mid-tone comes out pale
var texture = new TextureLoader(game.GraphicsDevice).Color("input.png");
var grid = new UniformGrid
{
Width = 1000,
Height = 1000,
Columns = 9,
Rows = 9,
Margin = new Thickness(8, 8, 8, 8)
};
grid.Children.Add(CreateCard(texture));
for (var a = 0; a < 9; a++)
{
var anchor = (Anchor)a;
for (var s = 0; s < 4; s++)
{
var stretch = (Stretch)s;
using (var canvas = game.CreateTextureCanvas(new Size2(1024, 1024)))
{
canvas.DrawTexture(texture, new Rectangle(0, 128, 256, 256), new Rectangle(128, 256, 768, 512), null, stretch, anchor, SamplingPattern.Expanded);
var card = CreateCard(canvas.ToTexture());
card.SetGridColumn(a);
card.SetGridRow(s * 2 + 1);
grid.Children.Add(card);
}
using (var canvas = game.CreateTextureCanvas(new Size2(1024, 1024)))
{
canvas.DrawTexture(texture, new Rectangle(0, 128, 256, 256), new Rectangle(256, 128, 512, 768), null, stretch, anchor);
var card = CreateCard(canvas.ToTexture());
card.SetGridColumn(a);
card.SetGridRow(s * 2 + 2);
grid.Children.Add(card);
}
}
}
var entity = new Entity { Scene = rootScene };
entity.Add(new UIComponent { Page = new UIPage { RootElement = grid } });
}
static Border CreateCard(Texture texture)
{
var card = new Border
{
BorderColor = new Color(25, 25, 25),
BackgroundColor = new Color(120, 120, 120),
BorderThickness = new Thickness(2, 2, 2, 2),
Padding = new Thickness(8, 8, 8, 8),
Margin = new Thickness(4, 4, 4, 4),
Content = new StackPanel
{
Orientation = Orientation.Vertical,
Children =
{
new ImageElement
{
Source = new SpriteFromTexture { Texture = texture }
}
}
}
};
return card;
}
/*
---example-metadata
slug: image-processing
title:
en: Image Processing (TextureCanvas)
level: Advanced
category: Rendering
complexity: 4
order: 40
description:
en: |-
Every combination of anchor and stretch that TextureCanvas can apply, drawn as a grid of thumbnails
so the options can be compared rather than read about. Each cell blits a region of a source texture
into a destination rectangle with one setting changed, and renders the result into a UI card. The
canvases are disposed as they go - each one owns a GPU render target, and a grid of them left open
would be an easy leak.
concepts:
- Drawing into an offscreen target with CreateTextureCanvas
- "Blitting a source rectangle into a destination rectangle"
- "How Anchor and Stretch interact when the aspect ratios differ"
- "Choosing a SamplingPattern for the resample"
- Turning a canvas into a Texture for display
- Disposing each canvas so its render target is released
- Laying results out in a UniformGrid of UI cards
tags:
- 2D
- Rendering
- Texture
- TextureCanvas
- Image Processing
- UI
- Disposal
related:
- E02_3D_Material
- E09_3D_SceneRenderer
enabled: true
created: 2023-12-21
---
*/
























