Images and textures

Loading, drawing and tinting pictures; filters, wrap modes, pixels and screen grabs.

Image

Image(path=None)

A picture loaded from a file, kept in memory and drawn as a Texture.

load reads PNG, JPEG, GIF, BMP, TIFF and anything else Pillow reads. The pixels stay in memory, so the image can be drawn in any window: the texture is uploaded on the first draw in a window (and again in a new window). Drawing is tinted by the current tint. At a pixel ratio of 1, an image drawn at its own size on whole-number coordinates is pixel exact. Edit pixels in place and call update, or replace them by setting pixels. On a 2x (Retina) display, one image pixel per point means each pixel is magnified to 2x2 screen pixels with linear filtering, so draw it at half its size to keep it pixel for pixel (see Texture). EXIF orientation is ignored: a phone photo loads the way its camera stored it, possibly sideways.

class Sketch(kiln.App):
    def setup(self):
        self.photo = kiln.Image("photo.png")

    def draw(self):
        kiln.tint(255)
        self.photo.draw(10, 10)

Parameters

Name Type Description Default
path str | PathLike | None An image file to load right away, like load(path). None

Attributes

Name Type Description
width int Width in pixels, 0 before loading and after release (read-only).
height int Height in pixels, like width.

Methods

Name Description
draw Draws the image, tinted by the current tint.
from_pixels Makes an image from a numpy array.
grab_screen Copies a rectangle of the screen into the image.
load Reads an image file into memory and uploads it.
release Frees the image’s pixels and its texture.
save Writes the image to a file.
update Sends the pixels to the GPU again.

draw

Image.draw(x, y, w=None, h=None, *, source=None)

Draws the image, tinted by the current tint.

(x, y) is the top-left corner, or the centre after rect_mode(RectMode.CENTER), then moved by the anchor. The current transform applies. Drawing an image that isn’t loaded warns and draws nothing.

source=(sx, sy, sw, sh) draws only that part of the image, in image pixels; see Texture.draw.

Parameters
Name Type Description Default
x float Left (or centre) x. required
y float Top (or centre) y. required
w float | None Drawn width; defaults to the image’s width. None
h float | None Drawn height; defaults to the image’s height. None
source tuple[float, float] | tuple[float, float, float, float] | None The part to draw, (sx, sy) or (sx, sy, sw, sh) in image pixels; sw and sh default to the drawn size. None

from_pixels

Image.from_pixels(pixels)

Makes an image from a numpy array.

Parameters
Name Type Description Default
pixels np.ndarray As Image.pixels takes them. required
Returns
Name Type Description
Image The new Image.

grab_screen

Image.grab_screen(x, y, w, h)

Copies a rectangle of the screen into the image.

Draws made so far this frame are rendered first, so the grab includes them. x, y, w and h are in points, the same units as drawing, with the origin at the window’s top-left, while the image has the rectangle’s pixels: w * pixel_ratio wide. Drawing it back at (x, y, w, h) therefore covers what was grabbed, at any pixel ratio. The rectangle is clipped to the window.

Inside with fbo: it grabs from the Fbo instead, in the Fbo’s units (one per pixel), so the image is exactly w x h; a float Fbo makes a float image.

Parameters
Name Type Description Default
x float Left edge in points. required
y float Top edge in points. required
w float Width in points. required
h float Height in points. required
Raises
Name Type Description
ValueError The rectangle is empty or outside the window (or Fbo).
RuntimeError No App is running.

load

Image.load(path)

Reads an image file into memory and uploads it.

A file that can’t be read leaves the image as it was and returns False (with a warning) instead of raising.

Parameters
Name Type Description Default
path str | PathLike The file to read. required
Returns
Name Type Description
bool True if the file was read.

release

Image.release()

Frees the image’s pixels and its texture.

Draws of it made earlier in the frame are kept. The texture’s GPU image goes back to the pool (see Texture.release). Afterwards the image is empty (width 0, allocated False) until it is loaded or set again.

save

Image.save(path)

Writes the image to a file.

The format comes from the extension (PNG, JPEG, BMP, TIFF, …). Float pixels are saved as 8 bits (0-1 becomes 0-255). JPEG has no alpha, so it is dropped.

Parameters
Name Type Description Default
path str | PathLike The file to write. required
Returns
Name Type Description
bool True if it was written; False, with a warning, for an empty image
bool or a file Pillow can’t write.

update

Image.update()

Sends the pixels to the GPU again.

Call it after editing the array pixels gives. Draws made earlier in the frame keep the old pixels. Without an open window it does nothing: the first draw uploads.

Texture

Texture(width=0, height=0, format=TextureFormat.RGBA8)

Pixels on the GPU, drawn as a quad tinted by the current tint.

A texture belongs to the window that was open when it was allocated, so create it in App.setup (or later). Image keeps its pixels in memory too and is the easier way to draw a picture; use a Texture when the pixels only need to live on the GPU.

Drawing many quads of one texture in a row costs a single draw call. Sampling is linear and clamps at the edges (change that with filter and wrap). At a pixel ratio of 1, a texture drawn at its own size on whole-number coordinates is pixel exact. On a 2x (Retina) display a point is two pixels, so the same draw magnifies each texel to 2x2 pixels with linear filtering, which softens it slightly; draw it at half its size for one texel per pixel, or use TextureFilter.NEAREST for crisp magnified pixels. Quad edges are anti-aliased, so a rotated texture has smooth edges, and a draw on whole pixels still covers them exactly.

Released textures give their GPU image back to a pool that the next texture of a similar size reuses, along with its pipelines, so creating and releasing a texture every frame costs no new GPU objects. A texture that repeats, mirrors or has mipmaps needs an image of exactly its size, reused only by one of the same size: at the same size that is free too, but varying sizes cost a new image and new pipelines each time.

Parameters

Name Type Description Default
width int Width in pixels; with height, allocates right away. 0
height int Height in pixels. 0
format TextureFormat | TextureFormatValue How the pixels are stored. TextureFormat.RGBA8

Attributes

Name Type Description
width Width in pixels: 0 until allocated, and again after release. A texture whose window has closed keeps its size, though allocated is then False.
height Height in pixels, like width.
format The TextureFormat.

Methods

Name Description
allocate Creates (or recreates) the texture’s GPU storage, transparent black.
draw Draws the texture as a quad, tinted by the current tint.
generate_mipmaps Rebuilds the mipmaps from the current pixels.
read Returns the texture’s pixels as a numpy array.
release Frees the texture’s GPU storage.
upload Uploads pixels, allocating the texture first if it isn’t, or is another size.

allocate

Texture.allocate(width, height, format=TextureFormat.RGBA8)

Creates (or recreates) the texture’s GPU storage, transparent black.

Parameters
Name Type Description Default
width int Width in pixels, at least 1. required
height int Height in pixels, at least 1. required
format TextureFormat | TextureFormatValue How the pixels are stored. TextureFormat.RGBA8
Raises
Name Type Description
ValueError A size below 1.
RuntimeError No App is running (call it from setup, update or draw).

draw

Texture.draw(x, y, w=None, h=None, *, source=None)

Draws the texture as a quad, tinted by the current tint.

(x, y) is the top-left corner, or the centre after rect_mode(RectMode.CENTER), then moved by the anchor (see anchor). The current transform applies. The tint multiplies every pixel, so tint(255) (the default) draws the texture as it is and tint(255, 128) at half opacity. Drawing an unallocated texture warns and draws nothing.

At a pixel ratio of 1, a draw at the texture’s own size on whole coordinates is pixel exact; on a 2x display it is magnified 2x with linear filtering (see the class docstring). Edges are anti-aliased.

A negative w or h mirrors the texture: draw(x, y, -w, h) flips it left to right, drawn from x leftwards (x becomes its right edge).

source=(sx, sy, sw, sh) draws only that part of the texture, in texture pixels; sw and sh default to the drawn size. With TextureWrap.REPEAT or MIRRORED_REPEAT the part may reach past the texture, which tiles it: draw(0, 0, 400, 300, source=(0, 0, 1600, 1200)) repeats a texture so that each copy is a quarter of its drawn size. With the default clamping it repeats the edge texels.

Parameters
Name Type Description Default
x float Left (or centre) x. required
y float Top (or centre) y. required
w float | None Drawn width; defaults to the texture’s width. Negative mirrors. None
h float | None Drawn height; defaults to the texture’s height. Negative mirrors. None
source tuple[float, float] | tuple[float, float, float, float] | None The part to draw, (sx, sy) or (sx, sy, sw, sh) in texture pixels; the whole texture if omitted. None
Raises
Name Type Description
RuntimeError No App is running, or the texture was made in a window that has closed.

generate_mipmaps

Texture.generate_mipmaps()

Rebuilds the mipmaps from the current pixels.

Turns mipmaps on first if they aren’t. Needed only after the pixels changed some other way; upload already does it.

read

Texture.read()

Returns the texture’s pixels as a numpy array.

Reads only the texture’s own pixels, not the spare area of the (pooled) GPU image behind it. A call stalls until the GPU has finished.

Returns
Name Type Description
np.ndarray An (h, w, 4) array, top row first: uint8 for RGBA8, float32
np.ndarray for the float formats. If the texture isn’t allocated, a warning
np.ndarray and an empty (0, 0, 4) uint8 array, as for drawing it (which
np.ndarray warns and draws nothing).
Raises
Name Type Description
RuntimeError No App is running, or the texture’s window has closed.

release

Texture.release()

Frees the texture’s GPU storage.

Draws of it made earlier in the frame are kept. The storage goes back to a pool for the next texture of a similar size (and is freed for good when the pool is full, or with the window), so releasing and creating textures every frame is cheap. The texture can be allocated again afterwards. Textures are also released when garbage collected, and with their window.

upload

Texture.upload(pixels)

Uploads pixels, allocating the texture first if it isn’t, or is another size.

Parameters
Name Type Description Default
pixels np.ndarray An (h, w) or (h, w, c) array with c = 1 (gray), 2 (gray, alpha), 3 (RGB) or 4 (RGBA), top row first. uint8 values are 0-255; float values are 0-1 (and may exceed it in a float texture). required

Draws of the texture made earlier in the frame keep the old pixels. Pixels that are rejected leave the texture as it was.

Raises
Name Type Description
ValueError The array has another shape.
TypeError The array is neither uint8 nor floating point.
RuntimeError No App is running.

TextureFormat

TextureFormat()

How a texture stores its pixels.

The members stand for the OpenGL internal formats (GL_RGBA8, GL_RGBA16F, GL_RGBA32F); "rgba8" works as well as TextureFormat.RGBA8.

  • RGBA8: four 8-bit channels, 0-255. The default, and what images use.
  • RGBA16F: four half floats, for values outside 0-1 (offscreen accumulation, GPGPU). 16-bit-float precision is requested, but the pixels are stored as 32-bit floats (like RGBA32F, at twice the memory) until zengl fixes its rgba16float transfers, which in zengl 2.7.3 move 16 bytes per pixel for an 8-byte format.
  • RGBA32F: four 32-bit floats.

TextureFilter

TextureFilter()

How a texture is sampled when it is drawn smaller or larger than it is.

  • NEAREST: the closest texel: crisp, blocky pixels when magnified.
  • LINEAR: a blend of the four closest texels (the default).
  • The *_MIPMAP_* filters, for shrinking only, pick from (and for LINEAR_MIPMAP_LINEAR blend between) pre-shrunk copies of the texture, so detailed images shrink without shimmering. They need mipmaps, which Kiln turns on for you (see Texture.mipmaps).

TextureWrap

TextureWrap()

What a texture does for coordinates outside it. Equivalent to the GL wrap constants.

The members stand for the GL wrap constants; "repeat" works as well as TextureWrap.REPEAT.

  • CLAMP_TO_EDGE: repeat the edge texels (the default).
  • REPEAT: tile the texture.
  • MIRRORED_REPEAT: tile it, flipping every other copy.

save_screen

save_screen(path)

Saves the frame drawn so far to an image file.

Draws made so far this frame are rendered first. The file has the framebuffer’s pixels, so at a pixel ratio of 2 it is twice the window’s size in points. Inside with fbo: it saves the Fbo instead (its pixels, float values clipped to 0-1). The format comes from the extension.

Parameters

Name Type Description Default
path str | PathLike The file to write, e.g. "frame.png". required

Returns

Name Type Description
bool True if it was written; False, with a warning, if it couldn’t be.

Raises

Name Type Description
RuntimeError No App is running.