0025008: wrong PPM generated in mage_AlienPixMap::savePPM
[occt.git] / src / Image / Image_AlienPixMap.cxx
1 // Created on: 2010-09-16
2 // Created by: KGV
3 // Copyright (c) 2010-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #ifdef HAVE_CONFIG_H
17   #include <config.h>
18 #endif
19
20 #ifdef HAVE_FREEIMAGE
21   #include <FreeImage.h>
22
23   #ifdef _MSC_VER
24     #pragma comment( lib, "FreeImage.lib" )
25   #endif
26 #endif
27
28 #include <Image_AlienPixMap.hxx>
29 #include <gp.hxx>
30 #include <TCollection_AsciiString.hxx>
31 #include <fstream>
32 #include <algorithm>
33
34 #ifdef HAVE_FREEIMAGE
35 namespace
36 {
37   static Image_PixMap::ImgFormat convertFromFreeFormat (FREE_IMAGE_TYPE       theFormatFI,
38                                                         FREE_IMAGE_COLOR_TYPE theColorTypeFI,
39                                                         unsigned              theBitsPerPixel)
40   {
41     switch (theFormatFI)
42     {
43       case FIT_RGBF:   return Image_PixMap::ImgRGBF;
44       case FIT_RGBAF:  return Image_PixMap::ImgRGBAF;
45       case FIT_FLOAT:  return Image_PixMap::ImgGrayF;
46       case FIT_BITMAP:
47       {
48         switch (theColorTypeFI)
49         {
50           case FIC_MINISBLACK:
51           {
52             return Image_PixMap::ImgGray;
53           }
54           case FIC_RGB:
55           {
56             if (Image_PixMap::IsBigEndianHost())
57             {
58               return (theBitsPerPixel == 32) ? Image_PixMap::ImgRGB32 : Image_PixMap::ImgRGB;
59             }
60             else
61             {
62               return (theBitsPerPixel == 32) ? Image_PixMap::ImgBGR32 : Image_PixMap::ImgBGR;
63             }
64           }
65           case FIC_RGBALPHA:
66           {
67             return Image_PixMap::IsBigEndianHost() ? Image_PixMap::ImgRGBA : Image_PixMap::ImgBGRA;
68           }
69           default:
70             return Image_PixMap::ImgUNKNOWN;
71         }
72       }
73       default:
74         return Image_PixMap::ImgUNKNOWN;
75     }
76   }
77
78   static FREE_IMAGE_TYPE convertToFreeFormat (Image_PixMap::ImgFormat theFormat)
79   {
80     switch (theFormat)
81     {
82       case Image_PixMap::ImgGrayF:
83         return FIT_FLOAT;
84       case Image_PixMap::ImgRGBAF:
85         return FIT_RGBAF;
86       case Image_PixMap::ImgRGBF:
87         return FIT_RGBF;
88       case Image_PixMap::ImgRGBA:
89       case Image_PixMap::ImgBGRA:
90       case Image_PixMap::ImgRGB32:
91       case Image_PixMap::ImgBGR32:
92       case Image_PixMap::ImgRGB:
93       case Image_PixMap::ImgBGR:
94       case Image_PixMap::ImgGray:
95         return FIT_BITMAP;
96       default:
97         return FIT_UNKNOWN;
98     }
99   }
100 };
101 #endif
102
103 IMPLEMENT_STANDARD_HANDLE (Image_AlienPixMap, Image_PixMap)
104 IMPLEMENT_STANDARD_RTTIEXT(Image_AlienPixMap, Image_PixMap)
105
106 // =======================================================================
107 // function : Image_AlienPixMap
108 // purpose  :
109 // =======================================================================
110 Image_AlienPixMap::Image_AlienPixMap()
111 : myLibImage (NULL)
112 {
113   SetTopDown (false);
114 }
115
116 // =======================================================================
117 // function : ~Image_AlienPixMap
118 // purpose  :
119 // =======================================================================
120 Image_AlienPixMap::~Image_AlienPixMap()
121 {
122   Clear();
123 }
124
125 // =======================================================================
126 // function : InitWrapper
127 // purpose  :
128 // =======================================================================
129 bool Image_AlienPixMap::InitWrapper (ImgFormat,
130                                      Standard_Byte*,
131                                      const Standard_Size,
132                                      const Standard_Size,
133                                      const Standard_Size)
134 {
135   Clear();
136   return false;
137 }
138
139 // =======================================================================
140 // function : InitTrash
141 // purpose  :
142 // =======================================================================
143 #ifdef HAVE_FREEIMAGE
144 bool Image_AlienPixMap::InitTrash (ImgFormat           thePixelFormat,
145                                    const Standard_Size theSizeX,
146                                    const Standard_Size theSizeY,
147                                    const Standard_Size /*theSizeRowBytes*/)
148 {
149   Clear();
150   FREE_IMAGE_TYPE aFormatFI = convertToFreeFormat (thePixelFormat);
151   int aBitsPerPixel = (int )Image_PixMap::SizePixelBytes (thePixelFormat) * 8;
152   if (aFormatFI == FIT_UNKNOWN)
153   {
154     aFormatFI     = FIT_BITMAP;
155     aBitsPerPixel = 24;
156   }
157
158   FIBITMAP* anImage = FreeImage_AllocateT (aFormatFI, (int )theSizeX, (int )theSizeY, aBitsPerPixel);
159   Image_PixMap::ImgFormat aFormat = convertFromFreeFormat (FreeImage_GetImageType (anImage),
160                                                            FreeImage_GetColorType (anImage),
161                                                            FreeImage_GetBPP (anImage));
162   if (thePixelFormat == Image_PixMap::ImgBGR32
163    || thePixelFormat == Image_PixMap::ImgRGB32)
164   {
165     //FreeImage_SetTransparent (anImage, FALSE);
166     aFormat = (aFormat == Image_PixMap::ImgBGRA) ? Image_PixMap::ImgBGR32 : Image_PixMap::ImgRGB32;
167   }
168
169   Image_PixMap::InitWrapper (aFormat, FreeImage_GetBits (anImage),
170                              FreeImage_GetWidth (anImage), FreeImage_GetHeight (anImage), FreeImage_GetPitch (anImage));
171   SetTopDown (false);
172
173   // assign image after wrapper initialization (virtual Clear() called inside)
174   myLibImage = anImage;
175   return true;
176 }
177 #else
178 bool Image_AlienPixMap::InitTrash (ImgFormat           thePixelFormat,
179                                    const Standard_Size theSizeX,
180                                    const Standard_Size theSizeY,
181                                    const Standard_Size theSizeRowBytes)
182 {
183   return Image_PixMap::InitTrash (thePixelFormat, theSizeX, theSizeY, theSizeRowBytes);
184 }
185 #endif
186
187 // =======================================================================
188 // function : InitCopy
189 // purpose  :
190 // =======================================================================
191 bool Image_AlienPixMap::InitCopy (const Image_PixMap& theCopy)
192 {
193   if (&theCopy == this)
194   {
195     // self-copying disallowed
196     return false;
197   }
198   if (!InitTrash (theCopy.Format(), theCopy.SizeX(), theCopy.SizeY(), theCopy.SizeRowBytes()))
199   {
200     return false;
201   }
202
203   if (myImgFormat == theCopy.Format())
204   {
205     if (SizeRowBytes() == theCopy.SizeRowBytes()
206      && TopDownInc()   == theCopy.TopDownInc())
207     {
208       // copy with one call
209       memcpy (ChangeData(), theCopy.Data(), std::min (SizeBytes(), theCopy.SizeBytes()));
210       return true;
211     }
212
213     // copy row-by-row
214     const Standard_Size aRowSizeBytes = std::min (SizeRowBytes(), theCopy.SizeRowBytes());
215     for (Standard_Size aRow = 0; aRow < myData.SizeY; ++aRow)
216     {
217       memcpy (ChangeRow (aRow), theCopy.Row (aRow), aRowSizeBytes);
218     }
219     return true;
220   }
221
222   // pixel format conversion required
223   Clear();
224   return false;
225 }
226
227 // =======================================================================
228 // function : Clear
229 // purpose  :
230 // =======================================================================
231 void Image_AlienPixMap::Clear()
232 {
233   Image_PixMap::Clear();
234 #ifdef HAVE_FREEIMAGE
235   if (myLibImage != NULL)
236   {
237     FreeImage_Unload (myLibImage);
238     myLibImage = NULL;
239   }
240 #endif
241 }
242
243 // =======================================================================
244 // function : Load
245 // purpose  :
246 // =======================================================================
247 #ifdef HAVE_FREEIMAGE
248 bool Image_AlienPixMap::Load (const TCollection_AsciiString& theImagePath)
249 {
250   Clear();
251   FREE_IMAGE_FORMAT aFIF = FreeImage_GetFileType (theImagePath.ToCString(), 0);
252   if (aFIF == FIF_UNKNOWN)
253   {
254     // no signature? try to guess the file format from the file extension
255     aFIF = FreeImage_GetFIFFromFilename (theImagePath.ToCString());
256   }
257   if ((aFIF == FIF_UNKNOWN) || !FreeImage_FIFSupportsReading (aFIF))
258   {
259     // unsupported image format
260     return false;
261   }
262
263   int aLoadFlags = 0;
264   if (aFIF == FIF_GIF)
265   {
266     // 'Play' the GIF to generate each frame (as 32bpp) instead of returning raw frame data when loading
267     aLoadFlags = GIF_PLAYBACK;
268   }
269   else if (aFIF == FIF_ICO)
270   {
271     // convert to 32bpp and create an alpha channel from the AND-mask when loading
272     aLoadFlags = ICO_MAKEALPHA;
273   }
274
275   FIBITMAP* anImage = FreeImage_Load (aFIF, theImagePath.ToCString(), aLoadFlags);
276   if (anImage == NULL)
277   {
278     return false;
279   }
280
281   Image_PixMap::ImgFormat aFormat = convertFromFreeFormat (FreeImage_GetImageType (anImage),
282                                                            FreeImage_GetColorType (anImage),
283                                                            FreeImage_GetBPP (anImage));
284   if (aFormat == Image_PixMap::ImgUNKNOWN)
285   {
286     //anImage = FreeImage_ConvertTo24Bits (anImage);
287     return false;
288   }
289
290   Image_PixMap::InitWrapper (aFormat, FreeImage_GetBits (anImage),
291                              FreeImage_GetWidth (anImage), FreeImage_GetHeight (anImage), FreeImage_GetPitch (anImage));
292   SetTopDown (false);
293
294   // assign image after wrapper initialization (virtual Clear() called inside)
295   myLibImage = anImage;
296   return true;
297 }
298 #else
299 bool Image_AlienPixMap::Load (const TCollection_AsciiString&)
300 {
301   Clear();
302   return false;
303 }
304 #endif
305
306 // =======================================================================
307 // function : savePPM
308 // purpose  :
309 // =======================================================================
310 bool Image_AlienPixMap::savePPM (const TCollection_AsciiString& theFileName) const
311 {
312   if (IsEmpty())
313   {
314     return false;
315   }
316
317   // Open file
318   FILE* aFile = fopen (theFileName.ToCString(), "wb");
319   if (aFile == NULL)
320   {
321     return false;
322   }
323
324   // Write header
325   fprintf (aFile, "P6\n%d %d\n255\n", (int )SizeX(), (int )SizeY());
326   fprintf (aFile, "# Image stored by OpenCASCADE framework in linear RGB colorspace\n");
327
328   // Write pixel data
329   Quantity_Color aColor;
330   Quantity_Parameter aDummy;
331   Standard_Byte aByte;
332   for (Standard_Size aRow = 0; aRow < SizeY(); ++aRow)
333   {
334     for (Standard_Size aCol = 0; aCol < SizeX(); ++aCol)
335     {
336       // extremely SLOW but universal (implemented for all supported pixel formats)
337       aColor = PixelColor ((Standard_Integer )aCol, (Standard_Integer )aRow, aDummy);
338       aByte = Standard_Byte(aColor.Red() * 255.0);   fwrite (&aByte, 1, 1, aFile);
339       aByte = Standard_Byte(aColor.Green() * 255.0); fwrite (&aByte, 1, 1, aFile);
340       aByte = Standard_Byte(aColor.Blue() * 255.0);  fwrite (&aByte, 1, 1, aFile);
341     }
342   }
343
344   // Close file
345   fclose (aFile);
346   return true;
347 }
348
349 // =======================================================================
350 // function : Save
351 // purpose  :
352 // =======================================================================
353 bool Image_AlienPixMap::Save (const TCollection_AsciiString& theFileName)
354 {
355 #ifdef HAVE_FREEIMAGE
356   if (myLibImage == NULL)
357   {
358     return false;
359   }
360
361   FREE_IMAGE_FORMAT anImageFormat = FreeImage_GetFIFFromFilename (theFileName.ToCString());
362   if (anImageFormat == FIF_UNKNOWN)
363   {
364     std::cerr << "Image_PixMap, image format doesn't supported!\n";
365     return false;
366   }
367
368   if (IsTopDown())
369   {
370     FreeImage_FlipVertical (myLibImage);
371     SetTopDown (false);
372   }
373
374   // FreeImage doesn't provide flexible format convertion API
375   // so we should perform multiple convertions in some cases!
376   FIBITMAP* anImageToDump = myLibImage;
377   switch (anImageFormat)
378   {
379     case FIF_PNG:
380     case FIF_BMP:
381     {
382       if (Format() == Image_PixMap::ImgBGR32
383        || Format() == Image_PixMap::ImgRGB32)
384       {
385         // stupid FreeImage treats reserved byte as alpha if some bytes not set to 0xFF
386         for (Standard_Size aRow = 0; aRow < SizeY(); ++aRow)
387         {
388           for (Standard_Size aCol = 0; aCol < SizeX(); ++aCol)
389           {
390             myData.ChangeValue (aRow, aCol)[3] = 0xFF;
391           }
392         }
393       }
394       else if (FreeImage_GetImageType (myLibImage) != FIT_BITMAP)
395       {
396         anImageToDump = FreeImage_ConvertToType (myLibImage, FIT_BITMAP);
397       }
398       break;
399     }
400     case FIF_GIF:
401     {
402       FIBITMAP* aTmpBitmap = myLibImage;
403       if (FreeImage_GetImageType (myLibImage) != FIT_BITMAP)
404       {
405         aTmpBitmap = FreeImage_ConvertToType (myLibImage, FIT_BITMAP);
406         if (aTmpBitmap == NULL)
407         {
408           return false;
409         }
410       }
411
412       if (FreeImage_GetBPP (aTmpBitmap) != 24)
413       {
414         FIBITMAP* aTmpBitmap24 = FreeImage_ConvertTo24Bits (aTmpBitmap);
415         if (aTmpBitmap != myLibImage)
416         {
417           FreeImage_Unload (aTmpBitmap);
418         }
419         if (aTmpBitmap24 == NULL)
420         {
421           return false;
422         }
423         aTmpBitmap = aTmpBitmap24;
424       }
425
426       // need convertion to image with pallete (requires 24bit bitmap)
427       anImageToDump = FreeImage_ColorQuantize (aTmpBitmap, FIQ_NNQUANT);
428       if (aTmpBitmap != myLibImage)
429       {
430         FreeImage_Unload (aTmpBitmap);
431       }
432       break;
433     }
434     case FIF_EXR:
435     {
436       if (Format() == Image_PixMap::ImgGray)
437       {
438         anImageToDump = FreeImage_ConvertToType (myLibImage, FIT_FLOAT);
439       }
440       else if (Format() == Image_PixMap::ImgRGBA
441             || Format() == Image_PixMap::ImgBGRA)
442       {
443         anImageToDump = FreeImage_ConvertToType (myLibImage, FIT_RGBAF);
444       }
445       else
446       {
447         FREE_IMAGE_TYPE aImgTypeFI = FreeImage_GetImageType (myLibImage);
448         if (aImgTypeFI != FIT_RGBF
449          && aImgTypeFI != FIT_RGBAF
450          && aImgTypeFI != FIT_FLOAT)
451         {
452           anImageToDump = FreeImage_ConvertToType (myLibImage, FIT_RGBF);
453         }
454       }
455       break;
456     }
457     default:
458     {
459       if (FreeImage_GetImageType (myLibImage) != FIT_BITMAP)
460       {
461         anImageToDump = FreeImage_ConvertToType (myLibImage, FIT_BITMAP);
462         if (anImageToDump == NULL)
463         {
464           return false;
465         }
466       }
467
468       if (FreeImage_GetBPP (anImageToDump) != 24)
469       {
470         FIBITMAP* aTmpBitmap24 = FreeImage_ConvertTo24Bits (anImageToDump);
471         if (anImageToDump != myLibImage)
472         {
473           FreeImage_Unload (anImageToDump);
474         }
475         if (aTmpBitmap24 == NULL)
476         {
477           return false;
478         }
479         anImageToDump = aTmpBitmap24;
480       }
481       break;
482     }
483   }
484
485   if (anImageToDump == NULL)
486   {
487     return false;
488   }
489
490   bool isSaved = (FreeImage_Save (anImageFormat, anImageToDump, theFileName.ToCString()) != FALSE);
491   if (anImageToDump != myLibImage)
492   {
493     FreeImage_Unload (anImageToDump);
494   }
495   return isSaved;
496 #else
497   const Standard_Integer aLen = theFileName.Length();
498   if ((aLen >= 4) && (theFileName.Value (aLen - 3) == '.')
499       && strcasecmp( theFileName.ToCString() + aLen - 3, "ppm") == 0 )
500   {
501     return savePPM (theFileName);
502   }
503   std::cerr << "Image_PixMap, no image library available! Image saved in PPM format.\n";
504   return savePPM (theFileName);
505 #endif
506 }
507
508 // =======================================================================
509 // function : AdjustGamma
510 // purpose  :
511 // =======================================================================
512 #ifdef HAVE_FREEIMAGE
513 Standard_EXPORT bool Image_AlienPixMap::AdjustGamma (const Standard_Real theGammaCorr)
514 {
515   return FreeImage_AdjustGamma (myLibImage, theGammaCorr) != FALSE;
516 }
517 #else
518 Standard_EXPORT bool Image_AlienPixMap::AdjustGamma (const Standard_Real)
519 {
520     return false;
521 }
522 #endif