This release is a pre-release and may not be stable for production use.
bc7enc-py
⚠️ This is a work in progress ⚠️
Python bindings for bc7enc_rdo, a state of the art RDO BC1-7 GPU texture encoder library.
Development
CMake is required to build the bc7enc as a shared library. Test fixtures under
tests/data/ are stored via git-lfs — install it and
run git lfs install before cloning/pulling, or pytest will fail against
LFS pointer files instead of the real images.
# Development
pip install -e .[tests]
# Build wheel for the current platform (only Windows and Linux supported (64))
pip wheel
# or using build, which will provide a more verbose output
python -m build --wheel
# Running tests
pytest
Usage
The API for now focus on cases for unpacking the dds data where no header might be present, hence the need to specify width, height and format.
from PIL import Image # Optional
from bc7enc import unpack_dds
# Decoding
with open("/path/to/image.dds", "rb") as f:
pixels = unpack_dds(
f,
1024, # width
1024, # height
"DXT1", # format -> DXT1 (BC1), DXT5 (BC3), or BC7
128, # Start of DDS data: 128 for the legacy DXT1/DXT3/DXT5 FourCC
# header, 148 for formats stored via the DX10 extended header
# (BC7 has no legacy FourCC, so it always uses this).
)
# Use the raw pixels to create a png image
im = Image.frombytes("RGBA", (1024, 1024), bytes(pixels))
im.save("/path/to/image.png")
Width and height don't need to be multiples of 4 (or powers of 2) — per the
DDS/BC spec, block storage rounds up (ceil(width / 4) * ceil(height / 4)
blocks), which unpack_dds/pack_dds handle automatically.
Experimental: unpack_dds also accepts an optional unswizzle argument,
for reconstructing a standard RGB tangent-space normal map from one that's
been swizzled to exploit BC3/DXT5's higher-precision alpha channel (X moved
into alpha instead of the usual RGB layout):
"agnm"(Alpha-Green Normal Map, aka "DXT5nm"): X <- alpha, Y <- green, Z is not stored at all and gets reconstructed from X/Y since normal vectors are unit length. Decoded without the swizzle, these textures look flat green instead of the usual purple."rxgb"(the Doom 3/idTech4 convention): X <- alpha; unlike"agnm", Y and Z are still stored normally in green/blue, so no reconstruction is needed for them — only the now-redundant red channel is discarded (conventionally filled with white). Decoded without the swizzle, these textures look pink/magenta instead of purple.
pixels = unpack_dds(f, 1024, 1024, "DXT5", 128, unswizzle="agnm") # or "rxgb"
from PIL import Image
from bc7enc import pack_dds
# Encoding
im = Image.open("/path/to/image.png").convert("RGBA")
blocks = pack_dds(im.tobytes(), im.width, im.height, "BC7") # or DXT1, DXT5, BC5
# `blocks` is raw block data (no DDS header). Wrapping it in a valid DDS
# container requires a DX10 extended header for BC7/BC5 (no legacy FourCC)
# — see tests/data/README.md for a worked example of building one.
pack_dds accepts format-specific keyword arguments, forwarded to the
underlying encoder:
DXT1/BC1:level(0-18, default 10),allow_3color,use_transparent_texels_for_blackDXT5/BC3:level(0-18, default 10)BC5:chan0,chan1— which two source channels to encode (default 0, 1 i.e. R, G)BC7:params, abc7enc_compress_block_params(defaults tobc7enc_compress_block_params_init())
bc7enc_rdo only exposes single 4x4-block encoders (bc7enc_compress_block,
rgbcx::encode_bc1/encode_bc3/encode_bc5) — there's no whole-image DXT1/
DXT3/DXT5 encoder to call directly. pack_dds gets this by looping blocks in
wrapper.cpp (our own file) and calling whichever single-block encoder the
format needs, without modifying the bc7enc_rdo submodule itself.
Mipmaps: bc7enc_rdo has no concept of mip levels either — it's a pure
block compressor, same posture as Microsoft's
DirectXTex (whose GenerateMipMaps
and CompressEx are entirely separate: one resamples an uncompressed image,
the other loops over an already-built chain compressing each level
independently). pack_dds follows the same split — it's opt-in and doesn't
change the function's return type:
blocks = pack_dds(im.tobytes(), im.width, im.height, "BC7", mipmaps=True)
# blocks is still plain bytes: every mip level's block data concatenated in
# order (base level first), same "no DDS header" contract as the single-
# level case. mipmaps=<int> caps the chain at that many levels instead of
# going all the way down to 1x1.
Level dimensions follow the standard mip halving rule (dim > 1 ? dim // 2 : 1,
independent per axis). Resampling uses a 2x2 box filter when both base
dimensions are powers of 2 (exact averaging, since every level then halves
cleanly), and falls back to a general bilinear resize otherwise — mirroring
DirectXTex's own fallback rule (TEX_FILTER_BOX only for power-of-2 input,
TEX_FILTER_LINEAR otherwise). See generate_mip_chain and wrapper.cpp's
downsample_box/downsample_bilinear.
Metadata
Release files for bc7enc-py 0.1.0a1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bc7enc_py-0.1.0a1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| bc7enc_py-0.1.0a1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64, Linux glibc 2.28+ x86-64 | Details |
Total release size: 516.1 kB
Release files / bc7enc_py-0.1.0a1-py3-none-win_amd64.whl
| Download URL | bc7enc_py-0.1.0a1-py3-none-win_amd64.whl |
|---|---|
| Size | 257.2 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
e9e7adddc441ad12aecb70bb3613fe7ddb8b512d415b1d37657e4c3759b9f701
|
|
BLAKE2b-256 checksum How to use checksums |
a0d6689a5d49c5566602d0b9262a09b1cc177c07a69db78668032310633b1bce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / bc7enc_py-0.1.0a1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | bc7enc_py-0.1.0a1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 258.9 kB |
| Tags | Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
3e2d2f5e4a90ed49084e7b792510208dd5017bc4bb5efa090e15fc383ecd37a5
|
|
BLAKE2b-256 checksum How to use checksums |
ccf5b0a1f25488dbd6b034a8e452230faf708813b72e4a3c4467a574400929b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logSecurity for bc7enc-py 0.1.0a1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for bc7enc_py-0.1.0a1-py3-none-win_amd64.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.