DDSType.java

/*
 * Copyright (c) 2024, Paul Allen, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.imageio.plugins.dds;

import static com.twelvemonkeys.imageio.plugins.dds.BlockCompression.*;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.*;

/**
 * <a href="https://learn.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#compression-algorithms">Compression Algorithms</a>
 * <a href="https://github.com/microsoft/DirectXTK12/wiki/DDSTextureLoader#remarks">An extended Non-DX10 FourCC list</a>
 */
enum DDSType {
    // Compressed types
    DXT1('D' + ('X' << 8) + ('T' << 16) + ('1' << 24),  8, DXGI.DXGI_FORMAT_BC1_UNORM, BC1),
    DXT2('D' + ('X' << 8) + ('T' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
    DXT3('D' + ('X' << 8) + ('T' << 16) + ('3' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
    DXT4('D' + ('X' << 8) + ('T' << 16) + ('4' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),
    DXT5('D' + ('X' << 8) + ('T' << 16) + ('5' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),

    ATI1('A' + ('T' << 8) + ('I' << 16) + ('1' << 24),  8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4), // AKA BC4U
    BC4U('B' + ('C' << 8) + ('4' << 16) + ('U' << 24),  8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4),
    BC4S('B' + ('C' << 8) + ('4' << 16) + ('S' << 24),  8, DXGI.DXGI_FORMAT_BC4_SNORM, BC4),
    ATI2('A' + ('T' << 8) + ('I' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5), // AKA BC5U
    BC5U('B' + ('C' << 8) + ('5' << 16) + ('U' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5),
    BC5S('B' + ('C' << 8) + ('5' << 16) + ('S' << 24), 16, DXGI.DXGI_FORMAT_BC5_SNORM, BC5),

    // Special case, see DXT10Header.dxgiFormat for real format
    DXT10('D' + ('X' << 8) + ('1' << 16) + ('0' << 24), -1, DXGI.DXGI_FORMAT_UNKNOWN, null),

    // Custom uncompressed pixel formats
    // TODO: Consider swapping byte order to reflect the DXGI format?
    A1R5G5B5(2, DXGI.DXGI_FORMAT_B5G5R5A1_UNORM,    A1R5G5B5_MASKS),
    X1R5G5B5(2, DXGI.DXGI_FORMAT_UNKNOWN,           X1R5G5B5_MASKS),
    A4R4G4B4(2, DXGI.DXGI_FORMAT_B4G4R4A4_UNORM,    A4R4G4B4_MASKS),
    X4R4G4B4(2, DXGI.DXGI_FORMAT_UNKNOWN,           X4R4G4B4_MASKS),
    R5G6B5(  2, DXGI.DXGI_FORMAT_B5G6R5_UNORM,      R5G6B5_MASKS),
    R8G8B8(  3, DXGI.DXGI_FORMAT_UNKNOWN,           R8G8B8_MASKS),
    A8B8G8R8(4, DXGI.DXGI_FORMAT_R8G8B8A8_UNORM,    A8B8G8R8_MASKS),
    X8B8G8R8(4, DXGI.DXGI_FORMAT_UNKNOWN,           X8B8G8R8_MASKS),
    A8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8A8_UNORM,    A8R8G8B8_MASKS),
    X8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8X8_UNORM,    X8R8G8B8_MASKS);

    private final int fourCC;
    private final int blockSize;
    private final int dxgiFormat;
    final BlockCompression compression;
    final int[] rgbaMasks;

    DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression) {
        this(fourCC, blockSize, dxgiFormat, compression, null);
    }

    DDSType(int blockSize, int dxgiFormat, int[] rgbaMasks) {
        this(0, blockSize, dxgiFormat, null, rgbaMasks);
    }

    DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression, int[] rgbaMasks) {
        this.fourCC = fourCC;
        this.blockSize = blockSize;
        this.dxgiFormat = dxgiFormat;
        this.compression = compression;
        this.rgbaMasks = rgbaMasks;
    }

    public int fourCC() {
        return fourCC;
    }

    public int blockSize() {
        return blockSize;
    }

    public boolean isFourCC() {
        return fourCC != 0;
    }

    public boolean isBlockCompression() {
        return compression != null;
    }

    public int dxgiFormat() {
        return dxgiFormat;
    }

    public static DDSType fromFourCC(int fourCC) {
        if (fourCC != 0) {
            for (DDSType type : values()) {
                if (fourCC == type.fourCC()) {
                    return type;
                }
            }
        }

        throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", fourCC));
    }

    public static DDSType fromDXGIFormat(int dxgiFormat) {
        switch (dxgiFormat) {
            case DXGI.DXGI_FORMAT_R8G8B8A8_TYPELESS:
            case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM:
            case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
            case DXGI.DXGI_FORMAT_R8G8B8A8_UINT:
                return A8B8G8R8; // ABGR

            case DXGI.DXGI_FORMAT_B8G8R8A8_TYPELESS:
            case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM:
            case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
                return A8R8G8B8; // ARGB

            case DXGI.DXGI_FORMAT_B8G8R8X8_TYPELESS:
            case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM:
            case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
                return X8R8G8B8;

            case DXGI.DXGI_FORMAT_B5G5R5A1_UNORM:
                return A1R5G5B5;

            case DXGI.DXGI_FORMAT_B4G4R4A4_UNORM:
                return A4R4G4B4;

            case DXGI.DXGI_FORMAT_B5G6R5_UNORM:
                return R5G6B5;

            case DXGI.DXGI_FORMAT_BC1_TYPELESS:
            case DXGI.DXGI_FORMAT_BC1_UNORM:
            case DXGI.DXGI_FORMAT_BC1_UNORM_SRGB:
                return DXT1;

            case DXGI.DXGI_FORMAT_BC2_TYPELESS:
            case DXGI.DXGI_FORMAT_BC2_UNORM:
            case DXGI.DXGI_FORMAT_BC2_UNORM_SRGB:
                return DXT2;

            case DXGI.DXGI_FORMAT_BC3_TYPELESS:
            case DXGI.DXGI_FORMAT_BC3_UNORM:
            case DXGI.DXGI_FORMAT_BC3_UNORM_SRGB:
                return DXT4;

            case DXGI.DXGI_FORMAT_BC4_TYPELESS:
            case DXGI.DXGI_FORMAT_BC4_UNORM:
                return BC4U;

            case DXGI.DXGI_FORMAT_BC4_SNORM:
                return BC4S;

            case DXGI.DXGI_FORMAT_BC5_TYPELESS:
            case DXGI.DXGI_FORMAT_BC5_UNORM:
                return BC5U;

            case DXGI.DXGI_FORMAT_BC5_SNORM:
                return BC5S;

            default:
                throw new IllegalArgumentException("Unsupported DXGI_FORMAT: " + dxgiFormat);
        }
    }
}