0031478: Visualization, TKOpenGl - allow uploading Cubemap in compressed DDS format...
[occt.git] / src / Image / Image_Texture.cxx
1 // Author: Kirill Gavrilov
2 // Copyright (c) 2015-2019 OPEN CASCADE SAS
3 //
4 // This file is part of Open CASCADE Technology software library.
5 //
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
11 //
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
14
15 #include <Image_Texture.hxx>
16
17 #include <Image_AlienPixMap.hxx>
18 #include <Image_DDSParser.hxx>
19 #include <Image_SupportedFormats.hxx>
20 #include <Message.hxx>
21 #include <Message_Messenger.hxx>
22 #include <OSD_OpenFile.hxx>
23
24 IMPLEMENT_STANDARD_RTTIEXT(Image_Texture, Standard_Transient)
25
26 // ================================================================
27 // Function : Image_Texture
28 // Purpose  :
29 // ================================================================
30 Image_Texture::Image_Texture (const TCollection_AsciiString& theFileName)
31 : myImagePath (theFileName),
32   myOffset (-1),
33   myLength (-1)
34 {
35   // share textures with unique file paths
36   if (!theFileName.IsEmpty())
37   {
38     myTextureId = TCollection_AsciiString ("texture://") + theFileName;
39   }
40 }
41
42 // ================================================================
43 // Function : Image_Texture
44 // Purpose  :
45 // ================================================================
46 Image_Texture::Image_Texture (const TCollection_AsciiString& theFileName,
47                               int64_t theOffset,
48                               int64_t theLength)
49 : myImagePath (theFileName),
50   myOffset (theOffset),
51   myLength (theLength)
52 {
53   // share textures with unique file paths
54   if (!theFileName.IsEmpty())
55   {
56     char aBuff[60];
57     Sprintf (aBuff, ";%" PRId64 ",%" PRId64, theOffset, theLength);
58     myTextureId = TCollection_AsciiString ("texture://") + theFileName + aBuff;
59   }
60 }
61
62 // ================================================================
63 // Function : Image_Texture
64 // Purpose  :
65 // ================================================================
66 Image_Texture::Image_Texture (const Handle(NCollection_Buffer)& theBuffer,
67                               const TCollection_AsciiString& theId)
68 : myBuffer (theBuffer),
69   myOffset (-1),
70   myLength (-1)
71 {
72   if (!theId.IsEmpty())
73   {
74     myTextureId = TCollection_AsciiString ("texturebuf://") + theId;
75   }
76 }
77
78 // ================================================================
79 // Function : ReadCompressedImage
80 // Purpose  :
81 // ================================================================
82 Handle(Image_CompressedPixMap) Image_Texture::ReadCompressedImage (const Handle(Image_SupportedFormats)& theSupported) const
83 {
84   if (!theSupported->HasCompressed())
85   {
86     return Handle(Image_CompressedPixMap)();
87   }
88
89   if (!myBuffer.IsNull())
90   {
91     return Image_DDSParser::Load (theSupported, myBuffer, 0);
92   }
93   else if (myOffset >= 0)
94   {
95     return Image_DDSParser::Load (theSupported, myImagePath, 0, myOffset);
96   }
97
98   TCollection_AsciiString aFilePathLower = myImagePath;
99   aFilePathLower.LowerCase();
100   if (!aFilePathLower.EndsWith (".dds"))
101   {
102     // do not waste time on file system access in case of wrong file extension
103     return Handle(Image_CompressedPixMap)();
104   }
105   return Image_DDSParser::Load (theSupported, myImagePath, 0);
106 }
107
108 // ================================================================
109 // Function : ReadImage
110 // Purpose  :
111 // ================================================================
112 Handle(Image_PixMap) Image_Texture::ReadImage (const Handle(Image_SupportedFormats)& ) const
113 {
114   Handle(Image_PixMap) anImage;
115   if (!myBuffer.IsNull())
116   {
117     anImage = loadImageBuffer (myBuffer, myTextureId);
118   }
119   else if (myOffset >= 0)
120   {
121     anImage = loadImageOffset (myImagePath, myOffset, myLength);
122   }
123   else
124   {
125     anImage = loadImageFile (myImagePath);
126   }
127
128   if (anImage.IsNull())
129   {
130     return Handle(Image_PixMap)();
131   }
132   return anImage;
133 }
134
135 // ================================================================
136 // Function : loadImageFile
137 // Purpose  :
138 // ================================================================
139 Handle(Image_PixMap) Image_Texture::loadImageFile (const TCollection_AsciiString& thePath) const
140 {
141   Handle(Image_AlienPixMap) anImage = new Image_AlienPixMap();
142   if (!anImage->Load (thePath))
143   {
144     return Handle(Image_PixMap)();
145   }
146   return anImage;
147 }
148
149 // ================================================================
150 // Function : loadImageBuffer
151 // Purpose  :
152 // ================================================================
153 Handle(Image_PixMap) Image_Texture::loadImageBuffer (const Handle(NCollection_Buffer)& theBuffer,
154                                                      const TCollection_AsciiString& theId) const
155 {
156   if (theBuffer.IsNull())
157   {
158     return Handle(Image_PixMap)();
159   }
160   else if (theBuffer->Size() > (Standard_Size )IntegerLast())
161   {
162     Message::SendFail (TCollection_AsciiString ("Error: Image file size is too big '") + theId + "'");
163     return Handle(Image_PixMap)();
164   }
165
166   Handle(Image_AlienPixMap) anImage = new Image_AlienPixMap();
167   if (!anImage->Load (theBuffer->Data(), (int )theBuffer->Size(), theId))
168   {
169     return Handle(Image_PixMap)();
170   }
171   return anImage;
172 }
173
174 // ================================================================
175 // Function : loadImageOffset
176 // Purpose  :
177 // ================================================================
178 Handle(Image_PixMap) Image_Texture::loadImageOffset (const TCollection_AsciiString& thePath,
179                                                      int64_t theOffset,
180                                                      int64_t theLength) const
181 {
182   if (theLength > IntegerLast())
183   {
184     Message::SendFail (TCollection_AsciiString ("Error: Image file size is too big '") + thePath + "'");
185     return Handle(Image_PixMap)();
186   }
187
188   std::ifstream aFile;
189   OSD_OpenStream (aFile, thePath.ToCString(), std::ios::in | std::ios::binary);
190   if (!aFile)
191   {
192     Message::SendFail (TCollection_AsciiString ("Error: Image file '") + thePath + "' cannot be opened");
193     return Handle(Image_PixMap)();
194   }
195   aFile.seekg ((std::streamoff )theOffset, std::ios_base::beg);
196   if (!aFile.good())
197   {
198     Message::SendFail (TCollection_AsciiString ("Error: Image is defined with invalid file offset '") + thePath + "'");
199     return Handle(Image_PixMap)();
200   }
201
202   Handle(Image_AlienPixMap) anImage = new Image_AlienPixMap();
203   if (!anImage->Load (aFile, thePath))
204   {
205     return Handle(Image_PixMap)();
206   }
207   return anImage;
208 }
209
210 // ================================================================
211 // Function : ProbeImageFileFormat
212 // Purpose  :
213 // ================================================================
214 TCollection_AsciiString Image_Texture::ProbeImageFileFormat() const
215 {
216   static const Standard_Size THE_PROBE_SIZE = 20;
217   char aBuffer[THE_PROBE_SIZE];
218   if (!myBuffer.IsNull())
219   {
220     memcpy (aBuffer, myBuffer->Data(), myBuffer->Size() < THE_PROBE_SIZE ? myBuffer->Size() : THE_PROBE_SIZE);
221   }
222   else
223   {
224     std::ifstream aFileIn;
225     OSD_OpenStream (aFileIn, myImagePath.ToCString(), std::ios::in | std::ios::binary);
226     if (!aFileIn)
227     {
228       Message::SendFail (TCollection_AsciiString ("Error: Unable to open file '") + myImagePath + "'");
229       return false;
230     }
231     if (myOffset >= 0)
232     {
233       aFileIn.seekg ((std::streamoff )myOffset, std::ios_base::beg);
234       if (!aFileIn.good())
235       {
236         Message::SendFail (TCollection_AsciiString ("Error: Image is defined with invalid file offset '") + myImagePath + "'");
237         return false;
238       }
239     }
240
241     if (!aFileIn.read (aBuffer, THE_PROBE_SIZE))
242     {
243       Message::SendFail (TCollection_AsciiString ("Error: unable to read image file '") + myImagePath + "'");
244       return false;
245     }
246   }
247
248   if (memcmp (aBuffer, "\x89" "PNG\r\n" "\x1A" "\n", 8) == 0)
249   {
250     return "png";
251   }
252   else if (memcmp (aBuffer, "\xFF\xD8\xFF", 3) == 0)
253   {
254     return "jpg";
255   }
256   else if (memcmp (aBuffer, "GIF87a", 6) == 0
257         || memcmp (aBuffer, "GIF89a", 6) == 0)
258   {
259     return "gif";
260   }
261   else if (memcmp (aBuffer, "II\x2A\x00", 4) == 0
262         || memcmp (aBuffer, "MM\x00\x2A", 4) == 0)
263   {
264     return "tiff";
265   }
266   else if (memcmp (aBuffer, "BM", 2) == 0)
267   {
268     return "bmp";
269   }
270   else if (memcmp (aBuffer,     "RIFF", 4) == 0
271         && memcmp (aBuffer + 8, "WEBP", 4) == 0)
272   {
273     return "webp";
274   }
275   else if (memcmp (aBuffer, "DDS ", 4) == 0)
276   {
277     return "dds";
278   }
279   return "";
280 }
281
282 // ================================================================
283 // Function : WriteImage
284 // Purpose  :
285 // ================================================================
286 Standard_Boolean Image_Texture::WriteImage (const TCollection_AsciiString& theFile)
287 {
288   Handle(NCollection_Buffer) aBuffer = myBuffer;
289   if (myBuffer.IsNull())
290   {
291     std::ifstream aFileIn;
292     OSD_OpenStream (aFileIn, myImagePath.ToCString(), std::ios::in | std::ios::binary);
293     if (!aFileIn)
294     {
295       Message::SendFail (TCollection_AsciiString ("Error: Unable to open file ") + myImagePath + "!");
296       return Standard_False;
297     }
298
299     Standard_Size aLen = (Standard_Size )myLength;
300     if (myOffset >= 0)
301     {
302       aFileIn.seekg ((std::streamoff )myOffset, std::ios_base::beg);
303       if (!aFileIn.good())
304       {
305         Message::SendFail (TCollection_AsciiString ("Error: Image is defined with invalid file offset '") + myImagePath + "'");
306         return Standard_False;
307       }
308     }
309     else
310     {
311       aFileIn.seekg (0, std::ios_base::end);
312       aLen = (Standard_Size )aFileIn.tellg();
313       aFileIn.seekg (0, std::ios_base::beg);
314     }
315
316     aBuffer = new NCollection_Buffer (NCollection_BaseAllocator::CommonBaseAllocator(), aLen);
317     if (!aFileIn.read ((char* )aBuffer->ChangeData(), aBuffer->Size()))
318     {
319       Message::SendFail (TCollection_AsciiString ("Error: unable to read image file '") + myImagePath + "'");
320       return Standard_False;
321     }
322   }
323
324   std::ofstream aFileOut;
325   OSD_OpenStream (aFileOut, theFile.ToCString(), std::ios::out | std::ios::binary | std::ios::trunc);
326   if (!aFileOut)
327   {
328     Message::SendFail (TCollection_AsciiString ("Error: Unable to create file '") + theFile + "'");
329     return Standard_False;
330   }
331
332   aFileOut.write ((const char* )aBuffer->Data(), aBuffer->Size());
333   aFileOut.close();
334   if (!aFileOut.good())
335   {
336     Message::SendFail (TCollection_AsciiString ("Error: Unable to write file '") + theFile + "'");
337     return Standard_False;
338   }
339   return Standard_True;
340 }
341
342 //=======================================================================
343 //function : DumpJson
344 //purpose  : 
345 //=======================================================================
346 void Image_Texture::DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth) const
347 {
348   OCCT_DUMP_TRANSIENT_CLASS_BEGIN (theOStream)
349
350   OCCT_DUMP_FIELD_VALUE_STRING (theOStream, myTextureId)
351   OCCT_DUMP_FIELD_VALUE_STRING (theOStream, myImagePath)
352
353   OCCT_DUMP_FIELD_VALUES_DUMPED (theOStream, theDepth, myBuffer.get())
354
355   OCCT_DUMP_FIELD_VALUE_NUMERICAL (theOStream, myOffset)
356   OCCT_DUMP_FIELD_VALUE_NUMERICAL (theOStream, myLength)
357 }