# gfx/ktx2

`import "github.com/matjam/bunyip/gfx/ktx2"`

Package ktx2 reads and writes KTX2 texture files and encodes and decodes the block-compressed formats they carry. It is the offline half of the texture pipeline: bunyip-tex turns a PNG or JPEG into a KTX2 file holding BC1, BC3, BC4, BC5 or BC7 blocks and the whole mip chain, and gfx.NewCompressedTexture uploads that file straight into a compressed image with the levels as they are, so nothing is compressed or downsampled while a game runs.

### What is written {#hdr-What_is_written}

A file holds one 2D image: a format, a size, and one byte slice per mip level, level 0 first. Array layers, cube faces, 3D textures and supercompression are neither written nor read, and a file that uses them is an error. The data format descriptor is written in its basic form so other KTX2 tools accept the file, and it is not read back: the format number is what the loader needs.

### Colour {#hdr-Colour}

Encode premultiplies a colour image in linear light before compressing it, the same way gfx.NewTexture does, so a compressed texture blends like an uncompressed one. A format that is not sRGB holds data rather than colour, so its texels are encoded as they stand and its mip chain is averaged without a gamma step.

### ASTC {#hdr-ASTC}

ASTC blocks are carried but not encoded or decoded: a file that already holds ASTC parses, names its format and uploads on a device that samples it, and there is nothing to fall back on where a device does not.

## Functions

<a id="PSNR"></a>

### PSNR

```go
func PSNR(a, b *image.RGBA, channels int) float64
```

PSNR is the peak signal-to-noise ratio between two images of the same size, in decibels over the channels given: three for colour alone, four to include alpha. It is what bunyip-tex reports and what the tests hold an encoder to. Identical images have no noise, which it reports as positive infinity.

## Types

<a id="File"></a>

<a id="File.Format"></a>

<a id="File.Width"></a>

<a id="File.Height"></a>

<a id="File.Levels"></a>

### File

```go
type File struct {
	Format        Format
	Width, Height int
	Levels        [][]byte
}
```

File is one 2D texture: a format, a size and its mip levels, level 0 first. Levels holds the blocks as the format packs them, ready to upload.

<a id="Encode"></a>

#### Encode

```go
func Encode(src image.Image, opts Options) (*File, error)
```

Encode compresses an image into a KTX2 file ready for gfx.NewCompressedTexture. A colour image, meaning one in an sRGB format, is premultiplied in linear light first, the same way gfx.NewTexture does it, so a compressed texture blends like an uncompressed one; a format that is not sRGB holds data rather than colour and its texels are encoded as they stand.

<a id="Parse"></a>

#### Parse

```go
func Parse(data []byte) (*File, error)
```

Parse reads a KTX2 file with one face, depth at most one, at most one layer and no supercompression. Dimensions must be 1..65536, every level must have its exact format-specific byte size, and the chain cannot extend beyond 1x1. A zero level count is read as one supplied base level. Levels alias data: keep the input unchanged while the File is in use.

<a id="File.Bytes"></a>

#### File.Bytes

```go
func (f *File) Bytes() ([]byte, error)
```

Bytes encodes the file. The levels are written smallest first, which is the order the specification recommends, with the level index in level order as it requires.

<a id="File.Decodable"></a>

#### File.Decodable

```go
func (f *File) Decodable() bool
```

Decodable reports whether DecodeLevel can expand the file's format, which is what gfx asks before falling back to software on a device that cannot sample it.

<a id="File.DecodeLevel"></a>

#### File.DecodeLevel

```go
func (f *File) DecodeLevel(level int) (*image.RGBA, error)
```

DecodeLevel expands one mip level back to RGBA, for a device that cannot sample the format and for checking what an encoder produced. BC4 puts its one channel in red and BC5 its two in red and green, in both cases with the rest of the texel left at zero and alpha opaque.

<a id="File.LevelSize"></a>

#### File.LevelSize

```go
func (f *File) LevelSize(level int) (w, h int)
```

LevelSize is the size of a mip level in texels, each dimension halved per level and never below one.

<a id="Format"></a>

### Format

```go
type Format uint32
```

Format is the Vulkan format number a KTX2 file names for its texel data. Naming a format permits container parsing and GPU upload; it does not imply CPU codec support. Encode supports RGBA8, BC1, BC3, unsigned BC4/BC5 and BC7. DecodeLevel supports the same formats, with BC7 limited to modes 1 and 6. Other named formats, including ASTC, require a device that can sample them directly.

<a id="Undefined"></a>

<a id="R8G8B8A8Unorm"></a>

<a id="R8G8B8A8SRGB"></a>

<a id="BC1RGBUnorm"></a>

<a id="BC1RGBSRGB"></a>

<a id="BC1RGBAUnorm"></a>

<a id="BC1RGBASRGB"></a>

<a id="BC2Unorm"></a>

<a id="BC2SRGB"></a>

<a id="BC3Unorm"></a>

<a id="BC3SRGB"></a>

