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