How to decode BC1 and change the color palette and encode it again

Dec 11, 2012 at 6:37 PM

Hi all. I have loaded a dds file which is compressed using BC1 and I want to decode it and manipulate the pixels and color palette and encode it again. I believe it should possible using D3DXDecodeBC1 and D3DXEncodeBC1 in BH.cpp, but I don't know how to use them after loading the dds file. I have problem with argument parameters like how to connect them to the loaded dds file. (I don't want to decompress the dds file)

Coordinator
Dec 11, 2012 at 7:22 PM

Typically you would load the BC1 DDS file using LoadFromDDSFile, decompress to an RGB format using Decompress, manipulate the Image pixels, then compress using Compress, and then write the result using SaveToDDSFile.

If you want to manipulate the individually encoded blocks without decompressing the whole image, then you should just use LoadFromDDSFile and then walk the data yourself in the resulting Image. Look at the code in DirectXTexCompress.cpp for examples of working with the Image data. It's pretty simple: 

// cImage is the compressed Image

    // Determine BC format decoder
    BC_DECODE pfDecode;
    size_t sbpp;
    switch(cImage.format)
    {
    case DXGI_FORMAT_BC1_UNORM:
    case DXGI_FORMAT_BC1_UNORM_SRGB:    pfDecode = D3DXDecodeBC1;   sbpp = 8;   break;
    case DXGI_FORMAT_BC2_UNORM:
    case DXGI_FORMAT_BC2_UNORM_SRGB:    pfDecode = D3DXDecodeBC2;   sbpp = 16;  break;
    case DXGI_FORMAT_BC3_UNORM:
    case DXGI_FORMAT_BC3_UNORM_SRGB:    pfDecode = D3DXDecodeBC3;   sbpp = 16;  break;
    case DXGI_FORMAT_BC4_UNORM:         pfDecode = D3DXDecodeBC4U;  sbpp = 8;   break;
    case DXGI_FORMAT_BC4_SNORM:         pfDecode = D3DXDecodeBC4S;  sbpp = 8;   break;
    case DXGI_FORMAT_BC5_UNORM:         pfDecode = D3DXDecodeBC5U;  sbpp = 16;  break;
    case DXGI_FORMAT_BC5_SNORM:         pfDecode = D3DXDecodeBC5S;  sbpp = 16;  break;
    case DXGI_FORMAT_BC6H_UF16:         pfDecode = D3DXDecodeBC6HU; sbpp = 16;  break;
    case DXGI_FORMAT_BC6H_SF16:         pfDecode = D3DXDecodeBC6HS; sbpp = 16;  break;
    case DXGI_FORMAT_BC7_UNORM:
    case DXGI_FORMAT_BC7_UNORM_SRGB:    pfDecode = D3DXDecodeBC7;   sbpp = 16;  break;
    default:
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    XMVECTOR temp[16];
    const uint8_t *pSrc = cImage.pixels;
    for( size_t h=0; h < cImage.height; h += 4 )
    {
        const uint8_t *sptr = pSrc;
        for( size_t count = 0; count < cImage.rowPitch; count += sbpp )
        {
            pfDecode( temp, sptr );
            // temp now contains the 16 pixels as XMVECTOR RGBA values for this 4x4 block

            sptr += sbpp;
       }

        pSrc += cImage.rowPitch;
   }
Dec 12, 2012 at 10:49 AM

Thanks Walbourn for your prompt reply. 

I tried to access the temp's pixels in two ways:

1) for example: temp[0]. then there are some m128_f32 and things like that which I am not familiar with those things.

2) for example as pointer: temp-> then I get the same stuff as in the first way

Please let me know for example, how I can change the color pixel of each pixel in the 4*4 block.

Another issue is that as I understand in the DXT1 (BC1) when a texture is compressed using DXT1 its pixels are segmented into 4*4 block of pixels and for each block a 4 color palette is calculated and after the compression all the RGB colors are converted to those 4 colors only. The question is how can I access to those colors in a dds file after loading and change them or just print them out.

