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>
23 IMPLEMENT_STANDARD_HANDLE (Image_Diff, Standard_Transient)
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;
131 // =======================================================================
132 // function : Image_Diff
134 // =======================================================================
135 Image_Diff::Image_Diff()
136 : myColorTolerance (0.0),
137 myIsBorderFilterOn (Standard_False)
142 // =======================================================================
143 // function : ~Image_Diff
145 // =======================================================================
146 Image_Diff::~Image_Diff()
148 releaseGroupsOfDiffPixels();
151 // =======================================================================
154 // =======================================================================
155 Standard_Boolean Image_Diff::Init (const Handle(Image_PixMap)& theImageRef,
156 const Handle(Image_PixMap)& theImageNew,
157 const Standard_Boolean theToBlackWhite)
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())
169 std::cerr << "Images has different format or dimensions\n";
170 return Standard_False;
172 else if (!isSupportedFormat (theImageRef->Format()))
174 std::cerr << "Images has unsupported pixel format\n";
175 return Standard_False;
177 else if (theImageRef->SizeX() >= 0xFFFF
178 || theImageRef->SizeY() >= 0xFFFF)
180 std::cerr << "Image too large\n";
181 return Standard_False;
184 myImageRef = theImageRef;
185 myImageNew = theImageNew;
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)
193 for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
195 Image_ColorXXX24& aPixel1 = myImageRef->ChangeValue<Image_ColorXXX24> (aRow, aCol);
196 if (!isBlack (aPixel1))
200 Image_ColorXXX24& aPixel2 = myImageNew->ChangeValue<Image_ColorXXX24> (aRow, aCol);
201 if (!isBlack (aPixel2))
209 return Standard_True;
213 // =======================================================================
216 // =======================================================================
217 Standard_Boolean Image_Diff::Init (const TCollection_AsciiString& theImgPathRef,
218 const TCollection_AsciiString& theImgPathNew,
219 const Standard_Boolean theToBlackWhite)
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))
226 std::cerr << "Failed to load image(s) file(s)\n";
227 return Standard_False;
229 return Init (anImgRef, anImgNew, theToBlackWhite);
232 // =======================================================================
233 // function : SetColorTolerance
235 // =======================================================================
236 void Image_Diff::SetColorTolerance (const Standard_Real theTolerance)
238 myColorTolerance = theTolerance;
241 // =======================================================================
242 // function : ColorTolerance
244 // =======================================================================
245 Standard_Real Image_Diff::ColorTolerance() const
247 return myColorTolerance;
250 // =======================================================================
251 // function : SetBorderFilterOn
253 // =======================================================================
254 void Image_Diff::SetBorderFilterOn (const Standard_Boolean theToIgnore)
256 myIsBorderFilterOn = theToIgnore;
259 // =======================================================================
260 // function : IsBorderFilterOn
262 // =======================================================================
263 Standard_Boolean Image_Diff::IsBorderFilterOn() const
265 return myIsBorderFilterOn;
268 // =======================================================================
269 // function : Compare
271 // =======================================================================
272 Standard_Integer Image_Diff::Compare()
274 // Number of different pixels (by color)
275 Standard_Integer aNbDiffColors = 0;
276 myDiffPixels.Clear();
278 if (myImageRef.IsNull() || myImageNew.IsNull())
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);
289 // we don't care about RGB/BGR/RGBA/BGRA/RGB32/BGR32 differences
290 // because we just compute summ of r g b components
292 // compare colors of each pixel
293 for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
295 for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
297 aDiff = myImageNew->Value<Image_ColorXXX24> (aRow, aCol) - myImageRef->Value<Image_ColorXXX24> (aRow, aCol);
298 if (dotSquared (aDiff) > aDiffThreshold)
300 const Standard_Size aValue = pixel2Int (aRow, aCol);
301 myDiffPixels.Append (aValue);
307 // take into account a border effect
308 if (myIsBorderFilterOn && myDiffPixels.Length() > 0)
310 aNbDiffColors = ignoreBorderEffect();
313 return aNbDiffColors;
316 // =======================================================================
317 // function : SaveDiffImage
319 // =======================================================================
320 Standard_Boolean Image_Diff::SaveDiffImage (Image_PixMap& theDiffImage) const
322 if (myImageRef.IsNull() || myImageNew.IsNull())
324 return Standard_False;
327 if (theDiffImage.IsEmpty()
328 || theDiffImage.SizeX() != myImageRef->SizeX()
329 || theDiffImage.SizeY() != myImageRef->SizeY()
330 || !isSupportedFormat (theDiffImage.Format()))
332 if (!theDiffImage.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY()))
334 return Standard_False;
338 Standard_Size aRow, aCol;
339 const Image_ColorXXX24 aWhite = {{255, 255, 255}};
341 // initialize black image for dump
342 memset (theDiffImage.ChangeData(), 0, theDiffImage.SizeBytes());
343 if (myGroupsOfDiffPixels.IsEmpty())
345 if (myIsBorderFilterOn)
347 return Standard_True;
350 for (Standard_Integer aPixelId = 0; aPixelId < myDiffPixels.Length(); ++aPixelId)
352 const Standard_Size aValue = myDiffPixels.Value (aPixelId);
353 int2Pixel (aValue, aRow, aCol);
354 theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
357 return Standard_True;
360 Standard_Integer aGroupId = 1;
361 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
363 if (myLinearGroups.Contains (aGroupId))
365 continue; // skip linear groups
368 const TColStd_MapOfInteger* aGroup = aGrIter.Value();
369 for (TColStd_MapIteratorOfMapOfInteger aPixelIter(*aGroup);
370 aPixelIter.More(); aPixelIter.Next())
372 int2Pixel (aPixelIter.Key(), aRow, aCol);
373 theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
377 return Standard_True;
380 // =======================================================================
381 // function : SaveDiffImage
383 // =======================================================================
384 Standard_Boolean Image_Diff::SaveDiffImage (const TCollection_AsciiString& theDiffPath) const
386 if (myImageRef.IsNull() || myImageNew.IsNull() || theDiffPath.IsEmpty())
388 return Standard_False;
391 Image_AlienPixMap aDiff;
392 if (!aDiff.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY())
393 || !SaveDiffImage (aDiff))
395 return Standard_False;
399 return aDiff.Save (theDiffPath);
402 // =======================================================================
403 // function : ignoreBorderEffect
405 // =======================================================================
406 Standard_Integer Image_Diff::ignoreBorderEffect()
408 if (myImageRef.IsNull() || myImageNew.IsNull())
413 // allocate groups of different pixels
414 releaseGroupsOfDiffPixels();
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)
422 const Standard_Size aValue1 = myDiffPixels.Value (aPixelId1);
423 int2Pixel (aValue1, aRow1, aCol1);
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)
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)
433 // A neighbour is found. Create a new group and add both pixels.
434 if (myGroupsOfDiffPixels.IsEmpty())
436 TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
437 aGroup->Add ((Standard_Integer)aValue1);
438 aGroup->Add ((Standard_Integer)aValue2);
439 myGroupsOfDiffPixels.Append (aGroup);
443 // Find a group the pixels belong to.
444 Standard_Boolean isFound = Standard_False;
445 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
447 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
448 if (aGroup->Contains ((Standard_Integer)aValue1))
450 aGroup->Add ((Standard_Integer)aValue2);
451 isFound = Standard_True;
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);
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)
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())
478 int2Pixel (aPixelIter.Key(), aRow1, aCol1);
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)
485 if (NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
486 && aGroup->Contains ((Standard_Integer)NEIGHBOR_PIXELS[aNgbrIter].pixel2Int (aRow1, aCol1)))
492 if (aNeighboursNb > 2)
494 isLine = Standard_False;
497 } // for pixels inside group...
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.
506 for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
508 if ( NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
509 && !NEIGHBOR_PIXELS[aNgbrIter].isBlack (*myImageRef, aRow1, aCol1))
515 if (aNeighboursNb > 1)
517 myLinearGroups.Add (aGroupId);
522 // number of different groups of pixels (except linear groups)
523 Standard_Integer aNbDiffColors = 0;
525 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
527 if (!myLinearGroups.Contains (aGroupId))
531 return aNbDiffColors;
534 // =======================================================================
535 // function : releaseGroupsOfDiffPixels
537 // =======================================================================
538 void Image_Diff::releaseGroupsOfDiffPixels()
540 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
542 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
545 myGroupsOfDiffPixels.Clear();
546 myLinearGroups.Clear();