0031478: Visualization, TKOpenGl - allow uploading Cubemap in compressed DDS format...
[occt.git] / src / Image / Image_DDSParser.cxx
1 // Copyright (c) 2020 OPEN CASCADE SAS
2 //
3 // This file is part of Open CASCADE Technology software library.
4 //
5 // This library is free software; you can redistribute it and/or modify it under
6 // the terms of the GNU Lesser General Public License version 2.1 as published
7 // by the Free Software Foundation, with special exception defined in the file
8 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9 // distribution for complete text of the license and disclaimer of any warranty.
10 //
11 // Alternatively, this file may be used under the terms of Open CASCADE
12 // commercial license or contractual agreement.
13
14 #include <Image_DDSParser.hxx>
15
16 #include <Image_PixMap.hxx>
17 #include <Image_SupportedFormats.hxx>
18 #include <Message.hxx>
19 #include <OSD_OpenFile.hxx>
20
21 IMPLEMENT_STANDARD_RTTIEXT(Image_CompressedPixMap, Standard_Transient)
22
23 //! DDS Pixel Format structure.
24 struct Image_DDSParser::DDSPixelFormat
25 {
26   uint32_t Size;
27   uint32_t Flags;
28   uint32_t FourCC;
29   uint32_t RGBBitCount;
30   uint32_t RBitMask;
31   uint32_t GBitMask;
32   uint32_t BBitMask;
33   uint32_t ABitMask;
34 };
35
36 //! DDS File header structure.
37 struct Image_DDSParser::DDSFileHeader
38 {
39   //! Caps2 flag indicating complete (6 faces) cubemap.
40   enum { DDSCompleteCubemap = 0xFE00 };
41
42   //! Return TRUE if cubmap flag is set.
43   bool IscompleteCubemap() const { return (Caps2 & DDSFileHeader::DDSCompleteCubemap) == DDSFileHeader::DDSCompleteCubemap; }
44
45   uint32_t Size;
46   uint32_t Flags;
47   uint32_t Height;
48   uint32_t Width;
49   uint32_t PitchOrLinearSize;
50   uint32_t Depth;
51   uint32_t MipMapCount;
52   uint32_t Reserved1[11];
53   DDSPixelFormat PixelFormatDef;
54   uint32_t Caps;
55   uint32_t Caps2;
56   uint32_t Caps3;
57   uint32_t Caps4;
58   uint32_t Reserved2;
59 };
60
61 // =======================================================================
62 // function : Load
63 // purpose  :
64 // =======================================================================
65 Handle(Image_CompressedPixMap) Image_DDSParser::Load (const Handle(Image_SupportedFormats)& theSupported,
66                                                       const TCollection_AsciiString& theFile,
67                                                       const Standard_Integer theFaceIndex,
68                                                       const int64_t theFileOffset)
69 {
70   std::ifstream aFile;
71   OSD_OpenStream (aFile, theFile.ToCString(), std::ios::in | std::ios::binary);
72
73   char aHeader[128] = {};
74   if (!aFile.is_open()
75    || !aFile.good())
76   {
77     return Handle(Image_CompressedPixMap)();
78   }
79   if (theFileOffset != 0)
80   {
81     aFile.seekg ((std::streamoff )theFileOffset, std::ios::beg);
82   }
83   aFile.read (aHeader, 128);
84   Standard_Size aNbReadBytes = (Standard_Size )aFile.gcount();
85   if (aNbReadBytes < 128
86    || ::memcmp (aHeader, "DDS ", 4) != 0)
87   {
88     return Handle(Image_CompressedPixMap)();
89   }
90
91   Handle(Image_CompressedPixMap) aDef = parseHeader (*(const DDSFileHeader* )(aHeader + 4));
92   if (aDef.IsNull())
93   {
94     return Handle(Image_CompressedPixMap)();
95   }
96
97   if (!theSupported.IsNull()
98    && !theSupported->IsSupported (aDef->CompressedFormat()))
99   {
100     return Handle(Image_CompressedPixMap)();
101   }
102
103   if (theFaceIndex < 0)
104   {
105     return aDef;
106   }
107
108   if (theFaceIndex >= aDef->NbFaces()
109    || aDef->FaceBytes() == 0)
110   {
111     Message::SendFail (TCollection_AsciiString ("DDS Reader error - invalid face index #") + theFaceIndex + " within file\n" + theFile);
112     return Handle(Image_CompressedPixMap)();
113   }
114
115   const Standard_Size anOffset = aDef->FaceBytes() * theFaceIndex;
116   if (anOffset != 0)
117   {
118     aFile.seekg ((std::streamoff )anOffset, std::ios::cur);
119   }
120   Handle(NCollection_Buffer) aBuffer = new NCollection_Buffer (Image_PixMap::DefaultAllocator(), aDef->FaceBytes());
121   aFile.read ((char* )aBuffer->ChangeData(), aDef->FaceBytes());
122   aNbReadBytes = (Standard_Size )aFile.gcount();
123   if (aNbReadBytes < aDef->FaceBytes())
124   {
125     Message::SendFail (TCollection_AsciiString ("DDS Reader error - unable to read face #") + theFaceIndex + " data from file\n" + theFile);
126     return Handle(Image_CompressedPixMap)();
127   }
128   aDef->SetFaceData (aBuffer);
129   return aDef;
130 }
131
132 // =======================================================================
133 // function : Load
134 // purpose  :
135 // =======================================================================
136 Handle(Image_CompressedPixMap) Image_DDSParser::Load (const Handle(Image_SupportedFormats)& theSupported,
137                                                       const Handle(NCollection_Buffer)& theBuffer,
138                                                       const Standard_Integer theFaceIndex)
139 {
140   if (theBuffer.IsNull()
141    || theBuffer->Size() < 128
142    || ::memcmp (theBuffer->Data(), "DDS ", 4) != 0)
143   {
144     return Handle(Image_CompressedPixMap)();
145   }
146
147   Handle(Image_CompressedPixMap) aDef = parseHeader (*(const DDSFileHeader* )(theBuffer->Data() + 4));
148   if (aDef.IsNull())
149   {
150     return Handle(Image_CompressedPixMap)();
151   }
152
153   if (!theSupported.IsNull()
154    && !theSupported->IsSupported (aDef->CompressedFormat()))
155   {
156     return Handle(Image_CompressedPixMap)();
157   }
158
159   if (theFaceIndex < 0)
160   {
161     return aDef;
162   }
163
164   if (theFaceIndex >= aDef->NbFaces()
165    || aDef->FaceBytes() == 0)
166   {
167     Message::SendFail (TCollection_AsciiString ("DDS Reader error - invalid face index #") + theFaceIndex + " within buffer");
168     return Handle(Image_CompressedPixMap)();
169   }
170
171   const Standard_Size anOffset = aDef->FaceBytes() * theFaceIndex + 128;
172   if (theBuffer->Size() < anOffset + aDef->FaceBytes())
173   {
174     Message::SendFail (TCollection_AsciiString ("DDS Reader error - unable to read face #") + theFaceIndex + " data from buffer");
175     return Handle(Image_CompressedPixMap)();
176   }
177
178   Handle(NCollection_Buffer) aBuffer = new NCollection_Buffer (Image_PixMap::DefaultAllocator(), aDef->FaceBytes());
179   memcpy (aBuffer->ChangeData(), theBuffer->Data() + anOffset, aDef->FaceBytes());
180   aDef->SetFaceData (aBuffer);
181   return aDef;
182 }
183
184 // =======================================================================
185 // function : parseHeader
186 // purpose  :
187 // =======================================================================
188 Handle(Image_CompressedPixMap) Image_DDSParser::parseHeader (const DDSFileHeader& theHeader)
189 {
190   if (theHeader.Size != 124
191    || theHeader.Width  == 0
192    || theHeader.Height == 0
193    || theHeader.PixelFormatDef.Size != 32)
194   {
195     return Handle(Image_CompressedPixMap)();
196   }
197
198   Image_Format aBaseFormat = Image_Format_UNKNOWN;
199   Image_CompressedFormat aFormat = Image_CompressedFormat_UNKNOWN;
200   Standard_Integer aBlockSize = 8;
201   const bool hasAlpha = (theHeader.PixelFormatDef.Flags & 0x1) != 0;
202   if (::memcmp (&theHeader.PixelFormatDef.FourCC, "DXT5", 4) == 0)
203   {
204     aBaseFormat = Image_Format_RGBA;
205     aFormat = Image_CompressedFormat_RGBA_S3TC_DXT5;
206     aBlockSize = 16;
207   }
208   else if (::memcmp (&theHeader.PixelFormatDef.FourCC, "DXT3", 4) == 0)
209   {
210     aBaseFormat = Image_Format_RGBA;
211     aFormat = Image_CompressedFormat_RGBA_S3TC_DXT3;
212     aBlockSize = 16;
213   }
214   else if (::memcmp (&theHeader.PixelFormatDef.FourCC, "DXT1", 4) == 0)
215   {
216     aBaseFormat = hasAlpha ? Image_Format_RGBA : Image_Format_RGB;
217     aFormat = hasAlpha ? Image_CompressedFormat_RGBA_S3TC_DXT1 : Image_CompressedFormat_RGB_S3TC_DXT1;
218     aBlockSize = 8;
219   }
220   if (aFormat == Image_CompressedFormat_UNKNOWN)
221   {
222     return Handle(Image_CompressedPixMap)();
223   }
224
225   Handle(Image_CompressedPixMap) aDef = new Image_CompressedPixMap();
226   aDef->SetSize ((Standard_Integer )theHeader.Width, (Standard_Integer )theHeader.Height);
227   aDef->SetNbFaces (theHeader.IscompleteCubemap() != 0 ? 6 : 1);
228   aDef->SetBaseFormat (aBaseFormat);
229   aDef->SetCompressedFormat (aFormat);
230
231   const Standard_Integer aNbMipMaps = Max ((Standard_Integer )theHeader.MipMapCount, 1);
232   aDef->ChangeMipMaps().Resize (0, aNbMipMaps - 1, false);
233   {
234     Standard_Size aFaceSize = 0;
235     NCollection_Vec2<Standard_Integer> aMipSizeXY (aDef->SizeX(), aDef->SizeY());
236     for (Standard_Integer aMipIter = 0;; ++aMipIter)
237     {
238       const Standard_Integer aMipLength = ((aMipSizeXY.x() + 3) / 4) * ((aMipSizeXY.y() + 3) / 4) * aBlockSize;
239       aFaceSize += aMipLength;
240       aDef->ChangeMipMaps().SetValue (aMipIter, aMipLength);
241       if (aMipIter + 1 >= aNbMipMaps)
242       {
243         break;
244       }
245
246       aMipSizeXY /= 2;
247       if (aMipSizeXY.x() == 0) { aMipSizeXY.x() = 1; }
248       if (aMipSizeXY.y() == 0) { aMipSizeXY.y() = 1; }
249     }
250     aDef->SetCompleteMipMapSet (aMipSizeXY.x() == 1 && aMipSizeXY.y() == 1);
251     aDef->SetFaceBytes (aFaceSize);
252   }
253
254   return aDef;
255 }