0031478: Visualization, TKOpenGl - allow uploading Cubemap in compressed DDS format...
[occt.git] / src / Image / Image_DDSParser.cxx
diff --git a/src/Image/Image_DDSParser.cxx b/src/Image/Image_DDSParser.cxx
new file mode 100644 (file)
index 0000000..7addc93
--- /dev/null
@@ -0,0 +1,255 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <Image_DDSParser.hxx>
+
+#include <Image_PixMap.hxx>
+#include <Image_SupportedFormats.hxx>
+#include <Message.hxx>
+#include <OSD_OpenFile.hxx>
+
+IMPLEMENT_STANDARD_RTTIEXT(Image_CompressedPixMap, Standard_Transient)
+
+//! DDS Pixel Format structure.
+struct Image_DDSParser::DDSPixelFormat
+{
+  uint32_t Size;
+  uint32_t Flags;
+  uint32_t FourCC;
+  uint32_t RGBBitCount;
+  uint32_t RBitMask;
+  uint32_t GBitMask;
+  uint32_t BBitMask;
+  uint32_t ABitMask;
+};
+
+//! DDS File header structure.
+struct Image_DDSParser::DDSFileHeader
+{
+  //! Caps2 flag indicating complete (6 faces) cubemap.
+  enum { DDSCompleteCubemap = 0xFE00 };
+
+  //! Return TRUE if cubmap flag is set.
+  bool IscompleteCubemap() const { return (Caps2 & DDSFileHeader::DDSCompleteCubemap) == DDSFileHeader::DDSCompleteCubemap; }
+
+  uint32_t Size;
+  uint32_t Flags;
+  uint32_t Height;
+  uint32_t Width;
+  uint32_t PitchOrLinearSize;
+  uint32_t Depth;
+  uint32_t MipMapCount;
+  uint32_t Reserved1[11];
+  DDSPixelFormat PixelFormatDef;
+  uint32_t Caps;
+  uint32_t Caps2;
+  uint32_t Caps3;
+  uint32_t Caps4;
+  uint32_t Reserved2;
+};
+
+// =======================================================================
+// function : Load
+// purpose  :
+// =======================================================================
+Handle(Image_CompressedPixMap) Image_DDSParser::Load (const Handle(Image_SupportedFormats)& theSupported,
+                                                      const TCollection_AsciiString& theFile,
+                                                      const Standard_Integer theFaceIndex,
+                                                      const int64_t theFileOffset)
+{
+  std::ifstream aFile;
+  OSD_OpenStream (aFile, theFile.ToCString(), std::ios::in | std::ios::binary);
+
+  char aHeader[128] = {};
+  if (!aFile.is_open()
+   || !aFile.good())
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+  if (theFileOffset != 0)
+  {
+    aFile.seekg ((std::streamoff )theFileOffset, std::ios::beg);
+  }
+  aFile.read (aHeader, 128);
+  Standard_Size aNbReadBytes = (Standard_Size )aFile.gcount();
+  if (aNbReadBytes < 128
+   || ::memcmp (aHeader, "DDS ", 4) != 0)
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  Handle(Image_CompressedPixMap) aDef = parseHeader (*(const DDSFileHeader* )(aHeader + 4));
+  if (aDef.IsNull())
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  if (!theSupported.IsNull()
+   && !theSupported->IsSupported (aDef->CompressedFormat()))
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  if (theFaceIndex < 0)
+  {
+    return aDef;
+  }
+
+  if (theFaceIndex >= aDef->NbFaces()
+   || aDef->FaceBytes() == 0)
+  {
+    Message::SendFail (TCollection_AsciiString ("DDS Reader error - invalid face index #") + theFaceIndex + " within file\n" + theFile);
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  const Standard_Size anOffset = aDef->FaceBytes() * theFaceIndex;
+  if (anOffset != 0)
+  {
+    aFile.seekg ((std::streamoff )anOffset, std::ios::cur);
+  }
+  Handle(NCollection_Buffer) aBuffer = new NCollection_Buffer (Image_PixMap::DefaultAllocator(), aDef->FaceBytes());
+  aFile.read ((char* )aBuffer->ChangeData(), aDef->FaceBytes());
+  aNbReadBytes = (Standard_Size )aFile.gcount();
+  if (aNbReadBytes < aDef->FaceBytes())
+  {
+    Message::SendFail (TCollection_AsciiString ("DDS Reader error - unable to read face #") + theFaceIndex + " data from file\n" + theFile);
+    return Handle(Image_CompressedPixMap)();
+  }
+  aDef->SetFaceData (aBuffer);
+  return aDef;
+}
+
+// =======================================================================
+// function : Load
+// purpose  :
+// =======================================================================
+Handle(Image_CompressedPixMap) Image_DDSParser::Load (const Handle(Image_SupportedFormats)& theSupported,
+                                                      const Handle(NCollection_Buffer)& theBuffer,
+                                                      const Standard_Integer theFaceIndex)
+{
+  if (theBuffer.IsNull()
+   || theBuffer->Size() < 128
+   || ::memcmp (theBuffer->Data(), "DDS ", 4) != 0)
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  Handle(Image_CompressedPixMap) aDef = parseHeader (*(const DDSFileHeader* )(theBuffer->Data() + 4));
+  if (aDef.IsNull())
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  if (!theSupported.IsNull()
+   && !theSupported->IsSupported (aDef->CompressedFormat()))
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  if (theFaceIndex < 0)
+  {
+    return aDef;
+  }
+
+  if (theFaceIndex >= aDef->NbFaces()
+   || aDef->FaceBytes() == 0)
+  {
+    Message::SendFail (TCollection_AsciiString ("DDS Reader error - invalid face index #") + theFaceIndex + " within buffer");
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  const Standard_Size anOffset = aDef->FaceBytes() * theFaceIndex + 128;
+  if (theBuffer->Size() < anOffset + aDef->FaceBytes())
+  {
+    Message::SendFail (TCollection_AsciiString ("DDS Reader error - unable to read face #") + theFaceIndex + " data from buffer");
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  Handle(NCollection_Buffer) aBuffer = new NCollection_Buffer (Image_PixMap::DefaultAllocator(), aDef->FaceBytes());
+  memcpy (aBuffer->ChangeData(), theBuffer->Data() + anOffset, aDef->FaceBytes());
+  aDef->SetFaceData (aBuffer);
+  return aDef;
+}
+
+// =======================================================================
+// function : parseHeader
+// purpose  :
+// =======================================================================
+Handle(Image_CompressedPixMap) Image_DDSParser::parseHeader (const DDSFileHeader& theHeader)
+{
+  if (theHeader.Size != 124
+   || theHeader.Width  == 0
+   || theHeader.Height == 0
+   || theHeader.PixelFormatDef.Size != 32)
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  Image_Format aBaseFormat = Image_Format_UNKNOWN;
+  Image_CompressedFormat aFormat = Image_CompressedFormat_UNKNOWN;
+  Standard_Integer aBlockSize = 8;
+  const bool hasAlpha = (theHeader.PixelFormatDef.Flags & 0x1) != 0;
+  if (::memcmp (&theHeader.PixelFormatDef.FourCC, "DXT5", 4) == 0)
+  {
+    aBaseFormat = Image_Format_RGBA;
+    aFormat = Image_CompressedFormat_RGBA_S3TC_DXT5;
+    aBlockSize = 16;
+  }
+  else if (::memcmp (&theHeader.PixelFormatDef.FourCC, "DXT3", 4) == 0)
+  {
+    aBaseFormat = Image_Format_RGBA;
+    aFormat = Image_CompressedFormat_RGBA_S3TC_DXT3;
+    aBlockSize = 16;
+  }
+  else if (::memcmp (&theHeader.PixelFormatDef.FourCC, "DXT1", 4) == 0)
+  {
+    aBaseFormat = hasAlpha ? Image_Format_RGBA : Image_Format_RGB;
+    aFormat = hasAlpha ? Image_CompressedFormat_RGBA_S3TC_DXT1 : Image_CompressedFormat_RGB_S3TC_DXT1;
+    aBlockSize = 8;
+  }
+  if (aFormat == Image_CompressedFormat_UNKNOWN)
+  {
+    return Handle(Image_CompressedPixMap)();
+  }
+
+  Handle(Image_CompressedPixMap) aDef = new Image_CompressedPixMap();
+  aDef->SetSize ((Standard_Integer )theHeader.Width, (Standard_Integer )theHeader.Height);
+  aDef->SetNbFaces (theHeader.IscompleteCubemap() != 0 ? 6 : 1);
+  aDef->SetBaseFormat (aBaseFormat);
+  aDef->SetCompressedFormat (aFormat);
+
+  const Standard_Integer aNbMipMaps = Max ((Standard_Integer )theHeader.MipMapCount, 1);
+  aDef->ChangeMipMaps().Resize (0, aNbMipMaps - 1, false);
+  {
+    Standard_Size aFaceSize = 0;
+    NCollection_Vec2<Standard_Integer> aMipSizeXY (aDef->SizeX(), aDef->SizeY());
+    for (Standard_Integer aMipIter = 0;; ++aMipIter)
+    {
+      const Standard_Integer aMipLength = ((aMipSizeXY.x() + 3) / 4) * ((aMipSizeXY.y() + 3) / 4) * aBlockSize;
+      aFaceSize += aMipLength;
+      aDef->ChangeMipMaps().SetValue (aMipIter, aMipLength);
+      if (aMipIter + 1 >= aNbMipMaps)
+      {
+        break;
+      }
+
+      aMipSizeXY /= 2;
+      if (aMipSizeXY.x() == 0) { aMipSizeXY.x() = 1; }
+      if (aMipSizeXY.y() == 0) { aMipSizeXY.y() = 1; }
+    }
+    aDef->SetCompleteMipMapSet (aMipSizeXY.x() == 1 && aMipSizeXY.y() == 1);
+    aDef->SetFaceBytes (aFaceSize);
+  }
+
+  return aDef;
+}