0027961: Visualization - remove unused and no more working OpenGl_AVIWriter
[occt.git] / src / Image / Image_Diff.cxx
CommitLineData
6aca4d39 1// Created on: 2012-07-10
692613e5 2// Created by: VRO
6aca4d39 3// Copyright (c) 2012-2014 OPEN CASCADE SAS
692613e5 4//
973c2be1 5// This file is part of Open CASCADE Technology software library.
692613e5 6//
d5f74e42 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
973c2be1 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.
692613e5 12//
973c2be1 13// Alternatively, this file may be used under the terms of Open CASCADE
14// commercial license or contractual agreement.
692613e5 15
16#include <Image_Diff.hxx>
17#include <Image_AlienPixMap.hxx>
18
19#include <TColStd_MapIteratorOfMapOfInteger.hxx>
20
21#include <cstdlib>
22
692613e5 23
92efcf78 24IMPLEMENT_STANDARD_RTTIEXT(Image_Diff,Standard_Transient)
25
ca0c0b11 26//! POD structure for packed RGB color value (3 bytes)
27struct 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
34inline Image_ColorXXX24 operator- (const Image_ColorXXX24& theA,
35 const Image_ColorXXX24& theB)
36{
37 return Image_ColorSub3 (theA, theB);
38}
39
692613e5 40//! Dot squared for difference of two colors
ca0c0b11 41inline Standard_Integer dotSquared (const Image_ColorXXX24& theColor)
692613e5 42{
43 // explicitly convert to integer
ca0c0b11 44 const Standard_Integer r = theColor.v[0];
45 const Standard_Integer g = theColor.v[1];
46 const Standard_Integer b = theColor.v[2];
692613e5 47 return r * r + g * g + b * b;
48}
49
50//! @return true if pixel is black
ca0c0b11 51inline bool isBlack (const Image_ColorXXX24& theColor)
692613e5 52{
ca0c0b11 53 return theColor.v[0] == 0
54 && theColor.v[1] == 0
55 && theColor.v[2] == 0;
692613e5 56}
57
58//! Converts a pixel position (row, column) to one integer value
59inline 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)
66inline 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
74namespace
75{
185e6ec0 76
77 inline ptrdiff_t getAbs (const ptrdiff_t theValue)
78 {
79 return theValue >= 0 ? theValue : -theValue;
80 }
81
692613e5 82 static const Standard_Size NEIGHBOR_PIXELS_NB = 8;
f24125b9 83 static struct
692613e5 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
ca0c0b11 95 inline bool isBlack (const Image_PixMap& theData,
692613e5 96 const Standard_Size theRowCenter,
97 const Standard_Size theColCenter) const
98 {
ca0c0b11 99 return ::isBlack (theData.Value<Image_ColorXXX24> (theRowCenter + Standard_Size(row_inc),
100 theColCenter + Standard_Size(col_inc)));
692613e5 101 }
185e6ec0 102
ca0c0b11 103 inline bool isValid (const Image_PixMap& theData,
185e6ec0 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 }
692613e5 112 }
f24125b9 113 const NEIGHBOR_PIXELS[NEIGHBOR_PIXELS_NB] =
692613e5 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 }
68858c7d 129
130} // anonymous namespace
692613e5 131
132// =======================================================================
133// function : Image_Diff
134// purpose :
135// =======================================================================
136Image_Diff::Image_Diff()
137: myColorTolerance (0.0),
138 myIsBorderFilterOn (Standard_False)
139{
140 //
141}
142
143// =======================================================================
144// function : ~Image_Diff
145// purpose :
146// =======================================================================
147Image_Diff::~Image_Diff()
148{
149 releaseGroupsOfDiffPixels();
150}
151
152// =======================================================================
153// function : Init
154// purpose :
155// =======================================================================
156Standard_Boolean Image_Diff::Init (const Handle(Image_PixMap)& theImageRef,
157 const Handle(Image_PixMap)& theImageNew,
158 const Standard_Boolean theToBlackWhite)
159{
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())
169 {
0797d9d3 170#ifdef OCCT_DEBUG
692613e5 171 std::cerr << "Images has different format or dimensions\n";
63c629aa 172#endif
692613e5 173 return Standard_False;
174 }
175 else if (!isSupportedFormat (theImageRef->Format()))
176 {
0797d9d3 177#ifdef OCCT_DEBUG
692613e5 178 std::cerr << "Images has unsupported pixel format\n";
63c629aa 179#endif
692613e5 180 return Standard_False;
181 }
182 else if (theImageRef->SizeX() >= 0xFFFF
183 || theImageRef->SizeY() >= 0xFFFF)
184 {
0797d9d3 185#ifdef OCCT_DEBUG
692613e5 186 std::cerr << "Image too large\n";
63c629aa 187#endif
692613e5 188 return Standard_False;
189 }
190
191 myImageRef = theImageRef;
192 myImageNew = theImageNew;
193
194 if (theToBlackWhite)
195 {
196 // Convert the images to white/black
ca0c0b11 197 const Image_ColorXXX24 aWhite = {{255, 255, 255}};
198 for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
692613e5 199 {
ca0c0b11 200 for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
692613e5 201 {
ca0c0b11 202 Image_ColorXXX24& aPixel1 = myImageRef->ChangeValue<Image_ColorXXX24> (aRow, aCol);
692613e5 203 if (!isBlack (aPixel1))
204 {
205 aPixel1 = aWhite;
206 }
ca0c0b11 207 Image_ColorXXX24& aPixel2 = myImageNew->ChangeValue<Image_ColorXXX24> (aRow, aCol);
692613e5 208 if (!isBlack (aPixel2))
209 {
210 aPixel2 = aWhite;
211 }
212 }
213 }
214 }
215
216 return Standard_True;
217}
218
219
220// =======================================================================
221// function : Init
222// purpose :
223// =======================================================================
224Standard_Boolean Image_Diff::Init (const TCollection_AsciiString& theImgPathRef,
225 const TCollection_AsciiString& theImgPathNew,
226 const Standard_Boolean theToBlackWhite)
227{
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))
232 {
0797d9d3 233#ifdef OCCT_DEBUG
692613e5 234 std::cerr << "Failed to load image(s) file(s)\n";
63c629aa 235#endif
692613e5 236 return Standard_False;
237 }
238 return Init (anImgRef, anImgNew, theToBlackWhite);
239}
240
241// =======================================================================
242// function : SetColorTolerance
243// purpose :
244// =======================================================================
245void Image_Diff::SetColorTolerance (const Standard_Real theTolerance)
246{
247 myColorTolerance = theTolerance;
248}
249
250// =======================================================================
251// function : ColorTolerance
252// purpose :
253// =======================================================================
254Standard_Real Image_Diff::ColorTolerance() const
255{
256 return myColorTolerance;
257}
258
259// =======================================================================
260// function : SetBorderFilterOn
261// purpose :
262// =======================================================================
263void Image_Diff::SetBorderFilterOn (const Standard_Boolean theToIgnore)
264{
265 myIsBorderFilterOn = theToIgnore;
266}
267
268// =======================================================================
269// function : IsBorderFilterOn
270// purpose :
271// =======================================================================
272Standard_Boolean Image_Diff::IsBorderFilterOn() const
273{
274 return myIsBorderFilterOn;
275}
276
277// =======================================================================
278// function : Compare
279// purpose :
280// =======================================================================
281Standard_Integer Image_Diff::Compare()
282{
283 // Number of different pixels (by color)
284 Standard_Integer aNbDiffColors = 0;
285 myDiffPixels.Clear();
286
287 if (myImageRef.IsNull() || myImageNew.IsNull())
288 {
289 return -1;
290 }
291
936f43da 292 // first check if images are exactly teh same
293 if (! memcmp (myImageNew->Data(), myImageRef->Data(), myImageRef->SizeBytes()))
294 {
295 return 0;
296 }
297
692613e5 298 // Tolerance of comparison operation for color
299 // Maximum difference between colors (white - black) = 100%
ca0c0b11 300 Image_ColorXXX24 aDiff = {{255, 255, 255}};
692613e5 301 const Standard_Integer aMaxDiffColor = dotSquared (aDiff);
302 const Standard_Integer aDiffThreshold = Standard_Integer(Standard_Real(aMaxDiffColor) * myColorTolerance);
303
304 // we don't care about RGB/BGR/RGBA/BGRA/RGB32/BGR32 differences
305 // because we just compute summ of r g b components
692613e5 306
307 // compare colors of each pixel
308 for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
309 {
310 for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
311 {
ca0c0b11 312 aDiff = myImageNew->Value<Image_ColorXXX24> (aRow, aCol) - myImageRef->Value<Image_ColorXXX24> (aRow, aCol);
692613e5 313 if (dotSquared (aDiff) > aDiffThreshold)
314 {
315 const Standard_Size aValue = pixel2Int (aRow, aCol);
316 myDiffPixels.Append (aValue);
317 ++aNbDiffColors;
318 }
319 }
320 }
321
322 // take into account a border effect
323 if (myIsBorderFilterOn && myDiffPixels.Length() > 0)
324 {
325 aNbDiffColors = ignoreBorderEffect();
326 }
327
328 return aNbDiffColors;
329}
330
331// =======================================================================
332// function : SaveDiffImage
333// purpose :
334// =======================================================================
335Standard_Boolean Image_Diff::SaveDiffImage (Image_PixMap& theDiffImage) const
336{
337 if (myImageRef.IsNull() || myImageNew.IsNull())
338 {
339 return Standard_False;
340 }
341
342 if (theDiffImage.IsEmpty()
343 || theDiffImage.SizeX() != myImageRef->SizeX()
344 || theDiffImage.SizeY() != myImageRef->SizeY()
345 || !isSupportedFormat (theDiffImage.Format()))
346 {
347 if (!theDiffImage.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY()))
348 {
349 return Standard_False;
350 }
351 }
352
353 Standard_Size aRow, aCol;
ca0c0b11 354 const Image_ColorXXX24 aWhite = {{255, 255, 255}};
692613e5 355
356 // initialize black image for dump
357 memset (theDiffImage.ChangeData(), 0, theDiffImage.SizeBytes());
358 if (myGroupsOfDiffPixels.IsEmpty())
359 {
360 if (myIsBorderFilterOn)
361 {
362 return Standard_True;
363 }
364
365 for (Standard_Integer aPixelId = 0; aPixelId < myDiffPixels.Length(); ++aPixelId)
366 {
367 const Standard_Size aValue = myDiffPixels.Value (aPixelId);
368 int2Pixel (aValue, aRow, aCol);
ca0c0b11 369 theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
692613e5 370 }
371
372 return Standard_True;
373 }
374
375 Standard_Integer aGroupId = 1;
376 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
377 {
378 if (myLinearGroups.Contains (aGroupId))
379 {
380 continue; // skip linear groups
381 }
382
383 const TColStd_MapOfInteger* aGroup = aGrIter.Value();
384 for (TColStd_MapIteratorOfMapOfInteger aPixelIter(*aGroup);
385 aPixelIter.More(); aPixelIter.Next())
386 {
387 int2Pixel (aPixelIter.Key(), aRow, aCol);
ca0c0b11 388 theDiffImage.ChangeValue<Image_ColorXXX24> (aRow, aCol) = aWhite;
692613e5 389 }
390 }
391
392 return Standard_True;
393}
394
395// =======================================================================
396// function : SaveDiffImage
397// purpose :
398// =======================================================================
399Standard_Boolean Image_Diff::SaveDiffImage (const TCollection_AsciiString& theDiffPath) const
400{
401 if (myImageRef.IsNull() || myImageNew.IsNull() || theDiffPath.IsEmpty())
402 {
403 return Standard_False;
404 }
405
406 Image_AlienPixMap aDiff;
407 if (!aDiff.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY())
408 || !SaveDiffImage (aDiff))
409 {
410 return Standard_False;
411 }
412
413 // save image
414 return aDiff.Save (theDiffPath);
415}
416
417// =======================================================================
418// function : ignoreBorderEffect
419// purpose :
420// =======================================================================
421Standard_Integer Image_Diff::ignoreBorderEffect()
422{
423 if (myImageRef.IsNull() || myImageNew.IsNull())
424 {
425 return 0;
426 }
427
692613e5 428 // allocate groups of different pixels
429 releaseGroupsOfDiffPixels();
430
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.
1d47d8d0 433 Standard_Size aRow1 = 0, aCol1 = 0, aRow2, aCol2;
692613e5 434 Standard_Integer aLen1 = (myDiffPixels.Length() > 0) ? (myDiffPixels.Length() - 1) : 0;
435 for (Standard_Integer aPixelId1 = 0; aPixelId1 < aLen1; ++aPixelId1)
436 {
437 const Standard_Size aValue1 = myDiffPixels.Value (aPixelId1);
438 int2Pixel (aValue1, aRow1, aCol1);
439
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)
442 {
443 const Standard_Size aValue2 = myDiffPixels.Value (aPixelId2);
444 int2Pixel (aValue2, aRow2, aCol2);
185e6ec0 445 if (getAbs (ptrdiff_t (aCol1 - aCol2)) <= 1 &&
446 getAbs (ptrdiff_t (aRow1 - aRow2)) <= 1)
692613e5 447 {
448 // A neighbour is found. Create a new group and add both pixels.
449 if (myGroupsOfDiffPixels.IsEmpty())
450 {
451 TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
7dc9e047 452 aGroup->Add ((Standard_Integer)aValue1);
453 aGroup->Add ((Standard_Integer)aValue2);
692613e5 454 myGroupsOfDiffPixels.Append (aGroup);
455 }
456 else
457 {
458 // Find a group the pixels belong to.
459 Standard_Boolean isFound = Standard_False;
460 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
461 {
462 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
7dc9e047 463 if (aGroup->Contains ((Standard_Integer)aValue1))
692613e5 464 {
7dc9e047 465 aGroup->Add ((Standard_Integer)aValue2);
692613e5 466 isFound = Standard_True;
467 break;
468 }
469 }
470
471 if (!isFound)
472 {
473 // Create a new group
474 TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
7dc9e047 475 aGroup->Add ((Standard_Integer)aValue1);
476 aGroup->Add ((Standard_Integer)aValue2);
692613e5 477 myGroupsOfDiffPixels.Append (aGroup);
478 }
479 }
480 }
481 }
482 }
483
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)
487 {
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())
492 {
493 int2Pixel (aPixelIter.Key(), aRow1, aCol1);
494 aNeighboursNb = 0;
495
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)
499 {
ca0c0b11 500 if (NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
7dc9e047 501 && aGroup->Contains ((Standard_Integer)NEIGHBOR_PIXELS[aNgbrIter].pixel2Int (aRow1, aCol1)))
692613e5 502 {
503 ++aNeighboursNb;
504 }
505 }
506
507 if (aNeighboursNb > 2)
508 {
509 isLine = Standard_False;
510 break;
511 }
512 } // for pixels inside group...
513
514 if (isLine)
515 {
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.
520 aNeighboursNb = 0;
521 for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
522 {
ca0c0b11 523 if ( NEIGHBOR_PIXELS[aNgbrIter].isValid (*myImageRef, aRow1, aCol1)
524 && !NEIGHBOR_PIXELS[aNgbrIter].isBlack (*myImageRef, aRow1, aCol1))
692613e5 525 {
526 ++aNeighboursNb;
527 }
528 }
529
530 if (aNeighboursNb > 1)
531 {
532 myLinearGroups.Add (aGroupId);
533 }
534 }
535 } // for groups...
536
537 // number of different groups of pixels (except linear groups)
538 Standard_Integer aNbDiffColors = 0;
539 aGroupId = 1;
540 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
541 {
542 if (!myLinearGroups.Contains (aGroupId))
543 ++aNbDiffColors;
544 }
545
546 return aNbDiffColors;
547}
548
549// =======================================================================
550// function : releaseGroupsOfDiffPixels
551// purpose :
552// =======================================================================
553void Image_Diff::releaseGroupsOfDiffPixels()
554{
555 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
556 {
557 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
558 delete aGroup;
559 }
560 myGroupsOfDiffPixels.Clear();
561 myLinearGroups.Clear();
562}