/*
* 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.data;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Log;
import dan.dit.whatsthat.util.general.MultistepPercentProgressListener;
import dan.dit.whatsthat.util.general.PercentProgressListener;
import dan.dit.whatsthat.util.image.ColorMetric;
import dan.dit.whatsthat.util.mosaic.matching.TileMatcher;
import dan.dit.whatsthat.util.mosaic.reconstruction.AutoLayerReconstructor;
import dan.dit.whatsthat.util.mosaic.reconstruction.FixedLayerReconstructor;
import dan.dit.whatsthat.util.mosaic.reconstruction.MosaicFragment;
import dan.dit.whatsthat.util.mosaic.reconstruction.MultiRectReconstructor;
import dan.dit.whatsthat.util.mosaic.reconstruction.Reconstructor;
import dan.dit.whatsthat.util.mosaic.reconstruction.RectReconstructor;
import dan.dit.whatsthat.util.mosaic.reconstruction.pattern.CirclePatternReconstructor;
import dan.dit.whatsthat.util.mosaic.reconstruction.pattern.LegoPatternReconstructor;
import dan.dit.whatsthat.util.mosaic.reconstruction.pattern.PatternReconstructor;
/**
* This class uses a tile matcher as a source pool for MosaicTiles and reconstructs
* an mosaic for a source bitmap with some Reconstructor type.
* @param <S>
*/
public class MosaicMaker<S> {
private final BitmapSource<S> mBitmapSource;
private TileMatcher<S> mMatcher;
private boolean mUseAlpha;
private ColorMetric mColorMetric;
public interface ProgressCallback extends PercentProgressListener {
boolean isCancelled();
}
private static class MultiStepPercentProgressCallback extends MultistepPercentProgressListener implements ProgressCallback {
private final ProgressCallback mCallback;
public MultiStepPercentProgressCallback(ProgressCallback callback, int steps) {
super(callback, steps);
mCallback = callback;
}
@Override
public boolean isCancelled() {
return mCallback.isCancelled();
}
}
public MosaicMaker(TileMatcher<S> tileMatcher, BitmapSource<S> bitmapSource, boolean useAlpha, ColorMetric metric) {
if (tileMatcher == null || bitmapSource == null) {
throw new IllegalArgumentException("No matcher or source given.");
}
mMatcher = tileMatcher;
mBitmapSource = bitmapSource;
mUseAlpha = useAlpha;
setColorMetric(metric);
}
public void setUseAlpha(boolean useAlpha) {
mUseAlpha = useAlpha;
mMatcher.setUseAlpha(useAlpha);
}
public void setColorMetric(ColorMetric metric) {
mColorMetric = metric;
if (mColorMetric == null) {
throw new IllegalArgumentException("No color metric given.");
}
mMatcher.setColorMetric(metric);
}
public Bitmap makeMultiRect(Bitmap source, int wantedRows, int wantedColumns, double mergeFactor, ProgressCallback progress) {
Reconstructor reconstructor = new MultiRectReconstructor(source,
wantedRows, wantedColumns, mergeFactor, mUseAlpha, mColorMetric);
return make(mMatcher, mBitmapSource, reconstructor, progress);
}
public Bitmap makeRect(Bitmap source, int wantedRows, int wantedColumns, ProgressCallback progress) {
Reconstructor reconstructor = new RectReconstructor(source,
wantedRows, wantedColumns);
return make(mMatcher, mBitmapSource, reconstructor, progress);
}
public Bitmap makeAutoLayer(Bitmap source, double mergeFactor, ProgressCallback progress) {
MultiStepPercentProgressCallback multiProgress = new MultiStepPercentProgressCallback(progress, 2);
Reconstructor reconstructor = new AutoLayerReconstructor(source, mergeFactor, mUseAlpha, mColorMetric, multiProgress);
multiProgress.nextStep();
Bitmap result = make(mMatcher, mBitmapSource, reconstructor, multiProgress);
multiProgress.nextStep();
return result;
}
public Bitmap makeFixedLayer(Bitmap source, int clusterCount, ProgressCallback progress) {
MultiStepPercentProgressCallback multiProgress = new MultiStepPercentProgressCallback(progress, 2);
Reconstructor reconstructor = new FixedLayerReconstructor(source, clusterCount, mUseAlpha, mColorMetric, multiProgress);
multiProgress.nextStep();
Bitmap result = make(mMatcher, mBitmapSource, reconstructor, multiProgress);
multiProgress.nextStep();
return result;
}
public static Bitmap makePattern(Resources res, Bitmap source, String patternName,
boolean useAlpha, ColorMetric metric,
int rows, int columns, ProgressCallback progress) {
MultiStepPercentProgressCallback multiProgress = new MultiStepPercentProgressCallback(progress, 2);
PatternReconstructor reconstructor;
switch (patternName) {
default:
// fall through
case CirclePatternReconstructor.NAME:
reconstructor = new CirclePatternReconstructor(source, rows, columns, Color
.TRANSPARENT);
break;
case LegoPatternReconstructor.NAME:
reconstructor = new LegoPatternReconstructor(res, source, rows, columns, Color
.TRANSPARENT);
break;
}
multiProgress.nextStep();
Bitmap result = make(reconstructor.<Void>makeMatcher(useAlpha, metric), reconstructor
.<Void>makeSource(),
reconstructor,
multiProgress);
multiProgress.nextStep();
return result;
}
private static <S>Bitmap make(TileMatcher<S> matcher, BitmapSource<S> source, Reconstructor
reconstructor,
ProgressCallback progress) {
if (reconstructor == null) {
throw new IllegalArgumentException("No reconstructor given to make mosaic.");
}
while (!reconstructor.hasAll() && (progress == null || !progress.isCancelled())) {
MosaicFragment nextFrag = reconstructor.nextFragment();
Bitmap nextImage;
do {
MosaicTile<S> tile = matcher.getBestMatch(nextFrag.getAverageRGB());
if (tile == null) {
// matcher has no more tiles!
Log.e("HomeStuff", "Mosaic maker ran out of tiles!");
return null;
}
nextImage = source.getBitmap(tile, nextFrag.getWidth(), nextFrag.getHeight());
if (nextImage == null) {
// no image?! maybe the image (file) got invalid (image deleted, damaged,...)
// delete it from matcher
// and cache and search again
matcher.removeTile(tile);
}
// will terminate since the matcher will lose a tile each iteration or find a valid one,
// if no tile found anymore, returns false
} while (nextImage == null);
if (!reconstructor.giveNext(nextImage)) {
// reconstructor did not accept the give image, but it was valid and of correct dimension,
// does not occur
// with RectReconstructor / MultiRectReconstructor, but who knows, if I ever forget
// this I would be stuck here
Log.e("HomeStuff", "Given image not accepted by reconstructor!");
return null;
}
if (progress != null) {
progress.onProgressUpdate(reconstructor.estimatedProgressPercent());
}
}
if (progress != null && !progress.isCancelled()) {
progress.onProgressUpdate(PercentProgressListener.PROGRESS_COMPLETE);
}
if (progress != null && progress.isCancelled()) {
return null;
}
return reconstructor.getReconstructed();
}
public boolean usesAlpha() {
return mUseAlpha;
}
public ColorMetric getColorMetric() {
return mColorMetric;
}
}