<a id="BC4Unorm"></a>

<a id="BC4SNorm"></a>

<a id="BC5Unorm"></a>

<a id="BC5SNorm"></a>

<a id="BC6HUfloat"></a>

<a id="BC6HSfloat"></a>

<a id="BC7Unorm"></a>

<a id="BC7SRGB"></a>

<a id="ASTC4x4Unorm"></a>

<a id="ASTC4x4SRGB"></a>

<a id="ASTC12x12Unorm"></a>

<a id="ASTC12x12SRGB"></a>

```go
const (
	Undefined Format = 0

	R8G8B8A8Unorm Format = 37
	R8G8B8A8SRGB  Format = 43

	// BC1 is four bits a texel: three colour channels and one bit of
	// alpha in the RGBA forms. The encoder writes the opaque four-colour
	// mode, so the RGBA forms decode with every texel opaque.
	BC1RGBUnorm  Format = 131
	BC1RGBSRGB   Format = 132
	BC1RGBAUnorm Format = 133
	BC1RGBASRGB  Format = 134

	BC2Unorm Format = 135
	BC2SRGB  Format = 136

	// BC3 is eight bits a texel: a BC1 colour block and an eight-value
	// alpha block, which is the format for sprites with soft edges.
	BC3Unorm Format = 137
	BC3SRGB  Format = 138

	// BC4 is one channel at four bits a texel and BC5 is two at eight,
	// for masks, height fields and tangent-space normal maps.
	BC4Unorm Format = 139
	BC4SNorm Format = 140
	BC5Unorm Format = 141
	BC5SNorm Format = 142

	BC6HUfloat Format = 143
	BC6HSfloat Format = 144

	// BC7 is eight bits a texel with alpha, and is the best of these for
	// colour that has to hold up close.
	BC7Unorm Format = 145
	BC7SRGB  Format = 146

	// The ASTC formats, 4x4 through 12x12. The package passes these
	// through: it neither encodes nor decodes them.
	ASTC4x4Unorm   Format = 157
	ASTC4x4SRGB    Format = 158
	ASTC12x12Unorm Format = 183
	ASTC12x12SRGB  Format = 184
)
```

The formats this package names. The numbers are Vulkan's, which is what a KTX2 header carries, so gfx hands one straight to the driver.

<a id="Named"></a>

#### Named

```go
func Named(name string, linear bool) (Format, error)
```

Named turns a name such as "bc7" or "bc1" into the format's sRGB or linear pair, for a command-line flag. The known names are bc1, bc3, bc4, bc5 and bc7; bc4 and bc5 hold no colour, so they are always linear.

<a id="Format.ASTC"></a>

#### Format.ASTC

```go
func (f Format) ASTC() bool
```

ASTC reports whether the format is one of the ASTC block formats, which this package carries but does not encode or decode.

<a id="Format.BlockBytes"></a>

#### Format.BlockBytes

```go
func (f Format) BlockBytes() int
```

BlockBytes is how many bytes one block holds.

<a id="Format.BlockSize"></a>

#### Format.BlockSize

```go
func (f Format) BlockSize() (w, h int)
```

BlockSize is the texels one block covers, 4 by 4 for every BC format and 1 by 1 for the uncompressed ones.

<a id="Format.Compressed"></a>

#### Format.Compressed

```go
func (f Format) Compressed() bool
```

Compressed reports whether the format stores blocks rather than texels.

<a id="Format.LevelBytes"></a>

#### Format.LevelBytes

```go
func (f Format) LevelBytes(w, h int) int
```

LevelBytes is how many bytes a mip level of a size takes in the format, with the size rounded up to whole blocks.

<a id="Format.SRGB"></a>

#### Format.SRGB

```go
func (f Format) SRGB() bool
```

SRGB reports whether sampling the format decodes it from sRGB to linear light, which is what a colour texture wants and a mask or a normal map does not.

<a id="Format.String"></a>

#### Format.String

```go
func (f Format) String() string
```

String names the format the way the Vulkan constant does, without the prefix.

<a id="Options"></a>

<a id="Options.Format"></a>

<a id="Options.NoMipmaps"></a>

<a id="Options.Fast"></a>

### Options

```go
type Options struct {
	// Format is the block format to write. Zero means BC7SRGB, which
	// suits colour that has to hold up close; BC1 is a quarter of the
	// size and drops alpha, BC3 keeps alpha at half of BC7's size, and
	// BC4 and BC5 are for one and two channels of data.
	Format Format
	// NoMipmaps writes level 0 alone. The default is the whole chain
	// down to one texel, averaged in linear light for an sRGB format, so
	// a distant surface does not shimmer and nothing is downsampled while
	// the game runs.
	NoMipmaps bool
	// Fast keeps the BC7 encoder to its single-subset mode instead of
	// also searching the sixty-four two-subset partitions. It is several
	// times quicker and a little worse on blocks that straddle an edge.
	Fast bool
}
```

Options says how Encode compresses an image.
