diff --git a/dds/color.go b/dds/color.go index 03eab07..44846bf 100644 --- a/dds/color.go +++ b/dds/color.go @@ -5,27 +5,27 @@ func mapBits1To8(x uint16) uint8 { } func mapBits2To8(x uint16) uint8 { - return uint8((x * 340) >> 2) + return uint8(x<<6 | x<<4 | x<<2 | x) } func mapBits3To8(x uint16) uint8 { - return uint8((x * 292) >> 3) + return uint8(x<<5 | x<<2 | x>>1) } func mapBits4To8(x uint16) uint8 { - return uint8(x * 17) + return uint8(x<<4 | x) } func mapBits5To8(x uint16) uint8 { - return uint8((x*527 + 23) >> 6) + return uint8(x<<3 | x>>2) } func mapBits6To8(x uint16) uint8 { - return uint8((x*259 + 33) >> 6) + return uint8(x<<2 | x>>4) } func mapBits7To8(x uint16) uint8 { - return uint8((x * 129) >> 6) + return uint8(x<<1 | x>>6) } func colorR5G6B5ToRGB(x uint16) (uint8, uint8, uint8) { @@ -34,39 +34,3 @@ func colorR5G6B5ToRGB(x uint16) (uint8, uint8, uint8) { b5 := x & 0b0000_0000_0001_1111 return mapBits5To8(r5), mapBits6To8(g6), mapBits5To8(b5) } - -// Program to brute force the mapBitsXTo8 parameters -/* -package main - -import ( - "fmt" - "math" -) - -func main() { - const startBits = 5 - const rsh = 6 - - const n = 1 << startBits - - for a := 0; a < 800; a++ { - for b := 0; b < 128; b++ { - bad := false - for i := 0; i < n; i++ { - expect := int(math.Floor(float64(i)*255/(n-1) + 0.5)) - try := (i*a + b) >> rsh - if try != expect { - bad = true - break - } - } - if !bad { - fmt.Printf("mapBits%vTo8(x uint16) uint8 {\n", startBits) - fmt.Printf(" return uint8((x*%v + %v) >> %v)\n", a, b, rsh) - fmt.Printf("}\n") - } - } - } -} -*/ diff --git a/dds/dds.go b/dds/dds.go index 4cd7ddc..137504d 100644 --- a/dds/dds.go +++ b/dds/dds.go @@ -114,8 +114,11 @@ func DecodeInfo(r io.Reader) (Info, error) { case DXGIFormatBC5UNorm: info.ColorModel = color.NRGBAModel info.Decompress = Decompress3Dc - case DXGIFormatBC7UNorm, DXGIFormatBC7UNormSRGB: - return Info{}, errors.New("BC7 compression unsupported") + case DXGIFormatBC7UNorm: + info.ColorModel = color.NRGBAModel + info.Decompress = DecompressBC7 + case DXGIFormatBC7UNormSRGB: + return Info{}, errors.New("BC7 SRGB compression unsupported") default: return Info{}, fmt.Errorf("unsupported DXGI format: %v", dx10.DXGIFormat) } diff --git a/dds/dds_test.go b/dds/dds_test.go index c620552..c97558b 100644 --- a/dds/dds_test.go +++ b/dds/dds_test.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "encoding/hex" "image" - "image/png" + _ "image/png" "os" "testing" @@ -34,47 +34,84 @@ func testImageChecksum(t *testing.T, img image.Image, expectedSumHexStr string) } } -func testDDSImage(t *testing.T, path string, checksumHex string, save bool) { - r, err := os.Open(path) - if err != nil { - t.Fatal(err) - } +func checkDDSImageEqual(t *testing.T, ddsPath, comparePath string, allowDelta int64) { + var ddsImg image.Image + var compareImg image.Image - img, name, err := image.Decode(r) - if err != nil { - t.Fatal(err) - } + { + r, err := os.Open(ddsPath) + if err != nil { + t.Fatal(err) + } + defer r.Close() + + var name string + ddsImg, name, err = image.Decode(r) + if err != nil { + t.Fatal(err) + } - if name != "dds" { - t.Fatalf("expected \"dds\" image, but got \"%v\"", name) + if name != "dds" { + t.Fatalf("expected \"dds\" image, but got \"%v\"", name) + } } - if save { - w, err := os.Create("out.png") + { + r, err := os.Open(comparePath) if err != nil { t.Fatal(err) } - if err := png.Encode(w, img); err != nil { + defer r.Close() + + compareImg, _, err = image.Decode(r) + if err != nil { t.Fatal(err) } } - testImageChecksum(t, img, checksumHex) + if ddsImg.Bounds() != compareImg.Bounds() { + t.Fatal("DDS image and compare image must have the same bounds") + } + + for y := ddsImg.Bounds().Min.Y; y < ddsImg.Bounds().Max.Y; y++ { + for x := ddsImg.Bounds().Min.X; y < ddsImg.Bounds().Max.X; y++ { + r0, g0, b0, a0 := ddsImg.At(x, y).RGBA() + r1, g1, b1, a1 := compareImg.At(x, y).RGBA() + dR, dG, dB, dA := int64(r1)-int64(r0), int64(g1)-int64(g0), int64(b1)-int64(b0), int64(a1)-int64(a0) + if dR < 0 { + dR = -dR + } + if dG < 0 { + dG = -dG + } + if dB < 0 { + dB = -dB + } + if dA < 0 { + dA = -dA + } + // Allow slight deviations due to different mappings + if dR > allowDelta || dG > allowDelta || dB > allowDelta || dA > allowDelta { + t.Fatalf("DDS image and compare image are not equal (x=%v, y=%v: dds: %v, compare: %v)", x, y, [4]uint32{r0, g0, b0, a0}, [4]uint32{r1, g1, b1, a1}) + } + } + } } func TestDDSImage(t *testing.T) { - testDDSImage(t, "testimg-bc1.dds", "079b4749d42c07f36bc6daa7bb2f5476beca92f38f4798e4c98e86624a50d931", false) - testDDSImage(t, "testimg-bc3.dds", "b8127ddcbddd112914bf0a70c8a7116ec311d3f17e5773177ccc403ff610ca6a", false) - testDDSImage(t, "testimg-bc4.dds", "26587032b504ca06724a35e9cb437895ce6e6e491d3a6245089cd396888224c2", false) - testDDSImage(t, "testimg-bc5.dds", "449e0bb16584f6174218c10d7401bd79feff5cffe71d7c28b9fea16d5e6e4daa", false) - testDDSImage(t, "testimg-rgb8.dds", "17a28fb962d0277240418a5f14fb5b14b1c528fcda019d0c9f69de2426886402", false) - testDDSImage(t, "testimg-rgba8.dds", "17a28fb962d0277240418a5f14fb5b14b1c528fcda019d0c9f69de2426886402", false) - testDDSImage(t, "testimg-r5g6r5.dds", "dda7c4a7d79e36aa746929c88de36311797d6c64ef35e3b604366f7d8ee9dafc", false) - testDDSImage(t, "testimg-l8.dds", "b2c503dfcccd074d59dd1fa344053250bec3c84eee8b689617097c8c90e28bbe", false) + checkDDSImageEqual(t, "testimgs/dds/testimg-bc1.dds", "testimgs/compare/testimg-bc1.png", 257) + checkDDSImageEqual(t, "testimgs/dds/testimg-bc3.dds", "testimgs/compare/testimg-bc3.png", 257) + checkDDSImageEqual(t, "testimgs/dds/testimg-bc4.dds", "testimgs/compare/testimg-bc4.png", 257) + checkDDSImageEqual(t, "testimgs/dds/testimg-bc5.dds", "testimgs/compare/testimg-bc5.png", 257) + checkDDSImageEqual(t, "testimgs/dds/testimg-bc7.dds", "testimgs/compare/testimg-bc7.png", 0) + checkDDSImageEqual(t, "testimgs/dds/testimg-rgb8.dds", "testimgs/compare/testimg.png", 0) + checkDDSImageEqual(t, "testimgs/dds/testimg-rgba8.dds", "testimgs/compare/testimg.png", 0) + checkDDSImageEqual(t, "testimgs/dds/testimg-r5g6r5.dds", "testimgs/compare/testimg-r5g6r5.png", 0) + checkDDSImageEqual(t, "testimgs/dds/testimg-l8.dds", "testimgs/compare/testimg-l8.png", 0) } func TestDDSMipMaps(t *testing.T) { - r, err := os.Open("testimg-bc3.dds") + r, err := os.Open("testimgs/dds/testimg-bc3.dds") if err != nil { t.Fatal(err) } @@ -92,7 +129,7 @@ func TestDDSMipMaps(t *testing.T) { t.Fatalf("expected 10 mipmap, but got %v", len(dds.Images[0].MipMaps)) } - testImageChecksum(t, dds.Images[0].MipMaps[0], "b8127ddcbddd112914bf0a70c8a7116ec311d3f17e5773177ccc403ff610ca6a") - testImageChecksum(t, dds.Images[0].MipMaps[1], "293c4be8a6c13bdedf3b8ff5f81d5fc8fe82c468277737f32e6e6035cd4169a6") - testImageChecksum(t, dds.Images[0].MipMaps[2], "1a7ab642e80bf2a3634b5df75b2b0d1ce4c15047626115a3f33c9b7db8a21ae3") + testImageChecksum(t, dds.Images[0].MipMaps[0], "17a28fb962d0277240418a5f14fb5b14b1c528fcda019d0c9f69de2426886402") + testImageChecksum(t, dds.Images[0].MipMaps[1], "dacf23b70aa1422e232c2d496c6cf845e57f3c9e3132823edb603787e843800c") + testImageChecksum(t, dds.Images[0].MipMaps[2], "db0772a48c675b7e1ee58a85c0b7438f8fb3b9b12bd5040fc5cdadb9d44b2324") } diff --git a/dds/decompress.go b/dds/decompress.go index 43420f4..235eb88 100644 --- a/dds/decompress.go +++ b/dds/decompress.go @@ -13,6 +13,213 @@ import ( // https://github.com/ImageMagick/ImageMagick/blob/main/coders/dds.c +var bc7ModeInfo = [8]struct { + PartitionBits uint8 + NumSubsets uint8 + ColorPrecision uint8 + AlphaPrecision uint8 + NumPBits uint8 + IndexPrecision uint8 + Index2Precision uint8 +}{ + {4, 3, 4, 0, 6, 3, 0}, + {6, 2, 6, 0, 2, 3, 0}, + {6, 3, 5, 0, 0, 2, 0}, + {6, 2, 7, 0, 4, 2, 0}, + {0, 1, 5, 6, 0, 2, 3}, + {0, 1, 7, 8, 0, 2, 2}, + {0, 1, 7, 7, 2, 4, 0}, + {6, 2, 5, 5, 4, 2, 0}, +} + +var bc7PartitionTable = [2][64][16]uint8{ + { // Partition set for 2 subsets + {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}, + {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}, + {0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1}, + {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1}, + {0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1}, + {0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1}, + {0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1}, + {0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1}, + {0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0}, + {0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0}, + {0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0}, + {0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1}, + {0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0}, + {0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0}, + {0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0}, + {0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0}, + {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, + {0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0}, + {0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0}, + {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, + {0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1}, + {0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0}, + {0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0}, + {0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0}, + {0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0}, + {0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1}, + {0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1}, + {0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0}, + {0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0}, + {0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0}, + {0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0}, + {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0}, + {0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1}, + {0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1}, + {0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0}, + {0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0}, + {0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0}, + {0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1}, + {0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1}, + {0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0}, + {0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0}, + {0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1}, + {0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1}, + {0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, + {0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1}, + {0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}, + {0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0}, + {0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1}, + }, + + { // Partition set for 3 subsets + {0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 1, 2, 2, 2, 2}, + {0, 0, 0, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1}, + {0, 0, 0, 0, 2, 0, 0, 1, 2, 2, 1, 1, 2, 2, 1, 1}, + {0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 1, 0, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2}, + {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 2, 2}, + {0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2}, + {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2}, + {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2}, + {0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2}, + {0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2}, + {0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2}, + {0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2}, + {0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0}, + {0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2}, + {0, 1, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0}, + {0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2}, + {0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1}, + {0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2}, + {0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1}, + {0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2}, + {0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 1, 0, 2, 2, 1, 0}, + {0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1, 0, 0, 0, 0}, + {0, 0, 1, 2, 0, 0, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2}, + {0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1, 0, 1, 1, 0}, + {0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1}, + {0, 0, 2, 2, 1, 1, 0, 2, 1, 1, 0, 2, 0, 0, 2, 2}, + {0, 1, 1, 0, 0, 1, 1, 0, 2, 0, 0, 2, 2, 2, 2, 2}, + {0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1}, + {0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 1, 1, 2, 2, 2, 1}, + {0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 2, 2, 2}, + {0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 2, 0, 0, 1, 1}, + {0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 2, 2, 0, 2, 2, 2}, + {0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0}, + {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0}, + {0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0}, + {0, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 0, 1, 2, 0}, + {0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1}, + {0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1}, + {0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2}, + {0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1}, + {0, 0, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2, 1, 1, 2, 2}, + {0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 1, 1}, + {0, 2, 2, 0, 1, 2, 2, 1, 0, 2, 2, 0, 1, 2, 2, 1}, + {0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 1}, + {0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1}, + {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2}, + {0, 2, 2, 2, 0, 1, 1, 1, 0, 2, 2, 2, 0, 1, 1, 1}, + {0, 0, 0, 2, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 2}, + {0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2}, + {0, 2, 2, 2, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2}, + {0, 0, 0, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2}, + {0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2}, + {0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2}, + {0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2}, + {0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2}, + {0, 0, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2}, + {0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1}, + {0, 2, 2, 2, 1, 2, 2, 2, 0, 2, 2, 2, 1, 2, 2, 2}, + {0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0}, + }, +} + +var bc7AnchorIndexTable = [4][64]uint8{ + // Anchor index values for the first subset + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + // Anchor index values for the second subset of two-subset partitioning + { + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 2, 8, 2, 2, 8, 8, 15, + 2, 8, 2, 2, 8, 8, 2, 2, + 15, 15, 6, 8, 2, 8, 15, 15, + 2, 8, 2, 2, 2, 15, 15, 6, + 6, 2, 6, 8, 15, 15, 2, 2, + 15, 15, 15, 15, 15, 2, 2, 15, + }, + // Anchor index values for the second subset of three-subset partitioning + { + 3, 3, 15, 15, 8, 3, 15, 15, + 8, 8, 6, 6, 6, 5, 3, 3, + 3, 3, 8, 15, 3, 3, 6, 10, + 5, 8, 8, 6, 8, 5, 15, 15, + 8, 15, 3, 5, 6, 10, 8, 15, + 15, 3, 15, 5, 15, 15, 15, 15, + 3, 15, 5, 5, 5, 8, 5, 10, + 5, 10, 8, 13, 15, 12, 3, 3, + }, + // Anchor index values for the third subset of three-subset partitioning + { + 15, 8, 8, 3, 15, 15, 3, 8, + 15, 15, 15, 15, 15, 15, 15, 8, + 15, 8, 15, 3, 15, 8, 15, 8, + 3, 15, 6, 10, 15, 15, 10, 8, + 15, 3, 15, 10, 10, 8, 9, 10, + 6, 15, 8, 15, 3, 6, 6, 8, + 15, 3, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 3, 15, 15, 8, + }, +} + +var bc7Weight2 = []uint8{0, 21, 43, 64} +var bc7Weight3 = []uint8{0, 9, 18, 27, 37, 46, 55, 64} +var bc7Weight4 = []uint8{0, 4, 9, 13, 17, 21, 26, 30, 34, + 38, 43, 47, 51, 55, 60, 64} + type DecompressFunc func(buf []uint8, r io.Reader, width, height int, info Info) error func avgU8(xs ...uint8) uint8 { @@ -369,7 +576,17 @@ func decode3DcBlock(data []uint8) [8]uint8 { return c } -func get3DcBits(data []uint8, startBit *uint64, numBits uint8) uint8 { +func getBit(data []uint8, startBit *uint64) bool { + index := (*startBit) >> 3 + base := (*startBit) - (index << 3) + (*startBit)++ + if index >= uint64(len(data)) { + return false + } + return (data[index]>>base)&1 != 0 +} + +func getBits(data []uint8, startBit *uint64, numBits uint8) uint8 { index := (*startBit) >> 3 base := (*startBit) - (index << 3) if index >= uint64(len(data)) { @@ -405,7 +622,7 @@ func Decompress3DcPlus(buf []uint8, r io.Reader, width, height int, info Info) e startBit := uint64(16) for j := 0; j < 4; j++ { for i := 0; i < 4; i++ { - lum := c[get3DcBits(data[:], &startBit, 3)] + lum := c[getBits(data[:], &startBit, 3)] if x+i >= width || y+j >= height { continue @@ -439,8 +656,8 @@ func Decompress3Dc(buf []uint8, r io.Reader, width, height int, info Info) error startBitG := uint64(80) for j := 0; j < 4; j++ { for i := 0; i < 4; i++ { - r := cR[get3DcBits(data[:], &startBitR, 3)] - g := cG[get3DcBits(data[:], &startBitG, 3)] + r := cR[getBits(data[:], &startBitR, 3)] + g := cG[getBits(data[:], &startBitG, 3)] if x+i >= width || y+j >= height { continue @@ -457,3 +674,269 @@ func Decompress3Dc(buf []uint8, r io.Reader, width, height int, info Info) error } return nil } + +func readBC7Endpoints(data [16]uint8, mode uint64, startBit *uint64) (r [6]uint8, g [6]uint8, b [6]uint8, a [6]uint8) { + numSubsets := bc7ModeInfo[mode].NumSubsets + colorBits := bc7ModeInfo[mode].ColorPrecision + + for i := 0; i < int(numSubsets)*2; i++ { + r[i] = getBits(data[:], startBit, colorBits) + } + for i := 0; i < int(numSubsets)*2; i++ { + g[i] = getBits(data[:], startBit, colorBits) + } + for i := 0; i < int(numSubsets)*2; i++ { + b[i] = getBits(data[:], startBit, colorBits) + } + + alphaBits := bc7ModeInfo[mode].AlphaPrecision + hasAlpha := mode >= 4 + if hasAlpha { + for i := 0; i < int(numSubsets)*2; i++ { + a[i] = getBits(data[:], startBit, alphaBits) + } + } else { + for i := 0; i < int(numSubsets)*2; i++ { + a[i] = 255 + } + } + + hasPBits := mode == 0 || mode == 1 || mode == 3 || mode == 6 || mode == 7 + + if hasPBits { + for i := 0; i < int(numSubsets)*2; i++ { + r[i] <<= 1 + g[i] <<= 1 + b[i] <<= 1 + a[i] <<= 1 + } + + if mode == 1 { + pBit0 := getBit(data[:], startBit) + pBit1 := getBit(data[:], startBit) + + if pBit0 { + r[0] |= 1 + g[0] |= 1 + b[0] |= 1 + r[1] |= 1 + g[1] |= 1 + b[1] |= 1 + } + + if pBit1 { + r[2] |= 1 + g[2] |= 1 + b[2] |= 1 + r[3] |= 1 + g[3] |= 1 + b[3] |= 1 + } + } else { + for i := 0; i < int(numSubsets)*2; i++ { + pBit := getBit(data[:], startBit) + if pBit { + r[i] |= 1 + g[i] |= 1 + b[i] |= 1 + a[i] |= 1 + } + } + } + + colorBits++ + alphaBits++ + } + + for i := 0; i < int(numSubsets)*2; i++ { + r[i] <<= (8 - colorBits) + g[i] <<= (8 - colorBits) + b[i] <<= (8 - colorBits) + a[i] <<= (8 - alphaBits) + + r[i] |= r[i] >> colorBits + g[i] |= g[i] >> colorBits + b[i] |= b[i] >> colorBits + a[i] |= a[i] >> alphaBits + } + + if !hasAlpha { + for i := 0; i < int(numSubsets)*2; i++ { + a[i] = 255 + } + } + + return +} + +func getBC7SubsetIndex(numSubsets, partitionID uint8, pixelIndex int) uint8 { + if numSubsets == 2 { + return bc7PartitionTable[0][partitionID][pixelIndex] + } + if numSubsets == 3 { + return bc7PartitionTable[1][partitionID][pixelIndex] + } + return 0 +} + +func isBC7PixelAnchorIndex(subsetIndex, numSubsets uint8, pixelIndex int, partitionID uint8) bool { + tableIndex := 0 + if subsetIndex == 0 { + tableIndex = 0 + } else if subsetIndex == 1 && numSubsets == 2 { + tableIndex = 1 + } else if subsetIndex == 1 && numSubsets == 3 { + tableIndex = 2 + } else { + tableIndex = 3 + } + + return int(bc7AnchorIndexTable[tableIndex][partitionID]) == pixelIndex +} + +func DecompressBC7(buf []uint8, r io.Reader, width, height int, info Info) error { + if info.ColorModel != color.NRGBAModel { + return errors.New("BC7 compression expects NRGBA color model") + } + + for y := 0; y < height; y += 4 { + for x := 0; x < width; x += 4 { + var data [16]uint8 + if _, err := io.ReadFull(r, data[:]); err != nil { + return err + } + + startBit := uint64(0) + for startBit <= 8 && !getBit(data[:], &startBit) { + } + mode := startBit - 1 + + if mode > 7 { + return fmt.Errorf("invalid mode: %v", mode) + } + + numSubsets := bc7ModeInfo[mode].NumSubsets + partitionID := uint8(0) + + if mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 7 { + partitionID = getBits(data[:], &startBit, bc7ModeInfo[mode].PartitionBits) + if partitionID > 63 { + return fmt.Errorf("invalid partition ID: %v", partitionID) + } + } + + rotation := uint8(0) + if mode == 4 || mode == 5 { + rotation = getBits(data[:], &startBit, 2) + } + + selectorBit := false + if mode == 4 { + selectorBit = getBit(data[:], &startBit) + } + + cR, cG, cB, cA := readBC7Endpoints(data, mode, &startBit) + + indexPrec := bc7ModeInfo[mode].IndexPrecision + index2Prec := bc7ModeInfo[mode].Index2Precision + + var alphaIndices [16]uint8 + if mode == 4 && selectorBit { + indexPrec = 3 + if getBit(data[:], &startBit) { + alphaIndices[0] = 1 + } + for i := 1; i < 16; i++ { + alphaIndices[i] = getBits(data[:], &startBit, 2) + } + } + + var numBits uint8 + var subsetIndices [16]uint8 + var colorIndices [16]uint8 + for i := 0; i < 16; i++ { + subsetIndices[i] = getBC7SubsetIndex(numSubsets, partitionID, i) + numBits = indexPrec + if isBC7PixelAnchorIndex(subsetIndices[i], numSubsets, i, partitionID) { + numBits-- + } + colorIndices[i] = getBits(data[:], &startBit, numBits) + } + + if mode == 5 || (mode == 4 && !selectorBit) { + alphaIndices[0] = getBits(data[:], &startBit, index2Prec-1) + for i := 1; i < 16; i++ { + alphaIndices[i] = getBits(data[:], &startBit, index2Prec) + } + } + + var areaW, areaH int + if width-x < 4 { + areaW = width - x + } else { + areaW = 4 + } + if height-y < 4 { + areaH = height - y + } else { + areaH = 4 + } + for i := 0; i < areaW*areaH; i++ { + c0 := 2 * subsetIndices[i] + c1 := 2*subsetIndices[i] + 1 + c2 := colorIndices[i] + + weight := uint8(64) + switch indexPrec { + case 2: + if int(c2) < len(bc7Weight2) { + weight = bc7Weight2[c2] + } + case 3: + if int(c2) < len(bc7Weight3) { + weight = bc7Weight3[c2] + } + default: + if int(c2) < len(bc7Weight4) { + weight = bc7Weight4[c2] + } + } + + r := uint8(((64-int(weight))*int(cR[c0]) + int(weight)*int(cR[c1]) + 32) >> 6) + g := uint8(((64-int(weight))*int(cG[c0]) + int(weight)*int(cG[c1]) + 32) >> 6) + b := uint8(((64-int(weight))*int(cB[c0]) + int(weight)*int(cB[c1]) + 32) >> 6) + a := uint8(((64-int(weight))*int(cA[c0]) + int(weight)*int(cA[c1]) + 32) >> 6) + + if mode == 4 || mode == 5 { + a0 := alphaIndices[i] + if int(a0) < len(bc7Weight2) { + weight = bc7Weight2[a0] + } + if mode == 4 && !selectorBit && int(a0) < len(bc7Weight3) { + weight = bc7Weight3[a0] + } + if c0 < 6 && c1 < 6 { + a = uint8(((64-int(weight))*int(cA[c0]) + int(weight)*int(cA[c1]) + 32) >> 6) + } + } + + switch rotation { + case 1: + a, r = r, a + case 2: + a, g = g, a + case 3: + a, b = b, a + } + + areaX, areaY := i%areaW, i/areaW + idx := 4 * ((y+areaY)*width + (x + areaX)) + buf[idx+0] = r + buf[idx+1] = g + buf[idx+2] = b + buf[idx+3] = a + } + } + } + return nil +} diff --git a/dds/testimgs/compare/testimg-bc1.png b/dds/testimgs/compare/testimg-bc1.png new file mode 100644 index 0000000..c3ce72a Binary files /dev/null and b/dds/testimgs/compare/testimg-bc1.png differ diff --git a/dds/testimgs/compare/testimg-bc3.png b/dds/testimgs/compare/testimg-bc3.png new file mode 100644 index 0000000..77c70ee Binary files /dev/null and b/dds/testimgs/compare/testimg-bc3.png differ diff --git a/dds/testimgs/compare/testimg-bc4.png b/dds/testimgs/compare/testimg-bc4.png new file mode 100644 index 0000000..b1ab53c Binary files /dev/null and b/dds/testimgs/compare/testimg-bc4.png differ diff --git a/dds/testimgs/compare/testimg-bc5.png b/dds/testimgs/compare/testimg-bc5.png new file mode 100644 index 0000000..4a8da92 Binary files /dev/null and b/dds/testimgs/compare/testimg-bc5.png differ diff --git a/dds/testimgs/compare/testimg-bc7.png b/dds/testimgs/compare/testimg-bc7.png new file mode 100644 index 0000000..af62d08 Binary files /dev/null and b/dds/testimgs/compare/testimg-bc7.png differ diff --git a/dds/testimgs/compare/testimg-l8.png b/dds/testimgs/compare/testimg-l8.png new file mode 100644 index 0000000..398f6d8 Binary files /dev/null and b/dds/testimgs/compare/testimg-l8.png differ diff --git a/dds/testimgs/compare/testimg-r5g6r5.png b/dds/testimgs/compare/testimg-r5g6r5.png new file mode 100644 index 0000000..4dacbae Binary files /dev/null and b/dds/testimgs/compare/testimg-r5g6r5.png differ diff --git a/dds/testimgs/compare/testimg.png b/dds/testimgs/compare/testimg.png new file mode 100644 index 0000000..2f5a697 Binary files /dev/null and b/dds/testimgs/compare/testimg.png differ diff --git a/dds/testimg-bc1.dds b/dds/testimgs/dds/testimg-bc1.dds similarity index 100% rename from dds/testimg-bc1.dds rename to dds/testimgs/dds/testimg-bc1.dds diff --git a/dds/testimg-bc3.dds b/dds/testimgs/dds/testimg-bc3.dds similarity index 100% rename from dds/testimg-bc3.dds rename to dds/testimgs/dds/testimg-bc3.dds diff --git a/dds/testimg-bc4.dds b/dds/testimgs/dds/testimg-bc4.dds similarity index 100% rename from dds/testimg-bc4.dds rename to dds/testimgs/dds/testimg-bc4.dds diff --git a/dds/testimg-bc5.dds b/dds/testimgs/dds/testimg-bc5.dds similarity index 100% rename from dds/testimg-bc5.dds rename to dds/testimgs/dds/testimg-bc5.dds diff --git a/dds/testimgs/dds/testimg-bc7.dds b/dds/testimgs/dds/testimg-bc7.dds new file mode 100644 index 0000000..9d67407 Binary files /dev/null and b/dds/testimgs/dds/testimg-bc7.dds differ diff --git a/dds/testimg-l8.dds b/dds/testimgs/dds/testimg-l8.dds similarity index 100% rename from dds/testimg-l8.dds rename to dds/testimgs/dds/testimg-l8.dds diff --git a/dds/testimg-r5g6r5.dds b/dds/testimgs/dds/testimg-r5g6r5.dds similarity index 100% rename from dds/testimg-r5g6r5.dds rename to dds/testimgs/dds/testimg-r5g6r5.dds diff --git a/dds/testimg-rgb8.dds b/dds/testimgs/dds/testimg-rgb8.dds similarity index 100% rename from dds/testimg-rgb8.dds rename to dds/testimgs/dds/testimg-rgb8.dds diff --git a/dds/testimg-rgba8.dds b/dds/testimgs/dds/testimg-rgba8.dds similarity index 100% rename from dds/testimg-rgba8.dds rename to dds/testimgs/dds/testimg-rgba8.dds