1 // Created on: 2012-07-10
3 // Copyright (c) 2012-2014 OPEN CASCADE SAS
5 // This file is part of Open CASCADE Technology software library.
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.
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
16 #include <Image_Diff.hxx>
17 #include <Image_AlienPixMap.hxx>
19 #include <TColStd_MapIteratorOfMapOfInteger.hxx>
24 IMPLEMENT_STANDARD_RTTIEXT(Image_Diff,Standard_Transient)
26 //! POD structure for packed RGB color value (3 bytes)
27 struct Image_ColorXXX24
30 typedef Standard_Byte ComponentType_t; //!< Component type
31 static Standard_Integer Length() { return 3; } //!< Returns the number of components
34 inline Image_ColorXXX24 operator- (const Image_ColorXXX24& theA,
35 const Image_ColorXXX24& theB)
37 return Image_ColorSub3 (theA, theB);
40 //! Dot squared for difference of two colors
41 inline Standard_Integer dotSquared (const Image_ColorXXX24& theColor)
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;
50 //! @return true if pixel is black
51 inline bool isBlack (const Image_ColorXXX24& theColor)
53 return theColor.v[0] == 0
55 && theColor.v[2] == 0;
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)
62 return aCol + (aRow << 15);
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)
70 theRow = (theValue >> 15);
71 theCol = theValue - (theRow << 15);
77 inline ptrdiff_t getAbs (const ptrdiff_t theValue)
79 return theValue >= 0 ? theValue : -theValue;
82 static const Standard_Size NEIGHBOR_PIXELS_NB = 8;
85 Standard_Integer row_inc;
86 Standard_Integer col_inc;
88 inline Standard_Size pixel2Int (const Standard_Size theRowCenter,
89 const Standard_Size theColCenter) const
91 return ::pixel2Int (theRowCenter + Standard_Size(row_inc),
92 theColCenter + Standard_Size(col_inc));
95 inline bool isBlack (const Image_PixMap& theData,
96 const Standard_Size theRowCenter,
97 const Standard_Size theColCenter) const
99 return ::isBlack (theData.Value<Image_ColorXXX24> (theRowCenter + Standard_Size(row_inc),
100 theColCenter + Standard_Size(col_inc)));
103 inline bool isValid (const Image_PixMap& theData,
104 const Standard_Size theRowCenter,
105 const Standard_Size theColCenter) const
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();
113 const NEIGHBOR_PIXELS[NEIGHBOR_PIXELS_NB] =
115 {-1, -1}, {-1, 0}, {-1, 1},
117 { 1, -1}, { 1, 0}, { 1, 1}
120 static bool isSupportedFormat (const Image_PixMap::ImgFormat theFormat)
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;
130 } // anonymous namespace
132 // =======================================================================
133 // function : Image_Diff
135 // =======================================================================
136 Image_Diff::Image_Diff()
137 : myColorTolerance (0.0),
138 myIsBorderFilterOn (Standard_False)
143 // =======================================================================
144 // function : ~Image_Diff
146 // =======================================================================
147 Image_Diff::~Image_Diff()
149 releaseGroupsOfDiffPixels();
152 // =======================================================================
155 // =======================================================================
156 Standard_Boolean Image_Diff::Init (const Handle(Image_PixMap)& theImageRef,
157 const Handle(Image_PixMap)& theImageNew,
158 const Standard_Boolean theToBlackWhite)
160 myImageRef.Nullify();
161 myImageNew.Nullify();
162 myDiffPixels.Clear();
163 releaseGroupsOfDiffPixels();
164 if (theImageRef.IsNull() || theImageNew.IsNull()
165 || theImageRef->IsEmpty() || theImageNew->IsEmpty()
166 || theImageRef->SizeX() != theImageNew->SizeX()
167 || theImageRef->SizeY() != theImageNew->SizeY()
168 || theImageRef->Format() != theImageNew->Format())
171 std::cerr << "Images has different format or dimensions\n";
173 return Standard_False;
175 else if (!isSupportedFormat (theImageRef->Format()))
178 std::cerr << "Images has unsupported pixel format\n";
180 return Standard_False;
182 else if (theImageRef->SizeX() >= 0xFFFF
183 || theImageRef->SizeY() >= 0xFFFF)
186 std::cerr << "Image too large\n";
188 return Standard_False;
191 myImageRef = theImageRef;
192 myImageNew = theImageNew;
196 // Convert the images to white/black
197 const Image_ColorXXX24 aWhite = {{255, 255, 255}};
198 for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
200 for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
202 Image_ColorXXX24& aPixel1 = myImageRef->ChangeValue<Image_ColorXXX24> (aRow, aCol);
203 if (!isBlack (aPixel1))
207 Image_ColorXXX24& aPixel2 = myImageNew->ChangeValue<Image_ColorXXX24> (aRow, aCol);
208 if (!isBlack (aPixel2))
216 return Standard_True;
220 // =======================================================================
223 // =======================================================================
224 Standard_Boolean Image_Diff::Init (const TCollection_AsciiString& theImgPathRef,
225 const TCollection_AsciiString& theImgPathNew,
226 const Standard_Boolean theToBlackWhite)
228 Handle(Image_AlienPixMap) anImgRef = new Image_AlienPixMap();
229 Handle(Image_AlienPixMap) anImgNew = new Image_AlienPixMap();
230 if (!anImgRef->Load (theImgPathRef)
231 || !anImgNew->Load (theImgPathNew))
234 std::cerr << "Failed to load image(s) file(s)\n";
236 return Standard_False;
238 return Init (anImgRef, anImgNew, theToBlackWhite);
241 // =======================================================================
242 // function : SetColorTolerance
244 // =======================================================================
245 void Image_Diff::SetColorTolerance (const Standard_Real theTolerance)
247 myColorTolerance = theTolerance;
250 // =======================================================================
251 // function : ColorTolerance
253 // =======================================================================
254 Standard_Real Image_Diff::ColorTolerance() const
256 return myColorTolerance;
259 // =======================================================================
260 // function : SetBorderFilterOn
262 // =======================================================================
263 void Image_Diff::SetBorderFilterOn (const Standard_Boolean theToIgnore)
265 myIsBorderFilterOn = theToIgnore;
268 // =======================================================================
269 // function : IsBorderFilterOn
271 // =======================================================================
272 Standard_Boolean Image_Diff::IsBorderFilterOn() const
274 return myIsBorderFilterOn;
277 // =======================================================================
278 // function : Compare
280 // =======================================================================
281 Standard_Integer Image_Diff::Compare()
283 // Number of different pixels (by color)
284 Standard_Integer aNbDiffColors = 0;
285 myDiffPixels.Clear();
287 if (myImageRef.IsNull() || myImageNew.IsNull())
292 // first check if images are exactly teh same
293 if (! memcmp (myImageNew->Data(), myImageRef->Data(), myImageRef->SizeBytes()))
298 // Tolerance of comparison operation for color
299 // Maximum difference between colors (white - black) = 100%
300 Image_ColorXXX24 aDiff = {{255, 255, 255}};
301 const Standard_Integer aMaxDiffColor = dotSquared (aDiff);
302 const Standard_Integer aDiffThreshold = Standard_Integer(Standard_Real(aMaxDiffColor) * myColorTolerance);
304 // we don't care about RGB/BGR/RGBA/BGRA/RGB32/BGR32 differences
305 // because we just compute summ of r g b components
307 // compare colors of each pixel
308 for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
310 for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
312 aDiff = myImageNew->Value<Image_ColorXXX24> (aRow, aCol) - myImageRef->Value<Image_ColorXXX24> (aRow, aCol);
313 if (dotSquared (aDiff) > aDiffThreshold)
315 const Standard_Size aValue = pixel2Int (aRow, aCol);
316 myDiffPixels.Append (aValue);
322 // take into account a border effect
323 if (myIsBorderFilterOn && myDiffPixels.Length() > 0)
325 aNbDiffColors = ignoreBorderEffect();
328 return aNbDiffColors;
331 // =======================================================================
332 // function : SaveDiffImage
334 // =======================================================================
335 Standard_Boolean Image_Diff::SaveDiffImage (Image_PixMap& theDiffImage) const
337 if (myImageRef.IsNull() || myImageNew.IsNull())
339 return Standard_False;
342 if (theDiffImage.IsEmpty()
343 || theDiffImage.SizeX() != myImageRef->SizeX()
344 || theDiffImage.SizeY() != myImageRef->SizeY()
345 || !isSupportedFormat (theDiffImage.Format()))
347 if (!theDiffImage.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY()))
349 return Standard_False;
353 Standard_Size aRow, aCol;
354 const Image_ColorXXX24 aWhite = {{255, 255, 255}};
356 // initialize black image for dump
357 memset (theDiffImage.ChangeData(), 0, theDiffImage.SizeBytes());
358 if (myGroupsOfDiffPixels.IsEmpty())
360 if (myIsBorderFilterOn)
362 return Standard_True;
365 for (Standard_Integer aPixelId = 0; aPixelId < myDiffPixels.Length(); ++aPixelId)
367 const Standard_Size aValue = myDiffPixels.Value (aPixelId);
368 int2Pixel (aValue, aRow, aCol);
369 theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
372 return Standard_True;
375 Standard_Integer aGroupId = 1;
376 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
378 if (myLinearGroups.Contains (aGroupId))
380 continue; // skip linear groups
383 const TColStd_MapOfInteger* aGroup = aGrIter.Value();
384 for (TColStd_MapIteratorOfMapOfInteger aPixelIter(*aGroup);
385 aPixelIter.More(); aPixelIter.Next())
387 int2Pixel (aPixelIter.Key(), aRow, aCol);
388 theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
392 return Standard_True;
395 // =======================================================================
396 // function : SaveDiffImage
398 // =======================================================================
399 Standard_Boolean Image_Diff::SaveDiffImage (const TCollection_AsciiString& theDiffPath) const
401 if (myImageRef.IsNull() || myImageNew.IsNull() || theDiffPath.IsEmpty())
403 return Standard_False;
406 Image_AlienPixMap aDiff;
407 if (!aDiff.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY())
408 || !SaveDiffImage (aDiff))
410 return Standard_False;
414 return aDiff.Save (theDiffPath);
417 // =======================================================================
418 // function : ignoreBorderEffect
420 // =======================================================================
421 Standard_Integer Image_Diff::ignoreBorderEffect()
423 if (myImageRef.IsNull() || myImageNew.IsNull())
428 // allocate groups of different pixels
429 releaseGroupsOfDiffPixels();
431 // Find a different area (a set of close to each other pixels which colors differ in both images).
432 // It filters alone pixels with different color.
433 Standard_Size aRow1 = 0, aCol1 = 0, aRow2, aCol2;
434 Standard_Integer aLen1 = (myDiffPixels.Length() > 0) ? (myDiffPixels.Length() - 1) : 0;
435 for (Standard_Integer aPixelId1 = 0; aPixelId1 < aLen1; ++aPixelId1)
437 const Standard_Size aValue1 = myDiffPixels.Value (aPixelId1);
438 int2Pixel (aValue1, aRow1, aCol1);
440 // Check other pixels in the list looking for a neighbour of this one
441 for (Standard_Integer aPixelId2 = aPixelId1 + 1; aPixelId2 < myDiffPixels.Length(); ++aPixelId2)
443 const Standard_Size aValue2 = myDiffPixels.Value (aPixelId2);
444 int2Pixel (aValue2, aRow2, aCol2);
445 if (getAbs (ptrdiff_t (aCol1 - aCol2)) <= 1 &&
446 getAbs (ptrdiff_t (aRow1 - aRow2)) <= 1)
448 // A neighbour is found. Create a new group and add both pixels.
449 if (myGroupsOfDiffPixels.IsEmpty())
451 TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
452 aGroup->Add ((Standard_Integer)aValue1);
453 aGroup->Add ((Standard_Integer)aValue2);
454 myGroupsOfDiffPixels.Append (aGroup);
458 // Find a group the pixels belong to.
459 Standard_Boolean isFound = Standard_False;
460 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
462 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
463 if (aGroup->Contains ((Standard_Integer)aValue1))
465 aGroup->Add ((Standard_Integer)aValue2);
466 isFound = Standard_True;
473 // Create a new group
474 TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
475 aGroup->Add ((Standard_Integer)aValue1);
476 aGroup->Add ((Standard_Integer)aValue2);
477 myGroupsOfDiffPixels.Append (aGroup);
484 // filter linear groups which represent border of a solid shape
485 Standard_Integer aGroupId = 1;
486 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
488 Standard_Integer aNeighboursNb = 0;
489 Standard_Boolean isLine = Standard_True;
490 const TColStd_MapOfInteger* aGroup = aGrIter.Value();
491 for (TColStd_MapIteratorOfMapOfInteger aPixelIter (*aGroup); aPixelIter.More(); aPixelIter.Next())
493 int2Pixel (aPixelIter.Key(), aRow1, aCol1);
496 // pixels of a line have only 1 or 2 neighbour pixels inside the same group
497 // check all neighbour pixels on presence in the group
498 for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
500 if (NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
501 && aGroup->Contains ((Standard_Integer)NEIGHBOR_PIXELS[aNgbrIter].pixel2Int (aRow1, aCol1)))
507 if (aNeighboursNb > 2)
509 isLine = Standard_False;
512 } // for pixels inside group...
516 // Test a pixel of the linear group on belonging to a solid shape.
517 // Consider neighbour pixels of the last pixel of the linear group in the 1st image.
518 // If the pixel has greater than 1 not black neighbour pixel, it is a border of a shape.
519 // Otherwise, it may be a topological edge, for example.
521 for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
523 if ( NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
524 && !NEIGHBOR_PIXELS[aNgbrIter].isBlack (*myImageRef, aRow1, aCol1))
530 if (aNeighboursNb > 1)
532 myLinearGroups.Add (aGroupId);
537 // number of different groups of pixels (except linear groups)
538 Standard_Integer aNbDiffColors = 0;
540 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
542 if (!myLinearGroups.Contains (aGroupId))
546 return aNbDiffColors;
549 // =======================================================================
550 // function : releaseGroupsOfDiffPixels
552 // =======================================================================
553 void Image_Diff::releaseGroupsOfDiffPixels()
555 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
557 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
560 myGroupsOfDiffPixels.Clear();
561 myLinearGroups.Clear();