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