0031703: Data Exchange, RWGltf_CafWriter - add option putting textures inside GLB...
[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 : MimeType
212 // Purpose  :
213 // ================================================================
214 TCollection_AsciiString Image_Texture::MimeType() const
215 {
216   const TCollection_AsciiString aType = ProbeImageFileFormat();
217   if (aType == "jpg")
218   {
219     return "image/jpeg";
220   }
221   else if (aType == "png"
222         || aType == "bmp"
223         || aType == "webp"
224         || aType == "gif"
225         || aType == "tiff")
226   {
227     return TCollection_AsciiString ("image/") + aType;
228   }
229   else if (aType == "dds")
230   {
231     return "image/vnd-ms.dds";
232   }
233   else if (!aType.IsEmpty())
234   {
235     return TCollection_AsciiString ("image/x-") + aType;
236   }
237   return TCollection_AsciiString();
238 }
239
240 // ================================================================
241 // Function : ProbeImageFileFormat
242 // Purpose  :
243 // ================================================================
244 TCollection_AsciiString Image_Texture::ProbeImageFileFormat() const
245 {
246   static const Standard_Size THE_PROBE_SIZE = 20;
247   char aBuffer[THE_PROBE_SIZE];
248   if (!myBuffer.IsNull())
249   {
250     memcpy (aBuffer, myBuffer->Data(), myBuffer->Size() < THE_PROBE_SIZE ? myBuffer->Size() : THE_PROBE_SIZE);
251   }
252   else
253   {
254     std::ifstream aFileIn;
255     OSD_OpenStream (aFileIn, myImagePath.ToCString(), std::ios::in | std::ios::binary);
256     if (!aFileIn)
257     {
258       Message::SendFail (TCollection_AsciiString ("Error: Unable to open file '") + myImagePath + "'");
259       return false;
260     }
261     if (myOffset >= 0)
262     {
263       aFileIn.seekg ((std::streamoff )myOffset, std::ios_base::beg);
264       if (!aFileIn.good())
265       {
266         Message::SendFail (TCollection_AsciiString ("Error: Image is defined with invalid file offset '") + myImagePath + "'");
267         return false;
268       }
269     }
270
271     if (!aFileIn.read (aBuffer, THE_PROBE_SIZE))
272     {
273       Message::SendFail (TCollection_AsciiString ("Error: unable to read image file '") + myImagePath + "'");
274       return false;
275     }
276   }
277
278   if (memcmp (aBuffer, "\x89" "PNG\r\n" "\x1A" "\n", 8) == 0)
279   {
280     return "png";
281   }
282   else if (memcmp (aBuffer, "\xFF\xD8\xFF", 3) == 0)
283   {
284     return "jpg";
285   }
286   else if (memcmp (aBuffer, "GIF87a", 6) == 0
287         || memcmp (aBuffer, "GIF89a", 6) == 0)
288   {
289     return "gif";
290   }
291   else if (memcmp (aBuffer, "II\x2A\x00", 4) == 0
292         || memcmp (aBuffer, "MM\x00\x2A", 4) == 0)
293   {
294     return "tiff";
295   }
296   else if (memcmp (aBuffer, "BM", 2) == 0)
297   {
298     return "bmp";
299   }
300   else if (memcmp (aBuffer,     "RIFF", 4) == 0
301         && memcmp (aBuffer + 8, "WEBP", 4) == 0)
302   {
303     return "webp";
304   }
305   else if (memcmp (aBuffer, "DDS ", 4) == 0)
306   {
307     return "dds";
308   }
309   return "";
310 }
311
312 // ================================================================
313 // Function : WriteImage
314 // Purpose  :
315 // ================================================================
316 Standard_Boolean Image_Texture::WriteImage (const TCollection_AsciiString& theFile)
317 {
318   std::ofstream aFileOut;
319   OSD_OpenStream (aFileOut, theFile.ToCString(), std::ios::out | std::ios::binary | std::ios::trunc);
320   if (!aFileOut)
321   {
322     Message::SendFail (TCollection_AsciiString ("Error: Unable to create file '") + theFile + "'");
323     return false;
324   }
325
326   if (!WriteImage (aFileOut, theFile))
327   {
328     return false;
329   }
330
331   aFileOut.close();
332   if (!aFileOut.good())
333   {
334     Message::SendFail (TCollection_AsciiString ("Error: Unable to write file '") + theFile + "'");
335     return false;
336   }
337   return true;
338 }
339
340 // ================================================================
341 // Function : WriteImage
342 // Purpose  :
343 // ================================================================
344 Standard_Boolean Image_Texture::WriteImage (std::ostream& theStream,
345                                             const TCollection_AsciiString& theFile)
346 {
347   if (!myBuffer.IsNull())
348   {
349     theStream.write ((const char* )myBuffer->Data(), myBuffer->Size());
350     if (!theStream.good())
351     {
352       Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' cannot be written");
353       return false;
354     }
355     return true;
356   }
357
358   std::ifstream aFileIn;
359   OSD_OpenStream (aFileIn, myImagePath.ToCString(), std::ios::in | std::ios::binary);
360   if (!aFileIn)
361   {
362     Message::SendFail (TCollection_AsciiString ("Error: Unable to open file ") + myImagePath + "!");
363     return false;
364   }
365
366   int64_t aLen = myLength;
367   if (myOffset >= 0)
368   {
369     aFileIn.seekg ((std::streamoff )myOffset, std::ios_base::beg);
370     if (!aFileIn.good())
371     {
372       Message::SendFail (TCollection_AsciiString ("Error: Image is defined with invalid file offset '") + myImagePath + "'");
373       return false;
374     }
375   }
376   else
377   {
378     aFileIn.seekg (0, std::ios_base::end);
379     aLen = (int64_t )aFileIn.tellg();
380     aFileIn.seekg (0, std::ios_base::beg);
381   }
382
383   Standard_Integer aChunkSize = 4096;
384   NCollection_Array1<Standard_Byte> aBuffer (0, aChunkSize - 1);
385   for (int64_t aChunkIter = 0; aChunkIter < aLen; aChunkIter += aChunkSize)
386   {
387     if (aChunkIter + aChunkSize >= aLen)
388     {
389       aChunkSize = Standard_Integer(aLen - aChunkIter);
390     }
391     if (!aFileIn.read ((char* )&aBuffer.ChangeFirst(), aChunkSize))
392     {
393       Message::SendFail (TCollection_AsciiString ("Error: unable to read image file '") + myImagePath + "'");
394       return false;
395     }
396     theStream.write ((const char* )&aBuffer.First(), aChunkSize);
397   }
398   if (!theStream.good())
399   {
400     Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' can not be written");
401     return false;
402   }
403   return true;
404 }
405
406 //=======================================================================
407 //function : DumpJson
408 //purpose  : 
409 //=======================================================================
410 void Image_Texture::DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth) const
411 {
412   OCCT_DUMP_TRANSIENT_CLASS_BEGIN (theOStream)
413
414   OCCT_DUMP_FIELD_VALUE_STRING (theOStream, myTextureId)
415   OCCT_DUMP_FIELD_VALUE_STRING (theOStream, myImagePath)
416
417   OCCT_DUMP_FIELD_VALUES_DUMPED (theOStream, theDepth, myBuffer.get())
418
419   OCCT_DUMP_FIELD_VALUE_NUMERICAL (theOStream, myOffset)
420   OCCT_DUMP_FIELD_VALUE_NUMERICAL (theOStream, myLength)
421 }