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 | |
27 | IMPLEMENT_STANDARD_HANDLE (Image_Diff, Standard_Transient) |
28 | IMPLEMENT_STANDARD_RTTIEXT(Image_Diff, Standard_Transient) |
29 | |
30 | //! Dot squared for difference of two colors |
31 | inline 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 |
41 | inline 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 |
49 | inline 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) |
56 | inline 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 | |
64 | namespace |
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 | // ======================================================================= |
109 | Image_Diff::Image_Diff() |
110 | : myColorTolerance (0.0), |
111 | myIsBorderFilterOn (Standard_False) |
112 | { |
113 | // |
114 | } |
115 | |
116 | // ======================================================================= |
117 | // function : ~Image_Diff |
118 | // purpose : |
119 | // ======================================================================= |
120 | Image_Diff::~Image_Diff() |
121 | { |
122 | releaseGroupsOfDiffPixels(); |
123 | } |
124 | |
125 | // ======================================================================= |
126 | // function : Init |
127 | // purpose : |
128 | // ======================================================================= |
129 | Standard_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 | // ======================================================================= |
193 | Standard_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 | // ======================================================================= |
212 | void Image_Diff::SetColorTolerance (const Standard_Real theTolerance) |
213 | { |
214 | myColorTolerance = theTolerance; |
215 | } |
216 | |
217 | // ======================================================================= |
218 | // function : ColorTolerance |
219 | // purpose : |
220 | // ======================================================================= |
221 | Standard_Real Image_Diff::ColorTolerance() const |
222 | { |
223 | return myColorTolerance; |
224 | } |
225 | |
226 | // ======================================================================= |
227 | // function : SetBorderFilterOn |
228 | // purpose : |
229 | // ======================================================================= |
230 | void Image_Diff::SetBorderFilterOn (const Standard_Boolean theToIgnore) |
231 | { |
232 | myIsBorderFilterOn = theToIgnore; |
233 | } |
234 | |
235 | // ======================================================================= |
236 | // function : IsBorderFilterOn |
237 | // purpose : |
238 | // ======================================================================= |
239 | Standard_Boolean Image_Diff::IsBorderFilterOn() const |
240 | { |
241 | return myIsBorderFilterOn; |
242 | } |
243 | |
244 | // ======================================================================= |
245 | // function : Compare |
246 | // purpose : |
247 | // ======================================================================= |
248 | Standard_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 | // ======================================================================= |
298 | Standard_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 | // ======================================================================= |
363 | Standard_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 | // ======================================================================= |
385 | Standard_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 | // ======================================================================= |
517 | void 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 | } |