/* * 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.graphics.Color; import android.util.Log; import java.util.Arrays; import java.util.Random; import dan.dit.whatsthat.util.general.PercentProgressListener; import dan.dit.whatsthat.util.image.ColorMetric; /** * 'Better' version of AutoLayerReconstructor using the k-means algorithm * with the given ColorMetric. In contrast to the other layer reconstructor * the amount of layers needs to be fixed and given though. Results are cleaner for the same run speed * and similar parameter settings, though the parameter needs to be determined by the user('s preference) * and cannot be set to an arbitrary constant. * Created by daniel on 03.07.15. */ public class FixedLayerReconstructor extends Reconstructor { private static final int MAX_RECALCULATIONS_BASE = 5; private static final int MAX_RECALCULATIONS_LINEAR_GROWTH = 1; private final boolean mUseAlpha; private final ColorMetric mColorMetric; private MosaicFragment mFragment; private Bitmap mResult; private int[] mPixelClusterNumber; private Bitmap[] mClusterBitmaps; private int[] mClusterColors; private int mCurrCluster; public FixedLayerReconstructor(Bitmap source, int clusterCount, boolean useAlpha, ColorMetric metric, PercentProgressListener progress) { mUseAlpha = useAlpha; mColorMetric = metric; init(source, clusterCount, progress); } private void init(Bitmap source, final int clusterCount, PercentProgressListener progress) { if (clusterCount < 1) { throw new IllegalArgumentException("No clusters?!" + clusterCount); } final int width = source.getWidth(); final int height = source.getHeight(); mFragment = new MosaicFragment(0, 0, 0); mResult = obtainBaseBitmap(width, height, Bitmap.Config.ARGB_8888); mClusterBitmaps = new Bitmap[clusterCount]; int[] pixelColors = new int[width * height]; mPixelClusterNumber = new int[width * height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { pixelColors[x + y * width] = source.getPixel(x, y); } } // k-means algorithm Random rand = new Random(); int[] clusterCenters = new int[clusterCount]; double[] clusterWeights = new double[clusterCount]; /* //init (with random centers, also other possibilities as convergence greatly depends on starting values) for (int i = 0; i < clusterCenters.length; i++) { clusterCenters[i] = pixelColors[rand.nextInt(pixelColors.length)]; }*/ // init k-means++ style by preferring centers that are further away from the last center clusterCenters[0] = pixelColors[rand.nextInt(pixelColors.length)]; final double maxDist = mColorMetric.maxValue(mUseAlpha); for (int cluster = 1; cluster < clusterCount; cluster++) { clusterCenters[cluster] = pixelColors[rand.nextInt(pixelColors.length)]; for (int i = 0; i < pixelColors.length; i++) { double distFraction = mColorMetric.getDistance(pixelColors[i], clusterCenters[cluster - 1], mUseAlpha) / maxDist; distFraction *= distFraction; distFraction *= distFraction; distFraction *= i / (double) pixelColors.length; if (rand.nextDouble() < distFraction) { clusterCenters[cluster] = pixelColors[i]; break; } } } int[] clusterCenterRed = new int[clusterCount]; int[] clusterCenterGreen = new int[clusterCount]; int[] clusterCenterBlue = new int[clusterCount]; int[] clusterCenterAlpha = mUseAlpha ? new int[clusterCount] : null; int[] clusterSize = new int[clusterCount]; int redistributionCount = 0; int maxRedistributions = MAX_RECALCULATIONS_BASE + MAX_RECALCULATIONS_LINEAR_GROWTH * clusterCount; int changed = Integer.MAX_VALUE; int lastChanged; do { // for improved speed we expect monotonous convergence in the amount of pixels changed, if this ever increases again we stop lastChanged = changed; changed = 0; // redistribute into clusters Arrays.fill(clusterWeights, 0.); for (int i = 0; i < pixelColors.length; i++) { int currColor = pixelColors[i]; double minWeightIncrease = Double.MAX_VALUE; int minWeightIncreaseIndex = 0; for (int cluster = 0; cluster < clusterCount; cluster++) { double currWeightIncrease = mColorMetric.getDistance(clusterCenters[cluster], currColor, mUseAlpha); if (currWeightIncrease < minWeightIncrease) { minWeightIncrease = currWeightIncrease; minWeightIncreaseIndex = cluster; } } clusterWeights[minWeightIncreaseIndex] += minWeightIncrease; int oldNumber = mPixelClusterNumber[i]; mPixelClusterNumber[i] = minWeightIncreaseIndex; if (oldNumber != minWeightIncreaseIndex) { changed++; } } redistributionCount++; // recalculate centers Arrays.fill(clusterCenterRed, 0); Arrays.fill(clusterCenterGreen, 0); Arrays.fill(clusterCenterBlue, 0); if (mUseAlpha) { Arrays.fill(clusterCenterAlpha, 0); } Arrays.fill(clusterSize, 0); for (int i = 0; i < pixelColors.length; i++) { int cluster = mPixelClusterNumber[i]; int color = pixelColors[i]; clusterCenterRed[cluster] += Color.red(color); clusterCenterGreen[cluster] += Color.green(color); clusterCenterBlue[cluster] += Color.blue(color); if (mUseAlpha) { clusterCenterAlpha[cluster] += Color.alpha(color); } clusterSize[cluster]++; } for (int cluster = 0; cluster < clusterCount; cluster++) { int size = clusterSize[cluster]; if (size > 0) { int red = clusterCenterRed[cluster] / size; int green = clusterCenterGreen[cluster] / size; int blue = clusterCenterBlue[cluster] / size; int alpha = mUseAlpha ? clusterCenterAlpha[cluster] / size : 255; clusterCenters[cluster] = Color.argb(alpha, red, green, blue); } } progress.onProgressUpdate((int) (PercentProgressListener.PROGRESS_COMPLETE * redistributionCount / (double) maxRedistributions)); Log.d("HomeStuff", "ClusterLayerReconstructor changed pixels: " + changed + " in run " + redistributionCount); } while (changed <= lastChanged && redistributionCount < maxRedistributions); Log.d("HomeStuff", "Finished ClusteredLayerReconstructor with " + redistributionCount + " recalculations for " + clusterCount + " clusters."); mClusterColors = clusterCenters; progress.onProgressUpdate(PercentProgressListener.PROGRESS_COMPLETE); } @Override public boolean giveNext(Bitmap nextFragmentImage) { if (nextFragmentImage == null || hasAll() || nextFragmentImage.getWidth() != mResult.getWidth() || nextFragmentImage.getHeight() != mResult.getHeight()) { return false; } mClusterBitmaps[mCurrCluster] = nextFragmentImage; mCurrCluster++; return true; } @Override public MosaicFragment nextFragment() { if (hasAll()) { return null; } mFragment.reset(mResult.getWidth(), mResult.getHeight(), mClusterColors[mCurrCluster]); return mFragment; } @Override public boolean hasAll() { return mCurrCluster >= mClusterColors.length; } @Override public Bitmap getReconstructed() { int width = mResult.getWidth(); for (int i = 0; i < mPixelClusterNumber.length; i++) { int x = i % width; int y = i / width; int cluster = mPixelClusterNumber[i]; mResult.setPixel(x, y, mClusterBitmaps[cluster].getPixel(x, y)); } return mResult; } @Override public int estimatedProgressPercent() { return (int) (100 * mCurrCluster / (double) mClusterColors.length); } }