ImageUtility2.h 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486
  1. // David Eberly, Geometric Tools, Redmond WA 98052
  2. // Copyright (c) 1998-2020
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // https://www.boost.org/LICENSE_1_0.txt
  5. // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
  6. // Version: 4.0.2019.08.13
  7. #pragma once
  8. #include <Mathematics/Image2.h>
  9. #include <cmath>
  10. #include <functional>
  11. #include <limits>
  12. // Image utilities for Image2<int> objects. TODO: Extend this to a template
  13. // class to allow the pixel type to be int*_t and uint*_t for * in
  14. // {8,16,32,64}.
  15. //
  16. // All but the Draw* functions are operations on binary images. Let the image
  17. // have d0 columns and d1 rows. The input image must have zeros on its
  18. // boundaries x = 0, x = d0-1, y = 0 and y = d1-1. The 0-valued pixels are
  19. // considered to be background. The 1-valued pixels are considered to be
  20. // foreground. In some of the operations, to save memory and time the input
  21. // image is modified by the algorithms. If you need to preserve the input
  22. // image, make a copy of it before calling these functions. Dilation and
  23. // erosion functions do not have the requirement that the boundary pixels of
  24. // the binary image inputs be zero.
  25. namespace WwiseGTE
  26. {
  27. class ImageUtility2
  28. {
  29. public:
  30. // Compute the 4-connected components of a binary image. The input
  31. // image is modified to avoid the cost of making a copy. On output,
  32. // the image values are the labels for the components. The array
  33. // components[k], k >= 1, contains the indices for the k-th component.
  34. static void GetComponents4(Image2<int>& image,
  35. std::vector<std::vector<size_t>>& components)
  36. {
  37. std::array<int, 4> neighbors;
  38. image.GetNeighborhood(neighbors);
  39. GetComponents(4, &neighbors[0], image, components);
  40. }
  41. // Compute the 8-connected components of a binary image. The input
  42. // image is modified to avoid the cost of making a copy. On output,
  43. // the image values are the labels for the components. The array
  44. // components[k], k >= 1, contains the indices for the k-th component.
  45. static void GetComponents8(Image2<int>& image,
  46. std::vector<std::vector<size_t>>& components)
  47. {
  48. std::array<int, 8> neighbors;
  49. image.GetNeighborhood(neighbors);
  50. GetComponents(8, &neighbors[0], image, components);
  51. }
  52. // Compute a dilation with a structuring element consisting of the
  53. // 4-connected neighbors of each pixel. The input image is binary
  54. // with 0 for background and 1 for foreground. The output image must
  55. // be an object different from the input image.
  56. static void Dilate4(Image2<int> const& input, Image2<int>& output)
  57. {
  58. std::array<std::array<int, 2>, 4> neighbors;
  59. input.GetNeighborhood(neighbors);
  60. Dilate(input, 4, &neighbors[0], output);
  61. }
  62. // Compute a dilation with a structuring element consisting of the
  63. // 8-connected neighbors of each pixel. The input image is binary
  64. // with 0 for background and 1 for foreground. The output image must
  65. // be an object different from the input image.
  66. static void Dilate8(Image2<int> const& input, Image2<int>& output)
  67. {
  68. std::array<std::array<int, 2>, 8> neighbors;
  69. input.GetNeighborhood(neighbors);
  70. Dilate(input, 8, &neighbors[0], output);
  71. }
  72. // Compute a dilation with a structing element consisting of neighbors
  73. // specified by offsets relative to the pixel. The input image is
  74. // binary with 0 for background and 1 for foreground. The output
  75. // image must be an object different from the input image.
  76. static void Dilate(Image2<int> const& input, int numNeighbors,
  77. std::array<int, 2> const* neighbors, Image2<int>& output)
  78. {
  79. LogAssert(&output != &input, "Input and output must be different.");
  80. output = input;
  81. // If the pixel at (x,y) is 1, then the pixels at (x+dx,y+dy) are
  82. // set to 1 where (dx,dy) is in the 'neighbors' array. Boundary
  83. // testing is used to avoid accessing out-of-range pixels.
  84. int const dim0 = input.GetDimension(0);
  85. int const dim1 = input.GetDimension(1);
  86. for (int y = 0; y < dim1; ++y)
  87. {
  88. for (int x = 0; x < dim0; ++x)
  89. {
  90. if (input(x, y) == 1)
  91. {
  92. for (int j = 0; j < numNeighbors; ++j)
  93. {
  94. int xNbr = x + neighbors[j][0];
  95. int yNbr = y + neighbors[j][1];
  96. if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1)
  97. {
  98. output(xNbr, yNbr) = 1;
  99. }
  100. }
  101. }
  102. }
  103. }
  104. }
  105. // Compute an erosion with a structuring element consisting of the
  106. // 4-connected neighbors of each pixel. The input image is binary
  107. // with 0 for background and 1 for foreground. The output image must
  108. // be an object different from the input image. If zeroExterior is
  109. // true, the image exterior is assumed to be 0, so 1-valued boundary
  110. // pixels are set to 0; otherwise, boundary pixels are set to 0 only
  111. // when they have neighboring image pixels that are 0.
  112. static void Erode4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
  113. {
  114. std::array<std::array<int, 2>, 4> neighbors;
  115. input.GetNeighborhood(neighbors);
  116. Erode(input, zeroExterior, 4, &neighbors[0], output);
  117. }
  118. // Compute an erosion with a structuring element consisting of the
  119. // 8-connected neighbors of each pixel. The input image is binary
  120. // with 0 for background and 1 for foreground. The output image must
  121. // be an object different from the input image. If zeroExterior is
  122. // true, the image exterior is assumed to be 0, so 1-valued boundary
  123. // pixels are set to 0; otherwise, boundary pixels are set to 0 only
  124. // when they have neighboring image pixels that are 0.
  125. static void Erode8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
  126. {
  127. std::array<std::array<int, 2>, 8> neighbors;
  128. input.GetNeighborhood(neighbors);
  129. Erode(input, zeroExterior, 8, &neighbors[0], output);
  130. }
  131. // Compute an erosion with a structuring element consisting of
  132. // neighbors specified by offsets relative to the pixel. The input
  133. // image is binary with 0 for background and 1 for foreground. The
  134. // output image must be an object different from the input image. If
  135. // zeroExterior is true, the image exterior is assumed to be 0, so
  136. // 1-valued boundary pixels are set to 0; otherwise, boundary pixels
  137. // are set to 0 only when they have neighboring image pixels that
  138. // are 0.
  139. static void Erode(Image2<int> const& input, bool zeroExterior,
  140. int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
  141. {
  142. LogAssert(&output != &input, "Input and output must be different.");
  143. output = input;
  144. // If the pixel at (x,y) is 1, it is changed to 0 when at least
  145. // one neighbor (x+dx,y+dy) is 0, where (dx,dy) is in the
  146. // 'neighbors' array.
  147. int const dim0 = input.GetDimension(0);
  148. int const dim1 = input.GetDimension(1);
  149. for (int y = 0; y < dim1; ++y)
  150. {
  151. for (int x = 0; x < dim0; ++x)
  152. {
  153. if (input(x, y) == 1)
  154. {
  155. for (int j = 0; j < numNeighbors; ++j)
  156. {
  157. int xNbr = x + neighbors[j][0];
  158. int yNbr = y + neighbors[j][1];
  159. if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1)
  160. {
  161. if (input(xNbr, yNbr) == 0)
  162. {
  163. output(x, y) = 0;
  164. break;
  165. }
  166. }
  167. else if (zeroExterior)
  168. {
  169. output(x, y) = 0;
  170. break;
  171. }
  172. }
  173. }
  174. }
  175. }
  176. }
  177. // Compute an opening with a structuring element consisting of the
  178. // 4-connected neighbors of each pixel. The input image is binary
  179. // with 0 for background and 1 for foreground. The output image must
  180. // be an object different from the input image. If zeroExterior is
  181. // true, the image exterior is assumed to consist of 0-valued pixels;
  182. // otherwise, the image exterior is assumed to consist of 1-valued
  183. // pixels.
  184. static void Open4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
  185. {
  186. Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
  187. Erode4(input, zeroExterior, temp);
  188. Dilate4(temp, output);
  189. }
  190. // Compute an opening with a structuring element consisting of the
  191. // 8-connected neighbors of each pixel. The input image is binary
  192. // with 0 for background and 1 for foreground. The output image must
  193. // be an object different from the input image. If zeroExterior is
  194. // true, the image exterior is assumed to consist of 0-valued pixels;
  195. // otherwise, the image exterior is assumed to consist of 1-valued
  196. // pixels.
  197. static void Open8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
  198. {
  199. Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
  200. Erode8(input, zeroExterior, temp);
  201. Dilate8(temp, output);
  202. }
  203. // Compute an opening with a structuring element consisting of
  204. // neighbors specified by offsets relative to the pixel. The input
  205. // image is binary with 0 for background and 1 for foreground. The
  206. // output image must be an object different from the input image. If
  207. // zeroExterior is true, the image exterior is assumed to consist of
  208. // 0-valued pixels; otherwise, the image exterior is assumed to
  209. // consist of 1-valued pixels.
  210. static void Open(Image2<int> const& input, bool zeroExterior,
  211. int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
  212. {
  213. Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
  214. Erode(input, zeroExterior, numNeighbors, neighbors, temp);
  215. Dilate(temp, numNeighbors, neighbors, output);
  216. }
  217. // Compute a closing with a structuring element consisting of the
  218. // 4-connected neighbors of each pixel. The input image is binary
  219. // with 0 for background and 1 for foreground. The output image must
  220. // be an object different from the input image. If zeroExterior is
  221. // true, the image exterior is assumed to consist of 0-valued pixels;
  222. // otherwise, the image exterior is assumed to consist of 1-valued
  223. // pixels.
  224. static void Close4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
  225. {
  226. Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
  227. Dilate4(input, temp);
  228. Erode4(temp, zeroExterior, output);
  229. }
  230. // Compute a closing with a structuring element consisting of the
  231. // 8-connected neighbors of each pixel. The input image is binary
  232. // with 0 for background and 1 for foreground. The output image must
  233. // be an object different from the input image. If zeroExterior is
  234. // true, the image exterior is assumed to consist of 0-valued pixels;
  235. // otherwise, the image exterior is assumed to consist of 1-valued
  236. // pixels.
  237. static void Close8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
  238. {
  239. Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
  240. Dilate8(input, temp);
  241. Erode8(temp, zeroExterior, output);
  242. }
  243. // Compute a closing with a structuring element consisting of
  244. // neighbors specified by offsets relative to the pixel. The input
  245. // image is binary with 0 for background and 1 for foreground. The
  246. // output image must be an object different from the input image. If
  247. // zeroExterior is true, the image exterior is assumed to consist of
  248. // 0-valued pixels; otherwise, the image exterior is assumed to
  249. // consist of 1-valued pixels.
  250. static void Close(Image2<int> const& input, bool zeroExterior,
  251. int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
  252. {
  253. Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
  254. Dilate(input, numNeighbors, neighbors, temp);
  255. Erode(temp, zeroExterior, numNeighbors, neighbors, output);
  256. }
  257. // Locate a pixel and walk around the edge of a component. The input
  258. // (x,y) is where the search starts for a nonzero pixel. If (x,y) is
  259. // outside the component, the walk is around the outside the
  260. // component. If the component has a hole and (x,y) is inside that
  261. // hole, the walk is around the boundary surrounding the hole. The
  262. // function returns 'true' on a success walk. The return value is
  263. // 'false' when no boundary was found from the starting (x,y).
  264. static bool ExtractBoundary(int x, int y, Image2<int>& image, std::vector<size_t>& boundary)
  265. {
  266. // Find a first boundary pixel.
  267. size_t const numPixels = image.GetNumPixels();
  268. size_t i;
  269. for (i = image.GetIndex(x, y); i < numPixels; ++i)
  270. {
  271. if (image[i])
  272. {
  273. break;
  274. }
  275. }
  276. if (i == numPixels)
  277. {
  278. // No boundary pixel found.
  279. return false;
  280. }
  281. std::array<int, 8> const dx = { -1, 0, +1, +1, +1, 0, -1, -1 };
  282. std::array<int, 8> const dy = { -1, -1, -1, 0, +1, +1, +1, 0 };
  283. // Create a new point list that contains the first boundary point.
  284. boundary.push_back(i);
  285. // The direction from background 0 to boundary pixel 1 is
  286. // (dx[7],dy[7]).
  287. std::array<int, 2> coord = image.GetCoordinates(i);
  288. int x0 = coord[0], y0 = coord[1];
  289. int cx = x0, cy = y0;
  290. int nx = x0 - 1, ny = y0, dir = 7;
  291. // Traverse the boundary in clockwise order. Mark visited pixels
  292. // as 2.
  293. image(cx, cy) = 2;
  294. bool notDone = true;
  295. while (notDone)
  296. {
  297. int j, nbr;
  298. for (j = 0, nbr = dir; j < 8; ++j, nbr = (nbr + 1) % 8)
  299. {
  300. nx = cx + dx[nbr];
  301. ny = cy + dy[nbr];
  302. if (image(nx, ny)) // next boundary pixel found
  303. {
  304. break;
  305. }
  306. }
  307. if (j == 8)
  308. {
  309. // (cx,cy) is isolated
  310. notDone = false;
  311. continue;
  312. }
  313. if (nx == x0 && ny == y0)
  314. {
  315. // boundary traversal completed
  316. notDone = false;
  317. continue;
  318. }
  319. // (nx,ny) is next boundary point, add point to list. Note
  320. // that the index for the pixel is computed for the original
  321. // image, not for the larger temporary image.
  322. boundary.push_back(image.GetIndex(nx, ny));
  323. // Mark visited pixels as 2.
  324. image(nx, ny) = 2;
  325. // Start search for next point.
  326. cx = nx;
  327. cy = ny;
  328. dir = (j + 5 + dir) % 8;
  329. }
  330. return true;
  331. }
  332. // Use a depth-first search for filling a 4-connected region. This is
  333. // nonrecursive, simulated by using a heap-allocated "stack". The
  334. // input (x,y) is the seed point that starts the fill.
  335. template <typename PixelType>
  336. static void FloodFill4(Image2<PixelType>& image, int x, int y,
  337. PixelType foreColor, PixelType backColor)
  338. {
  339. // Test for a valid seed.
  340. int const dim0 = image.GetDimension(0);
  341. int const dim1 = image.GetDimension(1);
  342. if (x < 0 || x >= dim0 || y < 0 || y >= dim1)
  343. {
  344. // The seed point is outside the image domain, so there is
  345. // nothing to fill.
  346. return;
  347. }
  348. // Allocate the maximum amount of space needed for the stack.
  349. // An empty stack has top == -1.
  350. size_t const numPixels = image.GetNumPixels();
  351. std::vector<int> xStack(numPixels), yStack(numPixels);
  352. // Push seed point onto stack if it has the background color. All
  353. // points pushed onto stack have background color backColor.
  354. int top = 0;
  355. xStack[top] = x;
  356. yStack[top] = y;
  357. while (top >= 0) // stack is not empty
  358. {
  359. // Read top of stack. Do not pop since we need to return to
  360. // this top value later to restart the fill in a different
  361. // direction.
  362. x = xStack[top];
  363. y = yStack[top];
  364. // Fill the pixel.
  365. image(x, y) = foreColor;
  366. int xp1 = x + 1;
  367. if (xp1 < dim0 && image(xp1, y) == backColor)
  368. {
  369. // Push pixel with background color.
  370. ++top;
  371. xStack[top] = xp1;
  372. yStack[top] = y;
  373. continue;
  374. }
  375. int xm1 = x - 1;
  376. if (0 <= xm1 && image(xm1, y) == backColor)
  377. {
  378. // Push pixel with background color.
  379. ++top;
  380. xStack[top] = xm1;
  381. yStack[top] = y;
  382. continue;
  383. }
  384. int yp1 = y + 1;
  385. if (yp1 < dim1 && image(x, yp1) == backColor)
  386. {
  387. // Push pixel with background color.
  388. ++top;
  389. xStack[top] = x;
  390. yStack[top] = yp1;
  391. continue;
  392. }
  393. int ym1 = y - 1;
  394. if (0 <= ym1 && image(x, ym1) == backColor)
  395. {
  396. // Push pixel with background color.
  397. ++top;
  398. xStack[top] = x;
  399. yStack[top] = ym1;
  400. continue;
  401. }
  402. // Done in all directions, pop and return to search other
  403. // directions of predecessor.
  404. --top;
  405. }
  406. }
  407. // Compute the L1-distance transform of the binary image. The function
  408. // returns the maximum distance and a point at which the maximum
  409. // distance is attained.
  410. static void GetL1Distance(Image2<int>& image, int& maxDistance, int& xMax, int& yMax)
  411. {
  412. int const dim0 = image.GetDimension(0);
  413. int const dim1 = image.GetDimension(1);
  414. int const dim0m1 = dim0 - 1;
  415. int const dim1m1 = dim1 - 1;
  416. // Use a grass-fire approach, computing distance from boundary to
  417. // interior one pass at a time.
  418. bool changeMade = true;
  419. int distance;
  420. for (distance = 1, xMax = 0, yMax = 0; changeMade; ++distance)
  421. {
  422. changeMade = false;
  423. int distanceP1 = distance + 1;
  424. for (int y = 1; y < dim1m1; ++y)
  425. {
  426. for (int x = 1; x < dim0m1; ++x)
  427. {
  428. if (image(x, y) == distance)
  429. {
  430. if (image(x - 1, y) >= distance
  431. && image(x + 1, y) >= distance
  432. && image(x, y - 1) >= distance
  433. && image(x, y + 1) >= distance)
  434. {
  435. image(x, y) = distanceP1;
  436. xMax = x;
  437. yMax = y;
  438. changeMade = true;
  439. }
  440. }
  441. }
  442. }
  443. }
  444. maxDistance = --distance;
  445. }
  446. // Compute the L2-distance transform of the binary image. The maximum
  447. // distance should not be larger than 100, so you have to ensure this
  448. // is the case for the input image. The function returns the maximum
  449. // distance and a point at which the maximum distance is attained.
  450. // Comments about the algorithm are in the source file.
  451. static void GetL2Distance(Image2<int> const& image, float& maxDistance,
  452. int& xMax, int& yMax, Image2<float>& transform)
  453. {
  454. // This program calculates the Euclidean distance transform of a
  455. // binary input image. The adaptive algorithm is guaranteed to
  456. // give exact distances for all distances < 100. The algorithm
  457. // was provided John Gauch at University of Kansas. The following
  458. // is a quote:
  459. ///
  460. // The basic idea is similar to a EDT described recently in PAMI
  461. // by Laymarie from McGill. By keeping the dx and dy offset to
  462. // the nearest edge (feature) point in the image, we can search to
  463. // see which dx dy is closest to a given point by examining a set
  464. // of neighbors. The Laymarie method (and Borgfors) look at a
  465. // fixed 3x3 or 5x5 neighborhood and call it a day. What we did
  466. // was calculate (painfully) what neighborhoods you need to look
  467. // at to guarentee that the exact distance is obtained. Thus,
  468. // you will see in the code, that we L2Check the current distance
  469. // and depending on what we have so far, we extend the search
  470. // region. Since our algorithm for L2Checking the exactness of
  471. // each neighborhood is on the order N^4, we have only gone to
  472. // N=100. In theory, you could make this large enough to get all
  473. // distances exact. We have implemented the algorithm to get all
  474. // distances < 100 to be exact.
  475. int const dim0 = image.GetDimension(0);
  476. int const dim1 = image.GetDimension(1);
  477. int const dim0m1 = dim0 - 1;
  478. int const dim1m1 = dim1 - 1;
  479. int x, y, distance;
  480. // Create and initialize intermediate images.
  481. Image2<int> xNear(dim0, dim1);
  482. Image2<int> yNear(dim0, dim1);
  483. Image2<int> dist(dim0, dim1);
  484. for (y = 0; y < dim1; ++y)
  485. {
  486. for (x = 0; x < dim0; ++x)
  487. {
  488. if (image(x, y) != 0)
  489. {
  490. xNear(x, y) = 0;
  491. yNear(x, y) = 0;
  492. dist(x, y) = std::numeric_limits<int>::max();
  493. }
  494. else
  495. {
  496. xNear(x, y) = x;
  497. yNear(x, y) = y;
  498. dist(x, y) = 0;
  499. }
  500. }
  501. }
  502. int const K1 = 1;
  503. int const K2 = 169; // 13^2
  504. int const K3 = 961; // 31^2
  505. int const K4 = 2401; // 49^2
  506. int const K5 = 5184; // 72^2
  507. // Pass in the ++ direction.
  508. for (y = 0; y < dim1; ++y)
  509. {
  510. for (x = 0; x < dim0; ++x)
  511. {
  512. distance = dist(x, y);
  513. if (distance > K1)
  514. {
  515. L2Check(x, y, -1, 0, xNear, yNear, dist);
  516. L2Check(x, y, -1, -1, xNear, yNear, dist);
  517. L2Check(x, y, 0, -1, xNear, yNear, dist);
  518. }
  519. if (distance > K2)
  520. {
  521. L2Check(x, y, -2, -1, xNear, yNear, dist);
  522. L2Check(x, y, -1, -2, xNear, yNear, dist);
  523. }
  524. if (distance > K3)
  525. {
  526. L2Check(x, y, -3, -1, xNear, yNear, dist);
  527. L2Check(x, y, -3, -2, xNear, yNear, dist);
  528. L2Check(x, y, -2, -3, xNear, yNear, dist);
  529. L2Check(x, y, -1, -3, xNear, yNear, dist);
  530. }
  531. if (distance > K4)
  532. {
  533. L2Check(x, y, -4, -1, xNear, yNear, dist);
  534. L2Check(x, y, -4, -3, xNear, yNear, dist);
  535. L2Check(x, y, -3, -4, xNear, yNear, dist);
  536. L2Check(x, y, -1, -4, xNear, yNear, dist);
  537. }
  538. if (distance > K5)
  539. {
  540. L2Check(x, y, -5, -1, xNear, yNear, dist);
  541. L2Check(x, y, -5, -2, xNear, yNear, dist);
  542. L2Check(x, y, -5, -3, xNear, yNear, dist);
  543. L2Check(x, y, -5, -4, xNear, yNear, dist);
  544. L2Check(x, y, -4, -5, xNear, yNear, dist);
  545. L2Check(x, y, -2, -5, xNear, yNear, dist);
  546. L2Check(x, y, -3, -5, xNear, yNear, dist);
  547. L2Check(x, y, -1, -5, xNear, yNear, dist);
  548. }
  549. }
  550. }
  551. // Pass in -- direction.
  552. for (y = dim1m1; y >= 0; --y)
  553. {
  554. for (x = dim0m1; x >= 0; --x)
  555. {
  556. distance = dist(x, y);
  557. if (distance > K1)
  558. {
  559. L2Check(x, y, 1, 0, xNear, yNear, dist);
  560. L2Check(x, y, 1, 1, xNear, yNear, dist);
  561. L2Check(x, y, 0, 1, xNear, yNear, dist);
  562. }
  563. if (distance > K2)
  564. {
  565. L2Check(x, y, 2, 1, xNear, yNear, dist);
  566. L2Check(x, y, 1, 2, xNear, yNear, dist);
  567. }
  568. if (distance > K3)
  569. {
  570. L2Check(x, y, 3, 1, xNear, yNear, dist);
  571. L2Check(x, y, 3, 2, xNear, yNear, dist);
  572. L2Check(x, y, 2, 3, xNear, yNear, dist);
  573. L2Check(x, y, 1, 3, xNear, yNear, dist);
  574. }
  575. if (distance > K4)
  576. {
  577. L2Check(x, y, 4, 1, xNear, yNear, dist);
  578. L2Check(x, y, 4, 3, xNear, yNear, dist);
  579. L2Check(x, y, 3, 4, xNear, yNear, dist);
  580. L2Check(x, y, 1, 4, xNear, yNear, dist);
  581. }
  582. if (distance > K5)
  583. {
  584. L2Check(x, y, 5, 1, xNear, yNear, dist);
  585. L2Check(x, y, 5, 2, xNear, yNear, dist);
  586. L2Check(x, y, 5, 3, xNear, yNear, dist);
  587. L2Check(x, y, 5, 4, xNear, yNear, dist);
  588. L2Check(x, y, 4, 5, xNear, yNear, dist);
  589. L2Check(x, y, 2, 5, xNear, yNear, dist);
  590. L2Check(x, y, 3, 5, xNear, yNear, dist);
  591. L2Check(x, y, 1, 5, xNear, yNear, dist);
  592. }
  593. }
  594. }
  595. // Pass in the +- direction.
  596. for (y = dim1m1; y >= 0; --y)
  597. {
  598. for (x = 0; x < dim0; ++x)
  599. {
  600. distance = dist(x, y);
  601. if (distance > K1)
  602. {
  603. L2Check(x, y, -1, 0, xNear, yNear, dist);
  604. L2Check(x, y, -1, 1, xNear, yNear, dist);
  605. L2Check(x, y, 0, 1, xNear, yNear, dist);
  606. }
  607. if (distance > K2)
  608. {
  609. L2Check(x, y, -2, 1, xNear, yNear, dist);
  610. L2Check(x, y, -1, 2, xNear, yNear, dist);
  611. }
  612. if (distance > K3)
  613. {
  614. L2Check(x, y, -3, 1, xNear, yNear, dist);
  615. L2Check(x, y, -3, 2, xNear, yNear, dist);
  616. L2Check(x, y, -2, 3, xNear, yNear, dist);
  617. L2Check(x, y, -1, 3, xNear, yNear, dist);
  618. }
  619. if (distance > K4)
  620. {
  621. L2Check(x, y, -4, 1, xNear, yNear, dist);
  622. L2Check(x, y, -4, 3, xNear, yNear, dist);
  623. L2Check(x, y, -3, 4, xNear, yNear, dist);
  624. L2Check(x, y, -1, 4, xNear, yNear, dist);
  625. }
  626. if (distance > K5)
  627. {
  628. L2Check(x, y, -5, 1, xNear, yNear, dist);
  629. L2Check(x, y, -5, 2, xNear, yNear, dist);
  630. L2Check(x, y, -5, 3, xNear, yNear, dist);
  631. L2Check(x, y, -5, 4, xNear, yNear, dist);
  632. L2Check(x, y, -4, 5, xNear, yNear, dist);
  633. L2Check(x, y, -2, 5, xNear, yNear, dist);
  634. L2Check(x, y, -3, 5, xNear, yNear, dist);
  635. L2Check(x, y, -1, 5, xNear, yNear, dist);
  636. }
  637. }
  638. }
  639. // Pass in the -+ direction.
  640. for (y = 0; y < dim1; ++y)
  641. {
  642. for (x = dim0m1; x >= 0; --x)
  643. {
  644. distance = dist(x, y);
  645. if (distance > K1)
  646. {
  647. L2Check(x, y, 1, 0, xNear, yNear, dist);
  648. L2Check(x, y, 1, -1, xNear, yNear, dist);
  649. L2Check(x, y, 0, -1, xNear, yNear, dist);
  650. }
  651. if (distance > K2)
  652. {
  653. L2Check(x, y, 2, -1, xNear, yNear, dist);
  654. L2Check(x, y, 1, -2, xNear, yNear, dist);
  655. }
  656. if (distance > K3)
  657. {
  658. L2Check(x, y, 3, -1, xNear, yNear, dist);
  659. L2Check(x, y, 3, -2, xNear, yNear, dist);
  660. L2Check(x, y, 2, -3, xNear, yNear, dist);
  661. L2Check(x, y, 1, -3, xNear, yNear, dist);
  662. }
  663. if (distance > K4)
  664. {
  665. L2Check(x, y, 4, -1, xNear, yNear, dist);
  666. L2Check(x, y, 4, -3, xNear, yNear, dist);
  667. L2Check(x, y, 3, -4, xNear, yNear, dist);
  668. L2Check(x, y, 1, -4, xNear, yNear, dist);
  669. }
  670. if (distance > K5)
  671. {
  672. L2Check(x, y, 5, -1, xNear, yNear, dist);
  673. L2Check(x, y, 5, -2, xNear, yNear, dist);
  674. L2Check(x, y, 5, -3, xNear, yNear, dist);
  675. L2Check(x, y, 5, -4, xNear, yNear, dist);
  676. L2Check(x, y, 4, -5, xNear, yNear, dist);
  677. L2Check(x, y, 2, -5, xNear, yNear, dist);
  678. L2Check(x, y, 3, -5, xNear, yNear, dist);
  679. L2Check(x, y, 1, -5, xNear, yNear, dist);
  680. }
  681. }
  682. }
  683. xMax = 0;
  684. yMax = 0;
  685. maxDistance = 0.0f;
  686. for (y = 0; y < dim1; ++y)
  687. {
  688. for (x = 0; x < dim0; ++x)
  689. {
  690. float fdistance = std::sqrt((float)dist(x, y));
  691. if (fdistance > maxDistance)
  692. {
  693. maxDistance = fdistance;
  694. xMax = x;
  695. yMax = y;
  696. }
  697. transform(x, y) = fdistance;
  698. }
  699. }
  700. }
  701. // Compute a skeleton of a binary image. Boundary pixels are trimmed
  702. // from the object one layer at a time based on their adjacency to
  703. // interior pixels. At each step the connectivity and cycles of the
  704. // object are preserved. The skeleton overwrites the contents of the
  705. // input image.
  706. static void GetSkeleton(Image2<int>& image)
  707. {
  708. int const dim0 = image.GetDimension(0);
  709. int const dim1 = image.GetDimension(1);
  710. // Trim pixels, mark interior as 4.
  711. bool notDone = true;
  712. while (notDone)
  713. {
  714. if (MarkInterior(image, 4, Interior4))
  715. {
  716. // No interior pixels, trimmed set is at most 2-pixels
  717. // thick.
  718. notDone = false;
  719. continue;
  720. }
  721. if (ClearInteriorAdjacent(image, 4))
  722. {
  723. // All remaining interior pixels are either articulation
  724. // points or part of blobs whose boundary pixels are all
  725. // articulation points. An example of the latter case is
  726. // shown below. The background pixels are marked with '.'
  727. // rather than '0' for readability. The interior pixels
  728. // are marked with '4' and the boundary pixels are marked
  729. // with '1'.
  730. //
  731. // .........
  732. // .....1...
  733. // ..1.1.1..
  734. // .1.141...
  735. // ..14441..
  736. // ..1441.1.
  737. // .1.11.1..
  738. // ..1..1...
  739. // .........
  740. //
  741. // This is a pathological problem where there are many
  742. // small holes (0-pixel with north, south, west, and east
  743. // neighbors all 1-pixels) that your application can try
  744. // to avoid by an initial pass over the image to fill in
  745. // such holes. Of course, you do have problems with
  746. // checkerboard patterns...
  747. notDone = false;
  748. continue;
  749. }
  750. }
  751. // Trim pixels, mark interior as 3.
  752. notDone = true;
  753. while (notDone)
  754. {
  755. if (MarkInterior(image, 3, Interior3))
  756. {
  757. // No interior pixels, trimmed set is at most 2-pixels
  758. // thick.
  759. notDone = false;
  760. continue;
  761. }
  762. if (ClearInteriorAdjacent(image, 3))
  763. {
  764. // All remaining 3-values can be safely removed since they
  765. // are not articulation points and the removal will not
  766. // cause new holes.
  767. for (int y = 0; y < dim1; ++y)
  768. {
  769. for (int x = 0; x < dim0; ++x)
  770. {
  771. if (image(x, y) == 3 && !IsArticulation(image, x, y))
  772. {
  773. image(x, y) = 0;
  774. }
  775. }
  776. }
  777. notDone = false;
  778. continue;
  779. }
  780. }
  781. // Trim pixels, mark interior as 2.
  782. notDone = true;
  783. while (notDone)
  784. {
  785. if (MarkInterior(image, 2, Interior2))
  786. {
  787. // No interior pixels, trimmed set is at most 1-pixel
  788. // thick. Call it a skeleton.
  789. notDone = false;
  790. continue;
  791. }
  792. if (ClearInteriorAdjacent(image, 2))
  793. {
  794. // Removes 2-values that are not articulation points.
  795. for (int y = 0; y < dim1; ++y)
  796. {
  797. for (int x = 0; x < dim0; ++x)
  798. {
  799. if (image(x, y) == 2 && !IsArticulation(image, x, y))
  800. {
  801. image(x, y) = 0;
  802. }
  803. }
  804. }
  805. notDone = false;
  806. continue;
  807. }
  808. }
  809. // Make the skeleton a binary image.
  810. size_t const numPixels = image.GetNumPixels();
  811. for (size_t i = 0; i < numPixels; ++i)
  812. {
  813. if (image[i] != 0)
  814. {
  815. image[i] = 1;
  816. }
  817. }
  818. }
  819. // In the remaining public member functions, the callback represents
  820. // the action you want applied to each pixel as it is visited.
  821. // Visit pixels in a (2*thick+1)x(2*thick+1) square centered at (x,y).
  822. static void DrawThickPixel(int x, int y, int thick,
  823. std::function<void(int, int)> const& callback)
  824. {
  825. for (int dy = -thick; dy <= thick; ++dy)
  826. {
  827. for (int dx = -thick; dx <= thick; ++dx)
  828. {
  829. callback(x + dx, y + dy);
  830. }
  831. }
  832. }
  833. // Visit pixels using Bresenham's line drawing algorithm.
  834. static void DrawLine(int x0, int y0, int x1, int y1,
  835. std::function<void(int, int)> const& callback)
  836. {
  837. // Starting point of line.
  838. int x = x0, y = y0;
  839. // Direction of line.
  840. int dx = x1 - x0, dy = y1 - y0;
  841. // Increment or decrement depending on direction of line.
  842. int sx = (dx > 0 ? 1 : (dx < 0 ? -1 : 0));
  843. int sy = (dy > 0 ? 1 : (dy < 0 ? -1 : 0));
  844. // Decision parameters for pixel selection.
  845. if (dx < 0)
  846. {
  847. dx = -dx;
  848. }
  849. if (dy < 0)
  850. {
  851. dy = -dy;
  852. }
  853. int ax = 2 * dx, ay = 2 * dy;
  854. int decX, decY;
  855. // Determine largest direction component, single-step related
  856. // variable.
  857. int maxValue = dx, var = 0;
  858. if (dy > maxValue)
  859. {
  860. var = 1;
  861. }
  862. // Traverse Bresenham line.
  863. switch (var)
  864. {
  865. case 0: // Single-step in x-direction.
  866. decY = ay - dx;
  867. for (/**/; /**/; x += sx, decY += ay)
  868. {
  869. callback(x, y);
  870. // Take Bresenham step.
  871. if (x == x1)
  872. {
  873. break;
  874. }
  875. if (decY >= 0)
  876. {
  877. decY -= ax;
  878. y += sy;
  879. }
  880. }
  881. break;
  882. case 1: // Single-step in y-direction.
  883. decX = ax - dy;
  884. for (/**/; /**/; y += sy, decX += ax)
  885. {
  886. callback(x, y);
  887. // Take Bresenham step.
  888. if (y == y1)
  889. {
  890. break;
  891. }
  892. if (decX >= 0)
  893. {
  894. decX -= ay;
  895. x += sx;
  896. }
  897. }
  898. break;
  899. }
  900. }
  901. // Visit pixels using Bresenham's circle drawing algorithm. Set
  902. // 'solid' to false for drawing only the circle. Set 'solid' to true
  903. // to draw all pixels on and inside the circle.
  904. static void DrawCircle(int xCenter, int yCenter, int radius, bool solid,
  905. std::function<void(int, int)> const& callback)
  906. {
  907. int x, y, dec;
  908. if (solid)
  909. {
  910. int xValue, yMin, yMax, i;
  911. for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x)
  912. {
  913. xValue = xCenter + x;
  914. yMin = yCenter - y;
  915. yMax = yCenter + y;
  916. for (i = yMin; i <= yMax; ++i)
  917. {
  918. callback(xValue, i);
  919. }
  920. xValue = xCenter - x;
  921. for (i = yMin; i <= yMax; ++i)
  922. {
  923. callback(xValue, i);
  924. }
  925. xValue = xCenter + y;
  926. yMin = yCenter - x;
  927. yMax = yCenter + x;
  928. for (i = yMin; i <= yMax; ++i)
  929. {
  930. callback(xValue, i);
  931. }
  932. xValue = xCenter - y;
  933. for (i = yMin; i <= yMax; ++i)
  934. {
  935. callback(xValue, i);
  936. }
  937. if (dec >= 0)
  938. {
  939. dec += -4 * (y--) + 4;
  940. }
  941. dec += 4 * x + 6;
  942. }
  943. }
  944. else
  945. {
  946. for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x)
  947. {
  948. callback(xCenter + x, yCenter + y);
  949. callback(xCenter + x, yCenter - y);
  950. callback(xCenter - x, yCenter + y);
  951. callback(xCenter - x, yCenter - y);
  952. callback(xCenter + y, yCenter + x);
  953. callback(xCenter + y, yCenter - x);
  954. callback(xCenter - y, yCenter + x);
  955. callback(xCenter - y, yCenter - x);
  956. if (dec >= 0)
  957. {
  958. dec += -4 * (y--) + 4;
  959. }
  960. dec += 4 * x + 6;
  961. }
  962. }
  963. }
  964. // Visit pixels in a rectangle of the specified dimensions. Set
  965. // 'solid' to false for drawing only the rectangle. Set 'solid' to
  966. // true to draw all pixels on and inside the rectangle.
  967. static void DrawRectangle(int xMin, int yMin, int xMax, int yMax,
  968. bool solid, std::function<void(int, int)> const& callback)
  969. {
  970. int x, y;
  971. if (solid)
  972. {
  973. for (y = yMin; y <= yMax; ++y)
  974. {
  975. for (x = xMin; x <= xMax; ++x)
  976. {
  977. callback(x, y);
  978. }
  979. }
  980. }
  981. else
  982. {
  983. for (x = xMin; x <= xMax; ++x)
  984. {
  985. callback(x, yMin);
  986. callback(x, yMax);
  987. }
  988. for (y = yMin + 1; y <= yMax - 1; ++y)
  989. {
  990. callback(xMin, y);
  991. callback(xMax, y);
  992. }
  993. }
  994. }
  995. // Visit the pixels using Bresenham's algorithm for the axis-aligned
  996. // ellipse ((x-xc)/a)^2 + ((y-yc)/b)^2 = 1, where xCenter is xc,
  997. // yCenter is yc, xExtent is a, and yExtent is b.
  998. static void DrawEllipse(int xCenter, int yCenter, int xExtent, int yExtent,
  999. std::function<void(int, int)> const& callback)
  1000. {
  1001. int xExtSqr = xExtent * xExtent, yExtSqr = yExtent * yExtent;
  1002. int x, y, dec;
  1003. x = 0;
  1004. y = yExtent;
  1005. dec = 2 * yExtSqr + xExtSqr * (1 - 2 * yExtent);
  1006. for (/**/; yExtSqr * x <= xExtSqr * y; ++x)
  1007. {
  1008. callback(xCenter + x, yCenter + y);
  1009. callback(xCenter - x, yCenter + y);
  1010. callback(xCenter + x, yCenter - y);
  1011. callback(xCenter - x, yCenter - y);
  1012. if (dec >= 0)
  1013. {
  1014. dec += 4 * xExtSqr * (1 - y);
  1015. --y;
  1016. }
  1017. dec += yExtSqr * (4 * x + 6);
  1018. }
  1019. if (y == 0 && x < xExtent)
  1020. {
  1021. // The discretization caused us to reach the y-axis before the
  1022. // x-values reached the ellipse vertices. Draw a solid line
  1023. // along the x-axis to those vertices.
  1024. for (/**/; x <= xExtent; ++x)
  1025. {
  1026. callback(xCenter + x, yCenter);
  1027. callback(xCenter - x, yCenter);
  1028. }
  1029. return;
  1030. }
  1031. x = xExtent;
  1032. y = 0;
  1033. dec = 2 * xExtSqr + yExtSqr * (1 - 2 * xExtent);
  1034. for (/**/; xExtSqr * y <= yExtSqr * x; ++y)
  1035. {
  1036. callback(xCenter + x, yCenter + y);
  1037. callback(xCenter - x, yCenter + y);
  1038. callback(xCenter + x, yCenter - y);
  1039. callback(xCenter - x, yCenter - y);
  1040. if (dec >= 0)
  1041. {
  1042. dec += 4 * yExtSqr * (1 - x);
  1043. --x;
  1044. }
  1045. dec += xExtSqr * (4 * y + 6);
  1046. }
  1047. if (x == 0 && y < yExtent)
  1048. {
  1049. // The discretization caused us to reach the x-axis before the
  1050. // y-values reached the ellipse vertices. Draw a solid line
  1051. // along the y-axis to those vertices.
  1052. for (/**/; y <= yExtent; ++y)
  1053. {
  1054. callback(xCenter, yCenter + y);
  1055. callback(xCenter, yCenter - y);
  1056. }
  1057. }
  1058. }
  1059. // Use a depth-first search for filling a 4-connected region. This is
  1060. // nonrecursive, simulated by using a heap-allocated "stack". The
  1061. // input (x,y) is the seed point that starts the fill. The x-value is
  1062. // in {0..xSize-1} and the y-value is in {0..ySize-1}.
  1063. template <typename PixelType>
  1064. static void DrawFloodFill4(int x, int y, int xSize, int ySize,
  1065. PixelType foreColor, PixelType backColor,
  1066. std::function<void(int, int, PixelType)> const& setCallback,
  1067. std::function<PixelType(int, int)> const& getCallback)
  1068. {
  1069. // Test for a valid seed.
  1070. if (x < 0 || x >= xSize || y < 0 || y >= ySize)
  1071. {
  1072. // The seed point is outside the image domain, so nothing to
  1073. // fill.
  1074. return;
  1075. }
  1076. // Allocate the maximum amount of space needed for the stack. An
  1077. // empty stack has top == -1.
  1078. int const numPixels = xSize * ySize;
  1079. std::vector<int> xStack(numPixels), yStack(numPixels);
  1080. // Push seed point onto stack if it has the background color. All
  1081. // points pushed onto stack have background color backColor.
  1082. int top = 0;
  1083. xStack[top] = x;
  1084. yStack[top] = y;
  1085. while (top >= 0) // stack is not empty
  1086. {
  1087. // Read top of stack. Do not pop since we need to return to
  1088. // this top value later to restart the fill in a different
  1089. // direction.
  1090. x = xStack[top];
  1091. y = yStack[top];
  1092. // Fill the pixel.
  1093. setCallback(x, y, foreColor);
  1094. int xp1 = x + 1;
  1095. if (xp1 < xSize && getCallback(xp1, y) == backColor)
  1096. {
  1097. // Push pixel with background color.
  1098. ++top;
  1099. xStack[top] = xp1;
  1100. yStack[top] = y;
  1101. continue;
  1102. }
  1103. int xm1 = x - 1;
  1104. if (0 <= xm1 && getCallback(xm1, y) == backColor)
  1105. {
  1106. // Push pixel with background color.
  1107. ++top;
  1108. xStack[top] = xm1;
  1109. yStack[top] = y;
  1110. continue;
  1111. }
  1112. int yp1 = y + 1;
  1113. if (yp1 < ySize && getCallback(x, yp1) == backColor)
  1114. {
  1115. // Push pixel with background color.
  1116. ++top;
  1117. xStack[top] = x;
  1118. yStack[top] = yp1;
  1119. continue;
  1120. }
  1121. int ym1 = y - 1;
  1122. if (0 <= ym1 && getCallback(x, ym1) == backColor)
  1123. {
  1124. // Push pixel with background color.
  1125. ++top;
  1126. xStack[top] = x;
  1127. yStack[top] = ym1;
  1128. continue;
  1129. }
  1130. // Done in all directions, pop and return to search other
  1131. // directions of the predecessor.
  1132. --top;
  1133. }
  1134. }
  1135. private:
  1136. // Connected component labeling using depth-first search.
  1137. static void GetComponents(int numNeighbors, int const* delta,
  1138. Image2<int>& image, std::vector<std::vector<size_t>>& components)
  1139. {
  1140. size_t const numPixels = image.GetNumPixels();
  1141. std::vector<int> numElements(numPixels);
  1142. std::vector<size_t> vstack(numPixels);
  1143. size_t i, numComponents = 0;
  1144. int label = 2;
  1145. for (i = 0; i < numPixels; ++i)
  1146. {
  1147. if (image[i] == 1)
  1148. {
  1149. int top = -1;
  1150. vstack[++top] = i;
  1151. int& count = numElements[numComponents + 1];
  1152. count = 0;
  1153. while (top >= 0)
  1154. {
  1155. size_t v = vstack[top];
  1156. image[v] = -1;
  1157. int j;
  1158. for (j = 0; j < numNeighbors; ++j)
  1159. {
  1160. size_t adj = v + delta[j];
  1161. if (image[adj] == 1)
  1162. {
  1163. vstack[++top] = adj;
  1164. break;
  1165. }
  1166. }
  1167. if (j == numNeighbors)
  1168. {
  1169. image[v] = label;
  1170. ++count;
  1171. --top;
  1172. }
  1173. }
  1174. ++numComponents;
  1175. ++label;
  1176. }
  1177. }
  1178. if (numComponents > 0)
  1179. {
  1180. components.resize(numComponents + 1);
  1181. for (i = 1; i <= numComponents; ++i)
  1182. {
  1183. components[i].resize(numElements[i]);
  1184. numElements[i] = 0;
  1185. }
  1186. for (i = 0; i < numPixels; ++i)
  1187. {
  1188. int value = image[i];
  1189. if (value != 0)
  1190. {
  1191. // Labels started at 2 to support the depth-first
  1192. // search, so they need to be decremented for the
  1193. // correct labels.
  1194. image[i] = --value;
  1195. components[value][numElements[value]] = i;
  1196. ++numElements[value];
  1197. }
  1198. }
  1199. }
  1200. }
  1201. // Support for GetL2Distance.
  1202. static void L2Check(int x, int y, int dx, int dy, Image2<int>& xNear,
  1203. Image2<int>& yNear, Image2<int>& dist)
  1204. {
  1205. int const dim0 = dist.GetDimension(0);
  1206. int const dim1 = dist.GetDimension(1);
  1207. int xp = x + dx, yp = y + dy;
  1208. if (0 <= xp && xp < dim0 && 0 <= yp && yp < dim1)
  1209. {
  1210. if (dist(xp, yp) < dist(x, y))
  1211. {
  1212. int dx0 = xNear(xp, yp) - x;
  1213. int dy0 = yNear(xp, yp) - y;
  1214. int newDist = dx0 * dx0 + dy0 * dy0;
  1215. if (newDist < dist(x, y))
  1216. {
  1217. xNear(x, y) = xNear(xp, yp);
  1218. yNear(x, y) = yNear(xp, yp);
  1219. dist(x, y) = newDist;
  1220. }
  1221. }
  1222. }
  1223. }
  1224. // Support for GetSkeleton.
  1225. static bool Interior2(Image2<int>& image, int x, int y)
  1226. {
  1227. bool b1 = (image(x, y - 1) != 0);
  1228. bool b3 = (image(x + 1, y) != 0);
  1229. bool b5 = (image(x, y + 1) != 0);
  1230. bool b7 = (image(x - 1, y) != 0);
  1231. return (b1 && b3) || (b3 && b5) || (b5 && b7) || (b7 && b1);
  1232. }
  1233. static bool Interior3(Image2<int>& image, int x, int y)
  1234. {
  1235. int numNeighbors = 0;
  1236. if (image(x - 1, y) != 0)
  1237. {
  1238. ++numNeighbors;
  1239. }
  1240. if (image(x + 1, y) != 0)
  1241. {
  1242. ++numNeighbors;
  1243. }
  1244. if (image(x, y - 1) != 0)
  1245. {
  1246. ++numNeighbors;
  1247. }
  1248. if (image(x, y + 1) != 0)
  1249. {
  1250. ++numNeighbors;
  1251. }
  1252. return numNeighbors == 3;
  1253. }
  1254. static bool Interior4(Image2<int>& image, int x, int y)
  1255. {
  1256. return image(x - 1, y) != 0
  1257. && image(x + 1, y) != 0
  1258. && image(x, y - 1) != 0
  1259. && image(x, y + 1) != 0;
  1260. }
  1261. static bool MarkInterior(Image2<int>& image, int value,
  1262. bool (*function)(Image2<int>&, int, int))
  1263. {
  1264. int const dim0 = image.GetDimension(0);
  1265. int const dim1 = image.GetDimension(1);
  1266. bool noInterior = true;
  1267. for (int y = 0; y < dim1; ++y)
  1268. {
  1269. for (int x = 0; x < dim0; ++x)
  1270. {
  1271. if (image(x, y) > 0)
  1272. {
  1273. if (function(image, x, y))
  1274. {
  1275. image(x, y) = value;
  1276. noInterior = false;
  1277. }
  1278. else
  1279. {
  1280. image(x, y) = 1;
  1281. }
  1282. }
  1283. }
  1284. }
  1285. return noInterior;
  1286. }
  1287. static bool IsArticulation(Image2<int>& image, int x, int y)
  1288. {
  1289. static std::array<int, 256> const articulation =
  1290. {
  1291. 0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
  1292. 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
  1293. 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
  1294. 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
  1295. 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1296. 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1297. 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
  1298. 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
  1299. 0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,
  1300. 1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,
  1301. 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
  1302. 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
  1303. 0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,
  1304. 1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,
  1305. 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
  1306. 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0
  1307. };
  1308. // Converts 8 neighbors of pixel (x,y) to an 8-bit value,
  1309. // bit = 1 iff pixel is set.
  1310. int byteMask = 0;
  1311. if (image(x - 1, y - 1) != 0)
  1312. {
  1313. byteMask |= 0x01;
  1314. }
  1315. if (image(x, y - 1) != 0)
  1316. {
  1317. byteMask |= 0x02;
  1318. }
  1319. if (image(x + 1, y - 1) != 0)
  1320. {
  1321. byteMask |= 0x04;
  1322. }
  1323. if (image(x + 1, y) != 0)
  1324. {
  1325. byteMask |= 0x08;
  1326. }
  1327. if (image(x + 1, y + 1) != 0)
  1328. {
  1329. byteMask |= 0x10;
  1330. }
  1331. if (image(x, y + 1) != 0)
  1332. {
  1333. byteMask |= 0x20;
  1334. }
  1335. if (image(x - 1, y + 1) != 0)
  1336. {
  1337. byteMask |= 0x40;
  1338. }
  1339. if (image(x - 1, y) != 0)
  1340. {
  1341. byteMask |= 0x80;
  1342. }
  1343. return articulation[byteMask] == 1;
  1344. }
  1345. static bool ClearInteriorAdjacent(Image2<int>& image, int value)
  1346. {
  1347. int const dim0 = image.GetDimension(0);
  1348. int const dim1 = image.GetDimension(1);
  1349. bool noRemoval = true;
  1350. for (int y = 0; y < dim1; ++y)
  1351. {
  1352. for (int x = 0; x < dim0; ++x)
  1353. {
  1354. if (image(x, y) == 1)
  1355. {
  1356. bool interiorAdjacent =
  1357. image(x - 1, y - 1) == value ||
  1358. image(x, y - 1) == value ||
  1359. image(x + 1, y - 1) == value ||
  1360. image(x + 1, y) == value ||
  1361. image(x + 1, y + 1) == value ||
  1362. image(x, y + 1) == value ||
  1363. image(x - 1, y + 1) == value ||
  1364. image(x - 1, y) == value;
  1365. if (interiorAdjacent && !IsArticulation(image, x, y))
  1366. {
  1367. image(x, y) = 0;
  1368. noRemoval = false;
  1369. }
  1370. }
  1371. }
  1372. }
  1373. return noRemoval;
  1374. }
  1375. };
  1376. }