Hi @AetherCLion! I think I’ve got an idea of what’s going on here.
For the first message:
- AM227_001_Diffuse_PTSH-min is a PNG file that uses an 8-bit-per-pixel palette. Because PNG has lossless compression, it only uses 10,823,121 bytes for 16,777,216 pixels.
- Because lossless compression is variable-rate, it’s very difficult to calculate the compressed size of an image without compressing it. For instance, a texture of white noise will create a large PNG or EXR file, while a texture with a single color will create a small PNG or EXR file.
- The Exporter saves EXR files using EXR’s default format, which is 16-bit-per-component floating-point HDR with lossless PIZ compression. This would be 100,663,296 bytes uncompressed, but PIZ compression brings it down to 26,953,689 bytes. (It’s larger than the original, but that’s because EXR can store more values; while PIZ compression takes advantage of how the data only has a limited number of values, it doesn’t do as well as PNG’s compression here).
- Substance Painter saves EXR files using 32-bit-per-component floating-point HDR (which I didn’t realize was possible until now)! Because OpenEXR’s lossless compression doesn’t perform as well on 32-bpc (bits per component) floating-point as on 16-bpc floating-point, it’s more than twice the size of the 16-bpc EXR file the Exporter created.
- The box in the lower-right in the Exporter reports the data size if the current file was saved as a DDS file (or KTX2 when Basis is selected). Other file types might have different sizes, but we display the DDS file size because this tool’s usually used to create DDS files, and because we don’t know which file type the user will save to until they click “Save As…”. In this case, BC1 uses 4 bits per pixel, so it uses 4096 * 4096 * (4 bits/8 bits per byte) = 8,366,608 bytes of data.
In short, the file sizes come from the formats they use:
- -min_dds is a DDS file that uses BC1 (4 bits per pixel)
- -min is a PNG file that uses an 8-bit palette (256 colors, 8 bits per pixel + lossless compression)
- -min_EXR and min_EXR-BC1 are EXR files that use 16-bpc floating-point (48 bits per pixel + lossless compression)
- the Substance Painter file is an EXR file that uses 32-bpc floating-point (96 bits per pixel + lossless compression)
For the second message, the NVTT EXR file is smaller because it quantized all the colors in the EXR file from 32-bit-per-channel floating-point to 16-bit-per-channel floating point; because 16bpc floating-point compresses better, it’s more than twice as small.
- There’s a really good idea for a feature here: maybe the Exporter should support saving EXR files as 32-bit! In the meantime, I think the formats the Exporter can write to that support 32-bit floating-point are 32x4f KTX2 (which will probably be the smallest since it has Zstandard lossless compression by default), .pfm (no support for alpha, 96 bits per pixel), and 32x4f DDS (128 bits per pixel).
For the third and fourth messages, this behavior is a bit counterintuitive, but it comes down to how BC6S works and how OpenEXR’s PIZ compression works! The short answer is that the input contains only 256 unique values; PIZ compression recognizes that to some extent and compresses the input well. Lossy compression codecs usually reduce entropy, but this is a case where BC6S encodes some pixels differently depending on other pixels in the block – so the output actually has more distinct values, and PIZ compression performs worse.
Here’s the longer explanation! To help visualize what’s going on, here I’ve displayed the hex contents of two DDS files. The color data for each one starts at offset 0x80, and stores the 16-bit floating-point luminance of each pixel in little-endian (least significant byte first). The one on the left is from a grayscale PNG file converted to EXR then to 16f DDS, and the one on the right is from that same file compressed to BC6S DDS and then to 16f DDS.
- The inputs are PGM files, probably using 1 byte per pixel, and storing only luminance. So there’s 256 different values for pixels.
- The 16-bit numbers we see on the left don’t look like round numbers, but there are only 256 different 16-bit numbers in the file on the left.
- The reason why is because EXR and 16f store linear colors. To convert from sRGB 0-255 to a 16-bit floating-point number, we divide by 255 and then apply the sRGB-to-linear transfer function. So the 16-bit numbers we see don’t look round (for instance, 0x1ce4 = 0.0047760009765625, which isn’t a multiple of 1/255; converting it back from linear to sRGB, we get 0.0588…, which is 15/255.
- Because there’s only 256 different 16-bit numbers, PIZ compression does relatively OK on this file.
- BC6S compression compresses each 4x4 pixel block as well as it can. But because it’s designed for positive and negative HDR RGB content, it uses a block compression algorithm that’s meant for that (instead of realizing that the input is really an 8-bit grayscale image converted to linear). Because of this block compression, we sometimes get blocks like the one stored in bytes 0x260-0x26F; the block contains multiple shades of gray, so the pixels stored in the bytes I’ve highlighted change.
- This means that the output file actually has more unique 16-bit colors, and so EXR’s PIZ compression performs much worse on it (specifically, its entropy coding performs worse because there are more distinct values)!
An analogy is that this is sort of like taking a PNG image, saving it as a JPEG image, and then saving it as a PNG image again. This test PNG file is 1,113 bytes (it compresses really well because it has lots of empty space and solid colors):

Here I’ve converted it to a 20,141 byte JPEG file using Photoshop quality 6:

Converted back to a PNG file, it uses 18,420 bytes, since JPEG compression introduced some artifacts that PNG spends extra space losslessly encoding:

Hopefully this answers your questions about why we sometimes see these effects when converting back and forth between different lossy and lossless formats.