/*
* 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.Canvas;
import android.util.Log;
import dan.dit.whatsthat.util.image.ColorAnalysisUtil;
/**
* This class models a specific {@link Reconstructor} which fragments
* an image into rectangulars of equal height and width. This is a fast
* and lightweight reconstruction, the default mosaic technique. As this
* asserts all rects to have equal height and equal width, the given values
* may be adjusted for the given image.
* @author Daniel
*
*/
public class RectReconstructor extends Reconstructor {
protected final int mRectHeight;
protected final int mRectWidth;
private int[][] resultingRGBA;
private Bitmap result;
protected Canvas mResultCanvas;
private int nextImageIndex;
/**
* Creates a new {@link RectReconstructor} which fragments an image
* into rects. The rects height and width are the same for all rects, so
* if the wanted height/width (calculated by image height/width divided by rows/column)
* are not divisors of the image's height/width, the
* rect height/width will be adjusted to the next possible divisor.<br>
* Images to provide by giveNext() will all need to have the same height and width.
* @param source The image to fragment into rectangulars.
* @param wantedRows The wanted amount of rows.
* @param wantedColumns The wanted amount of columns.
*/
public RectReconstructor(Bitmap source, int wantedRows, int wantedColumns) {
if (source == null) {
throw new NullPointerException();
}
if (wantedRows <= 0 || wantedColumns <= 0) {
throw new IllegalArgumentException("An image cannot be reconstructed to zero rows or columns.");
}
int actualRows = Reconstructor.getClosestCount(source.getHeight(), wantedRows);
int actualColumns = Reconstructor.getClosestCount(source.getWidth(), wantedColumns);
this.mRectHeight = source.getHeight() / actualRows;
this.mRectWidth = source.getWidth() / actualColumns;
this.resultingRGBA = new int[actualRows][actualColumns];
this.nextImageIndex = 0;
this.result = obtainBaseBitmap(this.mRectWidth * this.getColumns(), this.mRectHeight * this.getRows(),
Bitmap.Config.ARGB_8888);
Log.d("HomeStuff", "RectReconstructor: Source " + source.getWidth() + "/" + source.getHeight() + " result " + result.getWidth() + "/" + result.getHeight() + " actual rows/columns" + actualRows + "/" + actualColumns);
mResultCanvas = new Canvas(result);
evaluateResultingRGBA(source, actualRows, actualColumns);
}
private void evaluateResultingRGBA(Bitmap source, int rows, int columns) {
// evaluate the fragments average colors
for (int heightIndex = 0; heightIndex < rows; heightIndex++) {
for (int widthIndex = 0; widthIndex < columns; widthIndex++) {
this.resultingRGBA[heightIndex][widthIndex]
= evaluateRectValue(source, widthIndex * this.mRectWidth,
(widthIndex + 1) * this.mRectWidth,
heightIndex * this.mRectHeight,
(heightIndex + 1) * this.mRectHeight);
}
}
}
protected int evaluateRectValue(Bitmap source, int startX, int endX, int startY, int endY) {
return ColorAnalysisUtil.getAverageColor(source, startX, endX, startY, endY);
}
/**
* The amount of rows of the fragmenation.
* @return The amount of rows of the fragmentation. Greater than or equal 1.
*/
private int getRows() {
return this.resultingRGBA.length;
}
/**
* Returns the amount of columns of the fragmentation.
* @return The amount of columns of the fragmentation. Greater than or equal 1.
*/
private int getColumns() {
return this.resultingRGBA[0].length;
}
@Override
public boolean giveNext(Bitmap nextFragmentImage) {
if (!this.hasAll()
&& nextFragmentImage != null
&& nextFragmentImage.getWidth() == this.mRectWidth
&& nextFragmentImage.getHeight() == this.mRectHeight) {
mResultCanvas.drawBitmap(nextFragmentImage,
(this.nextImageIndex % this.getColumns()) * this.mRectWidth,
(this.nextImageIndex / this.getColumns()) * this.mRectHeight,
null);
this.nextImageIndex++;
return true;
}
return false;
}
@Override
public MosaicFragment nextFragment() {
if (this.hasAll()) {
return null;
} else {
return new MosaicFragment(this.mRectWidth, this.mRectHeight,
this.resultingRGBA[this.nextImageIndex / this.getColumns()]
[this.nextImageIndex % this.getColumns()]);
}
}
@Override
public boolean hasAll() {
return this.nextImageIndex >= this.getRows() * this.getColumns();
}
@Override
public Bitmap getReconstructed() {
if (this.hasAll()) {
Bitmap temp = this.result;
this.result = null;
return temp;
} else {
return null;
}
}
@Override
public int estimatedProgressPercent() {
return (int) (100 * nextImageIndex / (double) (getRows() * getColumns()));
}
}