All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.algart.contours.ContourFiller Maven / Gradle / Ivy

Go to download

Open-source Java libraries, supporting generalized smart arrays and matrices with elements of any types, including a wide set of 2D-, 3D- and multidimensional image processing and other algorithms, working with arrays and matrices.

There is a newer version: 1.4.23
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.algart.contours;

import net.algart.arrays.*;

import java.util.Objects;
import java.util.function.IntPredicate;
import java.util.function.IntUnaryOperator;

public final class ContourFiller {
    private static final int START_INTERSECTIONS_CAPACITY = 512;

    private final Contours contours;
    private final UpdatablePArray labels;
    private final int[][] intersectionsWithHorizontal;
    private final int[][] initialIntersectionsWithHorizontal;
    private final int[] intersectionsCount;
    private final int startX;
    private final int startY;
    private final int dimX;
    private final int dimY;

    private final int[] contourIndexes;
    private int numberOfNecessaryContours = 0;

    private int minContourY;
    private int maxContourY;
    private final ContourLength contourLength = new ContourLength();
    private int[] unpackedContour = null;

    private IntPredicate needToFill = null;
    private boolean needToUnpack = true;
    private boolean needToUnpackDiagonals = true;
    private int[] labelsMap = null;
    private int indexingBase = 0;
    private IntUnaryOperator labelToFillerDefault = value -> value;

    private ContourFiller(Contours contours, Class elementType, long startX, long startY, long dimX, long dimY) {
        this.contours = Objects.requireNonNull(contours, "Null contours");
        if (startX < Contours.MIN_ABSOLUTE_COORDINATE || startX > Contours.MAX_ABSOLUTE_COORDINATE
                || startY < Contours.MIN_ABSOLUTE_COORDINATE || startY > Contours.MAX_ABSOLUTE_COORDINATE) {
            throw new IllegalArgumentException("Start coordinates (" + startX + ", " + startY
                    + ") are out of allowed range "
                    + Contours.MIN_ABSOLUTE_COORDINATE + ".." + Contours.MAX_ABSOLUTE_COORDINATE);
        }
        if (dimX < 0 || dimY < 0) {
            throw new IllegalArgumentException("Negative dimX or dimY");
        }
        if (dimX > Integer.MAX_VALUE || dimY > Integer.MAX_VALUE || dimX * dimY > Integer.MAX_VALUE
                || dimY * (long) START_INTERSECTIONS_CAPACITY > Integer.MAX_VALUE) {
            throw new TooLargeArrayException("Cannot fill contours for matrix "
                    + dimX + "x" + dimY + ": it is too large");
        }
        if (Arrays.sizeOf(elementType) < 0) {
            throw new IllegalArgumentException("Unsupported element type " + elementType + ": it must be primitive");
        }
        this.startX = (int) startX;
        this.startY = (int) startY;
        this.dimX = (int) dimX;
        this.dimY = (int) dimY;
        this.labels = (UpdatablePArray) Arrays.SMM.newUnresizableArray(elementType, dimX * dimY);
        this.intersectionsWithHorizontal = new int[this.dimY][START_INTERSECTIONS_CAPACITY];
        this.initialIntersectionsWithHorizontal = this.intersectionsWithHorizontal.clone();
        // - shallow copy: saving references only
        this.intersectionsCount = new int[this.dimY];
        // - zero-filled by Java
        this.contourIndexes = new int[contours.numberOfContours()];
    }

    public static ContourFiller newInstance(
            Contours contours,
            long startX,
            long startY,
            long dimX,
            long dimY) {
        return new ContourFiller(contours, int.class, startX, startY, dimX, dimY);
    }

    public static ContourFiller newInstance(
            Contours contours,
            Class elementType,
            long startX,
            long startY,
            long dimX,
            long dimY) {
        return new ContourFiller(contours, elementType, startX, startY, dimX, dimY);
    }

    public IntPredicate getNeedToFill() {
        return needToFill;
    }

    public ContourFiller setNeedToFill(IntPredicate needToFill) {
        this.needToFill = needToFill;
        return this;
    }

    public boolean isNeedToUnpack() {
        return needToUnpack;
    }

    public ContourFiller setNeedToUnpack(boolean needToUnpack) {
        this.needToUnpack = needToUnpack;
        return this;
    }

    public boolean isNeedToUnpackDiagonals() {
        return needToUnpackDiagonals;
    }

    public ContourFiller setNeedToUnpackDiagonals(boolean needToUnpackDiagonals) {
        this.needToUnpackDiagonals = needToUnpackDiagonals;
        return this;
    }

    public int[] getLabelsMap() {
        return labelsMap;
    }

    public ContourFiller setLabelsMap(int[] labelsMap) {
        this.labelsMap = labelsMap;
        return this;
    }

    public int getIndexingBase() {
        return indexingBase;
    }

