/* * Copyright 2015 Daniel Dittmar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package dan.dit.whatsthat.util.mosaic.reconstruction; import android.graphics.Bitmap; import android.util.Log; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import dan.dit.whatsthat.util.general.PercentProgressListener; import dan.dit.whatsthat.util.image.ColorAnalysisUtil; import dan.dit.whatsthat.util.image.ColorMetric; /** * Created by daniel on 01.07.15. */ public class AutoLayerReconstructor extends Reconstructor { private Bitmap mResult; private MosaicFragment mFragment; private MosaicFragment mNext; private int mLayersApplied; private Iterator<Integer> mColorIterator; private final boolean mUseAlpha; private List<List<Integer>> mUsedColorsStartPosition; private List<Integer> mUsedColors; private int[] mPositionDeltas; private ColorMetric mColorMetric; public AutoLayerReconstructor(Bitmap source, double factor, boolean useAlpha, ColorMetric metric, PercentProgressListener progress) { mUseAlpha = useAlpha; mColorMetric = metric; init(source, factor, progress); } private void init(Bitmap source, double factor, PercentProgressListener progress) { long tic = System.currentTimeMillis(); final int width = source.getWidth(); final int height = source.getHeight(); mFragment = new MosaicFragment(0, 0, 0); mResult = obtainBaseBitmap(width, height, Bitmap.Config.ARGB_8888); final int[] colors = new int[width * height]; final int[] deltas = new int[width * height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { colors[x + y * width] = source.getPixel(x, y); } } final double maxSim = mColorMetric.maxValue(mUseAlpha); final double sim = ColorAnalysisUtil.factorToSimilarityBound(factor); final int simBound = (int) (sim * maxSim); final int alreadyReachedMarker = Integer.MIN_VALUE; List<Integer> usedColors = new ArrayList<>(); List<Integer> usedColorsStartPosition = new ArrayList<>(); for (int analyzedToIndex = 0; analyzedToIndex < colors.length; analyzedToIndex++) { // first try to evolve the current color further, under the assumption that similar colors are close to each other this is almost constant in operations int currColor = colors[analyzedToIndex]; if (deltas[analyzedToIndex] == 0) { // color not yet reached, use it as start color usedColors.add(currColor); usedColorsStartPosition.add(analyzedToIndex); } deltas[analyzedToIndex] = 0; // marker no longer required, reset delta for (int currIndex = analyzedToIndex + 1; currIndex < colors.length; currIndex++) { if (mColorMetric.getDistance(colors[currIndex], currColor, mUseAlpha) <= simBound) { colors[currIndex] = currColor; deltas[analyzedToIndex] = currIndex - analyzedToIndex; deltas[currIndex] = alreadyReachedMarker; // so it is not added as a new color when main loop reaches this index break; } } progress.onProgressUpdate((int) (100 * analyzedToIndex / (double) colors.length)); } Log.d("HomeStuff", "DominantLayerReconstructor finished main work (hopefully) in " + (System.currentTimeMillis() - tic) + " has colors " + usedColors.size()); // now create actual color circles around the filtered used colors, only required if too many colors matched above that are similar but didnt know it mUsedColorsStartPosition = new LinkedList<>(); mUsedColors = new LinkedList<>(); for (int i = 0; i < usedColors.size(); i++) { int currColorI = usedColors.get(i); mUsedColors.add(currColorI); List<Integer> startPositions = new LinkedList<>(); mUsedColorsStartPosition.add(startPositions); startPositions.add(usedColorsStartPosition.get(i)); for (int j = i + 1; j < usedColors.size(); j++) { Integer potentialColor = usedColors.get(j); if (mColorMetric.getDistance(currColorI, potentialColor, mUseAlpha) <= simBound) { // if the pixel != 0 check is not done you need to check here if delta is zero else multiple paths might leed together and result in way too many pixels being drawn startPositions.add(usedColorsStartPosition.get(j)); usedColors.remove(j); usedColorsStartPosition.remove(j); j--; } } } mPositionDeltas = deltas; Log.d("HomeStuff", "Finished DominantLayerReconstructor for " + width + "x" + height + " in " + (System.currentTimeMillis() - tic) + "ms, used colors: " + mUsedColors.size()); } @Override public boolean giveNext(Bitmap nextFragmentImage) { if (nextFragmentImage == null || mNext == null || nextFragmentImage.getWidth() != mResult.getWidth() || nextFragmentImage.getHeight() != mResult.getHeight()) { return false; } int width = mResult.getWidth(); List<Integer> startPositions = mUsedColorsStartPosition.get(mLayersApplied); for (int position : startPositions) { int delta; do { int x = position % width; int y = position / width; if (mResult.getPixel(x, y) != 0) { break; } mResult.setPixel(x, y, nextFragmentImage.getPixel(x, y)); delta = mPositionDeltas[position]; position += delta; } while (delta > 0); } mNext = null; mLayersApplied++; return true; } @Override public MosaicFragment nextFragment() { if (mColorIterator == null) { mColorIterator = mUsedColors.iterator(); } if (!mColorIterator.hasNext()) { return null; } if (mNext == null) { int currColor = mColorIterator.next(); mFragment.reset(mResult.getWidth(), mResult.getHeight(), currColor); mNext = mFragment; } return mNext; } @Override public boolean hasAll() { return mColorIterator != null && !mColorIterator.hasNext(); } @Override public Bitmap getReconstructed() { return mResult; } @Override public int estimatedProgressPercent() { int colors = mUsedColors.size(); if (colors <= 0) { return 0; } return (int) (100 * mLayersApplied / (double) colors); } }