1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486 |
- // David Eberly, Geometric Tools, Redmond WA 98052
- // Copyright (c) 1998-2020
- // Distributed under the Boost Software License, Version 1.0.
- // https://www.boost.org/LICENSE_1_0.txt
- // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
- // Version: 4.0.2019.08.13
- #pragma once
- #include <Mathematics/Image2.h>
- #include <cmath>
- #include <functional>
- #include <limits>
- // Image utilities for Image2<int> objects. TODO: Extend this to a template
- // class to allow the pixel type to be int*_t and uint*_t for * in
- // {8,16,32,64}.
- //
- // All but the Draw* functions are operations on binary images. Let the image
- // have d0 columns and d1 rows. The input image must have zeros on its
- // boundaries x = 0, x = d0-1, y = 0 and y = d1-1. The 0-valued pixels are
- // considered to be background. The 1-valued pixels are considered to be
- // foreground. In some of the operations, to save memory and time the input
- // image is modified by the algorithms. If you need to preserve the input
- // image, make a copy of it before calling these functions. Dilation and
- // erosion functions do not have the requirement that the boundary pixels of
- // the binary image inputs be zero.
- namespace WwiseGTE
- {
- class ImageUtility2
- {
- public:
- // Compute the 4-connected components of a binary image. The input
- // image is modified to avoid the cost of making a copy. On output,
- // the image values are the labels for the components. The array
- // components[k], k >= 1, contains the indices for the k-th component.
- static void GetComponents4(Image2<int>& image,
- std::vector<std::vector<size_t>>& components)
- {
- std::array<int, 4> neighbors;
- image.GetNeighborhood(neighbors);
- GetComponents(4, &neighbors[0], image, components);
- }
- // Compute the 8-connected components of a binary image. The input
- // image is modified to avoid the cost of making a copy. On output,
- // the image values are the labels for the components. The array
- // components[k], k >= 1, contains the indices for the k-th component.
- static void GetComponents8(Image2<int>& image,
- std::vector<std::vector<size_t>>& components)
- {
- std::array<int, 8> neighbors;
- image.GetNeighborhood(neighbors);
- GetComponents(8, &neighbors[0], image, components);
- }
- // Compute a dilation with a structuring element consisting of the
- // 4-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image.
- static void Dilate4(Image2<int> const& input, Image2<int>& output)
- {
- std::array<std::array<int, 2>, 4> neighbors;
- input.GetNeighborhood(neighbors);
- Dilate(input, 4, &neighbors[0], output);
- }
- // Compute a dilation with a structuring element consisting of the
- // 8-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image.
- static void Dilate8(Image2<int> const& input, Image2<int>& output)
- {
- std::array<std::array<int, 2>, 8> neighbors;
- input.GetNeighborhood(neighbors);
- Dilate(input, 8, &neighbors[0], output);
- }
- // Compute a dilation with a structing element consisting of neighbors
- // specified by offsets relative to the pixel. The input image is
- // binary with 0 for background and 1 for foreground. The output
- // image must be an object different from the input image.
- static void Dilate(Image2<int> const& input, int numNeighbors,
- std::array<int, 2> const* neighbors, Image2<int>& output)
- {
- LogAssert(&output != &input, "Input and output must be different.");
- output = input;
- // If the pixel at (x,y) is 1, then the pixels at (x+dx,y+dy) are
- // set to 1 where (dx,dy) is in the 'neighbors' array. Boundary
- // testing is used to avoid accessing out-of-range pixels.
- int const dim0 = input.GetDimension(0);
- int const dim1 = input.GetDimension(1);
- for (int y = 0; y < dim1; ++y)
- {
- for (int x = 0; x < dim0; ++x)
- {
- if (input(x, y) == 1)
- {
- for (int j = 0; j < numNeighbors; ++j)
- {
- int xNbr = x + neighbors[j][0];
- int yNbr = y + neighbors[j][1];
- if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1)
- {
- output(xNbr, yNbr) = 1;
- }
- }
- }
- }
- }
- }
- // Compute an erosion with a structuring element consisting of the
- // 4-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image. If zeroExterior is
- // true, the image exterior is assumed to be 0, so 1-valued boundary
- // pixels are set to 0; otherwise, boundary pixels are set to 0 only
- // when they have neighboring image pixels that are 0.
- static void Erode4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
- {
- std::array<std::array<int, 2>, 4> neighbors;
- input.GetNeighborhood(neighbors);
- Erode(input, zeroExterior, 4, &neighbors[0], output);
- }
- // Compute an erosion with a structuring element consisting of the
- // 8-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image. If zeroExterior is
- // true, the image exterior is assumed to be 0, so 1-valued boundary
- // pixels are set to 0; otherwise, boundary pixels are set to 0 only
- // when they have neighboring image pixels that are 0.
- static void Erode8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
- {
- std::array<std::array<int, 2>, 8> neighbors;
- input.GetNeighborhood(neighbors);
- Erode(input, zeroExterior, 8, &neighbors[0], output);
- }
- // Compute an erosion with a structuring element consisting of
- // neighbors specified by offsets relative to the pixel. The input
- // image is binary with 0 for background and 1 for foreground. The
- // output image must be an object different from the input image. If
- // zeroExterior is true, the image exterior is assumed to be 0, so
- // 1-valued boundary pixels are set to 0; otherwise, boundary pixels
- // are set to 0 only when they have neighboring image pixels that
- // are 0.
- static void Erode(Image2<int> const& input, bool zeroExterior,
- int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
- {
- LogAssert(&output != &input, "Input and output must be different.");
- output = input;
- // If the pixel at (x,y) is 1, it is changed to 0 when at least
- // one neighbor (x+dx,y+dy) is 0, where (dx,dy) is in the
- // 'neighbors' array.
- int const dim0 = input.GetDimension(0);
- int const dim1 = input.GetDimension(1);
- for (int y = 0; y < dim1; ++y)
- {
- for (int x = 0; x < dim0; ++x)
- {
- if (input(x, y) == 1)
- {
- for (int j = 0; j < numNeighbors; ++j)
- {
- int xNbr = x + neighbors[j][0];
- int yNbr = y + neighbors[j][1];
- if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1)
- {
- if (input(xNbr, yNbr) == 0)
- {
- output(x, y) = 0;
- break;
- }
- }
- else if (zeroExterior)
- {
- output(x, y) = 0;
- break;
- }
- }
- }
- }
- }
- }
- // Compute an opening with a structuring element consisting of the
- // 4-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image. If zeroExterior is
- // true, the image exterior is assumed to consist of 0-valued pixels;
- // otherwise, the image exterior is assumed to consist of 1-valued
- // pixels.
- static void Open4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
- {
- Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
- Erode4(input, zeroExterior, temp);
- Dilate4(temp, output);
- }
- // Compute an opening with a structuring element consisting of the
- // 8-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image. If zeroExterior is
- // true, the image exterior is assumed to consist of 0-valued pixels;
- // otherwise, the image exterior is assumed to consist of 1-valued
- // pixels.
- static void Open8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
- {
- Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
- Erode8(input, zeroExterior, temp);
- Dilate8(temp, output);
- }
- // Compute an opening with a structuring element consisting of
- // neighbors specified by offsets relative to the pixel. The input
- // image is binary with 0 for background and 1 for foreground. The
- // output image must be an object different from the input image. If
- // zeroExterior is true, the image exterior is assumed to consist of
- // 0-valued pixels; otherwise, the image exterior is assumed to
- // consist of 1-valued pixels.
- static void Open(Image2<int> const& input, bool zeroExterior,
- int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
- {
- Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
- Erode(input, zeroExterior, numNeighbors, neighbors, temp);
- Dilate(temp, numNeighbors, neighbors, output);
- }
- // Compute a closing with a structuring element consisting of the
- // 4-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image. If zeroExterior is
- // true, the image exterior is assumed to consist of 0-valued pixels;
- // otherwise, the image exterior is assumed to consist of 1-valued
- // pixels.
- static void Close4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
- {
- Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
- Dilate4(input, temp);
- Erode4(temp, zeroExterior, output);
- }
- // Compute a closing with a structuring element consisting of the
- // 8-connected neighbors of each pixel. The input image is binary
- // with 0 for background and 1 for foreground. The output image must
- // be an object different from the input image. If zeroExterior is
- // true, the image exterior is assumed to consist of 0-valued pixels;
- // otherwise, the image exterior is assumed to consist of 1-valued
- // pixels.
- static void Close8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
- {
- Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
- Dilate8(input, temp);
- Erode8(temp, zeroExterior, output);
- }
- // Compute a closing with a structuring element consisting of
- // neighbors specified by offsets relative to the pixel. The input
- // image is binary with 0 for background and 1 for foreground. The
- // output image must be an object different from the input image. If
- // zeroExterior is true, the image exterior is assumed to consist of
- // 0-valued pixels; otherwise, the image exterior is assumed to
- // consist of 1-valued pixels.
- static void Close(Image2<int> const& input, bool zeroExterior,
- int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
- {
- Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
- Dilate(input, numNeighbors, neighbors, temp);
- Erode(temp, zeroExterior, numNeighbors, neighbors, output);
- }
- // Locate a pixel and walk around the edge of a component. The input
- // (x,y) is where the search starts for a nonzero pixel. If (x,y) is
- // outside the component, the walk is around the outside the
- // component. If the component has a hole and (x,y) is inside that
- // hole, the walk is around the boundary surrounding the hole. The
- // function returns 'true' on a success walk. The return value is
- // 'false' when no boundary was found from the starting (x,y).
- static bool ExtractBoundary(int x, int y, Image2<int>& image, std::vector<size_t>& boundary)
- {
- // Find a first boundary pixel.
- size_t const numPixels = image.GetNumPixels();
- size_t i;
- for (i = image.GetIndex(x, y); i < numPixels; ++i)
- {
- if (image[i])
- {
- break;
- }
- }
- if (i == numPixels)
- {
- // No boundary pixel found.
- return false;
- }
- std::array<int, 8> const dx = { -1, 0, +1, +1, +1, 0, -1, -1 };
- std::array<int, 8> const dy = { -1, -1, -1, 0, +1, +1, +1, 0 };
- // Create a new point list that contains the first boundary point.
- boundary.push_back(i);
- // The direction from background 0 to boundary pixel 1 is
- // (dx[7],dy[7]).
- std::array<int, 2> coord = image.GetCoordinates(i);
- int x0 = coord[0], y0 = coord[1];
- int cx = x0, cy = y0;
- int nx = x0 - 1, ny = y0, dir = 7;
- // Traverse the boundary in clockwise order. Mark visited pixels
- // as 2.
- image(cx, cy) = 2;
- bool notDone = true;
- while (notDone)
- {
- int j, nbr;
- for (j = 0, nbr = dir; j < 8; ++j, nbr = (nbr + 1) % 8)
- {
- nx = cx + dx[nbr];
- ny = cy + dy[nbr];
- if (image(nx, ny)) // next boundary pixel found
- {
- break;
- }
- }
- if (j == 8)
- {
- // (cx,cy) is isolated
- notDone = false;
- continue;
- }
- if (nx == x0 && ny == y0)
- {
- // boundary traversal completed
- notDone = false;
- continue;
- }
- // (nx,ny) is next boundary point, add point to list. Note
- // that the index for the pixel is computed for the original
- // image, not for the larger temporary image.
- boundary.push_back(image.GetIndex(nx, ny));
- // Mark visited pixels as 2.
- image(nx, ny) = 2;
- // Start search for next point.
- cx = nx;
- cy = ny;
- dir = (j + 5 + dir) % 8;
- }
- return true;
- }
- // Use a depth-first search for filling a 4-connected region. This is
- // nonrecursive, simulated by using a heap-allocated "stack". The
- // input (x,y) is the seed point that starts the fill.
- template <typename PixelType>
- static void FloodFill4(Image2<PixelType>& image, int x, int y,
- PixelType foreColor, PixelType backColor)
- {
- // Test for a valid seed.
- int const dim0 = image.GetDimension(0);
- int const dim1 = image.GetDimension(1);
- if (x < 0 || x >= dim0 || y < 0 || y >= dim1)
- {
- // The seed point is outside the image domain, so there is
- // nothing to fill.
- return;
- }
- // Allocate the maximum amount of space needed for the stack.
- // An empty stack has top == -1.
- size_t const numPixels = image.GetNumPixels();
- std::vector<int> xStack(numPixels), yStack(numPixels);
- // Push seed point onto stack if it has the background color. All
- // points pushed onto stack have background color backColor.
- int top = 0;
- xStack[top] = x;
- yStack[top] = y;
- while (top >= 0) // stack is not empty
- {
- // Read top of stack. Do not pop since we need to return to
- // this top value later to restart the fill in a different
- // direction.
- x = xStack[top];
- y = yStack[top];
- // Fill the pixel.
- image(x, y) = foreColor;
- int xp1 = x + 1;
- if (xp1 < dim0 && image(xp1, y) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = xp1;
- yStack[top] = y;
- continue;
- }
- int xm1 = x - 1;
- if (0 <= xm1 && image(xm1, y) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = xm1;
- yStack[top] = y;
- continue;
- }
- int yp1 = y + 1;
- if (yp1 < dim1 && image(x, yp1) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = x;
- yStack[top] = yp1;
- continue;
- }
- int ym1 = y - 1;
- if (0 <= ym1 && image(x, ym1) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = x;
- yStack[top] = ym1;
- continue;
- }
- // Done in all directions, pop and return to search other
- // directions of predecessor.
- --top;
- }
- }
- // Compute the L1-distance transform of the binary image. The function
- // returns the maximum distance and a point at which the maximum
- // distance is attained.
- static void GetL1Distance(Image2<int>& image, int& maxDistance, int& xMax, int& yMax)
- {
- int const dim0 = image.GetDimension(0);
- int const dim1 = image.GetDimension(1);
- int const dim0m1 = dim0 - 1;
- int const dim1m1 = dim1 - 1;
- // Use a grass-fire approach, computing distance from boundary to
- // interior one pass at a time.
- bool changeMade = true;
- int distance;
- for (distance = 1, xMax = 0, yMax = 0; changeMade; ++distance)
- {
- changeMade = false;
- int distanceP1 = distance + 1;
- for (int y = 1; y < dim1m1; ++y)
- {
- for (int x = 1; x < dim0m1; ++x)
- {
- if (image(x, y) == distance)
- {
- if (image(x - 1, y) >= distance
- && image(x + 1, y) >= distance
- && image(x, y - 1) >= distance
- && image(x, y + 1) >= distance)
- {
- image(x, y) = distanceP1;
- xMax = x;
- yMax = y;
- changeMade = true;
- }
- }
- }
- }
- }
- maxDistance = --distance;
- }
- // Compute the L2-distance transform of the binary image. The maximum
- // distance should not be larger than 100, so you have to ensure this
- // is the case for the input image. The function returns the maximum
- // distance and a point at which the maximum distance is attained.
- // Comments about the algorithm are in the source file.
- static void GetL2Distance(Image2<int> const& image, float& maxDistance,
- int& xMax, int& yMax, Image2<float>& transform)
- {
- // This program calculates the Euclidean distance transform of a
- // binary input image. The adaptive algorithm is guaranteed to
- // give exact distances for all distances < 100. The algorithm
- // was provided John Gauch at University of Kansas. The following
- // is a quote:
- ///
- // The basic idea is similar to a EDT described recently in PAMI
- // by Laymarie from McGill. By keeping the dx and dy offset to
- // the nearest edge (feature) point in the image, we can search to
- // see which dx dy is closest to a given point by examining a set
- // of neighbors. The Laymarie method (and Borgfors) look at a
- // fixed 3x3 or 5x5 neighborhood and call it a day. What we did
- // was calculate (painfully) what neighborhoods you need to look
- // at to guarentee that the exact distance is obtained. Thus,
- // you will see in the code, that we L2Check the current distance
- // and depending on what we have so far, we extend the search
- // region. Since our algorithm for L2Checking the exactness of
- // each neighborhood is on the order N^4, we have only gone to
- // N=100. In theory, you could make this large enough to get all
- // distances exact. We have implemented the algorithm to get all
- // distances < 100 to be exact.
- int const dim0 = image.GetDimension(0);
- int const dim1 = image.GetDimension(1);
- int const dim0m1 = dim0 - 1;
- int const dim1m1 = dim1 - 1;
- int x, y, distance;
- // Create and initialize intermediate images.
- Image2<int> xNear(dim0, dim1);
- Image2<int> yNear(dim0, dim1);
- Image2<int> dist(dim0, dim1);
- for (y = 0; y < dim1; ++y)
- {
- for (x = 0; x < dim0; ++x)
- {
- if (image(x, y) != 0)
- {
- xNear(x, y) = 0;
- yNear(x, y) = 0;
- dist(x, y) = std::numeric_limits<int>::max();
- }
- else
- {
- xNear(x, y) = x;
- yNear(x, y) = y;
- dist(x, y) = 0;
- }
- }
- }
- int const K1 = 1;
- int const K2 = 169; // 13^2
- int const K3 = 961; // 31^2
- int const K4 = 2401; // 49^2
- int const K5 = 5184; // 72^2
- // Pass in the ++ direction.
- for (y = 0; y < dim1; ++y)
- {
- for (x = 0; x < dim0; ++x)
- {
- distance = dist(x, y);
- if (distance > K1)
- {
- L2Check(x, y, -1, 0, xNear, yNear, dist);
- L2Check(x, y, -1, -1, xNear, yNear, dist);
- L2Check(x, y, 0, -1, xNear, yNear, dist);
- }
- if (distance > K2)
- {
- L2Check(x, y, -2, -1, xNear, yNear, dist);
- L2Check(x, y, -1, -2, xNear, yNear, dist);
- }
- if (distance > K3)
- {
- L2Check(x, y, -3, -1, xNear, yNear, dist);
- L2Check(x, y, -3, -2, xNear, yNear, dist);
- L2Check(x, y, -2, -3, xNear, yNear, dist);
- L2Check(x, y, -1, -3, xNear, yNear, dist);
- }
- if (distance > K4)
- {
- L2Check(x, y, -4, -1, xNear, yNear, dist);
- L2Check(x, y, -4, -3, xNear, yNear, dist);
- L2Check(x, y, -3, -4, xNear, yNear, dist);
- L2Check(x, y, -1, -4, xNear, yNear, dist);
- }
- if (distance > K5)
- {
- L2Check(x, y, -5, -1, xNear, yNear, dist);
- L2Check(x, y, -5, -2, xNear, yNear, dist);
- L2Check(x, y, -5, -3, xNear, yNear, dist);
- L2Check(x, y, -5, -4, xNear, yNear, dist);
- L2Check(x, y, -4, -5, xNear, yNear, dist);
- L2Check(x, y, -2, -5, xNear, yNear, dist);
- L2Check(x, y, -3, -5, xNear, yNear, dist);
- L2Check(x, y, -1, -5, xNear, yNear, dist);
- }
- }
- }
- // Pass in -- direction.
- for (y = dim1m1; y >= 0; --y)
- {
- for (x = dim0m1; x >= 0; --x)
- {
- distance = dist(x, y);
- if (distance > K1)
- {
- L2Check(x, y, 1, 0, xNear, yNear, dist);
- L2Check(x, y, 1, 1, xNear, yNear, dist);
- L2Check(x, y, 0, 1, xNear, yNear, dist);
- }
- if (distance > K2)
- {
- L2Check(x, y, 2, 1, xNear, yNear, dist);
- L2Check(x, y, 1, 2, xNear, yNear, dist);
- }
- if (distance > K3)
- {
- L2Check(x, y, 3, 1, xNear, yNear, dist);
- L2Check(x, y, 3, 2, xNear, yNear, dist);
- L2Check(x, y, 2, 3, xNear, yNear, dist);
- L2Check(x, y, 1, 3, xNear, yNear, dist);
- }
- if (distance > K4)
- {
- L2Check(x, y, 4, 1, xNear, yNear, dist);
- L2Check(x, y, 4, 3, xNear, yNear, dist);
- L2Check(x, y, 3, 4, xNear, yNear, dist);
- L2Check(x, y, 1, 4, xNear, yNear, dist);
- }
- if (distance > K5)
- {
- L2Check(x, y, 5, 1, xNear, yNear, dist);
- L2Check(x, y, 5, 2, xNear, yNear, dist);
- L2Check(x, y, 5, 3, xNear, yNear, dist);
- L2Check(x, y, 5, 4, xNear, yNear, dist);
- L2Check(x, y, 4, 5, xNear, yNear, dist);
- L2Check(x, y, 2, 5, xNear, yNear, dist);
- L2Check(x, y, 3, 5, xNear, yNear, dist);
- L2Check(x, y, 1, 5, xNear, yNear, dist);
- }
- }
- }
- // Pass in the +- direction.
- for (y = dim1m1; y >= 0; --y)
- {
- for (x = 0; x < dim0; ++x)
- {
- distance = dist(x, y);
- if (distance > K1)
- {
- L2Check(x, y, -1, 0, xNear, yNear, dist);
- L2Check(x, y, -1, 1, xNear, yNear, dist);
- L2Check(x, y, 0, 1, xNear, yNear, dist);
- }
- if (distance > K2)
- {
- L2Check(x, y, -2, 1, xNear, yNear, dist);
- L2Check(x, y, -1, 2, xNear, yNear, dist);
- }
- if (distance > K3)
- {
- L2Check(x, y, -3, 1, xNear, yNear, dist);
- L2Check(x, y, -3, 2, xNear, yNear, dist);
- L2Check(x, y, -2, 3, xNear, yNear, dist);
- L2Check(x, y, -1, 3, xNear, yNear, dist);
- }
- if (distance > K4)
- {
- L2Check(x, y, -4, 1, xNear, yNear, dist);
- L2Check(x, y, -4, 3, xNear, yNear, dist);
- L2Check(x, y, -3, 4, xNear, yNear, dist);
- L2Check(x, y, -1, 4, xNear, yNear, dist);
- }
- if (distance > K5)
- {
- L2Check(x, y, -5, 1, xNear, yNear, dist);
- L2Check(x, y, -5, 2, xNear, yNear, dist);
- L2Check(x, y, -5, 3, xNear, yNear, dist);
- L2Check(x, y, -5, 4, xNear, yNear, dist);
- L2Check(x, y, -4, 5, xNear, yNear, dist);
- L2Check(x, y, -2, 5, xNear, yNear, dist);
- L2Check(x, y, -3, 5, xNear, yNear, dist);
- L2Check(x, y, -1, 5, xNear, yNear, dist);
- }
- }
- }
- // Pass in the -+ direction.
- for (y = 0; y < dim1; ++y)
- {
- for (x = dim0m1; x >= 0; --x)
- {
- distance = dist(x, y);
- if (distance > K1)
- {
- L2Check(x, y, 1, 0, xNear, yNear, dist);
- L2Check(x, y, 1, -1, xNear, yNear, dist);
- L2Check(x, y, 0, -1, xNear, yNear, dist);
- }
- if (distance > K2)
- {
- L2Check(x, y, 2, -1, xNear, yNear, dist);
- L2Check(x, y, 1, -2, xNear, yNear, dist);
- }
- if (distance > K3)
- {
- L2Check(x, y, 3, -1, xNear, yNear, dist);
- L2Check(x, y, 3, -2, xNear, yNear, dist);
- L2Check(x, y, 2, -3, xNear, yNear, dist);
- L2Check(x, y, 1, -3, xNear, yNear, dist);
- }
- if (distance > K4)
- {
- L2Check(x, y, 4, -1, xNear, yNear, dist);
- L2Check(x, y, 4, -3, xNear, yNear, dist);
- L2Check(x, y, 3, -4, xNear, yNear, dist);
- L2Check(x, y, 1, -4, xNear, yNear, dist);
- }
- if (distance > K5)
- {
- L2Check(x, y, 5, -1, xNear, yNear, dist);
- L2Check(x, y, 5, -2, xNear, yNear, dist);
- L2Check(x, y, 5, -3, xNear, yNear, dist);
- L2Check(x, y, 5, -4, xNear, yNear, dist);
- L2Check(x, y, 4, -5, xNear, yNear, dist);
- L2Check(x, y, 2, -5, xNear, yNear, dist);
- L2Check(x, y, 3, -5, xNear, yNear, dist);
- L2Check(x, y, 1, -5, xNear, yNear, dist);
- }
- }
- }
- xMax = 0;
- yMax = 0;
- maxDistance = 0.0f;
- for (y = 0; y < dim1; ++y)
- {
- for (x = 0; x < dim0; ++x)
- {
- float fdistance = std::sqrt((float)dist(x, y));
- if (fdistance > maxDistance)
- {
- maxDistance = fdistance;
- xMax = x;
- yMax = y;
- }
- transform(x, y) = fdistance;
- }
- }
- }
- // Compute a skeleton of a binary image. Boundary pixels are trimmed
- // from the object one layer at a time based on their adjacency to
- // interior pixels. At each step the connectivity and cycles of the
- // object are preserved. The skeleton overwrites the contents of the
- // input image.
- static void GetSkeleton(Image2<int>& image)
- {
- int const dim0 = image.GetDimension(0);
- int const dim1 = image.GetDimension(1);
- // Trim pixels, mark interior as 4.
- bool notDone = true;
- while (notDone)
- {
- if (MarkInterior(image, 4, Interior4))
- {
- // No interior pixels, trimmed set is at most 2-pixels
- // thick.
- notDone = false;
- continue;
- }
- if (ClearInteriorAdjacent(image, 4))
- {
- // All remaining interior pixels are either articulation
- // points or part of blobs whose boundary pixels are all
- // articulation points. An example of the latter case is
- // shown below. The background pixels are marked with '.'
- // rather than '0' for readability. The interior pixels
- // are marked with '4' and the boundary pixels are marked
- // with '1'.
- //
- // .........
- // .....1...
- // ..1.1.1..
- // .1.141...
- // ..14441..
- // ..1441.1.
- // .1.11.1..
- // ..1..1...
- // .........
- //
- // This is a pathological problem where there are many
- // small holes (0-pixel with north, south, west, and east
- // neighbors all 1-pixels) that your application can try
- // to avoid by an initial pass over the image to fill in
- // such holes. Of course, you do have problems with
- // checkerboard patterns...
- notDone = false;
- continue;
- }
- }
- // Trim pixels, mark interior as 3.
- notDone = true;
- while (notDone)
- {
- if (MarkInterior(image, 3, Interior3))
- {
- // No interior pixels, trimmed set is at most 2-pixels
- // thick.
- notDone = false;
- continue;
- }
- if (ClearInteriorAdjacent(image, 3))
- {
- // All remaining 3-values can be safely removed since they
- // are not articulation points and the removal will not
- // cause new holes.
- for (int y = 0; y < dim1; ++y)
- {
- for (int x = 0; x < dim0; ++x)
- {
- if (image(x, y) == 3 && !IsArticulation(image, x, y))
- {
- image(x, y) = 0;
- }
- }
- }
- notDone = false;
- continue;
- }
- }
- // Trim pixels, mark interior as 2.
- notDone = true;
- while (notDone)
- {
- if (MarkInterior(image, 2, Interior2))
- {
- // No interior pixels, trimmed set is at most 1-pixel
- // thick. Call it a skeleton.
- notDone = false;
- continue;
- }
- if (ClearInteriorAdjacent(image, 2))
- {
- // Removes 2-values that are not articulation points.
- for (int y = 0; y < dim1; ++y)
- {
- for (int x = 0; x < dim0; ++x)
- {
- if (image(x, y) == 2 && !IsArticulation(image, x, y))
- {
- image(x, y) = 0;
- }
- }
- }
- notDone = false;
- continue;
- }
- }
- // Make the skeleton a binary image.
- size_t const numPixels = image.GetNumPixels();
- for (size_t i = 0; i < numPixels; ++i)
- {
- if (image[i] != 0)
- {
- image[i] = 1;
- }
- }
- }
- // In the remaining public member functions, the callback represents
- // the action you want applied to each pixel as it is visited.
- // Visit pixels in a (2*thick+1)x(2*thick+1) square centered at (x,y).
- static void DrawThickPixel(int x, int y, int thick,
- std::function<void(int, int)> const& callback)
- {
- for (int dy = -thick; dy <= thick; ++dy)
- {
- for (int dx = -thick; dx <= thick; ++dx)
- {
- callback(x + dx, y + dy);
- }
- }
- }
- // Visit pixels using Bresenham's line drawing algorithm.
- static void DrawLine(int x0, int y0, int x1, int y1,
- std::function<void(int, int)> const& callback)
- {
- // Starting point of line.
- int x = x0, y = y0;
- // Direction of line.
- int dx = x1 - x0, dy = y1 - y0;
- // Increment or decrement depending on direction of line.
- int sx = (dx > 0 ? 1 : (dx < 0 ? -1 : 0));
- int sy = (dy > 0 ? 1 : (dy < 0 ? -1 : 0));
- // Decision parameters for pixel selection.
- if (dx < 0)
- {
- dx = -dx;
- }
- if (dy < 0)
- {
- dy = -dy;
- }
- int ax = 2 * dx, ay = 2 * dy;
- int decX, decY;
- // Determine largest direction component, single-step related
- // variable.
- int maxValue = dx, var = 0;
- if (dy > maxValue)
- {
- var = 1;
- }
- // Traverse Bresenham line.
- switch (var)
- {
- case 0: // Single-step in x-direction.
- decY = ay - dx;
- for (/**/; /**/; x += sx, decY += ay)
- {
- callback(x, y);
- // Take Bresenham step.
- if (x == x1)
- {
- break;
- }
- if (decY >= 0)
- {
- decY -= ax;
- y += sy;
- }
- }
- break;
- case 1: // Single-step in y-direction.
- decX = ax - dy;
- for (/**/; /**/; y += sy, decX += ax)
- {
- callback(x, y);
- // Take Bresenham step.
- if (y == y1)
- {
- break;
- }
- if (decX >= 0)
- {
- decX -= ay;
- x += sx;
- }
- }
- break;
- }
- }
- // Visit pixels using Bresenham's circle drawing algorithm. Set
- // 'solid' to false for drawing only the circle. Set 'solid' to true
- // to draw all pixels on and inside the circle.
- static void DrawCircle(int xCenter, int yCenter, int radius, bool solid,
- std::function<void(int, int)> const& callback)
- {
- int x, y, dec;
- if (solid)
- {
- int xValue, yMin, yMax, i;
- for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x)
- {
- xValue = xCenter + x;
- yMin = yCenter - y;
- yMax = yCenter + y;
- for (i = yMin; i <= yMax; ++i)
- {
- callback(xValue, i);
- }
- xValue = xCenter - x;
- for (i = yMin; i <= yMax; ++i)
- {
- callback(xValue, i);
- }
- xValue = xCenter + y;
- yMin = yCenter - x;
- yMax = yCenter + x;
- for (i = yMin; i <= yMax; ++i)
- {
- callback(xValue, i);
- }
- xValue = xCenter - y;
- for (i = yMin; i <= yMax; ++i)
- {
- callback(xValue, i);
- }
- if (dec >= 0)
- {
- dec += -4 * (y--) + 4;
- }
- dec += 4 * x + 6;
- }
- }
- else
- {
- for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x)
- {
- callback(xCenter + x, yCenter + y);
- callback(xCenter + x, yCenter - y);
- callback(xCenter - x, yCenter + y);
- callback(xCenter - x, yCenter - y);
- callback(xCenter + y, yCenter + x);
- callback(xCenter + y, yCenter - x);
- callback(xCenter - y, yCenter + x);
- callback(xCenter - y, yCenter - x);
- if (dec >= 0)
- {
- dec += -4 * (y--) + 4;
- }
- dec += 4 * x + 6;
- }
- }
- }
- // Visit pixels in a rectangle of the specified dimensions. Set
- // 'solid' to false for drawing only the rectangle. Set 'solid' to
- // true to draw all pixels on and inside the rectangle.
- static void DrawRectangle(int xMin, int yMin, int xMax, int yMax,
- bool solid, std::function<void(int, int)> const& callback)
- {
- int x, y;
- if (solid)
- {
- for (y = yMin; y <= yMax; ++y)
- {
- for (x = xMin; x <= xMax; ++x)
- {
- callback(x, y);
- }
- }
- }
- else
- {
- for (x = xMin; x <= xMax; ++x)
- {
- callback(x, yMin);
- callback(x, yMax);
- }
- for (y = yMin + 1; y <= yMax - 1; ++y)
- {
- callback(xMin, y);
- callback(xMax, y);
- }
- }
- }
- // Visit the pixels using Bresenham's algorithm for the axis-aligned
- // ellipse ((x-xc)/a)^2 + ((y-yc)/b)^2 = 1, where xCenter is xc,
- // yCenter is yc, xExtent is a, and yExtent is b.
- static void DrawEllipse(int xCenter, int yCenter, int xExtent, int yExtent,
- std::function<void(int, int)> const& callback)
- {
- int xExtSqr = xExtent * xExtent, yExtSqr = yExtent * yExtent;
- int x, y, dec;
- x = 0;
- y = yExtent;
- dec = 2 * yExtSqr + xExtSqr * (1 - 2 * yExtent);
- for (/**/; yExtSqr * x <= xExtSqr * y; ++x)
- {
- callback(xCenter + x, yCenter + y);
- callback(xCenter - x, yCenter + y);
- callback(xCenter + x, yCenter - y);
- callback(xCenter - x, yCenter - y);
- if (dec >= 0)
- {
- dec += 4 * xExtSqr * (1 - y);
- --y;
- }
- dec += yExtSqr * (4 * x + 6);
- }
- if (y == 0 && x < xExtent)
- {
- // The discretization caused us to reach the y-axis before the
- // x-values reached the ellipse vertices. Draw a solid line
- // along the x-axis to those vertices.
- for (/**/; x <= xExtent; ++x)
- {
- callback(xCenter + x, yCenter);
- callback(xCenter - x, yCenter);
- }
- return;
- }
- x = xExtent;
- y = 0;
- dec = 2 * xExtSqr + yExtSqr * (1 - 2 * xExtent);
- for (/**/; xExtSqr * y <= yExtSqr * x; ++y)
- {
- callback(xCenter + x, yCenter + y);
- callback(xCenter - x, yCenter + y);
- callback(xCenter + x, yCenter - y);
- callback(xCenter - x, yCenter - y);
- if (dec >= 0)
- {
- dec += 4 * yExtSqr * (1 - x);
- --x;
- }
- dec += xExtSqr * (4 * y + 6);
- }
- if (x == 0 && y < yExtent)
- {
- // The discretization caused us to reach the x-axis before the
- // y-values reached the ellipse vertices. Draw a solid line
- // along the y-axis to those vertices.
- for (/**/; y <= yExtent; ++y)
- {
- callback(xCenter, yCenter + y);
- callback(xCenter, yCenter - y);
- }
- }
- }
- // Use a depth-first search for filling a 4-connected region. This is
- // nonrecursive, simulated by using a heap-allocated "stack". The
- // input (x,y) is the seed point that starts the fill. The x-value is
- // in {0..xSize-1} and the y-value is in {0..ySize-1}.
- template <typename PixelType>
- static void DrawFloodFill4(int x, int y, int xSize, int ySize,
- PixelType foreColor, PixelType backColor,
- std::function<void(int, int, PixelType)> const& setCallback,
- std::function<PixelType(int, int)> const& getCallback)
- {
- // Test for a valid seed.
- if (x < 0 || x >= xSize || y < 0 || y >= ySize)
- {
- // The seed point is outside the image domain, so nothing to
- // fill.
- return;
- }
- // Allocate the maximum amount of space needed for the stack. An
- // empty stack has top == -1.
- int const numPixels = xSize * ySize;
- std::vector<int> xStack(numPixels), yStack(numPixels);
- // Push seed point onto stack if it has the background color. All
- // points pushed onto stack have background color backColor.
- int top = 0;
- xStack[top] = x;
- yStack[top] = y;
- while (top >= 0) // stack is not empty
- {
- // Read top of stack. Do not pop since we need to return to
- // this top value later to restart the fill in a different
- // direction.
- x = xStack[top];
- y = yStack[top];
- // Fill the pixel.
- setCallback(x, y, foreColor);
- int xp1 = x + 1;
- if (xp1 < xSize && getCallback(xp1, y) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = xp1;
- yStack[top] = y;
- continue;
- }
- int xm1 = x - 1;
- if (0 <= xm1 && getCallback(xm1, y) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = xm1;
- yStack[top] = y;
- continue;
- }
- int yp1 = y + 1;
- if (yp1 < ySize && getCallback(x, yp1) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = x;
- yStack[top] = yp1;
- continue;
- }
- int ym1 = y - 1;
- if (0 <= ym1 && getCallback(x, ym1) == backColor)
- {
- // Push pixel with background color.
- ++top;
- xStack[top] = x;
- yStack[top] = ym1;
- continue;
- }
- // Done in all directions, pop and return to search other
- // directions of the predecessor.
- --top;
- }
- }
- private:
- // Connected component labeling using depth-first search.
- static void GetComponents(int numNeighbors, int const* delta,
- Image2<int>& image, std::vector<std::vector<size_t>>& components)
- {
- size_t const numPixels = image.GetNumPixels();
- std::vector<int> numElements(numPixels);
- std::vector<size_t> vstack(numPixels);
- size_t i, numComponents = 0;
- int label = 2;
- for (i = 0; i < numPixels; ++i)
- {
- if (image[i] == 1)
- {
- int top = -1;
- vstack[++top] = i;
- int& count = numElements[numComponents + 1];
- count = 0;
- while (top >= 0)
- {
- size_t v = vstack[top];
- image[v] = -1;
- int j;
- for (j = 0; j < numNeighbors; ++j)
- {
- size_t adj = v + delta[j];
- if (image[adj] == 1)
- {
- vstack[++top] = adj;
- break;
- }
- }
- if (j == numNeighbors)
- {
- image[v] = label;
- ++count;
- --top;
- }
- }
- ++numComponents;
- ++label;
- }
- }
- if (numComponents > 0)
- {
- components.resize(numComponents + 1);
- for (i = 1; i <= numComponents; ++i)
- {
- components[i].resize(numElements[i]);
- numElements[i] = 0;
- }
- for (i = 0; i < numPixels; ++i)
- {
- int value = image[i];
- if (value != 0)
- {
- // Labels started at 2 to support the depth-first
- // search, so they need to be decremented for the
- // correct labels.
- image[i] = --value;
- components[value][numElements[value]] = i;
- ++numElements[value];
- }
- }
- }
- }
- // Support for GetL2Distance.
- static void L2Check(int x, int y, int dx, int dy, Image2<int>& xNear,
- Image2<int>& yNear, Image2<int>& dist)
- {
- int const dim0 = dist.GetDimension(0);
- int const dim1 = dist.GetDimension(1);
- int xp = x + dx, yp = y + dy;
- if (0 <= xp && xp < dim0 && 0 <= yp && yp < dim1)
- {
- if (dist(xp, yp) < dist(x, y))
- {
- int dx0 = xNear(xp, yp) - x;
- int dy0 = yNear(xp, yp) - y;
- int newDist = dx0 * dx0 + dy0 * dy0;
- if (newDist < dist(x, y))
- {
- xNear(x, y) = xNear(xp, yp);
- yNear(x, y) = yNear(xp, yp);
- dist(x, y) = newDist;
- }
- }
- }
- }
- // Support for GetSkeleton.
- static bool Interior2(Image2<int>& image, int x, int y)
- {
- bool b1 = (image(x, y - 1) != 0);
- bool b3 = (image(x + 1, y) != 0);
- bool b5 = (image(x, y + 1) != 0);
- bool b7 = (image(x - 1, y) != 0);
- return (b1 && b3) || (b3 && b5) || (b5 && b7) || (b7 && b1);
- }
- static bool Interior3(Image2<int>& image, int x, int y)
- {
- int numNeighbors = 0;
- if (image(x - 1, y) != 0)
- {
- ++numNeighbors;
- }
- if (image(x + 1, y) != 0)
- {
- ++numNeighbors;
- }
- if (image(x, y - 1) != 0)
- {
- ++numNeighbors;
- }
- if (image(x, y + 1) != 0)
- {
- ++numNeighbors;
- }
- return numNeighbors == 3;
- }
- static bool Interior4(Image2<int>& image, int x, int y)
- {
- return image(x - 1, y) != 0
- && image(x + 1, y) != 0
- && image(x, y - 1) != 0
- && image(x, y + 1) != 0;
- }
- static bool MarkInterior(Image2<int>& image, int value,
- bool (*function)(Image2<int>&, int, int))
- {
- int const dim0 = image.GetDimension(0);
- int const dim1 = image.GetDimension(1);
- bool noInterior = true;
- for (int y = 0; y < dim1; ++y)
- {
- for (int x = 0; x < dim0; ++x)
- {
- if (image(x, y) > 0)
- {
- if (function(image, x, y))
- {
- image(x, y) = value;
- noInterior = false;
- }
- else
- {
- image(x, y) = 1;
- }
- }
- }
- }
- return noInterior;
- }
- static bool IsArticulation(Image2<int>& image, int x, int y)
- {
- static std::array<int, 256> const articulation =
- {
- 0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
- 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
- 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
- 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
- 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
- 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
- 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
- 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
- 0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,
- 1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,
- 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,
- 1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,
- 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0
- };
- // Converts 8 neighbors of pixel (x,y) to an 8-bit value,
- // bit = 1 iff pixel is set.
- int byteMask = 0;
- if (image(x - 1, y - 1) != 0)
- {
- byteMask |= 0x01;
- }
- if (image(x, y - 1) != 0)
- {
- byteMask |= 0x02;
- }
- if (image(x + 1, y - 1) != 0)
- {
- byteMask |= 0x04;
- }
- if (image(x + 1, y) != 0)
- {
- byteMask |= 0x08;
- }
- if (image(x + 1, y + 1) != 0)
- {
- byteMask |= 0x10;
- }
- if (image(x, y + 1) != 0)
- {
- byteMask |= 0x20;
- }
- if (image(x - 1, y + 1) != 0)
- {
- byteMask |= 0x40;
- }
- if (image(x - 1, y) != 0)
- {
- byteMask |= 0x80;
- }
- return articulation[byteMask] == 1;
- }
- static bool ClearInteriorAdjacent(Image2<int>& image, int value)
- {
- int const dim0 = image.GetDimension(0);
- int const dim1 = image.GetDimension(1);
- bool noRemoval = true;
- for (int y = 0; y < dim1; ++y)
- {
- for (int x = 0; x < dim0; ++x)
- {
- if (image(x, y) == 1)
- {
- bool interiorAdjacent =
- image(x - 1, y - 1) == value ||
- image(x, y - 1) == value ||
- image(x + 1, y - 1) == value ||
- image(x + 1, y) == value ||
- image(x + 1, y + 1) == value ||
- image(x, y + 1) == value ||
- image(x - 1, y + 1) == value ||
- image(x - 1, y) == value;
- if (interiorAdjacent && !IsArticulation(image, x, y))
- {
- image(x, y) = 0;
- noRemoval = false;
- }
- }
- }
- }
- return noRemoval;
- }
- };
- }
|