I mean, after compression each pixel in a block should only have one of those 4 colors from the palette which is represented by a number meaning there are only 4 numbers which each pixel in a block can take, I want to access those numbers.

Coordinator
Dec 12, 2012 at 7:10 PM
Edited Dec 12, 2012 at 7:39 PM

XMVECTOR is a SIMD type from the DirectXMath library. This code decompresses the BC blocks into the 'post-compression' colors

If want you want to do is manipulate the compressed blocks, then you'll need to deal with the actual data-structures:

// cImage is the compressed Image

// Extracted from the BC.h private header in DIrectXTex
#pragma pack(push,1)
// BC1/DXT1 compression (4 bits per texel)
struct D3DX_BC1
{
    uint16_t    rgb[2]; // 565 colors
    uint32_t    bitmap; // 2bpp rgb bitmap
};
#pragma pack(pop)

    // Determine BC format decoder
    size_t sbpp;
    switch(cImage.format)
    {
    case DXGI_FORMAT_BC1_UNORM:
    case DXGI_FORMAT_BC1_UNORM_SRGB:    sbpp = 8;   break;
    default:
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    const uint8_t *pSrc = cImage.pixels;
    for( size_t h=0; h < cImage.height; h += 4 )
    {
        const uint8_t *sptr = pSrc;
        for( size_t count = 0; count < cImage.rowPitch; count += sbpp )
        {
            D3DX_BC1 *bc = reinterpret_cast<D3DX_BC1 *>( sptr );

            // bc->rgb[0] and bc->rgb[1] are the two colors.
            // Note that the block is decoded differently depending
            // on if bc->rgb[0] >= bc->rgb[1] or bc->rgb[0] < bc->rgb[1]

            sptr += sbpp;
       }

        pSrc += cImage.rowPitch;
   }

See Block Compression for details on how the encoding works.

Jan 29, 2013 at 9:32 PM

I stored the bc->bitmap in an dynamically allocated array with the same type as bc->bitmap which uint32_t. I manipulated the array and now I want to save the dds file again. I need to change the bc->bitmap and then save the same dds file using another name so in the end I would have the original file and the manipulated file. I tried to do as you said and tried to change the bc->bitmap but it says that the bc->bitmap is not modifiable. (I think because it is const).

 

// cImage is the compressed Image

// Extracted from the BC.h private header in DIrectXTex
#pragma pack(push,1)
// BC1/DXT1 compression (4 bits per texel)
struct D3DX_BC1
{
    uint16_t    rgb[2]; // 565 colors
    uint32_t    bitmap; // 2bpp rgb bitmap
};
#pragma pack(pop)

    // Determine BC format decoder
    size_t sbpp;
    switch(cImage.format)
    {
    case DXGI_FORMAT_BC1_UNORM:
    case DXGI_FORMAT_BC1_UNORM_SRGB:    sbpp = 8;   break;
    default:
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }

    int i =0 ;


    const uint8_t *pSrc = cImage.pixels;
    for( size_t h=0; h < cImage.height; h += 4 )
    {
        const uint8_t *sptr = pSrc;
        for( size_t count = 0; count < cImage.rowPitch; count += sbpp )
        {
            D3DX_BC1 *bc = reinterpret_cast<D3DX_BC1 *>( sptr ); 

            // bc->rgb[0] and bc->rgb[1] are the two colors.
            // Note that the block is decoded differently depending
            // on if bc->rgb[0] >= bc->rgb[1] or bc->rgb[0] < bc->rgb[1]

  bc->bitmap = blocArray[i]; // fails and says bc->bitmap is not modifiable. how to change the bit map of the file and save it to see the // changes in the pixels.
i++;
            sptr += sbpp;
       }

        pSrc += cImage.rowPitch;
   }

Coordinator
Jan 29, 2013 at 9:55 PM
Edited Jan 29, 2013 at 9:55 PM

What is the exact text of the error/warning? What is "blocArray"?

If you are trying to modify 'in place', then remove the const on pSrc and the const on sptr.

Jan 29, 2013 at 10:06 PM

blockArray is has all the bitmaps which I changed and I am going to replace them with the bitmap of the original image.

The error is : 

const DirectX::D3DX_BC1 *bc

error: expression must be a modifiable lvalue

I think the problem is with the "const DirectX::D3DX_BC1 *bc" being const not pSrc and sptr being const because I just tried what you said and didn't work.

Jan 30, 2013 at 9:53 AM

I changed this part of the code : 

 D3DX_BC1 *bc = reinterpret_cast<D3DX_BC1 *>( sptr ); 

to 

D3DX_BC1 *bc = (D3DX_BC1 *)sptr;

And I removed all the consts from sptr and pSrc Now there is no more error but still when I save the file the changes in the bc->bitmap is not effective and the original image is saved instead of the new one.

when I change the bc->bitmap It should change the bitmap of the image for that particular pixel. correct me if I am wrong please.

 

 

Coordinator
Jan 30, 2013 at 9:27 PM
<p>D3DX_BC1 *bc = reinterpret_cast&lt;D3DX_BC1 *&gt;( sptr );&nbsp; should work fine with the const' removed.</p> <p>I can't tell from your post what the code looks like for loading and saving the data, but you should probably just walk through it in a debugger to see what's happening.</p>
Jan 30, 2013 at 10:19 PM
This is kinda the whole idea of the code:
    TexMetadata info;
ScratchImage *image = new ScratchImage; 

    uint32_t *blockArr = new uint32_t[numOfBlocks]; // my image is 64*64 pixels so it has 256 blocks so numOfBlocks is 256

    for(int i = 0 ; i < 256 ; i++)   // just filling the array with some numbers to replace them into bc->bitmap later 
       blockArr[i] = i;

HRESULT hr = LoadFromDDSFile (L"c.dds", DDS_FLAGS_NONE, &info, *image);
if(FAILED(hr))
    {
            wprintf( L" FAILED (%x)\n", hr);
            delete image;
    }

const Image* img = image->GetImages();   //I think the problem is this const img which doesn't allow the change to be effective
assert( img );
size_t nimg = image->GetImageCount();
assert( nimg > 0 );

pragma pack(push,1)

pragma pack(pop)

// Determine BC format decoder
size_t sbpp;
switch(img->format)
{
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:    sbpp = 8;   break;
default:
    return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
}

int blockCount = 0;
uint8_t *pSrc = img->pixels;


for( size_t h=0 ; h < img->height ; h += 4 )
{
    uint8_t *sptr = pSrc;

    for( size_t count = 0; count < img->rowPitch; count += sbpp )
    {
                    D3DX_BC1 *bc = (D3DX_BC1 *)sptr;

        // here I want to replace the numbers instead of the real numbers
                    // this is just the general idea behind what I am trying to do. In fact I am just changing some of the bitmap numbers in a way for example to have more 2s than 1s because bitmap has 0 (00), 1 (01), 2( 10), 3(11).

        bc->bitmap = blockArr[blockCount] ;                 // holding the long number of each block which is 16 pixels long


        blockCount++;

        sptr += sbpp;
   }

    pSrc += img->rowPitch;
 }

       // here I am trying to save the file

       hr = SaveToDDSFile( img, nimg, image->GetMetadata(), DDS_FLAGS_NONE, L"m_c.dds");
   if ( FAILED(hr) )
    cout << "Saving failed!"<<endl;

delete [] blockArr;
But it will still save the same file with the same info and pixels. Please help
Coordinator
Jan 31, 2013 at 1:12 AM
your blockArr is running out of blocks depending on the size of the file.

Also, your code assumes the DDS in question does not have mipmaps, arrays, or arrays+mipmaps. You are only changing the 'top' level mip here.

How exactly are you looking at the m_c.dds file to determine it "didn't change"?
Jan 31, 2013 at 9:10 PM
Thank you walbourn. you are such a great help. I think managed to do what I wanted to for now of course :)