From b7b30c7620f8060880c3282ac0bbe1c65c95393a Mon Sep 17 00:00:00 2001 From: flask Date: Sun, 15 Nov 2015 21:03:35 +0900 Subject: [PATCH] Fix bug in face detection limit rage by index was wrong. limit by x,y position --- app/app.iml | 11 +- library/library.iml | 11 +- .../github/quadflask/smartcrop/SmartCrop.java | 703 +++++++++--------- 3 files changed, 356 insertions(+), 369 deletions(-) diff --git a/app/app.iml b/app/app.iml index b7e27c3..fbcf287 100644 --- a/app/app.iml +++ b/app/app.iml @@ -65,28 +65,21 @@ - - + - - - - - - - + diff --git a/library/library.iml b/library/library.iml index e4ee07d..e53d959 100644 --- a/library/library.iml +++ b/library/library.iml @@ -65,22 +65,13 @@ + - - - - - - - - - - diff --git a/library/src/main/java/com/github/quadflask/smartcrop/SmartCrop.java b/library/src/main/java/com/github/quadflask/smartcrop/SmartCrop.java index a381346..85489ea 100644 --- a/library/src/main/java/com/github/quadflask/smartcrop/SmartCrop.java +++ b/library/src/main/java/com/github/quadflask/smartcrop/SmartCrop.java @@ -18,354 +18,357 @@ * Created by flask on 2015. 10. 30.. */ public class SmartCrop { - private static final Paint DEBUG_TOP_CROP_RECT_PAINT = new Paint(); - - static { - DEBUG_TOP_CROP_RECT_PAINT.setColor(0xff00ccff); - DEBUG_TOP_CROP_RECT_PAINT.setStyle(Paint.Style.STROKE); - } - - private Options options; - private int[] cd; - - public SmartCrop() { - this(Options.DEFAULT); - } - - public SmartCrop(Options options) { - this.options = options; - } - - public static Observable analyzeWithObservable(final Options options, final Bitmap input) { - return Observable - .create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - subscriber.onNext(SmartCrop.analyze(options, input)); - } - }); - } - - public static CropResult analyze(Options options, Bitmap input) { - return new SmartCrop(options).analyze(input); - } - - public CropResult analyze(Bitmap input) { - input = createScaleDown(input, options.getAnalyzeSizeLimit()); - Image inputI = new Image(input); - Image outputI = new Image(input.getWidth(), input.getHeight()); - - prepareCie(inputI); - edgeDetect(inputI, outputI); - skinDetect(inputI, outputI); - saturationDetect(inputI, outputI); - faceDetect(inputI, outputI); - - Bitmap output = Bitmap.createBitmap(input.getWidth(), input.getHeight(), options.getBitmapConfig()); - output.setPixels(outputI.getRGB(), 0, input.getWidth(), 0, 0, input.getWidth(), input.getHeight()); - - Bitmap score = Bitmap.createBitmap(input.getWidth() / options.getScoreDownSample(), input.getHeight() / options.getScoreDownSample(), options.getBitmapConfig()); - new Canvas(score).drawBitmap(output, new Rect(0, 0, output.getWidth(), output.getHeight()), new Rect(0, 0, score.getWidth(), score.getHeight()), null); - Image scoreI = new Image(score); - - float topScore = Float.NEGATIVE_INFINITY; - Crop topCrop = null; - List crops = crops(scoreI); - - for (Crop crop : crops) { - crop.score = score(scoreI, crop); - if (crop.score.total > topScore) { - topCrop = crop; - topScore = crop.score.total; - } - crop.x *= options.getScoreDownSample(); - crop.y *= options.getScoreDownSample(); - crop.width *= options.getScoreDownSample(); - crop.height *= options.getScoreDownSample(); - } - - CropResult result = CropResult.newInstance(topCrop, crops, output, createCrop(input, topCrop)); - - if (topCrop != null) { - Canvas outputCanvas = new Canvas(output); - outputCanvas.drawRect(new Rect(topCrop.x, topCrop.y, topCrop.x + topCrop.width - 1, topCrop.y + topCrop.height - 1), DEBUG_TOP_CROP_RECT_PAINT); - } - - return result; - } - - private void faceDetect(Image inputI, Image outputI) { - FaceDetector.Face[] faces = new FaceDetector.Face[options.getMaxFaceCount()]; - FaceDetector faceDetector = new FaceDetector(inputI.width, inputI.height, faces.length); - - if (faceDetector.findFaces(inputI.bitmap, faces) > 0) { - float maxEyeDistance = 1; - for (FaceDetector.Face face : faces) - if (face != null) - maxEyeDistance = Math.max(maxEyeDistance, face.eyesDistance()); - - int[] rgb = outputI.getRGB(); - for (FaceDetector.Face face : faces) { - if (face != null) { - int score = clamp((int) (face.confidence() * face.eyesDistance() / maxEyeDistance * 255)); - - PointF midPoint = new PointF(); - face.getMidPoint(midPoint); - - int x = (int) midPoint.x; - int y = (int) midPoint.y; - - fillCircle(rgb, outputI.width, x, y, (int) (face.eyesDistance() * 5), score); - } - } - } - } - - private void fillCircle(int[] rgb, int w, int cx, int cy, int cr, int value) { - for (int x = cx - cr; x < cx + cr; x++) { - for (int y = cy - cr; y < cy + cr; y++) { - int p = y * w + x; - if (0 < p && p < rgb.length) { - int newVal = (int) (value * (cr - Math.sqrt((cx - x) * (cx - x) + (cy - y) * (cy - y))) / cr); - if (newVal > 0) { - int i = rgb[p]; - int r = clamp((i >> 16 & 0xff) + newVal); - int g = clamp((i >> 8 & 0xff) + newVal); - int b = clamp((i & 0xff) + newVal); - rgb[p] = 0xff000000 | r << 16 | g << 8 | b; - } - } - } - } - } - - private Bitmap createScaleDown(Bitmap input, int maxSize) { - input = input.copy(options.getBitmapConfig(), true); - float rate = (float) maxSize / Math.max(input.getWidth(), input.getHeight()); - return Bitmap.createScaledBitmap(input, (int) (rate * input.getWidth()), (int) (rate * input.getHeight()), true); - } - - public Bitmap createCrop(Bitmap input, Crop crop) { - int tw = options.getCropWidth(); - int th = options.getCropHeight(); - Bitmap image = Bitmap.createBitmap(tw, th, options.getBitmapConfig()); - new Canvas(image).drawBitmap(input, new Rect(crop.x, crop.y, crop.x + crop.width, crop.y + crop.height), new Rect(0, 0, tw, th), null); - return image; - } - - private List crops(Image image) { - List crops = new ArrayList<>(); - int width = image.width; - int height = image.height; - int minDimension = Math.min(width, height); - - for (float scale = options.getMaxScale(); scale >= options.getMinScale(); scale -= options.getScaleStep()) { - for (int y = 0; y + minDimension * scale <= height; y += options.getScoreDownSample()) { - for (int x = 0; x + minDimension * scale <= width; x += options.getScoreDownSample()) { - crops.add(new Crop(x, y, (int) (minDimension * scale), (int) (minDimension * scale))); - } - } - } - return crops; - } - - private Score score(Image output, Crop crop) { - Score score = new Score(); - int[] od = output.getRGB(); - int width = output.width; - int height = output.height; - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int p = y * width + x; - float importance = importance(crop, x, y); - float detail = (od[p] >> 8 & 0xff) / 255f; - score.skin += (od[p] >> 16 & 0xff) / 255f * (detail + options.getSkinBias()) * importance; - score.detail += detail * importance; - score.saturation += (od[p] & 0xff) / 255f * (detail + options.getSaturationBias()) * importance; - } - } - score.total = (score.detail * options.getDetailWeight() + score.skin * options.getSkinWeight() + score.saturation * options.getSaturationWeight()) / crop.width / crop.height; - return score; - } - - private float importance(Crop crop, int x, int y) { - if (crop.x > x || x >= crop.x + crop.width || crop.y > y || y >= crop.y + crop.height) - return options.getOutsideImportance(); - float fx = (float) (x - crop.x) / crop.width; - float fy = (float) (y - crop.y) / crop.height; - float px = Math.abs(0.5f - fx) * 2; - float py = Math.abs(0.5f - fy) * 2; - // distance from edg; - float dx = Math.max(px - 1.0f + options.getEdgeRadius(), 0); - float dy = Math.max(py - 1.0f + options.getEdgeRadius(), 0); - float d = (dx * dx + dy * dy) * options.getEdgeWeight(); - d += (float) (1.4142135f - Math.sqrt(px * px + py * py)); - if (options.isRuleOfThirds()) { - d += (Math.max(0, d + 0.5f) * 1.2f) * (thirds(px) + thirds(py)); - } - return d; - } - - static class Image { - Bitmap bitmap; - int width, height; - int[] data; - - public Image(int width, int height) { - this.width = width; - this.height = height; - this.data = new int[width * height]; - for (int i = 0; i < this.data.length; i++) - data[i] = 0xff000000; - } - - public Image(Bitmap bitmap) { - this(bitmap.getWidth(), bitmap.getHeight()); - this.bitmap = bitmap; - this.data = getPixelsFromBitmap(bitmap); - } - - public int[] getRGB() { - return data; - } - } - - private void prepareCie(Image i) { - int[] id = i.getRGB(); - cd = new int[id.length]; - int w = i.width; - int h = i.height; - - int p; - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - p = y * w + x; - cd[p] = cie(id[p]); - } - } - } - - private void edgeDetect(Image i, Image o) { - int[] od = o.getRGB(); - int w = i.width; - int h = i.height; - int p; - int lightness; - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - p = y * w + x; - if (x == 0 || x >= w - 1 || y == 0 || y >= h - 1) { - lightness = 0; - } else { - lightness = cd[p] * 8 - - cd[p - w - 1] - - cd[p - w] - - cd[p - w + 1] - - cd[p - 1] - - cd[p + 1] - - cd[p + w - 1] - - cd[p + w] - - cd[p + w + 1] - ; - } - - od[p] = clamp(lightness) << 8 | (od[p] & 0xffff00ff); - } - } - } - - private void skinDetect(Image i, Image o) { - int[] id = i.getRGB(); - int[] od = o.getRGB(); - int w = i.width; - int h = i.height; - float invSkinThreshold = 255f / (1 - options.getSkinThreshold()); - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int p = y * w + x; - float lightness = cd[p] / 255f; - float skin = calcSkinColor(id[p]); - if (skin > options.getSkinThreshold() && lightness >= options.getSkinBrightnessMin() && lightness <= options.getSkinBrightnessMax()) { - od[p] = ((Math.round((skin - options.getSkinThreshold()) * invSkinThreshold)) & 0xff) << 16 | (od[p] & 0xff00ffff); - } else { - od[p] &= 0xff00ffff; - } - } - } - } - - private void saturationDetect(Image i, Image o) { - int[] id = i.getRGB(); - int[] od = o.getRGB(); - int w = i.width; - int h = i.height; - float invSaturationThreshold = 255f / (1 - options.getSaturationThreshold()); - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int p = y * w + x; - float lightness = cd[p] / 255f; - float sat = saturation(id[p]); - if (sat > options.getSaturationThreshold() && lightness >= options.getSaturationBrightnessMin() && lightness <= options.getSaturationBrightnessMax()) { - od[p] = (Math.round((sat - options.getSaturationThreshold()) * invSaturationThreshold) & 0xff) | (od[p] & 0xffffff00); - } else { - od[p] &= 0xffffff00; - } - } - } - } - - private float calcSkinColor(int rgb) { - int r = rgb >> 16 & 0xff; - int g = rgb >> 8 & 0xff; - int b = rgb & 0xff; - - float mag = (float) Math.sqrt(r * r + g * g + b * b); - float rd = (r / mag - options.getSkinColor()[0]); - float gd = (g / mag - options.getSkinColor()[1]); - float bd = (b / mag - options.getSkinColor()[2]); - return 1f - (float) Math.sqrt(rd * rd + gd * gd + bd * bd); - } - - private int clamp(int v) { - return Math.max(0, Math.min(v, 0xff)); - } - - private int cie(int rgb) { - int r = rgb >> 16 & 0xff; - int g = rgb >> 8 & 0xff; - int b = rgb & 0xff; - return Math.min(0xff, (int) (0.2126f * b + 0.7152f * g + 0.0722f * r + .5f)); - } - - private float saturation(int rgb) { - float r = (rgb >> 16 & 0xff) / 255f; - float g = (rgb >> 8 & 0xff) / 255f; - float b = (rgb & 0xff) / 255f; - - float maximum = Math.max(r, Math.max(g, b)); - float minimum = Math.min(r, Math.min(g, b)); - if (maximum == minimum) { - return 0; - } - float l = (maximum + minimum) / 2f; - float d = maximum - minimum; - return l > 0.5f ? d / (2f - maximum - minimum) : d / (maximum + minimum); - } - - // gets value in the range of [0, 1] where 0 is the center of the pictures - // returns weight of rule of thirds [0, 1] - private float thirds(float x) { - x = ((x - (1 / 3f) + 1.0f) % 2.0f * 0.5f - 0.5f) * 16f; - return Math.max(1.0f - x * x, 0); - } - - private static int[] getPixelsFromBitmap(Bitmap bitmap) { - int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; - bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); - return pixels; - } + private static final Paint DEBUG_TOP_CROP_RECT_PAINT = new Paint(); + + static { + DEBUG_TOP_CROP_RECT_PAINT.setColor(0xff00ccff); + DEBUG_TOP_CROP_RECT_PAINT.setStyle(Paint.Style.STROKE); + } + + private Options options; + private int[] cd; + + public SmartCrop() { + this(Options.DEFAULT); + } + + public SmartCrop(Options options) { + this.options = options; + } + + public static Observable analyzeWithObservable(final Options options, final Bitmap input) { + return Observable + .create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + subscriber.onNext(SmartCrop.analyze(options, input)); + } + }); + } + + public static CropResult analyze(Options options, Bitmap input) { + return new SmartCrop(options).analyze(input); + } + + public CropResult analyze(Bitmap input) { + input = createScaleDown(input, options.getAnalyzeSizeLimit()); + Image inputI = new Image(input); + Image outputI = new Image(input.getWidth(), input.getHeight()); + + prepareCie(inputI); + edgeDetect(inputI, outputI); + skinDetect(inputI, outputI); + saturationDetect(inputI, outputI); + faceDetect(inputI, outputI); + + Bitmap output = Bitmap.createBitmap(input.getWidth(), input.getHeight(), options.getBitmapConfig()); + output.setPixels(outputI.getRGB(), 0, input.getWidth(), 0, 0, input.getWidth(), input.getHeight()); + + Bitmap score = Bitmap.createBitmap(input.getWidth() / options.getScoreDownSample(), input.getHeight() / options.getScoreDownSample(), options.getBitmapConfig()); + new Canvas(score).drawBitmap(output, new Rect(0, 0, output.getWidth(), output.getHeight()), new Rect(0, 0, score.getWidth(), score.getHeight()), null); + Image scoreI = new Image(score); + + float topScore = Float.NEGATIVE_INFINITY; + Crop topCrop = null; + List crops = crops(scoreI); + + for (Crop crop : crops) { + crop.score = score(scoreI, crop); + if (crop.score.total > topScore) { + topCrop = crop; + topScore = crop.score.total; + } + crop.x *= options.getScoreDownSample(); + crop.y *= options.getScoreDownSample(); + crop.width *= options.getScoreDownSample(); + crop.height *= options.getScoreDownSample(); + } + + CropResult result = CropResult.newInstance(topCrop, crops, output, createCrop(input, topCrop)); + + if (topCrop != null) { + Canvas outputCanvas = new Canvas(output); + outputCanvas.drawRect(new Rect(topCrop.x, topCrop.y, topCrop.x + topCrop.width - 1, topCrop.y + topCrop.height - 1), DEBUG_TOP_CROP_RECT_PAINT); + } + + return result; + } + + private void faceDetect(Image inputI, Image outputI) { + FaceDetector.Face[] faces = new FaceDetector.Face[options.getMaxFaceCount()]; + FaceDetector faceDetector = new FaceDetector(inputI.width, inputI.height, faces.length); + + if (faceDetector.findFaces(inputI.bitmap, faces) > 0) { + float maxEyeDistance = 1; + for (FaceDetector.Face face : faces) + if (face != null) + maxEyeDistance = Math.max(maxEyeDistance, face.eyesDistance()); + + int[] rgb = outputI.getRGB(); + for (FaceDetector.Face face : faces) { + if (face != null) { + int score = clamp((int) (face.confidence() * face.eyesDistance() / maxEyeDistance * 255)); + + PointF midPoint = new PointF(); + face.getMidPoint(midPoint); + + int x = (int) midPoint.x; + int y = (int) midPoint.y; + + fillCircle(rgb, outputI.width, outputI.height, x, y, (int) (face.eyesDistance() * 5), score); + } + } + } + } + + private void fillCircle(int[] rgb, int w, int h, int cx, int cy, int cr, int value) { + final int fx = Math.max(0, cx - cr); + final int tx = Math.min(w, cx + cr); + final int fy = Math.max(0, cy - cr); + final int ty = Math.min(h, cy + cr); + + for (int x = fx; x < tx; x++) { + for (int y = fy; y < ty; y++) { + int p = y * w + x; + int newVal = (int) (value * (cr - Math.sqrt((cx - x) * (cx - x) + (cy - y) * (cy - y))) / cr); + if (newVal > 0) { + int i = rgb[p]; + int r = clamp((i >> 16 & 0xff) + newVal); + int g = clamp((i >> 8 & 0xff) + newVal); + int b = clamp((i & 0xff) + newVal); + rgb[p] = 0xff000000 | r << 16 | g << 8 | b; + } + } + } + } + + private Bitmap createScaleDown(Bitmap input, int maxSize) { + input = input.copy(options.getBitmapConfig(), true); + float rate = (float) maxSize / Math.max(input.getWidth(), input.getHeight()); + return Bitmap.createScaledBitmap(input, (int) (rate * input.getWidth()), (int) (rate * input.getHeight()), true); + } + + public Bitmap createCrop(Bitmap input, Crop crop) { + int tw = options.getCropWidth(); + int th = options.getCropHeight(); + Bitmap image = Bitmap.createBitmap(tw, th, options.getBitmapConfig()); + new Canvas(image).drawBitmap(input, new Rect(crop.x, crop.y, crop.x + crop.width, crop.y + crop.height), new Rect(0, 0, tw, th), null); + return image; + } + + private List crops(Image image) { + List crops = new ArrayList<>(); + int width = image.width; + int height = image.height; + int minDimension = Math.min(width, height); + + for (float scale = options.getMaxScale(); scale >= options.getMinScale(); scale -= options.getScaleStep()) { + for (int y = 0; y + minDimension * scale <= height; y += options.getScoreDownSample()) { + for (int x = 0; x + minDimension * scale <= width; x += options.getScoreDownSample()) { + crops.add(new Crop(x, y, (int) (minDimension * scale), (int) (minDimension * scale))); + } + } + } + return crops; + } + + private Score score(Image output, Crop crop) { + Score score = new Score(); + int[] od = output.getRGB(); + int width = output.width; + int height = output.height; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int p = y * width + x; + float importance = importance(crop, x, y); + float detail = (od[p] >> 8 & 0xff) / 255f; + score.skin += (od[p] >> 16 & 0xff) / 255f * (detail + options.getSkinBias()) * importance; + score.detail += detail * importance; + score.saturation += (od[p] & 0xff) / 255f * (detail + options.getSaturationBias()) * importance; + } + } + score.total = (score.detail * options.getDetailWeight() + score.skin * options.getSkinWeight() + score.saturation * options.getSaturationWeight()) / crop.width / crop.height; + return score; + } + + private float importance(Crop crop, int x, int y) { + if (crop.x > x || x >= crop.x + crop.width || crop.y > y || y >= crop.y + crop.height) + return options.getOutsideImportance(); + float fx = (float) (x - crop.x) / crop.width; + float fy = (float) (y - crop.y) / crop.height; + float px = Math.abs(0.5f - fx) * 2; + float py = Math.abs(0.5f - fy) * 2; + // distance from edg; + float dx = Math.max(px - 1.0f + options.getEdgeRadius(), 0); + float dy = Math.max(py - 1.0f + options.getEdgeRadius(), 0); + float d = (dx * dx + dy * dy) * options.getEdgeWeight(); + d += (float) (1.4142135f - Math.sqrt(px * px + py * py)); + if (options.isRuleOfThirds()) { + d += (Math.max(0, d + 0.5f) * 1.2f) * (thirds(px) + thirds(py)); + } + return d; + } + + static class Image { + Bitmap bitmap; + int width, height; + int[] data; + + public Image(int width, int height) { + this.width = width; + this.height = height; + this.data = new int[width * height]; + for (int i = 0; i < this.data.length; i++) + data[i] = 0xff000000; + } + + public Image(Bitmap bitmap) { + this(bitmap.getWidth(), bitmap.getHeight()); + this.bitmap = bitmap; + this.data = getPixelsFromBitmap(bitmap); + } + + public int[] getRGB() { + return data; + } + } + + private void prepareCie(Image i) { + int[] id = i.getRGB(); + cd = new int[id.length]; + int w = i.width; + int h = i.height; + + int p; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + p = y * w + x; + cd[p] = cie(id[p]); + } + } + } + + private void edgeDetect(Image i, Image o) { + int[] od = o.getRGB(); + int w = i.width; + int h = i.height; + int p; + int lightness; + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + p = y * w + x; + if (x == 0 || x >= w - 1 || y == 0 || y >= h - 1) { + lightness = 0; + } else { + lightness = cd[p] * 8 + - cd[p - w - 1] + - cd[p - w] + - cd[p - w + 1] + - cd[p - 1] + - cd[p + 1] + - cd[p + w - 1] + - cd[p + w] + - cd[p + w + 1] + ; + } + + od[p] = clamp(lightness) << 8 | (od[p] & 0xffff00ff); + } + } + } + + private void skinDetect(Image i, Image o) { + int[] id = i.getRGB(); + int[] od = o.getRGB(); + int w = i.width; + int h = i.height; + float invSkinThreshold = 255f / (1 - options.getSkinThreshold()); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int p = y * w + x; + float lightness = cd[p] / 255f; + float skin = calcSkinColor(id[p]); + if (skin > options.getSkinThreshold() && lightness >= options.getSkinBrightnessMin() && lightness <= options.getSkinBrightnessMax()) { + od[p] = ((Math.round((skin - options.getSkinThreshold()) * invSkinThreshold)) & 0xff) << 16 | (od[p] & 0xff00ffff); + } else { + od[p] &= 0xff00ffff; + } + } + } + } + + private void saturationDetect(Image i, Image o) { + int[] id = i.getRGB(); + int[] od = o.getRGB(); + int w = i.width; + int h = i.height; + float invSaturationThreshold = 255f / (1 - options.getSaturationThreshold()); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int p = y * w + x; + float lightness = cd[p] / 255f; + float sat = saturation(id[p]); + if (sat > options.getSaturationThreshold() && lightness >= options.getSaturationBrightnessMin() && lightness <= options.getSaturationBrightnessMax()) { + od[p] = (Math.round((sat - options.getSaturationThreshold()) * invSaturationThreshold) & 0xff) | (od[p] & 0xffffff00); + } else { + od[p] &= 0xffffff00; + } + } + } + } + + private float calcSkinColor(int rgb) { + int r = rgb >> 16 & 0xff; + int g = rgb >> 8 & 0xff; + int b = rgb & 0xff; + + float mag = (float) Math.sqrt(r * r + g * g + b * b); + float rd = (r / mag - options.getSkinColor()[0]); + float gd = (g / mag - options.getSkinColor()[1]); + float bd = (b / mag - options.getSkinColor()[2]); + return 1f - (float) Math.sqrt(rd * rd + gd * gd + bd * bd); + } + + private int clamp(int v) { + return Math.max(0, Math.min(v, 0xff)); + } + + private int cie(int rgb) { + int r = rgb >> 16 & 0xff; + int g = rgb >> 8 & 0xff; + int b = rgb & 0xff; + return Math.min(0xff, (int) (0.2126f * b + 0.7152f * g + 0.0722f * r + .5f)); + } + + private float saturation(int rgb) { + float r = (rgb >> 16 & 0xff) / 255f; + float g = (rgb >> 8 & 0xff) / 255f; + float b = (rgb & 0xff) / 255f; + + float maximum = Math.max(r, Math.max(g, b)); + float minimum = Math.min(r, Math.min(g, b)); + if (maximum == minimum) { + return 0; + } + float l = (maximum + minimum) / 2f; + float d = maximum - minimum; + return l > 0.5f ? d / (2f - maximum - minimum) : d / (maximum + minimum); + } + + // gets value in the range of [0, 1] where 0 is the center of the pictures + // returns weight of rule of thirds [0, 1] + private float thirds(float x) { + x = ((x - (1 / 3f) + 1.0f) % 2.0f * 0.5f - 0.5f) * 16f; + return Math.max(1.0f - x * x, 0); + } + + private static int[] getPixelsFromBitmap(Bitmap bitmap) { + int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; + bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); + return pixels; + } }