    public ContourFiller setIndexingBase(int indexingBase) {
        if (indexingBase < 0) {
            throw new IllegalArgumentException("Indexing base cannot be negative: " + indexingBase);
        }
        this.indexingBase = indexingBase;
        return this;
    }

    public IntUnaryOperator getLabelToFillerDefault() {
        return Objects.requireNonNull(labelToFillerDefault, "Null default value function");
    }

    public ContourFiller setLabelToFillerDefault(IntUnaryOperator labelToFillerDefault) {
        this.labelToFillerDefault = labelToFillerDefault;
        return this;
    }

    public int dimX() {
        return dimX;
    }

    public int dimY() {
        return dimY;
    }

    public void findAndSortNecessaryContours() {
        final int n = contours.numberOfContours();
        final int[] contourSize = new int[n];
        final MutableIntArray maxSizeForLabel = Arrays.SMM.newEmptyIntArray();
        final ContourHeader header = new ContourHeader();
        int count = 0;
        for (int k = 0; k < n; k++) {
            if (needToFill != null && !needToFill.test(k)) {
                continue;
            }
            contours.getHeader(header, k);
            final int minX = header.minX();
            final int maxX = header.maxX();
            final int minY = header.minY();
            final int maxY = header.maxY();
            if (maxX < startX || minX >= startX + dimX || maxY < startY || minY >= startY + dimY) {
                continue;
            }
            final int diffY = maxY - minY;
            if (diffY < 0) {
                throw new AssertionError("Overflow in sizes of containing rectangle for contour #" + k
                        + "; it must be impossible due to check of Contour2DArray.MAX_ABSOLUTE_COORDINATE");
            }
            final int label = contours.getObjectLabel(k);
            if (label >= 0) {
                // - ignore order for strange negative labels
                if (label >= maxSizeForLabel.length()) {
                    maxSizeForLabel.length((long) label + 1);
                }
                if (diffY > maxSizeForLabel.getInt(label)) {
                    maxSizeForLabel.setInt(label, diffY);
                }
            }
            contourSize[count] = diffY;
            contourIndexes[count] = k;
            count++;
        }
        this.numberOfNecessaryContours = count;
        ArraySorter.getQuickSorter().sortIndexes(contourIndexes, 0, count, (firstIndex, secondIndex) -> {
            final int firstLabel = contours.getObjectLabel(firstIndex);
            final int secondLabel = contours.getObjectLabel(secondIndex);
            if (firstLabel != secondLabel) {
                if (firstLabel < 0 || secondLabel < 0) {
                    return firstLabel < secondLabel;
                    // - don't try to fill strange negative labels in correct order
                }
                final int firstSize = maxSizeForLabel.getInt(firstLabel);
                final int secondSize = maxSizeForLabel.getInt(secondLabel);
                if (firstSize != secondSize) {
                    return firstSize > secondSize;
                }
                // - greater first: small particle will be drawn over larger
                return firstLabel < secondLabel;
                // - for equal sizes we MUST provide correct order: each label must be continuous
            }
            final boolean firstInternal = contours.isInternalContour(firstIndex);
            final boolean secondInternal = contours.isInternalContour(secondIndex);
            if (firstInternal != secondInternal) {
                // external first
                return secondInternal;
            }
            return contourSize[firstIndex] < contourSize[secondIndex];
            // greater first
        });
    }

    public int numberOfNecessaryContours() {
        return numberOfNecessaryContours;
    }

    public void fillNecessaryContours() {
        final int maxTranslatedLabelPlus1 = labelsMap == null ? Integer.MIN_VALUE : labelsMap.length + indexingBase;
        for (int from = 0; from < numberOfNecessaryContours; ) {
            final int label = contours.getObjectLabel(contourIndexes[from]);
            int to = from + 1;
            while (to < numberOfNecessaryContours && contours.getObjectLabel(contourIndexes[to]) == label) {
                to++;
            }
            final int translatedLabel;
            if (label < maxTranslatedLabelPlus1 && label >= indexingBase) {
                assert labelsMap != null;
                // - now maxTranslatedLabelPlus1 != Integer.MIN_VALUE, so, labelsMap != null
                translatedLabel = labelsMap[label - indexingBase];
            } else {
                translatedLabel = labelToFillerDefault.applyAsInt(label);
            }
//            System.out.printf("Filling %d..%d contours for %d -> %d...%n", from, to, label, translatedLabel);
            fillContours(contourIndexes, from, to, translatedLabel);
            from = to;
        }
    }

