0023316: OpenGl package can not be compiled on RedHat40-64
[occt.git] / src / Image / Image_Diff.cxx
CommitLineData
692613e5 1// Created on: 2012-07-10
2// Created by: VRO
3// Copyright (c) 2012 OPEN CASCADE SAS
4//
5// The content of this file is subject to the Open CASCADE Technology Public
6// License Version 6.5 (the "License"). You may not use the content of this file
7// except in compliance with the License. Please obtain a copy of the License
8// at http://www.opencascade.org and read it completely before using this file.
9//
10// The Initial Developer of the Original Code is Open CASCADE S.A.S., having its
11// main offices at: 1, place des Freres Montgolfier, 78280 Guyancourt, France.
12//
13// The Original Code and all software distributed under the License is
14// distributed on an "AS IS" basis, without warranty of any kind, and the
15// Initial Developer hereby disclaims all such warranties, including without
16// limitation, any warranties of merchantability, fitness for a particular
17// purpose or non-infringement. Please see the License for the specific terms
18// and conditions governing the rights and limitations under the License.
19
20#include <Image_Diff.hxx>
21#include <Image_AlienPixMap.hxx>
22
23#include <TColStd_MapIteratorOfMapOfInteger.hxx>
24
25#include <cstdlib>
26
27IMPLEMENT_STANDARD_HANDLE (Image_Diff, Standard_Transient)
28IMPLEMENT_STANDARD_RTTIEXT(Image_Diff, Standard_Transient)
29
30//! Dot squared for difference of two colors
31inline Standard_Integer dotSquared (const Image_ColorRGB& theColor)
32{
33 // explicitly convert to integer
34 const Standard_Integer r = theColor.r();
35 const Standard_Integer g = theColor.g();
36 const Standard_Integer b = theColor.b();
37 return r * r + g * g + b * b;
38}
39
40//! @return true if pixel is black
41inline bool isBlack (const Image_ColorRGB& theColor)
42{
43 return theColor.r() == 0
44 && theColor.g() == 0
45 && theColor.b() == 0;
46}
47
48//! Converts a pixel position (row, column) to one integer value
49inline Standard_Size pixel2Int (const Standard_Size aRow,
50 const Standard_Size aCol)
51{
52 return aCol + (aRow << 15);
53}
54
55//! Converts an integer value to pixel coordinates (row, column)
56inline void int2Pixel (const Standard_Size theValue,
57 Standard_Size& theRow,
58 Standard_Size& theCol)
59{
60 theRow = (theValue >> 15);
61 theCol = theValue - (theRow << 15);
62}
63
64namespace
65{
66 static const Standard_Size NEIGHBOR_PIXELS_NB = 8;
67 struct
68 {
69 Standard_Integer row_inc;
70 Standard_Integer col_inc;
71
72 inline Standard_Size pixel2Int (const Standard_Size theRowCenter,
73 const Standard_Size theColCenter) const
74 {
75 return ::pixel2Int (theRowCenter + Standard_Size(row_inc),
76 theColCenter + Standard_Size(col_inc));
77 }
78
79 inline bool isBlack (const Image_PixMapData<Image_ColorRGB>& theData,
80 const Standard_Size theRowCenter,
81 const Standard_Size theColCenter) const
82 {
83 return ::isBlack (theData.Value (theRowCenter + Standard_Size(row_inc),
84 theColCenter + Standard_Size(col_inc)));
85 }
86 }
87 static const NEIGHBOR_PIXELS[NEIGHBOR_PIXELS_NB] =
88 {
89 {-1, -1}, {-1, 0}, {-1, 1},
90 { 0, -1}, { 0, 1},
91 { 1, -1}, { 1, 0}, { 1, 1}
92 };
93
94 static bool isSupportedFormat (const Image_PixMap::ImgFormat theFormat)
95 {
96 return theFormat == Image_PixMap::ImgRGB
97 || theFormat == Image_PixMap::ImgBGR
98 || theFormat == Image_PixMap::ImgRGB32
99 || theFormat == Image_PixMap::ImgBGR32
100 || theFormat == Image_PixMap::ImgRGBA
101 || theFormat == Image_PixMap::ImgBGRA;
102 }
103};
104
105// =======================================================================
106// function : Image_Diff
107// purpose :
108// =======================================================================
109Image_Diff::Image_Diff()
110: myColorTolerance (0.0),
111 myIsBorderFilterOn (Standard_False)
112{
113 //
114}
115
116// =======================================================================
117// function : ~Image_Diff
118// purpose :
119// =======================================================================
120Image_Diff::~Image_Diff()
121{
122 releaseGroupsOfDiffPixels();
123}
124
125// =======================================================================
126// function : Init
127// purpose :
128// =======================================================================
129Standard_Boolean Image_Diff::Init (const Handle(Image_PixMap)& theImageRef,
130 const Handle(Image_PixMap)& theImageNew,
131 const Standard_Boolean theToBlackWhite)
132{
133 myImageRef.Nullify();
134 myImageNew.Nullify();
135 myDiffPixels.Clear();
136 releaseGroupsOfDiffPixels();
137 if (theImageRef.IsNull() || theImageNew.IsNull()
138 || theImageRef->IsEmpty() || theImageNew->IsEmpty()
139 || theImageRef->SizeX() != theImageNew->SizeX()
140 || theImageRef->SizeY() != theImageNew->SizeY()
141 || theImageRef->Format() != theImageNew->Format())
142 {
143 std::cerr << "Images has different format or dimensions\n";
144 return Standard_False;
145 }
146 else if (!isSupportedFormat (theImageRef->Format()))
147 {
148 std::cerr << "Images has unsupported pixel format\n";
149 return Standard_False;
150 }
151 else if (theImageRef->SizeX() >= 0xFFFF
152 || theImageRef->SizeY() >= 0xFFFF)
153 {
154 std::cerr << "Image too large\n";
155 return Standard_False;
156 }
157
158 myImageRef = theImageRef;
159 myImageNew = theImageNew;
160
161 if (theToBlackWhite)
162 {
163 // Convert the images to white/black
164 const Image_ColorRGB aWhite = {{255, 255, 255}};
165 Image_PixMapData<Image_ColorRGB>& aDataRef = myImageRef->EditData<Image_ColorRGB>();
166 Image_PixMapData<Image_ColorRGB>& aDataNew = myImageNew->EditData<Image_ColorRGB>();
167 for (Standard_Size aRow = 0; aRow < aDataRef.SizeY(); ++aRow)
168 {
169 for (Standard_Size aCol = 0; aCol < aDataRef.SizeY(); ++aCol)
170 {
171 Image_ColorRGB& aPixel1 = aDataRef.ChangeValue (aRow, aCol);
172 Image_ColorRGB& aPixel2 = aDataNew.ChangeValue (aRow, aCol);
173 if (!isBlack (aPixel1))
174 {
175 aPixel1 = aWhite;
176 }
177 if (!isBlack (aPixel2))
178 {
179 aPixel2 = aWhite;
180 }
181 }
182 }
183 }
184
185 return Standard_True;
186}
187
188
189// =======================================================================
190// function : Init
191// purpose :
192// =======================================================================
193Standard_Boolean Image_Diff::Init (const TCollection_AsciiString& theImgPathRef,
194 const TCollection_AsciiString& theImgPathNew,
195 const Standard_Boolean theToBlackWhite)
196{
197 Handle(Image_AlienPixMap) anImgRef = new Image_AlienPixMap();
198 Handle(Image_AlienPixMap) anImgNew = new Image_AlienPixMap();
199 if (!anImgRef->Load (theImgPathRef)
200 || !anImgNew->Load (theImgPathNew))
201 {
202 std::cerr << "Failed to load image(s) file(s)\n";
203 return Standard_False;
204 }
205 return Init (anImgRef, anImgNew, theToBlackWhite);
206}
207
208// =======================================================================
209// function : SetColorTolerance
210// purpose :
211// =======================================================================
212void Image_Diff::SetColorTolerance (const Standard_Real theTolerance)
213{
214 myColorTolerance = theTolerance;
215}
216
217// =======================================================================
218// function : ColorTolerance
219// purpose :
220// =======================================================================
221Standard_Real Image_Diff::ColorTolerance() const
222{
223 return myColorTolerance;
224}
225
226// =======================================================================
227// function : SetBorderFilterOn
228// purpose :
229// =======================================================================
230void Image_Diff::SetBorderFilterOn (const Standard_Boolean theToIgnore)
231{
232 myIsBorderFilterOn = theToIgnore;
233}
234
235// =======================================================================
236// function : IsBorderFilterOn
237// purpose :
238// =======================================================================
239Standard_Boolean Image_Diff::IsBorderFilterOn() const
240{
241 return myIsBorderFilterOn;
242}
243
244// =======================================================================
245// function : Compare
246// purpose :
247// =======================================================================
248Standard_Integer Image_Diff::Compare()
249{
250 // Number of different pixels (by color)
251 Standard_Integer aNbDiffColors = 0;
252 myDiffPixels.Clear();
253
254 if (myImageRef.IsNull() || myImageNew.IsNull())
255 {
256 return -1;
257 }
258
259 // Tolerance of comparison operation for color
260 // Maximum difference between colors (white - black) = 100%
261 Image_ColorRGB aDiff = {{255, 255, 255}};
262 const Standard_Integer aMaxDiffColor = dotSquared (aDiff);
263 const Standard_Integer aDiffThreshold = Standard_Integer(Standard_Real(aMaxDiffColor) * myColorTolerance);
264
265 // we don't care about RGB/BGR/RGBA/BGRA/RGB32/BGR32 differences
266 // because we just compute summ of r g b components
267 const Image_PixMapData<Image_ColorRGB>& aDataRef = myImageRef->ReadData<Image_ColorRGB>();
268 const Image_PixMapData<Image_ColorRGB>& aDataNew = myImageNew->ReadData<Image_ColorRGB>();
269
270 // compare colors of each pixel
271 for (Standard_Size aRow = 0; aRow < myImageRef->SizeY(); ++aRow)
272 {
273 for (Standard_Size aCol = 0; aCol < myImageRef->SizeX(); ++aCol)
274 {
275 aDiff = aDataNew.Value (aRow, aCol) - aDataRef.Value (aRow, aCol);
276 if (dotSquared (aDiff) > aDiffThreshold)
277 {
278 const Standard_Size aValue = pixel2Int (aRow, aCol);
279 myDiffPixels.Append (aValue);
280 ++aNbDiffColors;
281 }
282 }
283 }
284
285 // take into account a border effect
286 if (myIsBorderFilterOn && myDiffPixels.Length() > 0)
287 {
288 aNbDiffColors = ignoreBorderEffect();
289 }
290
291 return aNbDiffColors;
292}
293
294// =======================================================================
295// function : SaveDiffImage
296// purpose :
297// =======================================================================
298Standard_Boolean Image_Diff::SaveDiffImage (Image_PixMap& theDiffImage) const
299{
300 if (myImageRef.IsNull() || myImageNew.IsNull())
301 {
302 return Standard_False;
303 }
304
305 if (theDiffImage.IsEmpty()
306 || theDiffImage.SizeX() != myImageRef->SizeX()
307 || theDiffImage.SizeY() != myImageRef->SizeY()
308 || !isSupportedFormat (theDiffImage.Format()))
309 {
310 if (!theDiffImage.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY()))
311 {
312 return Standard_False;
313 }
314 }
315
316 Standard_Size aRow, aCol;
317 const Image_ColorRGB aWhite = {{255, 255, 255}};
318 Image_PixMapData<Image_ColorRGB>& aDataOut = theDiffImage.EditData<Image_ColorRGB>();
319
320 // initialize black image for dump
321 memset (theDiffImage.ChangeData(), 0, theDiffImage.SizeBytes());
322 if (myGroupsOfDiffPixels.IsEmpty())
323 {
324 if (myIsBorderFilterOn)
325 {
326 return Standard_True;
327 }
328
329 for (Standard_Integer aPixelId = 0; aPixelId < myDiffPixels.Length(); ++aPixelId)
330 {
331 const Standard_Size aValue = myDiffPixels.Value (aPixelId);
332 int2Pixel (aValue, aRow, aCol);
333 aDataOut.ChangeValue (aRow, aCol) = aWhite;
334 }
335
336 return Standard_True;
337 }
338
339 Standard_Integer aGroupId = 1;
340 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
341 {
342 if (myLinearGroups.Contains (aGroupId))
343 {
344 continue; // skip linear groups
345 }
346
347 const TColStd_MapOfInteger* aGroup = aGrIter.Value();
348 for (TColStd_MapIteratorOfMapOfInteger aPixelIter(*aGroup);
349 aPixelIter.More(); aPixelIter.Next())
350 {
351 int2Pixel (aPixelIter.Key(), aRow, aCol);
352 aDataOut.ChangeValue (aRow, aCol) = aWhite;
353 }
354 }
355
356 return Standard_True;
357}
358
359// =======================================================================
360// function : SaveDiffImage
361// purpose :
362// =======================================================================
363Standard_Boolean Image_Diff::SaveDiffImage (const TCollection_AsciiString& theDiffPath) const
364{
365 if (myImageRef.IsNull() || myImageNew.IsNull() || theDiffPath.IsEmpty())
366 {
367 return Standard_False;
368 }
369
370 Image_AlienPixMap aDiff;
371 if (!aDiff.InitTrash (Image_PixMap::ImgRGB, myImageRef->SizeX(), myImageRef->SizeY())
372 || !SaveDiffImage (aDiff))
373 {
374 return Standard_False;
375 }
376
377 // save image
378 return aDiff.Save (theDiffPath);
379}
380
381// =======================================================================
382// function : ignoreBorderEffect
383// purpose :
384// =======================================================================
385Standard_Integer Image_Diff::ignoreBorderEffect()
386{
387 if (myImageRef.IsNull() || myImageNew.IsNull())
388 {
389 return 0;
390 }
391
392 const Image_PixMapData<Image_ColorRGB>& aDataRef = myImageRef->ReadData<Image_ColorRGB>();
393
394 // allocate groups of different pixels
395 releaseGroupsOfDiffPixels();
396
397 // Find a different area (a set of close to each other pixels which colors differ in both images).
398 // It filters alone pixels with different color.
399 Standard_Size aRow1, aCol1, aRow2, aCol2;
400 Standard_Integer aLen1 = (myDiffPixels.Length() > 0) ? (myDiffPixels.Length() - 1) : 0;
401 for (Standard_Integer aPixelId1 = 0; aPixelId1 < aLen1; ++aPixelId1)
402 {
403 const Standard_Size aValue1 = myDiffPixels.Value (aPixelId1);
404 int2Pixel (aValue1, aRow1, aCol1);
405
406 // Check other pixels in the list looking for a neighbour of this one
407 for (Standard_Integer aPixelId2 = aPixelId1 + 1; aPixelId2 < myDiffPixels.Length(); ++aPixelId2)
408 {
409 const Standard_Size aValue2 = myDiffPixels.Value (aPixelId2);
410 int2Pixel (aValue2, aRow2, aCol2);
411 if (std::abs (ptrdiff_t (aCol1 - aCol2)) <= 1 &&
412 std::abs (ptrdiff_t (aRow1 - aRow2)) <= 1)
413 {
414 // A neighbour is found. Create a new group and add both pixels.
415 if (myGroupsOfDiffPixels.IsEmpty())
416 {
417 TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
418 aGroup->Add (aValue1);
419 aGroup->Add (aValue2);
420 myGroupsOfDiffPixels.Append (aGroup);
421 }
422 else
423 {
424 // Find a group the pixels belong to.
425 Standard_Boolean isFound = Standard_False;
426 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
427 {
428 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
429 if (aGroup->Contains (aValue1))
430 {
431 aGroup->Add (aValue2);
432 isFound = Standard_True;
433 break;
434 }
435 }
436
437 if (!isFound)
438 {
439 // Create a new group
440 TColStd_MapOfInteger* aGroup = new TColStd_MapOfInteger();
441 aGroup->Add (aValue1);
442 aGroup->Add (aValue2);
443 myGroupsOfDiffPixels.Append (aGroup);
444 }
445 }
446 }
447 }
448 }
449
450 // filter linear groups which represent border of a solid shape
451 Standard_Integer aGroupId = 1;
452 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
453 {
454 Standard_Integer aNeighboursNb = 0;
455 Standard_Boolean isLine = Standard_True;
456 const TColStd_MapOfInteger* aGroup = aGrIter.Value();
457 for (TColStd_MapIteratorOfMapOfInteger aPixelIter (*aGroup); aPixelIter.More(); aPixelIter.Next())
458 {
459 int2Pixel (aPixelIter.Key(), aRow1, aCol1);
460 aNeighboursNb = 0;
461
462 // pixels of a line have only 1 or 2 neighbour pixels inside the same group
463 // check all neighbour pixels on presence in the group
464 for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
465 {
466 if (aGroup->Contains (NEIGHBOR_PIXELS[aNgbrIter].pixel2Int (aRow1, aCol1)))
467 {
468 ++aNeighboursNb;
469 }
470 }
471
472 if (aNeighboursNb > 2)
473 {
474 isLine = Standard_False;
475 break;
476 }
477 } // for pixels inside group...
478
479 if (isLine)
480 {
481 // Test a pixel of the linear group on belonging to a solid shape.
482 // Consider neighbour pixels of the last pixel of the linear group in the 1st image.
483 // If the pixel has greater than 1 not black neighbour pixel, it is a border of a shape.
484 // Otherwise, it may be a topological edge, for example.
485 aNeighboursNb = 0;
486 for (Standard_Size aNgbrIter = 0; aNgbrIter < NEIGHBOR_PIXELS_NB; ++aNgbrIter)
487 {
488 if (!NEIGHBOR_PIXELS[aNgbrIter].isBlack (aDataRef, aRow1, aCol1))
489 {
490 ++aNeighboursNb;
491 }
492 }
493
494 if (aNeighboursNb > 1)
495 {
496 myLinearGroups.Add (aGroupId);
497 }
498 }
499 } // for groups...
500
501 // number of different groups of pixels (except linear groups)
502 Standard_Integer aNbDiffColors = 0;
503 aGroupId = 1;
504 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next(), ++aGroupId)
505 {
506 if (!myLinearGroups.Contains (aGroupId))
507 ++aNbDiffColors;
508 }
509
510 return aNbDiffColors;
511}
512
513// =======================================================================
514// function : releaseGroupsOfDiffPixels
515// purpose :
516// =======================================================================
517void Image_Diff::releaseGroupsOfDiffPixels()
518{
519 for (ListOfMapOfInteger::Iterator aGrIter (myGroupsOfDiffPixels); aGrIter.More(); aGrIter.Next())
520 {
521 TColStd_MapOfInteger*& aGroup = aGrIter.ChangeValue();
522 delete aGroup;
523 }
524 myGroupsOfDiffPixels.Clear();
525 myLinearGroups.Clear();
526}