0027105: Make code ISO-compliant [-Wpedantic fixes]
[occt.git] / src / Image / Image_Diff.cxx
1 // Created on: 2012-07-10
2 // Created by: VRO
3 // Copyright (c) 2012-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 #include <Image_Diff.hxx>
17 #include <Image_AlienPixMap.hxx>
18
19 #include <TColStd_MapIteratorOfMapOfInteger.hxx>
20
21 #include <cstdlib>
22
23
24 IMPLEMENT_STANDARD_RTTIEXT(Image_Diff,Standard_Transient)
25
26 //! POD structure for packed RGB color value (3 bytes)
27 struct Image_ColorXXX24
28 {
29   Standard_Byte v[3];
30   typedef Standard_Byte ComponentType_t;         //!< Component type
31   static Standard_Integer Length() { return 3; } //!< Returns the number of components
32 };
33
34 inline Image_ColorXXX24 operator- (const Image_ColorXXX24& theA,
35                                    const Image_ColorXXX24& theB)
36 {
37   return Image_ColorSub3 (theA, theB);
38 }
39
40 //! Dot squared for difference of two colors
41 inline Standard_Integer dotSquared (const Image_ColorXXX24& theColor)
42 {
43   // explicitly convert to integer
44   const Standard_Integer r = theColor.v[0];
45   const Standard_Integer g = theColor.v[1];
46   const Standard_Integer b = theColor.v[2];
47   return r * r + g * g + b * b;
48 }
49
50 //! @return true if pixel is black
51 inline bool isBlack (const Image_ColorXXX24& theColor)
52 {
53   return theColor.v[0] == 0
54       && theColor.v[1] == 0
55       && theColor.v[2] == 0;
56 }
57
58 //! Converts a pixel position (row, column) to one integer value
59 inline Standard_Size pixel2Int (const Standard_Size aRow,
60                                 const Standard_Size aCol)
61 {
62   return aCol + (aRow << 15);
63 }
64
65 //! Converts an integer value to pixel coordinates (row, column)
66 inline void int2Pixel (const Standard_Size theValue,
67                        Standard_Size&      theRow,
68                        Standard_Size&      theCol)
69 {
70   theRow = (theValue >> 15);
71   theCol = theValue - (theRow << 15);
72 }
73
74 namespace
75 {
76
77   inline ptrdiff_t getAbs (const ptrdiff_t theValue)
78   {
79     return theValue >= 0 ? theValue : -theValue;
80   }
81
82   static const Standard_Size NEIGHBOR_PIXELS_NB = 8;
83   static struct
84   {
85     Standard_Integer row_inc;
86     Standard_Integer col_inc;
87
88     inline Standard_Size pixel2Int (const Standard_Size theRowCenter,
89                                     const Standard_Size theColCenter) const
90     {
91       return ::pixel2Int (theRowCenter + Standard_Size(row_inc),
92                           theColCenter + Standard_Size(col_inc));
93     }
94
95     inline bool isBlack (const Image_PixMap& theData,
96                          const Standard_Size theRowCenter,
97                          const Standard_Size theColCenter) const
98     {
99       return ::isBlack (theData.Value<Image_ColorXXX24> (theRowCenter + Standard_Size(row_inc),
100                                                          theColCenter + Standard_Size(col_inc)));
101     }
102
103     inline bool isValid (const Image_PixMap& theData,
104                          const Standard_Size theRowCenter,
105                          const Standard_Size theColCenter) const
106     {
107       const Standard_Size aRow = theRowCenter + Standard_Size(row_inc);
108       const Standard_Size aCol = theColCenter + Standard_Size(col_inc);
109       return aRow < theData.SizeX()  // this unsigned math checks Standard_Size(-1) at-once
110           && aCol < theData.SizeY();
111     }
112   }
113   const NEIGHBOR_PIXELS[NEIGHBOR_PIXELS_NB] =
114   {
115     {-1, -1}, {-1,  0}, {-1,  1},
116     { 0, -1},           { 0,  1},
117     { 1, -1}, { 1,  0}, { 1,  1}
118   };
119
120   static bool isSupportedFormat (const Image_PixMap::ImgFormat theFormat)
121   {
122     return theFormat == Image_PixMap::ImgRGB
123         || theFormat == Image_PixMap::ImgBGR
124         || theFormat == Image_PixMap::ImgRGB32
125         || theFormat == Image_PixMap::ImgBGR32
126         || theFormat == Image_PixMap::ImgRGBA
127         || theFormat == Image_PixMap::ImgBGRA;
128   }
129 }
130
131 // =======================================================================
132 // function : Image_Diff
133 // purpose  :
134 // =======================================================================
135 Image_Diff::Image_Diff()
136 : myColorTolerance (0.0),
137   myIsBorderFilterOn (Standard_False)
138 {
139   //
140 }
141
142 // =======================================================================
143 // function : ~Image_Diff
144 // purpose  :
145 // =======================================================================
146 Image_Diff::~Image_Diff()
147 {
148   releaseGroupsOfDiffPixels();
149 }
150
151 // =======================================================================
152 // function : Init
153 // purpose  :
154 // =======================================================================
155 Standard_Boolean Image_Diff::Init (const Handle(Image_PixMap)& theImageRef,
156                                    const Handle(Image_PixMap)& theImageNew,
157                                    const Standard_Boolean      theToBlackWhite)
158 {
159   myImageRef.Nullify();
160   myImageNew.Nullify();
161   myDiffPixels.Clear();
162   releaseGroupsOfDiffPixels();
163   if (theImageRef.IsNull()   || theImageNew.IsNull()
164    || theImageRef->IsEmpty() || theImageNew->IsEmpty()
165    || theImageRef->SizeX()   != theImageNew->SizeX()
166    || theImageRef->SizeY()   != theImageNew->SizeY()
167    || theImageRef->Format()  != theImageNew->Format())
168   {
169 #ifdef OCCT_DEBUG
170     std::cerr << "Images has different format or dimensions\n";
171 #endif
172     return Standard_False;
173   }
174   else if (!isSupportedFormat (theImageRef->Format()))
175   {
176 #ifdef OCCT_DEBUG
177     std::cerr << "Images has unsupported pixel format\n";
178 #endif
179     return Standard_False;
180   }
181   else if (theImageRef->SizeX() >= 0xFFFF
182         || theImageRef->SizeY() >= 0xFFFF)
183   {
184 #ifdef OCCT_DEBUG
185     std::cerr << "Image too large\n";
186 #endif
187     return Standard_False;
188   }
189
190   myImageRef = theImageRef;
191   myImageNew = theImageNew;
192
193   if (theToBlackWhite)
194   {
195     // Convert the images to white/black
196     const Image_ColorXXX24 aWhite = {{255, 255, 255}};
197     for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
198     {
199       for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
200       {
201         Image_ColorXXX24& aPixel1 = myImageRef->ChangeValue<Image_ColorXXX24> (aRow, aCol);
202         if (!isBlack (aPixel1))
203         {
204           aPixel1 = aWhite;
205         }
206         Image_ColorXXX24& aPixel2 = myImageNew->ChangeValue<Image_ColorXXX24> (aRow, aCol);
207         if (!isBlack (aPixel2))
208         {
209           aPixel2 = aWhite;
210         }
211       }
212     }
213   }
214
215   return Standard_True;
216 }
217
218
219 // =======================================================================
220 // function : Init
221 // purpose  :
222 // =======================================================================
223 Standard_Boolean Image_Diff::Init (const TCollection_AsciiString& theImgPathRef,
224                                    const TCollection_AsciiString& theImgPathNew,
225                                    const Standard_Boolean         theToBlackWhite)
226 {
227   Handle(Image_AlienPixMap) anImgRef = new Image_AlienPixMap();
228   Handle(Image_AlienPixMap) anImgNew = new Image_AlienPixMap();
229   if (!anImgRef->Load (theImgPathRef)
230    || !anImgNew->Load (theImgPathNew))
231   {
232 #ifdef OCCT_DEBUG
233     std::cerr << "Failed to load image(s) file(s)\n";
234 #endif
235     return Standard_False;
236   }
237   return Init (anImgRef, anImgNew, theToBlackWhite);
238 }
239
240 // =======================================================================
241 // function : SetColorTolerance
242 // purpose  :
243 // =======================================================================
244 void Image_Diff::SetColorTolerance (const Standard_Real theTolerance)
245 {
246   myColorTolerance = theTolerance;
247 }
248
249 // =======================================================================
250 // function : ColorTolerance
251 // purpose  :
252 // =======================================================================
253 Standard_Real Image_Diff::ColorTolerance() const
254 {
255   return myColorTolerance;
256 }
257
258 // =======================================================================
259 // function : SetBorderFilterOn
260 // purpose  :
261 // =======================================================================
262 void Image_Diff::SetBorderFilterOn (const Standard_Boolean theToIgnore)
263 {
264   myIsBorderFilterOn = theToIgnore;
265 }
266
267 // =======================================================================
268 // function : IsBorderFilterOn
269 // purpose  :
270 // =======================================================================
271 Standard_Boolean Image_Diff::IsBorderFilterOn() const
272 {
273   return myIsBorderFilterOn;
274 }
275
276 // =======================================================================
277 // function : Compare
278 // purpose  :
279 // =======================================================================
280 Standard_Integer Image_Diff::Compare()
281 {
282   // Number of different pixels (by color)
283   Standard_Integer aNbDiffColors = 0;
284   myDiffPixels.Clear();
285
286   if (myImageRef.IsNull() || myImageNew.IsNull())
287   {
288     return -1;
289   }
290
291   // first check if images are exactly teh same
292   if (! memcmp (myImageNew->Data(), myImageRef->Data(), myImageRef->SizeBytes()))
293   {
294     return 0;
295   }
296
297   // Tolerance of comparison operation for color
298   // Maximum difference between colors (white - black) = 100%
299   Image_ColorXXX24 aDiff = {{255, 255, 255}};
300   const Standard_Integer aMaxDiffColor  = dotSquared (aDiff);
301   const Standard_Integer aDiffThreshold = Standard_Integer(Standard_Real(aMaxDiffColor) * myColorTolerance);
302
303   // we don't care about RGB/BGR/RGBA/BGRA/RGB32/BGR32 differences
304   // because we just compute summ of r g b components
305
306   // compare colors of each pixel
307   for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
308   {
309     for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
310     {
311       aDiff = myImageNew->Value<Image_ColorXXX24> (aRow, aCol) - myImageRef->Value<Image_ColorXXX24> (aRow, aCol);
312       if (dotSquared (aDiff) > aDiffThreshold)
313       {
314         const Standard_Size aValue = pixel2Int (aRow, aCol);
315         myDiffPixels.Append (aValue);
316         ++aNbDiffColors;
317       }
318     }
319   }
320
321   // take into account a border effect
322   if (myIsBorderFilterOn && myDiffPixels.Length() > 0)
323   {
324     aNbDiffColors = ignoreBorderEffect();
325   }
326
327   return aNbDiffColors;
328 }
329
330 // =======================================================================
331 // function : SaveDiffImage
332 // purpose  :
333 // =======================================================================
334 Standard_Boolean Image_Diff::SaveDiffImage (Image_PixMap& theDiffImage) const
335 {
336   if (myImageRef.IsNull() || myImageNew.IsNull())
337   {
338     return Standard_False;
339   }
340
341   if (theDiffImage.IsEmpty()
342    || theDiffImage.SizeX() != myImageRef->SizeX()
343    || theDiffImage.SizeY() != myImageRef->SizeY()
344    || !isSupportedFormat (theDiffImage.Format()))
345   {
346     if (!theDiffImage.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY()))
347     {
348       return Standard_False;
349     }
350   }
351
352   Standard_Size aRow, aCol;
353   const Image_ColorXXX24 aWhite = {{255, 255, 255}};
354
355   // initialize black image for dump
356   memset (theDiffImage.ChangeData(), 0, theDiffImage.SizeBytes());
357   if (myGroupsOfDiffPixels.IsEmpty())
358   {
359     if (myIsBorderFilterOn)
360     {
361       return Standard_True;
362     }
363
364     for (Standard_Integer aPixelId = 0; aPixelId < myDiffPixels.Length(); ++aPixelId)
365     {
366       const Standard_Size aValue = myDiffPixels.Value (aPixelId);
367       int2Pixel (aValue, aRow, aCol);
368       theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
369     }
370
371     return Standard_True;
372   }
373
374   Standard_Integer aGroupId = 1;
375   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
376   {
377     if (myLinearGroups.Contains (aGroupId))
378     {
379       continue; // skip linear groups
380     }
381
382     const TColStd_MapOfInteger* aGroup = aGrIter.Value();
383     for (TColStd_MapIteratorOfMapOfInteger aPixelIter(*aGroup);
384          aPixelIter.More(); aPixelIter.Next())
385     {
386       int2Pixel (aPixelIter.Key(), aRow, aCol);
387       theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
388     }
389   }
390
391   return Standard_True;
392 }
393
394 // =======================================================================
395 // function : SaveDiffImage
396 // purpose  :
397 // =======================================================================
398 Standard_Boolean Image_Diff::SaveDiffImage (const TCollection_AsciiString& theDiffPath) const
399 {
400   if (myImageRef.IsNull() || myImageNew.IsNull() || theDiffPath.IsEmpty())
401   {
402     return Standard_False;
403   }
404
405   Image_AlienPixMap aDiff;
406   if (!aDiff.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY())
407    || !SaveDiffImage (aDiff))
408   {
409     return Standard_False;
410   }
411
412   // save image
413   return aDiff.Save (theDiffPath);
414 }
415
416 // =======================================================================
417 // function : ignoreBorderEffect
418 // purpose  :
419 // =======================================================================
420 Standard_Integer Image_Diff::ignoreBorderEffect()
421 {
422   if (myImageRef.IsNull() || myImageNew.IsNull())
423   {
424     return 0;
425   }
426
427   // allocate groups of different pixels
428   releaseGroupsOfDiffPixels();
429
430   // Find a different area (a set of close to each other pixels which colors differ in both images).
431   // It filters alone pixels with different color.
432   Standard_Size aRow1 = 0, aCol1 = 0, aRow2, aCol2;
433   Standard_Integer aLen1 = (myDiffPixels.Length() > 0) ? (myDiffPixels.Length() - 1) : 0;
434   for (Standard_Integer aPixelId1 = 0; aPixelId1 < aLen1; ++aPixelId1)
435   {
436     const Standard_Size aValue1 = myDiffPixels.Value (aPixelId1);
437     int2Pixel (aValue1, aRow1, aCol1);
438
439     // Check other pixels in the list looking for a neighbour of this one
440     for (Standard_Integer aPixelId2 = aPixelId1 + 1; aPixelId2 < myDiffPixels.Length(); ++aPixelId2)
441     {
442       const Standard_Size aValue2 = myDiffPixels.Value (aPixelId2);
443       int2Pixel (aValue2, aRow2, aCol2);
444       if (getAbs (ptrdiff_t (aCol1 - aCol2)) <= 1 &&
445           getAbs (ptrdiff_t (aRow1 - aRow2)) <= 1)
446       {
447         // A neighbour is found. Create a new group and add both pixels.
448         if (myGroupsOfDiffPixels.IsEmpty())
449         {
450           TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
451           aGroup->Add ((Standard_Integer)aValue1);
452           aGroup->Add ((Standard_Integer)aValue2);
453           myGroupsOfDiffPixels.Append (aGroup);
454         }
455         else
456         {
457           // Find a group the pixels belong to.
458           Standard_Boolean isFound = Standard_False;
459           for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
460           {
461             TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
462             if (aGroup->Contains ((Standard_Integer)aValue1))
463             {
464               aGroup->Add ((Standard_Integer)aValue2);
465               isFound = Standard_True;
466               break;
467             }
468           }
469
470           if (!isFound)
471           {
472             // Create a new group
473             TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
474             aGroup->Add ((Standard_Integer)aValue1);
475             aGroup->Add ((Standard_Integer)aValue2);
476             myGroupsOfDiffPixels.Append (aGroup);
477           }
478         }
479       }
480     }
481   }
482
483   // filter linear groups which represent border of a solid shape
484   Standard_Integer aGroupId = 1;
485   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
486   {
487     Standard_Integer aNeighboursNb = 0;
488     Standard_Boolean isLine = Standard_True;
489     const TColStd_MapOfInteger* aGroup = aGrIter.Value();
490     for (TColStd_MapIteratorOfMapOfInteger aPixelIter (*aGroup); aPixelIter.More(); aPixelIter.Next())
491     {
492       int2Pixel (aPixelIter.Key(), aRow1, aCol1);
493       aNeighboursNb = 0;
494
495       // pixels of a line have only 1 or 2 neighbour pixels inside the same group
496       // check all neighbour pixels on presence in the group
497       for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
498       {
499         if (NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
500          && aGroup->Contains ((Standard_Integer)NEIGHBOR_PIXELS[aNgbrIter].pixel2Int (aRow1, aCol1)))
501         {
502           ++aNeighboursNb;
503         }
504       }
505
506       if (aNeighboursNb > 2)
507       {
508         isLine = Standard_False;
509         break;
510       }
511     } // for pixels inside group...
512
513     if (isLine)
514     {
515       // Test a pixel of the linear group on belonging to a solid shape.
516       // Consider neighbour pixels of the last pixel of the linear group in the 1st image.
517       // If the pixel has greater than 1 not black neighbour pixel, it is a border of a shape.
518       // Otherwise, it may be a topological edge, for example.
519       aNeighboursNb = 0;
520       for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
521       {
522         if ( NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
523          && !NEIGHBOR_PIXELS[aNgbrIter].isBlack (*myImageRef, aRow1, aCol1))
524         {
525           ++aNeighboursNb;
526         }
527       }
528
529       if (aNeighboursNb > 1)
530       {
531         myLinearGroups.Add (aGroupId);
532       }
533     }
534   } // for groups...
535
536   // number of different groups of pixels (except linear groups)
537   Standard_Integer aNbDiffColors = 0;
538   aGroupId = 1;
539   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
540   {
541     if (!myLinearGroups.Contains (aGroupId))
542       ++aNbDiffColors;
543   }
544
545   return aNbDiffColors;
546 }
547
548 // =======================================================================
549 // function : releaseGroupsOfDiffPixels
550 // purpose  :
551 // =======================================================================
552 void Image_Diff::releaseGroupsOfDiffPixels()
553 {
554   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
555   {
556     TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
557     delete aGroup;
558   }
559   myGroupsOfDiffPixels.Clear();
560   myLinearGroups.Clear();
561 }