package dan.dit.whatsthat.util.mosaic.reconstruction.pattern; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.support.annotation.NonNull; import dan.dit.whatsthat.util.image.ColorAnalysisUtil; import dan.dit.whatsthat.util.image.ColorMetric; import dan.dit.whatsthat.util.image.ImageUtil; import dan.dit.whatsthat.util.mosaic.matching.TileMatcher; import dan.dit.whatsthat.util.mosaic.matching.TrivialMatcher; /** * Created by daniel on 05.12.15. */ public class CirclePatternReconstructor extends PatternReconstructor { public static final String NAME = "Circles"; private double[] mRaster; private double mAverageBrightness; public static class Source<S> extends PatternSource<S> { private Paint mPaint; private Bitmap mPatternBitmap; private Canvas mCanvas; public Source() { mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); } @Override protected Bitmap makePattern(int color, @NonNull Bitmap base) { Canvas canvas = mCanvas; canvas.drawColor(Color.TRANSPARENT); mPaint.setColor(color); canvas.drawCircle(base.getWidth() / 2, base.getHeight() / 2, Math.min(base.getWidth() / 2, base.getHeight() / 2), mPaint); return base; } protected @NonNull Bitmap obtainBitmap(int key, int width, int height) { if (mPatternBitmap != null && mPatternBitmap.getWidth() == width && mPatternBitmap .getHeight() == height) { return mPatternBitmap; } ImageUtil.CACHE.makeReusable(mPatternBitmap); mPatternBitmap = super.obtainBitmap(key, width, height); mCanvas = new Canvas(mPatternBitmap); return mPatternBitmap; } } public CirclePatternReconstructor(Bitmap source, int wantedRows, int wantedColumns, int groundingColor) { super(source, wantedRows, wantedColumns, groundingColor); } public <S> PatternSource<S> makeSource() { return new Source<>(); } @Override public <S> TileMatcher<S> makeMatcher(boolean useAlpha, ColorMetric metric) { return new TrivialMatcher<>(); } @Override protected int evaluateRectValue(Bitmap source, int startX, int endX, int startY, int endY) { ensureAverageBrightness(source); fillRaster(source, startX, endX, startY, endY); int width = endX - startX; int height = endY - startY; return calculateColor(mRaster, mAverageBrightness, width, height, 0, 0, Math.max(width, height) + 1); } private void fillRaster(Bitmap source, int startX, int endX, int startY, int endY) { int width = endX - startX; int height = endY - startY; if (mRaster == null || mRaster.length != width * height) { mRaster = new double[width * height]; } int index = 0; for (int j = startY; j < endY; j++) { for (int i = startX; i < endX; i++) { mRaster[index++] = ColorAnalysisUtil.getBrightnessWithAlpha(source.getPixel(i, j)); } } } private void ensureAverageBrightness(Bitmap source) { if (mAverageBrightness > 0.) { return; } mAverageBrightness = 0.; for (int y = 0; y < source.getHeight(); y++) { for (int x = 0; x < source.getWidth(); x++) { mAverageBrightness += ColorAnalysisUtil.getBrightnessWithAlpha(source.getPixel(x, y)); } } mAverageBrightness /= source.getWidth() * source.getHeight(); mAverageBrightness = Math.max(mAverageBrightness, Double.MIN_VALUE); // to ensure it is // positive and brightness not calculated over and over if it would be exactly zero } public static int calculateColor(double[] raster, double averageBrightness, int bitmapWidth, int bitmapHeight, float x, float y, float r) { // by default this calculates the average brightness of the area [x-r,x+r][y-r,y+r] int left = (int)(x - r); int right = (int)(x + r); int top = (int)(y - r); int bottom = (int)(y + r); double brightness = 0; double consideredPoints = 0; // do not only consider pixels within the circle but within the square defined by the circle bounds for (int i = Math.max(0, left); i <= Math.min(right, bitmapWidth - 1); i++) { for (int j = Math.max(0, top); j <= Math.min(bottom, bitmapHeight - 1); j++) { int rasterIndex = j * bitmapWidth + i; if (rasterIndex >= 0 && rasterIndex < raster.length) { brightness += raster[rasterIndex]; consideredPoints++; } } } // 1 = very bright -> white brightness /= consideredPoints; // logistic filter 1/(1+e^(-kx)) to minimize grey values and emphasize bright and dark ones // use higher k for less grey values brightness = 1. / (1. + Math.exp(-15. * (brightness - averageBrightness))); int grey = (int) (255. * brightness); return ColorAnalysisUtil.toRGB(grey, grey, grey, 255); } }