srctools.vtf

Reads and writes Valve’s texture format, VTF.

This is designed to be used with the Python Imaging Library to do the editing of pixels or saving/loading standard image files.

To compress to DXT formats, this uses the libsquish library. Currently, 16-bit HDR formats are not supported, only metdata can be read.

class srctools.vtf.VTF(
width: int,
height: int,
version: tuple[int,
int] = (7,
5),
*,
ref: VecBase | Vec_tuple | tuple[float,
float,
float] = FrozenVec(0,
0,
0),
frames: int = 1,
bump_scale: float = 1.0,
sheet_info: ~collections.abc.Mapping[int,
SheetSequence] = ...,
hotspot_info: list[HotspotRect] | None = None,
hotspot_flags: int = 0,
flags: ~srctools.vtf.VTFFlags = <VTFFlags.EMPTY: 0>,
fmt: ~srctools.vtf.ImageFormats = <ImageFormats[00] RGBA8888: r=8,
g=8,
b=8,
a=8,
size=32>,
thumb_fmt: ~srctools.vtf.ImageFormats = <ImageFormats[13] DXT1: size=64>,
depth: int = 1,
)

Valve Texture Format files, used in the Source Engine.

width: int

The width of the texture. This must be a power of two, but does not need to match the height.

height: int

The height of the texture. This must be a power of two, but does not need to match the width.

depth: int

The “depth” of the texture, used to produce a volumetric texture that has data for a space. This is mutually exclusive with VTFFlags.ENVMAP - it must be 1 for cubemaps.

version: tuple[int, int]

The version number of the file. Supported versions vary from (7, 2) - (7, 5).

reflectivity: Vec

An average of the colors in the texture, used to tint light bounced off surfaces.

bumpmap_scale: float

Indicates how deep a heightmap/bumpmap ranges. Seemingly unused.

resources: dict[ResourceID | bytes, Resource]

In version 7.3+, arbitrary resources may be stored in a VTF. ResourceID specify known resources, but any 4-byte ID may be used. If you do use a custom resource, keep in mind this could break if future srctools versions parse this normally.

sheet_info: dict[int, SheetSequence]

Textures used for particle system sprites may have this resource, defining subareas to randomly pick from when rendering.

hotspot_info: list[HotspotRect] | None

Strata Source adds the hotspot resource, defining regions used to automatically texture brushes.

hotspot_flags: int

Implementation-specific flags byte

flags: VTFFlags

Bitflags specifying behaviours and how the texture was compiled.

frame_count: int

The number of frames, greater than one for an animated texture.

first_frame_index: int

This field appears unused.

format: ImageFormats

The image format to use for the main image.

low_format: ImageFormats

The image format to use for a small thumbnail, usually ≤ 16×16. This is usually DXT1.

mipmap_count: int

The total number of mipmaps in the image.

classmethod read(
file: FileRSeek[bytes],
header_only: bool = False,
) VTF

Read in a VTF file.

Parameters:
  • file – The file to read from, must be seekable.

  • header_only – If set, only read metadata, skip the frames entirely. If accessed the image data will be opaque black.

save(
file: FileWBinarySeek,
version: tuple[int, int] | None = None,
sheet_seq_version: int = 1,
asw_or_later: bool = True,
mip_filter: FilterMode = FilterMode.BILINEAR,
) None

Write out the VTF file to this.

If a version is specified, this overrides the one in the object. The particle system version needs to be specified here. If ATI1N or ATI2N used, whether the engine is ASW or later needs to be specified.

load() None

Fully load all image frames from the VTF.

This allows closing the file stream.

clear_mipmaps(*, after: int = 0) None

Erase the contents of all mipmaps smaller than the given size.

When saved or compute_mipmaps() is called, these empty mipmaps will be recomputed from the largest mipmap. By default this clears all but the largest mipmap.

compute_mipmaps(filter: FilterMode = FilterMode.BILINEAR) None

Regenerate all mipmaps that have previously been cleared.

get(
*,
frame: int = 0,
depth: int = 0,
side: CubeSide | None = None,
mipmap: int = 0,
) Frame

Get a specific image frame.

If the texture is a cubemap, a side must be provided and depth must be 0.

class srctools.vtf.Frame(width: int, height: int)

A single frame of a VTF. This should not be constructed independently.

This is lazy, so it will only read from the file when actually used.

width: int
height: int
load() None

If the image has not been loaded, load it from the file stream.

clear() None

This clears the contents of the frame.

If the VTF is saved, this will be generated from the larger mipmaps.

fill(r: int = 0, g: int = 0, b: int = 0, a: int = 255) None

Fill the frame with the specified colour.

