0024534: Improve design of Image_PixMap class
[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 IMPLEMENT_STANDARD_HANDLE (Image_Diff, Standard_Transient)
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     std::cerr << "Images has different format or dimensions\n";
170     return Standard_False;
171   }
172   else if (!isSupportedFormat (theImageRef->Format()))
173   {
174     std::cerr << "Images has unsupported pixel format\n";
175     return Standard_False;
176   }
177   else if (theImageRef->SizeX() >= 0xFFFF
178         || theImageRef->SizeY() >= 0xFFFF)
179   {
180     std::cerr << "Image too large\n";
181     return Standard_False;
182   }
183
184   myImageRef = theImageRef;
185   myImageNew = theImageNew;
186
187   if (theToBlackWhite)
188   {
189     // Convert the images to white/black
190     const Image_ColorXXX24 aWhite = {{255, 255, 255}};
191     for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
192     {
193       for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
194       {
195         Image_ColorXXX24& aPixel1 = myImageRef->ChangeValue<Image_ColorXXX24> (aRow, aCol);
196         if (!isBlack (aPixel1))
197         {
198           aPixel1 = aWhite;
199         }
200         Image_ColorXXX24& aPixel2 = myImageNew->ChangeValue<Image_ColorXXX24> (aRow, aCol);
201         if (!isBlack (aPixel2))
202         {
203           aPixel2 = aWhite;
204         }
205       }
206     }
207   }
208
209   return Standard_True;
210 }
211
212
213 // =======================================================================
214 // function : Init
215 // purpose  :
216 // =======================================================================
217 Standard_Boolean Image_Diff::Init (const TCollection_AsciiString& theImgPathRef,
218                                    const TCollection_AsciiString& theImgPathNew,
219                                    const Standard_Boolean         theToBlackWhite)
220 {
221   Handle(Image_AlienPixMap) anImgRef = new Image_AlienPixMap();
222   Handle(Image_AlienPixMap) anImgNew = new Image_AlienPixMap();
223   if (!anImgRef->Load (theImgPathRef)
224    || !anImgNew->Load (theImgPathNew))
225   {
226     std::cerr << "Failed to load image(s) file(s)\n";
227     return Standard_False;
228   }
229   return Init (anImgRef, anImgNew, theToBlackWhite);
230 }
231
232 // =======================================================================
233 // function : SetColorTolerance
234 // purpose  :
235 // =======================================================================
236 void Image_Diff::SetColorTolerance (const Standard_Real theTolerance)
237 {
238   myColorTolerance = theTolerance;
239 }
240
241 // =======================================================================
242 // function : ColorTolerance
243 // purpose  :
244 // =======================================================================
245 Standard_Real Image_Diff::ColorTolerance() const
246 {
247   return myColorTolerance;
248 }
249
250 // =======================================================================
251 // function : SetBorderFilterOn
252 // purpose  :
253 // =======================================================================
254 void Image_Diff::SetBorderFilterOn (const Standard_Boolean theToIgnore)
255 {
256   myIsBorderFilterOn = theToIgnore;
257 }
258
259 // =======================================================================
260 // function : IsBorderFilterOn
261 // purpose  :
262 // =======================================================================
263 Standard_Boolean Image_Diff::IsBorderFilterOn() const
264 {
265   return myIsBorderFilterOn;
266 }
267
268 // =======================================================================
269 // function : Compare
270 // purpose  :
271 // =======================================================================
272 Standard_Integer Image_Diff::Compare()
273 {
274   // Number of different pixels (by color)
275   Standard_Integer aNbDiffColors = 0;
276   myDiffPixels.Clear();
277
278   if (myImageRef.IsNull() || myImageNew.IsNull())
279   {
280     return -1;
281   }
282
283   // Tolerance of comparison operation for color
284   // Maximum difference between colors (white - black) = 100%
285   Image_ColorXXX24 aDiff = {{255, 255, 255}};
286   const Standard_Integer aMaxDiffColor  = dotSquared (aDiff);
287   const Standard_Integer aDiffThreshold = Standard_Integer(Standard_Real(aMaxDiffColor) * myColorTolerance);
288
289   // we don't care about RGB/BGR/RGBA/BGRA/RGB32/BGR32 differences
290   // because we just compute summ of r g b components
291
292   // compare colors of each pixel
293   for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
294   {
295     for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
296     {
297       aDiff = myImageNew->Value<Image_ColorXXX24> (aRow, aCol) - myImageRef->Value<Image_ColorXXX24> (aRow, aCol);
298       if (dotSquared (aDiff) > aDiffThreshold)
299       {
300         const Standard_Size aValue = pixel2Int (aRow, aCol);
301         myDiffPixels.Append (aValue);
302         ++aNbDiffColors;
303       }
304     }
305   }
306
307   // take into account a border effect
308   if (myIsBorderFilterOn && myDiffPixels.Length() > 0)
309   {
310     aNbDiffColors = ignoreBorderEffect();
311   }
312
313   return aNbDiffColors;
314 }
315
316 // =======================================================================
317 // function : SaveDiffImage
318 // purpose  :
319 // =======================================================================
320 Standard_Boolean Image_Diff::SaveDiffImage (Image_PixMap& theDiffImage) const
321 {
322   if (myImageRef.IsNull() || myImageNew.IsNull())
323   {
324     return Standard_False;
325   }
326
327   if (theDiffImage.IsEmpty()
328    || theDiffImage.SizeX() != myImageRef->SizeX()
329    || theDiffImage.SizeY() != myImageRef->SizeY()
330    || !isSupportedFormat (theDiffImage.Format()))
331   {
332     if (!theDiffImage.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY()))
333     {
334       return Standard_False;
335     }
336   }
337
338   Standard_Size aRow, aCol;
339   const Image_ColorXXX24 aWhite = {{255, 255, 255}};
340
341   // initialize black image for dump
342   memset (theDiffImage.ChangeData(), 0, theDiffImage.SizeBytes());
343   if (myGroupsOfDiffPixels.IsEmpty())
344   {
345     if (myIsBorderFilterOn)
346     {
347       return Standard_True;
348     }
349
350     for (Standard_Integer aPixelId = 0; aPixelId < myDiffPixels.Length(); ++aPixelId)
351     {
352       const Standard_Size aValue = myDiffPixels.Value (aPixelId);
353       int2Pixel (aValue, aRow, aCol);
354       theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
355     }
356
357     return Standard_True;
358   }
359
360   Standard_Integer aGroupId = 1;
361   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
362   {
363     if (myLinearGroups.Contains (aGroupId))
364     {
365       continue; // skip linear groups
366     }
367
368     const TColStd_MapOfInteger* aGroup = aGrIter.Value();
369     for (TColStd_MapIteratorOfMapOfInteger aPixelIter(*aGroup);
370          aPixelIter.More(); aPixelIter.Next())
371     {
372       int2Pixel (aPixelIter.Key(), aRow, aCol);
373       theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
374     }
375   }
376
377   return Standard_True;
378 }
379
380 // =======================================================================
381 // function : SaveDiffImage
382 // purpose  :
383 // =======================================================================
384 Standard_Boolean Image_Diff::SaveDiffImage (const TCollection_AsciiString& theDiffPath) const
385 {
386   if (myImageRef.IsNull() || myImageNew.IsNull() || theDiffPath.IsEmpty())
387   {
388     return Standard_False;
389   }
390
391   Image_AlienPixMap aDiff;
392   if (!aDiff.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY())
393    || !SaveDiffImage (aDiff))
394   {
395     return Standard_False;
396   }
397
398   // save image
399   return aDiff.Save (theDiffPath);
400 }
401
402 // =======================================================================
403 // function : ignoreBorderEffect
404 // purpose  :
405 // =======================================================================
406 Standard_Integer Image_Diff::ignoreBorderEffect()
407 {
408   if (myImageRef.IsNull() || myImageNew.IsNull())
409   {
410     return 0;
411   }
412
413   // allocate groups of different pixels
414   releaseGroupsOfDiffPixels();
415
416   // Find a different area (a set of close to each other pixels which colors differ in both images).
417   // It filters alone pixels with different color.
418   Standard_Size aRow1 = 0, aCol1 = 0, aRow2, aCol2;
419   Standard_Integer aLen1 = (myDiffPixels.Length() > 0) ? (myDiffPixels.Length() - 1) : 0;
420   for (Standard_Integer aPixelId1 = 0; aPixelId1 < aLen1; ++aPixelId1)
421   {
422     const Standard_Size aValue1 = myDiffPixels.Value (aPixelId1);
423     int2Pixel (aValue1, aRow1, aCol1);
424
425     // Check other pixels in the list looking for a neighbour of this one
426     for (Standard_Integer aPixelId2 = aPixelId1 + 1; aPixelId2 < myDiffPixels.Length(); ++aPixelId2)
427     {
428       const Standard_Size aValue2 = myDiffPixels.Value (aPixelId2);
429       int2Pixel (aValue2, aRow2, aCol2);
430       if (getAbs (ptrdiff_t (aCol1 - aCol2)) <= 1 &&
431           getAbs (ptrdiff_t (aRow1 - aRow2)) <= 1)
432       {
433         // A neighbour is found. Create a new group and add both pixels.
434         if (myGroupsOfDiffPixels.IsEmpty())
435         {
436           TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
437           aGroup->Add ((Standard_Integer)aValue1);
438           aGroup->Add ((Standard_Integer)aValue2);
439           myGroupsOfDiffPixels.Append (aGroup);
440         }
441         else
442         {
443           // Find a group the pixels belong to.
444           Standard_Boolean isFound = Standard_False;
445           for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
446           {
447             TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
448             if (aGroup->Contains ((Standard_Integer)aValue1))
449             {
450               aGroup->Add ((Standard_Integer)aValue2);
451               isFound = Standard_True;
452               break;
453             }
454           }
455
456           if (!isFound)
457           {
458             // Create a new group
459             TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
460             aGroup->Add ((Standard_Integer)aValue1);
461             aGroup->Add ((Standard_Integer)aValue2);
462             myGroupsOfDiffPixels.Append (aGroup);
463           }
464         }
465       }
466     }
467   }
468
469   // filter linear groups which represent border of a solid shape
470   Standard_Integer aGroupId = 1;
471   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
472   {
473     Standard_Integer aNeighboursNb = 0;
474     Standard_Boolean isLine = Standard_True;
475     const TColStd_MapOfInteger* aGroup = aGrIter.Value();
476     for (TColStd_MapIteratorOfMapOfInteger aPixelIter (*aGroup); aPixelIter.More(); aPixelIter.Next())
477     {
478       int2Pixel (aPixelIter.Key(), aRow1, aCol1);
479       aNeighboursNb = 0;
480
481       // pixels of a line have only 1 or 2 neighbour pixels inside the same group
482       // check all neighbour pixels on presence in the group
483       for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
484       {
485         if (NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
486          && aGroup->Contains ((Standard_Integer)NEIGHBOR_PIXELS[aNgbrIter].pixel2Int (aRow1, aCol1)))
487         {
488           ++aNeighboursNb;
489         }
490       }
491
492       if (aNeighboursNb > 2)
493       {
494         isLine = Standard_False;
495         break;
496       }
497     } // for pixels inside group...
498
499     if (isLine)
500     {
501       // Test a pixel of the linear group on belonging to a solid shape.
502       // Consider neighbour pixels of the last pixel of the linear group in the 1st image.
503       // If the pixel has greater than 1 not black neighbour pixel, it is a border of a shape.
504       // Otherwise, it may be a topological edge, for example.
505       aNeighboursNb = 0;
506       for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
507       {
508         if ( NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
509          && !NEIGHBOR_PIXELS[aNgbrIter].isBlack (*myImageRef, aRow1, aCol1))
510         {
511           ++aNeighboursNb;
512         }
513       }
514
515       if (aNeighboursNb > 1)
516       {
517         myLinearGroups.Add (aGroupId);
518       }
519     }
520   } // for groups...
521
522   // number of different groups of pixels (except linear groups)
523   Standard_Integer aNbDiffColors = 0;
524   aGroupId = 1;
525   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
526   {
527     if (!myLinearGroups.Contains (aGroupId))
528       ++aNbDiffColors;
529   }
530
531   return aNbDiffColors;
532 }
533
534 // =======================================================================
535 // function : releaseGroupsOfDiffPixels
536 // purpose  :
537 // =======================================================================
538 void Image_Diff::releaseGroupsOfDiffPixels()
539 {
540   for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
541   {
542     TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
543     delete aGroup;
544   }
545   myGroupsOfDiffPixels.Clear();
546   myLinearGroups.Clear();
547 }