* Bitfield support for LoadDDS
authorBergmann89 <bergmann89@muo-game.de>
Tue, 12 Nov 2013 16:50:35 +0000 (17:50 +0100)
committerBergmann89 <bergmann89@muo-game.de>
Tue, 12 Nov 2013 16:50:35 +0000 (17:50 +0100)
* refactored SaveDDS

glBitmap.pas

index 72c81e0..e1e591d 100644 (file)
@@ -591,7 +591,7 @@ type
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
   TglBitmapFormat = (
-    tfEmpty = 0,
+    tfEmpty = 0, //must be smallest value!
 
     tfAlpha4,
     tfAlpha8,
@@ -1609,8 +1609,6 @@ end;
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 function FormatGetSupportedFiles(const aFormat: TglBitmapFormat): TglBitmapFileTypes;
 begin
-  result := [ftDDS];
-
   if (aFormat in [
         //4 bbp
         tfLuminance4,
@@ -1646,9 +1644,29 @@ begin
         tfRGB10A2, tfRGBA8, tfBGR10A2, tfBGRA8]) then
     result := result + [ftTGA];
 
-  //TODO Supported File Formats!
+  if (aFormat in [
+        //8 bpp
+        tfAlpha8, tfLuminance8, tfLuminance4Alpha4, tfLuminance6Alpha2,
+        tfR3G3B2, tfRGBA2, tfBGRA2,
+
+        //16 bpp
+        tfAlpha16, tfLuminance16, tfLuminance8Alpha8, tfLuminance12Alpha4,
+        tfRGB4, tfR5G6B5, tfRGB5, tfRGBA4, tfRGB5A1,
+        tfBGR4, tfB5G6R5, tfBGR5, tfBGRA4, tfBGR5A1,
 
-  (*
+        //24 bpp
+        tfRGB8, tfBGR8,
+
+        //32 bbp
+        tfLuminance16Alpha16,
+        tfRGBA8, tfRGB10A2,
+        tfBGRA8, tfBGR10A2,
+
+        //compressed
+        tfS3tcDtx1RGBA, tfS3tcDtx3RGBA, tfS3tcDtx5RGBA]) then
+    result := result + [ftDDS];
+
+  (* TODO
   {$IFDEF GLB_SUPPORT_PNG_WRITE}
   if aFormat in [
     tfAlpha4, tfAlpha8, tfAlpha12, tfAlpha16,
@@ -6974,7 +6992,7 @@ end;
 //DDS/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 const
-  DDS_MAGIC                   = $20534444;
+  DDS_MAGIC: Cardinal         = $20534444;
 
   // DDS_header.dwFlags
   DDSD_CAPS                   = $00000001;
@@ -6988,6 +7006,7 @@ const
 
   // DDS_header.sPixelFormat.dwFlags
   DDPF_ALPHAPIXELS            = $00000001;
+  DDPF_ALPHA                  = $00000002;
   DDPF_FOURCC                 = $00000004;
   DDPF_INDEXED                = $00000020;
   DDPF_RGB                    = $00000040;
@@ -7032,7 +7051,6 @@ type
   end;
 
   TDDSHeader = packed record
-    dwMagic: Cardinal;
     dwSize: Cardinal;
     dwFlags: Cardinal;
     dwHeight: Cardinal;
@@ -7050,8 +7068,14 @@ type
 function TglBitmap.LoadDDS(const aStream: TStream): Boolean;
 var
   Header: TDDSHeader;
+  Converter: TbmpBitfieldFormat;
 
   function GetDDSFormat: TglBitmapFormat;
+  var
+    fd: TFormatDescriptor;
+    i: Integer;
+    Range: TglBitmapColorRec;
+    match: Boolean;
   begin
     result := tfEmpty;
     with Header.PixelFormat do begin
@@ -7062,66 +7086,79 @@ var
           D3DFMT_DXT3: result := tfS3tcDtx3RGBA;
           D3DFMT_DXT5: result := tfS3tcDtx5RGBA;
         end;
-      end else
-
-      // RGB
-      if (dwFlags and (DDPF_RGB or DDPF_ALPHAPIXELS or DDPF_LUMINANCE)) > 0 then begin
-        case dwRGBBitCount of
-          8: begin
-            if ((dwFlags and DDPF_ALPHAPIXELS) > 0) then
-              result := tfAlpha8
-            else if ((dwFlags and DDPF_LUMINANCE) > 0) then
-              result := tfLuminance8;
-          end;
-
-          16: begin
-            if ((dwFlags and DDPF_ALPHAPIXELS) > 0) then begin
-              case CountSetBits(dwRBitMask) of
-                5: result := tfRGB5A1;
-                4: result := tfRGBA4;
-              else
-                result := tfLuminance8Alpha8;
-              end;
-            end else if (CountSetBits(dwGBitMask) = 6) then
-              result := tfR5G6B5
-            else
-              result := tfRGB5;
-          end;
+      end else if ((Header.PixelFormat.dwFlags and (DDPF_RGB or DDPF_ALPHAPIXELS or DDPF_LUMINANCE)) > 0) then begin
+
+        //find matching format
+        for result := High(TglBitmapFormat) downto Low(TglBitmapFormat) do begin
+          fd := TFormatDescriptor.Get(result);
+          if fd.MaskMatch(dwRBitMask, dwGBitMask, dwBBitMask, dwABitMask) and
+             (8 * fd.PixelSize = dwRGBBitCount) then
+            exit;
+        end;
 
-          24: begin
-            result := tfRGB8;
-          end;
+        //find format with same Range
+        Range.r := dwRBitMask;
+        Range.g := dwGBitMask;
+        Range.b := dwBBitMask;
+        Range.a := dwABitMask;
+        for i := 0 to 3 do begin
+          while ((Range.arr[i] and 1) = 0) and (Range.arr[i] > 0) do
+            Range.arr[i] := Range.arr[i] shr 1;
+        end;
+        for result := High(TglBitmapFormat) downto Low(TglBitmapFormat) do begin
+          fd := TFormatDescriptor.Get(result);
+          match := true;
+          for i := 0 to 3 do
+            if (fd.Range.arr[i] <> Range.arr[i]) then begin
+              match := false;
+              break;
+            end;
+          if match then
+            break;
+        end;
 
-          32: begin
-            if CountSetBits(dwRBitMask) = 10 then
-              result := tfRGB10A2
-            else
-              result := tfRGBA8;
-          end;
+        //no format with same range found -> use default
+        if (result = tfEmpty) then begin
+          if (dwABitMask > 0) then
+            result := tfBGRA8
+          else
+            result := tfBGR8;
         end;
 
-        if (dwRBitMask <> 0) and (dwBBitMask <> 0) and (dwRBitMask > dwBBitMask) then
-          result := TFormatDescriptor.Get(result).RGBInverted;
+        Converter := TbmpBitfieldFormat.Create;
+        Converter.RedMask   := dwRBitMask;
+        Converter.GreenMask := dwGBitMask;
+        Converter.BlueMask  := dwBBitMask;
+        Converter.AlphaMask := dwABitMask;
+        Converter.PixelSize := dwRGBBitCount / 8;
       end;
     end;
   end;
 
 var
   StreamPos: Int64;
-  Y, LineSize: Cardinal;
-  RowSize: Cardinal;
-  NewImage, TmpData: PByte;
+  x, y, j, LineSize, RowSize, Magic: Cardinal;
+  NewImage, TmpData, RowData, SrcData: PByte;
+  SourceMD, DestMD: Pointer;
+  Pixel: TglBitmapPixelData;
   ddsFormat: TglBitmapFormat;
   FormatDesc: TFormatDescriptor;
 
 begin
-  result := false;
-
-  // Header
+  result    := false;
+  Converter := nil;
   StreamPos := aStream.Position;
-  aStream.Read(Header, sizeof(Header));
 
-  if (Header.dwMagic <> DDS_MAGIC) or (Header.dwSize <> 124) or
+  // Magic
+  aStream.Read(Magic, sizeof(Magic));
+  if (Magic <> DDS_MAGIC) then begin
+    aStream.Position := StreamPos;
+    exit;
+  end;
+
+  //Header
+  aStream.Read(Header, sizeof(Header));
+  if (Header.dwSize <> SizeOf(Header)) or
      ((Header.dwFlags and (DDSD_PIXELFORMAT or DDSD_CAPS or DDSD_WIDTH or DDSD_HEIGHT)) <>
         (DDSD_PIXELFORMAT or DDSD_CAPS or DDSD_WIDTH or DDSD_HEIGHT)) then
   begin
@@ -7133,39 +7170,74 @@ begin
     raise EglBitmapException.Create('LoadDDS - CubeMaps are not supported');
 
   ddsFormat := GetDDSFormat;
-  if (ddsFormat = tfEmpty) then
-    raise EglBitmapException.Create('LoadDDS - unsupported Pixelformat found.');
-
-  FormatDesc := TFormatDescriptor.Get(ddsFormat);
-  LineSize   := Trunc(Header.dwWidth * FormatDesc.PixelSize);
-  GetMem(NewImage, Header.dwHeight * LineSize);
   try
-    TmpData := NewImage;
-
-    // Compressed
-    if ((Header.PixelFormat.dwFlags and DDPF_FOURCC) > 0) then begin
-      RowSize := Header.dwPitchOrLinearSize div Header.dwWidth;
-      for Y := 0 to Header.dwHeight-1 do begin
-        aStream.Read(TmpData^, RowSize);
-        Inc(TmpData, LineSize);
-      end;
-    end else
-
-    // Uncompressed
-    if (Header.PixelFormat.dwFlags and (DDPF_RGB or DDPF_ALPHAPIXELS or DDPF_LUMINANCE)) > 0 then begin
-      RowSize := (Header.PixelFormat.dwRGBBitCount * Header.dwWidth) shr 3;
-      for Y := 0 to Header.dwHeight-1 do begin
-        aStream.Read(TmpData^, RowSize);
-        Inc(TmpData, LineSize);
-      end;
-    end else
+    if (ddsFormat = tfEmpty) then
       raise EglBitmapException.Create('LoadDDS - unsupported Pixelformat found.');
 
-    SetDataPointer(NewImage, ddsFormat, Header.dwWidth, Header.dwHeight);
-    result := true;
-  except
-    FreeMem(NewImage);
-    raise;
+    FormatDesc := TFormatDescriptor.Get(ddsFormat);
+    LineSize   := Trunc(Header.dwWidth * FormatDesc.PixelSize);
+    GetMem(NewImage, Header.dwHeight * LineSize);
+    try
+      TmpData := NewImage;
+
+      //Converter needed
+      if Assigned(Converter) then begin
+        RowSize := Round(Header.dwWidth * Header.PixelFormat.dwRGBBitCount / 8);
+        GetMem(RowData, RowSize);
+        SourceMD := Converter.CreateMappingData;
+        DestMD   := FormatDesc.CreateMappingData;
+        try
+          for y := 0 to Header.dwHeight-1 do begin
+            TmpData := NewImage + y * LineSize;
+            SrcData := RowData;
+            aStream.Read(SrcData^, RowSize);
+            for x := 0 to Header.dwWidth-1 do begin
+              Converter.Unmap(SrcData, Pixel, SourceMD);
+              //TODO use converter function
+              for j := 0 to 3 do
+                if (Converter.Range.arr[j] <> FormatDesc.Range.arr[j]) then begin
+                  if (Converter.Range.arr[j] > 0) then
+                    Pixel.Data.arr[j] := Round(Pixel.Data.arr[j] / Converter.Range.arr[j] * FormatDesc.Range.arr[j])
+                  else
+                    Pixel.Data.arr[j] := 0;
+                end;
+              FormatDesc.Map(Pixel, TmpData, DestMD);
+            end;
+          end;
+        finally
+          Converter.FreeMappingData(SourceMD);
+          FormatDesc.FreeMappingData(DestMD);
+          FreeMem(RowData);
+        end;
+      end else
+
+      // Compressed
+      if ((Header.PixelFormat.dwFlags and DDPF_FOURCC) > 0) then begin
+        RowSize := Header.dwPitchOrLinearSize div Header.dwWidth;
+        for Y := 0 to Header.dwHeight-1 do begin
+          aStream.Read(TmpData^, RowSize);
+          Inc(TmpData, LineSize);
+        end;
+      end else
+
+      // Uncompressed
+      if (Header.PixelFormat.dwFlags and (DDPF_RGB or DDPF_ALPHAPIXELS or DDPF_LUMINANCE)) > 0 then begin
+        RowSize := (Header.PixelFormat.dwRGBBitCount * Header.dwWidth) shr 3;
+        for Y := 0 to Header.dwHeight-1 do begin
+          aStream.Read(TmpData^, RowSize);
+          Inc(TmpData, LineSize);
+        end;
+      end else
+        raise EglBitmapException.Create('LoadDDS - unsupported Pixelformat found.');
+
+      SetDataPointer(NewImage, ddsFormat, Header.dwWidth, Header.dwHeight);
+      result := true;
+    except
+      FreeMem(NewImage);
+      raise;
+    end;
+  finally
+    FreeAndNil(Converter);
   end;
 end;
 
@@ -7173,55 +7245,55 @@ end;
 procedure TglBitmap.SaveDDS(const aStream: TStream);
 var
   Header: TDDSHeader;
-  Pix: TglBitmapPixelData;
   FormatDesc: TFormatDescriptor;
 begin
-  //if not FormatIsUncompressed(InternalFormat) then
-  //  raise EglBitmapUnsupportedFormatFormat.Create('SaveDDS - ' + UNSUPPORTED_FORMAT);
+  if not (ftDDS in FormatGetSupportedFiles(Format)) then
+    raise EglBitmapUnsupportedFormatFormat.Create('SaveDDS - ' + UNSUPPORTED_FORMAT);
 
-  (* TODO if Format = tfAlpha8 then
-    FORMAT_DESCRIPTORS[tfLuminance8].PreparePixel(Pix);
-  else    *)
-    TFormatDescriptor.Get(Format).PreparePixel(Pix);
+  FormatDesc := TFormatDescriptor.Get(Format);
 
   // Generell
   FillChar(Header, SizeOf(Header), 0);
-  Header.dwMagic := DDS_MAGIC;
-  Header.dwSize  := 124;
-  Header.dwFlags := DDSD_PITCH or DDSD_CAPS or DDSD_PIXELFORMAT;
-
-  if Width > 0 then begin
-    Header.dwWidth := Width;
-    Header.dwFlags := Header.dwFlags or DDSD_WIDTH;
-  end;
-
-  if Height > 0 then begin
-    Header.dwHeight := Height;
-    Header.dwFlags := Header.dwFlags or DDSD_HEIGHT;
-  end;
+  Header.dwSize  := SizeOf(Header);
+  Header.dwFlags := DDSD_WIDTH or DDSD_HEIGHT or DDSD_CAPS or DDSD_PIXELFORMAT;
 
-  Header.dwPitchOrLinearSize := fRowSize;
-  Header.dwMipMapCount := 1;
+  Header.dwWidth  := Max(1, Width);
+  Header.dwHeight := Max(1, Height);
 
   // Caps
   Header.Caps.dwCaps1 := DDSCAPS_TEXTURE;
 
   // Pixelformat
-  Header.PixelFormat.dwSize  := Sizeof(Header.PixelFormat);
-  Header.PixelFormat.dwFlags := DDPF_RGB;
+  Header.PixelFormat.dwSize := sizeof(Header);
+  if (FormatDesc.IsCompressed) then begin
+    Header.PixelFormat.dwFlags := Header.PixelFormat.dwFlags or DDPF_FOURCC;
+    case Format of
+      tfS3tcDtx1RGBA: Header.PixelFormat.dwFourCC := D3DFMT_DXT1;
+      tfS3tcDtx3RGBA: Header.PixelFormat.dwFourCC := D3DFMT_DXT3;
+      tfS3tcDtx5RGBA: Header.PixelFormat.dwFourCC := D3DFMT_DXT5;
+    end;
+  end else if (Format in [tfAlpha8, tfAlpha16]) then begin
+    Header.PixelFormat.dwFlags       := Header.PixelFormat.dwFlags or DDPF_ALPHA;
+    Header.PixelFormat.dwRGBBitCount := Round(FormatDesc.PixelSize * 8);
+    Header.PixelFormat.dwABitMask    := FormatDesc.AlphaMask;
+  end else if (FormatDesc.RedMask = FormatDesc.GreenMask) and (FormatDesc.GreenMask = FormatDesc.BlueMask) then begin
+    Header.PixelFormat.dwFlags       := Header.PixelFormat.dwFlags or DDPF_LUMINANCE;
+    Header.PixelFormat.dwRGBBitCount := Round(FormatDesc.PixelSize * 8);
+    Header.PixelFormat.dwRBitMask    := FormatDesc.RedMask;
+    Header.PixelFormat.dwABitMask    := FormatDesc.AlphaMask;
+  end else begin
+    Header.PixelFormat.dwFlags := Header.PixelFormat.dwFlags or DDPF_RGB;
+    Header.PixelFormat.dwRGBBitCount := Round(FormatDesc.PixelSize * 8);
+    Header.PixelFormat.dwRBitMask    := FormatDesc.RedMask;
+    Header.PixelFormat.dwGBitMask    := FormatDesc.GreenMask;
+    Header.PixelFormat.dwBBitMask    := FormatDesc.BlueMask;
+    Header.PixelFormat.dwABitMask    := FormatDesc.AlphaMask;
+  end;
 
-  (* TODO tfAlpha8
-  if FORMAT_DESCRIPTORS[Format].HasAlpha and (Format <> tfAlpha8) then
+  if (FormatDesc.HasAlpha) then
     Header.PixelFormat.dwFlags := Header.PixelFormat.dwFlags or DDPF_ALPHAPIXELS;
-  *)
-
-  FormatDesc := TFormatDescriptor.Get(Format);
-  Header.PixelFormat.dwRGBBitCount  := Trunc(FormatDesc.PixelSize * 8);
-  Header.PixelFormat.dwRBitMask     := FormatDesc.RedMask;
-  Header.PixelFormat.dwGBitMask     := FormatDesc.GreenMask;
-  Header.PixelFormat.dwBBitMask     := FormatDesc.BlueMask;
-  Header.PixelFormat.dwABitMask := FormatDesc.AlphaMask;
 
+  aStream.Write(DDS_MAGIC, sizeof(DDS_MAGIC));
   aStream.Write(Header, SizeOf(Header));
   aStream.Write(Data^, FormatDesc.GetSize(Dimension));
 end;