    public void fillContours(int[] contourIndexes, int from, int to, int label) {
        openContour();
        for (int k = from; k < to; k++) {
            final int contourIndex = contourIndexes[k];
            unpackedContour = needToUnpack ?
                    contours.unpackContourAndReallocateResult(
                            unpackedContour, contourLength, contourIndex, needToUnpackDiagonals) :
                    contours.getContourPointsAndReallocateResult(
                            unpackedContour, contourLength, contourIndex);
            // - guarantees that the result contour points is a connected line, consisting from segments with length 1
            if (!contourLength.isDegenerated()) {
                addIntersectionsWithContour(unpackedContour, contourLength.getArrayLength());
            }
        }
        fillAndCloseContour(label);
    }

    public Matrix getLabels() {
        return Matrices.matrix(labels, dimX, dimY);
    }

    private void openContour() {
        maxContourY = Integer.MIN_VALUE;
        minContourY = Integer.MAX_VALUE;
    }

    private void addIntersectionsWithContour(int[] contour, int arrayLength) {
        final int dimY = this.dimY;
        final int startX = this.startX;
        final int startY = this.startY;
        // - JVM works better with local variables, not fields of an object
        for (int i = 0; i < arrayLength; ) {
            final int x = contour[i] - startX;
            final int y = contour[i + 1] - startY;
            if (y < 0 || y > dimY) {
                // - note: if y==dimY, it MAY be a suitable edge y-1..y
                int distance = y < 0 ? -y : y - dimY;
                i += distance << 1;
                // - we need at least such number of steps to appear inside minY..maxY+1,
                // because every step has length 1 (horizontal or vertical)
                continue;
            }
            final int lastI = i == 0 ? arrayLength - 2 : i - 2;
            final int lastX = contour[lastI] - startX;
            final int lastY = contour[lastI + 1] - startY;
            i += 2;

            final int dx = x - lastX;
            final int dy = y - lastY;
            if (dy == 0 ? dx != -1 && dx != 1 : dx != 0 || (dy != -1 && dy != 1))
                if (needToUnpack) {
                    throw new AssertionError("Invalid segment in unpacked contour: "
                            + lastX + "," + lastY + " -> " + x + "," + y + "; cannot appear in unpacked contours");
                } else {
                    throw new IllegalArgumentException("Invalid segment in the contour: "
                            + lastX + "," + lastY + " -> " + x + "," + y
                            + "; you must use setNeedToUnpack(true) (default mode) to process such contours");
                }
            if (dx == 0) {
                final int lessFromTwoY = dy < 0 ? y : lastY;
                if (lessFromTwoY >= 0 && lessFromTwoY < dimY) {
                    addIntersection(x, lessFromTwoY);
                }
            }
        }
    }

    private void fillAndCloseContour(int label) {
        final int dimX = this.dimX;
        for (int y = minContourY, disp = y * dimX; y <= maxContourY; y++, disp += dimX) {
            final int count = intersectionsCount[y];
            assert (count & 1) == 0 : "odd number of intersection with y=" + y + " (impossible for connected curve)";
            final int[] intersections = intersectionsWithHorizontal[y];
            java.util.Arrays.sort(intersections, 0, count);
            for (int k = 0; k < count; k += 2) {
                int fromX = intersections[k];
                int toX = intersections[k + 1];
                if (toX <= 0) {
                    continue;
                }
                if (fromX >= dimX) {
                    break;
                }
                if (fromX < 0) {
                    fromX = 0;
                }
                if (toX > dimX) {
                    toX = dimX;
                }
                assert fromX <= toX;
                labels.fill((long) disp + fromX, (long) toX - fromX, label);
            }
            removeAllIntersections(y);
        }
    }

    private void addIntersection(int x, int y) {
        if (y < minContourY) {
            minContourY = y;
        }
        if (y > maxContourY) {
            maxContourY = y;
        }
        final int count = intersectionsCount[y]++;
        int[] intersections = intersectionsWithHorizontal[y];
        if (count >= intersections.length) {
            intersections = ensureCapacity(y, (long) count + 1L);
        }
        intersections[count] = x;
    }

    private void removeAllIntersections(int y) {
        intersectionsCount[y] = 0;
        if (intersectionsWithHorizontal[y].length > START_INTERSECTIONS_CAPACITY) {
            intersectionsWithHorizontal[y] = initialIntersectionsWithHorizontal[y];
        }
    }

    private int[] ensureCapacity(int y, long newCount) {
        if (newCount > Integer.MAX_VALUE) {
            // - should not occur while filling current contours (with 31-bit indexing)
            throw new TooLargeArrayException("Too large array required");
        }

        final int length = intersectionsWithHorizontal[y].length;
        if (newCount > length) {
            final int newLength = Math.max(START_INTERSECTIONS_CAPACITY, Math.max((int) newCount,
                    (int) Math.min(Integer.MAX_VALUE, (long) (2.0 * length))));
            return intersectionsWithHorizontal[y] = java.util.Arrays.copyOf(
                    intersectionsWithHorizontal[y], newLength);
        } else {
            return intersectionsWithHorizontal[y];
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy