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 (likeRGBA32F, at twice the memory) until zengl fixes itsrgba16floattransfers, 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 forLINEAR_MIPMAP_LINEARblend between) pre-shrunk copies of the texture, so detailed images shrink without shimmering. They need mipmaps, which Kiln turns on for you (seeTexture.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. |