copy_from(source: Frame) None
copy_from(
source: bytes | bytearray | array[int] | memoryview,
format: ImageFormats = ImageFormats.RGBA8888,
) None

Overwrite this frame with other data.

The source can be another Frame, or any buffer with bytes-format data.

rescale_from(
larger: Frame,
filter: FilterMode = FilterMode.BILINEAR,
) None

Regenerate this image from the next mipmap.

The larger image must either have the same dimension, or exactly double.

to_PIL() PIL_Image

Convert the given frame into a PIL image.

Requires Pillow to be installed.

to_tkinter(
tk: tkinter.Misc | None = None,
*,
bg: tuple[int, int, int] | None = None,
) tkinter.PhotoImage

Convert the given frame into a Tkinter PhotoImage.

If bg is set, the image will be composited onto this background. Otherwise, alpha is ignored.

to_wx_image(bg: tuple[int, int, int] | None = None) Any

Convert the given frame into a wxPython wx.Image.

This requires wxPython to be installed. If bg is set, the image will be composited onto this background. Otherwise, alpha is ignored.

to_wx_bitmap(bg: tuple[int, int, int] | None = None) Any

Convert the given frame into a wxPython wx.Bitmap.

This requires wxPython to be installed. If bg is set, the image will be composited onto this background. Otherwise, alpha is ignored.

class srctools.vtf.FilterMode

Bases: Enum

The algorithm to use for generating mipmaps.

NEAREST = 0

Alias: UPPER_LEFT

Just use the upper-left pixel.

UPPER_RIGHT = 1

Just use the upper-right pixel.

LOWER_LEFT = 2

Just use the lower-left pixel.

LOWER_RIGHT = 3

Just use the lower-right pixel.

BILINEAR = 4

Alias: AVERAGE

Average the four pixels together.

class srctools.vtf.Pixel(r: int, g: int, b: int, a: int)

Data structure to hold colour data retrieved from a frame.

r: int
g: int
b: int
a: int
class srctools.vtf.ResourceID

Bases: bytes, Enum

For VTF format 7.3+, there is an extensible resource system.

Any 4-byte ID may be used. These are known IDs, some from Valve and some from elsewhere.

LOW_RES = b'\x01\x00\x00'

Valve ID. The low-res thumbnail. This is in a fixed position in earlier versions.

HIGH_RES = b'0\x00\x00'

Valve ID. The main image. This is in a fixed position in earlier versions.

PARTICLE_SHEET = b'\x10\x00\x00'

Valve ID. Used for particle spritesheets, decoded into sheet_info.

CRC = b'CRC'

A Cyclic Redundancy Checksum of the source image file.

LOD_SETTINGS = b'LOD'

Allows forcing specific mipmaps to be used for ‘medium’ shader settings.

EXTRA_FLAGS = b'TSO'

4 extra bytes of bitflags.

KEYVALUES = b'KVD'

Defined by VTFLib, an arbitrary block of keyvalues data.

STRATA_HOTSPOT = b'+\x00\x00'

Strata Source extension, rectangular regions used to automatically retexture brush faces.

class srctools.vtf.CubeSide

Bases: Enum

The sides of a cubemap texture.

RIGHT = 0
LEFT = 1
BACK = 2
FRONT = 3
UP = 4
DOWN = 5
SPHERE = 6
class srctools.vtf.ImageFormats

Bases: Enum

All VTF image formats, with their data sizes in the value.

RGBA8888 = (8, 8, 8, 8, 32, 0)
ABGR8888 = (8, 8, 8, 8, 32, 1)
RGB888 = (8, 8, 8, 0, 24, 2)
BGR888 = (8, 8, 8, 0, 24, 3)
RGB565 = (5, 6, 5, 0, 16, 4)
I8 = (8, 8, 8, 0, 8, 5)
IA88 = (8, 8, 8, 8, 16, 6)
P8 = (0, 0, 0, 0, 0, 7)
A8 = (0, 0, 0, 8, 8, 8)
RGB888_BLUESCREEN = (8, 8, 8, 0, 24, 9)
BGR888_BLUESCREEN = (8, 8, 8, 0, 24, 10)
ARGB8888 = (8, 8, 8, 8, 32, 11)
BGRA8888 = (8, 8, 8, 8, 32, 12)
DXT1 = (0, 0, 0, 0, 64, 13)
DXT3 = (0, 0, 0, 0, 128, 14)
DXT5 = (0, 0, 0, 0, 128, 15)
BGRX8888 = (8, 8, 8, 8, 32, 16)
BGR565 = (5, 6, 5, 0, 16, 17)
BGRX5551 = (5, 5, 5, 1, 16, 18)
BGRA4444 = (4, 4, 4, 4, 16, 19)
DXT1_ONEBITALPHA = (0, 0, 0, 0, 64, 20)
BGRA5551 = (5, 5, 5, 1, 16, 21)
UV88 = (0, 0, 0, 0, 16, 22)
UVWQ8888 = (0, 0, 0, 0, 32, 23)
RGBA16161616F = (16, 16, 16, 16, 64, 24)
RGBA16161616 = (16, 16, 16, 16, 64, 25)
UVLX8888 = (0, 0, 0, 0, 32, 26)
NONE = (0, 0, 0, 0, 0, 27)
ATI1N = (0, 0, 0, 0, 64, 28)
ATI2N = (0, 0, 0, 0, 128, 29)
property is_compressed: bool

