Table of Contents

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
---
*/