Samplers

Drawing Sprites talks about how the destination rectangle is able to scale a sprite, disregarding its image size. This is thanks to image interpolation, which is controllable via the cer::setSampler() function.

To illustrate this, let’s add a low-resolution image, for example this 32x32 dummy image:

A cer::Sampler describes how an image is interpolated and repeated across image coordinate boundaries. This is especially useful if you desire a pixelated look for your game, or for effects such as texture scrolling.

The cer::setSampler() function can be called at any time; cerlib remembers its state until it’s changed again. Now try loading the 32x32 image and draw it upscaled using a destination rectangle:

image = Image("Test32x32.png");

// Draw the image at position {200, 200}, but scale it up to 300x300 pixels.
drawSprite(Sprite {
    .image   = image,
    .dstRect = { 200, 200, 300, 300 },
});

The image should now be upscaled and blurry, since by default cerlib uses linear interpolation:

Now set a sampler before drawing the sprite that disables image interpolation:

setSampler(Sampler {
    .filter   = ImageFilter::Point,
    .addressU = ImageAddressMode::ClampToEdgeTexels,
    .addressV = ImageAddressMode::ClampToEdgeTexels,
});

drawSprite(Sprite {
    .image   = image,
    .dstRect = { 200, 200, 300, 300 },
});

This will result in a pixelated sprite:

filter refers to the interpolation mode. Point uses nearest neighbor filtering. The addressU and addressV fields refer to how the image is sampled when coordinates fall outside the image bounds. ClampToEdgeTexels for example specifies that every pixel that lies outside the image bounds results in the image border’s color. addressU specifically refers to pixels in the X-axis of the image, while addressV refers to pixels in the Y-axis.

As an example, try changing the addressU value to Repeat and addressV to Mirror:

setSampler(Sampler {
    .filter   = ImageFilter::Point,
    .addressU = ImageAddressMode::Repeat,
    .addressV = ImageAddressMode::Mirror,
});

drawSprite(Sprite {
    .image   = image,
    .dstRect = { 200, 200, 300, 300 },
    .srcRect = Rectangle{ 0, 0, 128, 128 },
});

We can now see that the image is repeated across the X-axis and mirrored across the Y-axis:

The image repeats four times, since we specified a source rectangle size of 128x128 pixels, while the image is 32x32. Remember that you could use the source rectangle to implement texture scrolling. If you are curious, try using the game’s total running time (GameTime::totalTime) as the X or Y value for the source rectangle’s position.

The default sampler is equivalent to cer::linearClamp (see below).

Predefined Samplers

cerlib provides the following predefined samplers:

namespace cer {
    constexpr auto pointRepeat = Sampler {
        .filter   = ImageFilter::Point,
        .addressU = ImageAddressMode::Repeat,
        .addressV = ImageAddressMode::Repeat,
    }

    constexpr auto pointClamp = Sampler {
        .filter   = ImageFilter::Point,
        .addressU = ImageAddressMode::ClampToEdgeTexels,
        .addressV = ImageAddressMode::ClampToEdgeTexels,
    }

    constexpr auto linearRepeat = Sampler {
        .filter   = ImageFilter::Linear,
        .addressU = ImageAddressMode::Repeat,
        .addressV = ImageAddressMode::Repeat,
    }

    constexpr auto linearClamp = Sampler {
        .filter   = ImageFilter::Linear,
        .addressU = ImageAddressMode::ClampToEdgeTexels,
        .addressV = ImageAddressMode::ClampToEdgeTexels,
    }
}

Using a predefined sampler is as simple as:

setSampler(pointRepeat);