Checks if the format is compressed in 4x4 blocks.

property is_transparent: bool

Checks if the format supports transparency.

frame_size(width: int, height: int) int

Compute the number of bytes needed for this image size.

bin_value(asw: bool) int

Return the enum value for the given format.

This is tricky, since for ATIxN it is different in ASW+.

class srctools.vtf.VTFFlags

Bases: Flag

The various image flags that may be set.

EMPTY = 0x0
POINT_SAMPLE = 0x1
TRILINEAR = 0x2
CLAMP_S = 0x4
CLAMP_T = 0x8
ANISOTROPIC = 0x10
HINT_DXT5 = 0x20
PWL_CORRECTED = 0x40
NORMAL = 0x80
NO_MIP = 0x100
NO_LOD = 0x200
ALL_MIPS = 0x400
PROCEDURAL = 0x800
ONEBITALPHA = 0x1000
EIGHTBITALPHA = 0x2000
ENVMAP = 0x4000
RENDER_TARGET = 0x8000
DEPTH_RENDER_TARGET = 0x10000
NO_DEBUG_OVERRIDE = 0x20000
SINGLE_COPY = 0x40000
PRE_SRGB = 0x80000
NO_DEPTH_BUFFER = 0x800000
CLAMP_U = 0x2000000
VERTEX_TEXTURE = 0x4000000
SS_BUMP = 0x8000000
BORDER = 0x20000000
class srctools.vtf.Resource(flags: int, data: bytes | int)

An arbitary resource contained in a VTF file.

This can either be a 32-bit unsigned integer (stored right in the header), or a block of binary data.

flags: int
data: bytes | int
class srctools.vtf.SheetSequence(
frames: list[tuple[float, TexCoord, TexCoord, TexCoord, TexCoord]],
clamp: bool,
duration: float,
)

VTFs may contain a number of sequences using different parts of the image.

MAX_COUNT = 64

Maximum possible number of sequences.

classmethod from_resource(data: bytes) dict[int, SheetSequence]

Decode from the resource data.

classmethod make_data(
sequences: Mapping[int, SheetSequence],
version: int = 0,
) bytes

Write out the binary form of this.

class srctools.vtf.TexCoord(left: float, top: float, right: float, bottom: float)

Sub-frame used for particle textures.

left: float
top: float
right: float
bottom: float
classmethod from_binary(buffer: bytes, offset: int) TexCoord

Parse from the SheetSequence resource data.

to_binary() bytes

Return the bytes form for the texture coordinate.

class srctools.vtf.HotspotRect(
min_x: int,
min_y: int,
max_x: int,
max_y: int,
*,
random_rotation: bool = False,
random_reflection: bool = False,
is_alternate: bool = False,
)

A set of rectangular regions used to automatically retexture brushes.

There are two methods to define this format. Hammer++ uses .rect keyvalues files, while Strata Source also allows a binary resource embedded in the VTF

Only one version of the VTF format exists, v0x1.

min_x: int
min_y: int
max_x: int
max_y: int
random_rotation: bool

Can the region be rotated randomly?

random_reflection: bool

Can the region be flipped horizontally?

is_alternate: bool
classmethod from_resource(data: bytes) tuple[list[HotspotRect], int]

Parse from the VTF resource data.

This returns the list of regions, and an arbitrary implementation-specific flags byte.

classmethod build_resource(
hotspots: Sequence[HotspotRect],
flags: int,
version: int = 1,
) bytes

Write out the VTF resource data.

Parameters:
  • hotspots – The regions to write.

  • flags – Implementation-specific flags.

  • version – Format version, currently only 0x1 exists.

classmethod parse_rect(
kv: Keyvalues,
) list[HotspotRect]

Parse a .rect file keyvalues block.

classmethod to_kv(
hotspots: Sequence[HotspotRect],
) Keyvalues

Rebuild a .rect keyvalues file.