unit kolDXTDecompress; (*********************************************************************NVMH2**** Path: C:\Dev\devrel\Nv_sdk_4\Direct3D\Decompress_DXTC File: Image_DXTC.cpp Copyright (C) 1999, 2000 NVIDIA Corporation This file is provided without support, instruction, or implied warranty of any kind. NVIDIA makes no guarantee of its fitness for a particular purpose and is not liable under any circumstances for any damages or loss whatsoever arising from the use or inability to use this file or items derived from it. Comments: Converted to Object Pascal by Martin "Kalroth" Danielsen. ******************************************************************************) interface uses Windows, KOL, kolMisc, kolPortalDatConvert_const_types; type PDXTCImage = ^TDXTCImage; TDXTCImage = object(TObj) private pCompBytes : pByte; pDecompBytes : pByte; nCompSize : Integer; nCompLineSz : Integer; strFormat : ShortString; CompFormat : PixFormat; DDSD : DDSURFACEDESC2; bMipTexture : Boolean; nWidth : Integer; nHeight : Integer; public destructor Free; function LoadFromStream(AStream: PStream): Boolean; function LoadFromFile(const AFilename: String): Boolean; function SaveToBmp(var ABitmap: PBitmap): Boolean; function SaveToBmpFile(const AFilename: String): Boolean; procedure DecodePixelFormat(var strPixelFormat: ShortString; pddpf: PDDPixelFormat); procedure AllocateDecompBytes; procedure Decompress; procedure DecompressDXT1; procedure DecompressDXT2; procedure DecompressDXT3; procedure DecompressDXT4; procedure DecompressDXT5; end; procedure GetColorBlockColors( pBlock: PDXTColBlock; col_0,col_1,col_2,col_3: PColor8888; wrd: Word ); procedure DecodeColorBlock( pImPos: PDWORD; pColorBlock: PDXTColBlock; width: Integer; col_0, col_1, col_2, col_3: PDWORD); procedure DecodeAlphaExplicit( pImPos: PDWORD; pAlphaBlock: PDXTAlphaBlockExplicit; width: Integer; alphazero: DWORD ); procedure DecodeAlpha3BitLinear( pImPos: PDWORD; pAlphaBlock: PDXTAlphaBlock3BitLinear; width: Integer; alphazero: DWORD ); function NewDXTCImage: PDXTCImage; implementation var gBits : Array[0..3] of Array[0..3] of Byte; gAlphas : Array[0..7] of Word; gACol : Array[0..3] of Array[0..3] of Color8888; function NewDXTCImage: PDXTCImage; begin New(result,Create); end; function GetNumberOfBits(Value: LongWord): Word; begin for result := 31 downto 0 do if Odd(Value shr result) then Break; end; procedure GetColorBlockColors( pBlock: PDXTColBlock; col_0,col_1,col_2,col_3: PColor8888; wrd: Word ); begin // r_bits : word = $001F; // 0000000000011111 // g_bits : word = $07E0; // 0000011111100000 // b_bits : word = $F800; // 1111100000000000 // 1111111111111111 -> 0000000000011111 -> 0000000011111000 col_0^.r := (pBlock^.col0 and r_bits) shl 3; // 1111111111111111 -> 0000011111100000 -> 0000000011111100 col_0^.g := (pBlock^.col0 and g_bits) shr 3; // 1111111111111111 -> 1111100000000000 -> 0000000011111000 col_0^.b := (pBlock^.col0 and b_bits) shr 8; col_0^.a := $FF; col_1^.r := (pBlock^.col1 and r_bits) shl 3; col_1^.g := (pBlock^.col1 and g_bits) shr 3; col_1^.b := (pBlock^.col1 and b_bits) shr 8; col_1^.a := $FF; col_2^.r := (col_0^.r * 2 + col_1^.r) div 3; col_2^.g := (col_0^.g * 2 + col_1^.g) div 3; col_2^.b := (col_0^.b * 2 + col_1^.b) div 3; col_2^.a := $FF; col_3^.r := (col_0^.r + col_1^.r * 2) div 3; col_3^.g := (col_0^.g + col_1^.g * 2) div 3; col_3^.b := (col_0^.b + col_1^.b * 2) div 3; col_3^.a := $FF; end; // GetColorBlockColors() procedure DecodeColorBlock( pImPos: PDWORD; pColorBlock: PDXTColBlock; width: Integer; col_0, col_1, col_2, col_3: PDWORD ); const // bit masks = 00000011, 00001100, 00110000, 11000000 //-- DWORD masks[] = { 3, 12, 3 << 4, 3 << 6 }; masks : Array[0..3] of DWORD = ( 3, 12, (3 shl 4), (3 shl 6) ); shift : Array[0..3] of Integer = ( 0, 2, 4, 6 ); var bits: DWORD; r,n: Integer; begin for r := 0 to 3 do begin for n := 0 to 3 do begin bits := pColorBlock^.row[r] and masks[n]; bits := bits shr shift[n]; case bits of 0 : begin pImPos^ := col_0^; Inc( pImPos ); end; 1 : begin pImPos^ := col_1^; Inc( pImPos ); end; 2 : begin pImPos^ := col_2^; Inc( pImPos ); end; 3 : begin pImPos^ := col_3^; Inc( pImpos ); end; // else // ShowMessage('Gah, jacked logic!'); end; end; Inc( pImPos, width-4 ); end; // for r := 0 to 3 .. end; // DecodeColorBlock() procedure DecodeAlphaExplicit( pImPos: PDWORD; pAlphaBlock: PDXTAlphaBlockExplicit; width: Integer; alphazero: DWORD ); var row, pix : Integer; wrd : Word; col : Color8888; begin // alphazero is a bit mask that when & with the image color // will zero the alpha bits, so if the image DWORDs are // ARGB then alphazero will be 0x00ffffff or if // RGBA then alphazero will be 0xffffff00 // alphazero constructed automaticaly from field order of Color8888 structure // decodes to 32 bit format only col.r := 0; col.g := 0; col.b := 0; for row := 0 to 3 do begin wrd := pAlphaBlock^.row[ row ]; for pix := 0 to 3 do begin pImPos^ := pImPos^ and alphazero; col.a := (wrd and $000F) shl 4; col.a := col.a or (col.a shl 4); pImPos^ := pImPos^ or DWORD(col); wrd := wrd shr 4; Inc( pImPos ); end; Inc( pImPos, width-4 ); end; end; // DecodeAlphaExplicit() procedure DecodeAlpha3BitLinear( pImPos: PDWORD; pAlphaBlock: PDXTAlphaBlock3BitLinear; width: Integer; alphazero: DWORD ); const mask : DWORD = $00000007; var bits : DWORD; row, pix : Integer; begin gAlphas[0] := pAlphaBlock^.alpha0; gAlphas[1] := pAlphaBlock^.alpha1; // 8-alpha or 6-alpha block? if ( gAlphas[0] > gAlphas[1] ) then begin // 8-alpha block: derive the other 6 alphas. // 000 = alpha_0, 001 = alpha_1, others are interpolated gAlphas[2] := Round( ( 6 * gAlphas[0] + gAlphas[1]) / 7 ); // bit code 010 gAlphas[3] := Round( ( 5 * gAlphas[0] + 2 * gAlphas[1]) / 7 ); // Bit code 011 gAlphas[4] := Round( ( 4 * gAlphas[0] + 3 * gAlphas[1]) / 7 ); // Bit code 100 gAlphas[5] := Round( ( 3 * gAlphas[0] + 4 * gAlphas[1]) / 7 ); // Bit code 101 gAlphas[6] := Round( ( 2 * gAlphas[0] + 5 * gAlphas[1]) / 7 ); // Bit code 110 gAlphas[7] := Round( ( gAlphas[0] + 6 * gAlphas[1]) / 7 ); // Bit code 111 end else begin // 6-alpha block: derive the other alphas. // 000 = alpha_0, 001 = alpha_1, others are interpolated gAlphas[2] := Round( (4 * gAlphas[0] + gAlphas[1]) / 5 ); // Bit code 010 gAlphas[3] := Round( (3 * gAlphas[0] + 2 * gAlphas[1]) / 5 ); // Bit code 011 gAlphas[4] := Round( (2 * gAlphas[0] + 3 * gAlphas[1]) / 5 ); // Bit code 100 gAlphas[5] := Round( ( gAlphas[0] + 4 * gAlphas[1]) / 5 ); // Bit code 101 gAlphas[6] := 0; // Bit code 110 gAlphas[7] := 255; // Bit code 111 end; // Decode 3-bit fields into array of 16 BYTES with same value // first two rows of 4 pixels each: // pRows = (Alpha3BitRows*) & ( pAlphaBlock->stuff[0] ); bits := DWORD( pAlphaBlock^.stuff[0] ); gBits[0][0] := Byte( bits and mask ); bits := bits shr 3; gBits[0][1] := Byte( bits and mask ); bits := bits shr 3; gBits[0][2] := Byte( bits and mask ); bits := bits shr 3; gBits[0][3] := Byte( bits and mask ); bits := bits shr 3; gBits[1][0] := Byte( bits and mask ); bits := bits shr 3; gBits[1][1] := Byte( bits and mask ); bits := bits shr 3; gBits[1][2] := Byte( bits and mask ); bits := bits shr 3; gBits[1][3] := Byte( bits and mask ); // now for last two rows: bits := DWORD( pAlphaBlock^.stuff[3] ); gBits[2][0] := Byte( bits and mask ); bits := bits shr 3; gBits[2][1] := Byte( bits and mask ); bits := bits shr 3; gBits[2][2] := Byte( bits and mask ); bits := bits shr 3; gBits[2][3] := Byte( bits and mask ); bits := bits shr 3; gBits[3][0] := Byte( bits and mask ); bits := bits shr 3; gBits[3][1] := Byte( bits and mask ); bits := bits shr 3; gBits[3][2] := Byte( bits and mask ); bits := bits shr 3; gBits[3][3] := Byte( bits and mask ); // decode the codes into alpha values for row := 0 to 3 do for pix := 0 to 3 do begin gACol[row][pix].a := Byte( gAlphas[gBits[row][pix]] ); // ASSERT( gACol[row][pix].r == 0 ); // ASSERT( gACol[row][pix].g == 0 ); // ASSERT( gACol[row][pix].b == 0 ); end; // Write out alpha values to the image bits for row := 0 to 3 do begin //Inc( pImPos, width-4 ); for pix := 0 to 3 do begin pImPos^ := pImPos^ and alphazero; pImPos^ := pImPos^ or DWORD( gACol[row][pix] ); Inc( pImPos ); end; end; end; // DecodeAlpha3BitLinear() (****************** The real fun! ******************) { TDXTCImage } procedure TDXTCImage.AllocateDecompBytes; begin if pDecompBytes <> nil then begin FreeMem(pDecompBytes); pDecompBytes := nil; end; GetMem( pDecompBytes, DDSD.dwWidth * DDSD.dwHeight * 4 * SizeOf(Byte) ); FillChar( pDecompBytes^, DDSD.dwWidth * DDSD.dwHeight * 4 * SizeOf(Byte), #0 ); if pDecompBytes = nil then begin ShowMessage('Unable to allocate memory.'); end; end; procedure TDXTCImage.DecodePixelFormat(var strPixelFormat: ShortString; pddpf: PDDPixelFormat); begin case pddpf^.dwFourCC of 0 : begin // This dds texture isn't compressed so write out ARGB format strPixelFormat := 'ARGB-'+ Int2Str( GetNumberOfBits( pddpf^.dwRGBAlphaBitMask ) )+ Int2Str( GetNumberOfBits( pddpf^.dwRBitMask ) )+ Int2Str( GetNumberOfBits( pddpf^.dwGBitMask ) )+ Int2Str( GetNumberOfBits( pddpf^.dwBBitMask ) ); if (pddpf^.dwBBitMask and DDPF_ALPHAPREMULT) = DDPF_ALPHAPREMULT then strPixelFormat := strPixelFormat + '-premul'; CompFormat := PF_ARGB; end; FOURCC_DXT1 : begin strPixelFormat := 'DXT1'; CompFormat := PF_DXT1; end; FOURCC_DXT2 : begin strPixelFormat := 'DXT2'; CompFormat := PF_DXT2; end; FOURCC_DXT3 : begin strPixelFormat := 'DXT3'; CompFormat := PF_DXT3; end; FOURCC_DXT4: begin strPixelFormat := 'DXT4'; CompFormat := PF_DXT4; end; FOURCC_DXT5 : begin strPixelFormat := 'DXT5'; CompFormat := PF_DXT4; end; else strPixelFormat := 'Format Unknown'; CompFormat := PF_UNKNOWN; end; end; procedure TDXTCImage.Decompress; begin if pCompBytes = nil then Exit; if pDecompBytes = nil then Exit; case CompFormat of PF_DXT1 : DecompressDXT1; PF_DXT2 : DecompressDXT2; PF_DXT3 : DecompressDXT3; PF_DXT4 : DecompressDXT4; PF_DXT5 : DecompressDXT5; end; end; procedure TDXTCImage.DecompressDXT1; var xblocks, yblocks : Integer; i, j : Integer; pBase : PDWORD; pImPos : PDWORD; pBlock : PDXTColBlock; col_0, col_1, col_2, col_3 : Color8888; wrd : Word; begin xblocks := DDSD.dwWidth div 4; yblocks := DDSD.dwHeight div 4; pBase := PDWORD(pDecompBytes); for j := 0 to (yblocks-1) do begin // 8 bytes per block pBlock := PDXTColBlock( DWORD(pCompBytes) + j * xblocks * 8 ); for i := 0 to (xblocks-1) do begin GetColorBlockColors( pBlock, @col_0, @col_1, @col_2, @col_3, wrd ); // now decode the color block into the bitmap bits pImPos := PDWORD( DWORD(pBase) + i*16 + (j*4) * nWidth * 4 ); DecodeColorBlock( pImPos, pBlock, nWidth, PDWORD(@col_0), PDWORD(@col_1), PDWORD(@col_2), PDWORD(@col_3) ); Inc(pBlock); end; end; end; procedure TDXTCImage.DecompressDXT2; begin // end; procedure TDXTCImage.DecompressDXT3; var xblocks, yblocks : Integer; i, j : Integer; pBase : PDWORD; pImPos : PDWORD; pBlock : PDXTColBlock; pAlphaBlock : PDXTAlphaBlockExplicit; alphazero : DWORD; col_0, col_1, col_2, col_3 : Color8888; wrd : Word; begin xblocks := DDSD.dwWidth div 4; yblocks := DDSD.dwHeight div 4; pBase := PDWORD(pDecompBytes); // fill alphazero with appropriate value to zero out alpha when // alphazero is ANDed with the image color 32 bit DWORD: col_0.a := 0; col_0.r := $FF; col_0.g := $FF; col_0.b := $FF; alphazero := DWORD(col_0); for j := 0 to (yblocks-1) do begin // 8 bytes per block // 1 block for alpha, 1 block for color pBlock := PDXTColBlock( DWORD(pCompBytes) + j * xblocks * 16 ); for i := 0 to (xblocks-1) do begin // Get alpha block pAlphaBlock := PDXTAlphaBlockExplicit(pBlock); Inc(pBlock); GetColorBlockColors( pBlock, @col_0, @col_1, @col_2, @col_3, wrd ); // now decode the color block into the bitmap bits pImPos := PDWORD( DWORD(pBase) + i*16 + (j*4) * nWidth * 4 ); DecodeColorBlock( pImPos, pBlock, nWidth, PDWORD(@col_0), PDWORD(@col_1), PDWORD(@col_2), PDWORD(@col_3) ); // Overwrite the previous alpha bits with the alpha block info DecodeAlphaExplicit( pImPos, pAlphaBlock, nWidth, alphazero ); Inc(pBlock); end; end; end; procedure TDXTCImage.DecompressDXT4; begin // end; procedure TDXTCImage.DecompressDXT5; begin end; destructor TDXTCImage.Free; begin if pCompBytes <> nil then FreeMem(pCompBytes); if pDecompBytes <> nil then FreeMem(pDecompBytes); inherited Free; end; function TDXTCImage.LoadFromStream(AStream: PStream): Boolean; var ddsd_ : DDSURFACEDESC2; dwMagic : DWORD; pDest : PByte; yp : DWORD; begin result := False; AStream.read(dwMagic,SizeOf(dwMagic)); if dwMagic <> FOURCC_DDS then Exit; AStream.read(ddsd_,SizeOf(ddsd_)); bMipTexture := (ddsd_.dwMipMapCount > 0); DecodePixelFormat( strFormat, @(ddsd_.ddpfPixelFormat) ); if (CompFormat = PF_UNKNOWN) then begin ShowMessage('LoadFromFile: Unrecognized format!'); Exit; end; DDSD := ddsd_; nHeight := ddsd_.dwHeight; nWidth := ddsd_.dwWidth; if (ddsd_.dwFlags and DDSD_LINEARSIZE) = DDSD_LINEARSIZE then begin nCompSize := ddsd_.dwLinearSize; GetMem(pCompBytes, nCompSize); if pCompBytes = nil then begin ShowMessage('LoadFromFile: Can''t allocate pCompBytes!'); Exit; end; FillChar( pCompBytes^, nCompSize, #0 ); AStream.Read(pCompBytes^,ddsd_.dwLinearSize); end else begin // dwBytesPerRow := ddsd_.dwWidth * ddsd_.ddpfPixelFormat.dwRGBBitCount div 8; nCompLineSz := (AStream.Size-128) div ddsd_.dwWidth; nCompSize := nCompLineSz * ddsd_.dwHeight; GetMem( pCompBytes, nCompSize ); if pCompBytes = nil then begin ShowMessage('LoadFromFile: Can''t allocate pCompBytes!'); Exit; end; FillChar( pCompBytes^, nCompSize, #0 ); pDest := pCompBytes; for yp := 0 to (ddsd_.dwHeight-1) do begin AStream.Read(pDest^,nCompLineSz); Inc(pDest,nCompLineSz); end; end; AllocateDecompBytes; end; function TDXTCImage.LoadFromFile(const AFilename: String): Boolean; var file_ : PStream; begin result := False; if LowerCase(ExtractFileExt(AFilename)) <> '.dds' then Exit; file_ := NewReadFileStream(AFilename); try result := LoadFromStream(file_); finally file_.Free; end; end; function TDXTCImage.SaveToBmpFile(const AFilename: String): Boolean; begin result := False; end; function TDXTCImage.SaveToBmp(var ABitmap: PBitmap): Boolean; var Row : PRGBQuad; y : Integer; pPos : PByte; begin result := False; if ABitmap = nil then Exit; ABitmap.Height := nHeight; ABitmap.Width := nWidth; ABitmap.PixelFormat := pf32bit; pPos := pDecompBytes; for y := 0 to (ABitmap.Height-1) do begin Row := ABitmap.Scanline[y]; Move( pPos^, Row^, nWidth*4 ); Inc( pPos, nWidth*4 ); end; end; end.