/*
* 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.image;
import android.graphics.Bitmap;
import android.graphics.Color;
/**
* This class is an utility class. It offers RGB convertion
* methods and helping methods to compare and store the rgb data.
* @author Daniel
*
*/
public final class ColorAnalysisUtil {
private ColorAnalysisUtil() {
}
/* To extract colors from ARGB int:
case RED:
return (rgb >> 16) & 0xFF;
case GREEN:
return (rgb >> 8) & 0xFF;
case BLUE:
return rgb & 0xFF;
case ALPHA:
return (rgb >> 24) & 0xFF;*/
public static int interpolateColorLinear(int fromColor, int toColor, float fraction) {
float antiFraction = 1.f - fraction;
return Color.argb((int) ((Color.alpha(toColor) * fraction + Color.alpha(fromColor) * antiFraction)),
(int) ((Color.red(toColor) * fraction + Color.red(fromColor) * antiFraction)),
(int) ((Color.green(toColor) * fraction + Color.green(fromColor) * antiFraction)),
(int) ((Color.blue(toColor) * fraction + Color.blue(fromColor) * antiFraction)));
}
public static int colorMultiples(int color, float multiple) {
return Color.argb((int) (Color.alpha(color) * multiple),
(int) (Color.red(color) * multiple),
(int) (Color.green(color) * multiple),
(int) (Color.blue(color) * multiple));
}
/**
* Compresses the given values into a single integer. This integer could then be read by fromRGB() and the
* given colors could be re-extracted.<br>
* If a value is not in range from 0 to 255, the data will be corrupt.
* @param red The red amount.
* @param green The green amount.
* @param blue The blue amount.
* @param alpha The alpha amount.
* @return A single integer storing the given information. Blue in the first 8 bits,
* green in the next 8 bits and red in
* the next 8 bits.
*/
public static int toRGB(int red, int green, int blue, int alpha) {
return blue | (green << 8) | (red << 16) | (alpha << 24);
}
/**
* Returns the standard norm of the given color in the 3 or 4 dimensional
* color space. This is the distance to the color
* (0,0,0(,0)) in the color space.
* @param rgb The RGBA color to check.
* @param useAlpha If <code>true</code> the alpha will be taken into account and
* the distance will be measured in 4 dimensional color space.
* @return The distance of this rgb color to the zero color (black).
*/
public static double norm(int rgb, boolean useAlpha) {
double result = 0;
int currColValue;
currColValue = Color.red(rgb);
result += currColValue * currColValue;
currColValue = Color.green(rgb);
result += currColValue * currColValue;
currColValue = Color.blue(rgb);
result += currColValue * currColValue;
if (useAlpha) {
currColValue = Color.red(rgb);
result += currColValue * currColValue;
}
return Math.sqrt(result);
}
public static double factorToSimilarityBound(double factor) {
// makes the factor in range [0,1]
double inBoundFactor = Math.max(0.0, Math.min(1.0, factor));
// uses a function to transform the given value to a more fitting result.
// For the in bound merge factor x it evaluates to : f(x)= e^(a*x^b)-1
// with a=Log(13/10) and b = Log(a/Log(101/100))/Log(2) (makes a maximum for f(1)=0.3
// , it is f(0.5)=0.01 and the strictly monotonic ascending behavior of the e-function
return Math.pow(Math.E, 0.26236 * Math.pow(inBoundFactor, 4.72068)) - 1.0;
}
/**
* Mixes the given rgb values. To take the size of the underlying
* picture in account, the pixel amount needs to be supplied.
* @param rgb1 The first RGB value.
* @param rgb2 The second RGB value.
* @param pixels1 The pixels of the image the first RGB value refers to.
* @param pixels2 The pixels of the image the second RGB value refers to.
* @return The mixed RGBA value or <code>-1</code> if a pixels value is lower than zero.
*/
public static int mix(int rgb1, int rgb2, int pixels1, int pixels2) {
int red;
int green;
int blue;
int alpha;
long currColValue1;
long currColValue2;
long totalPixels = pixels1 + pixels2;
if (pixels1 < 0 || pixels2 < 0) {
return -1;
}
long newCol;
//red
currColValue1 = Color.red(rgb1);
currColValue2 = Color.red(rgb2);
newCol = (currColValue1 * pixels1 + currColValue2 * pixels2) / totalPixels;
red = (int) newCol;
//green
currColValue1 = Color.green(rgb1);
currColValue2 = Color.green(rgb2);
newCol = (currColValue1 * pixels1 + currColValue2 * pixels2) / totalPixels;
green = (int) newCol;
//blue
currColValue1 = Color.blue(rgb1);
currColValue2 = Color.blue(rgb2);
newCol = (currColValue1 * pixels1 + currColValue2 * pixels2) / totalPixels;
blue = (int) newCol;
//alpha
currColValue1 = Color.alpha(rgb1);
currColValue2 = Color.alpha(rgb2);
newCol = (currColValue1 * pixels1 + currColValue2 * pixels2) / totalPixels;
alpha = (int) newCol;
return ColorAnalysisUtil.toRGB(red, green, blue, alpha);
}
public static int getAverageColor(Bitmap image) {
int width = image.getWidth();
int height = image.getHeight();
long averageRed = 0, averageGreen = 0, averageBlue = 0, averageAlpha = 0;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int rgba = image.getPixel(x, y);
averageRed += Color.red(rgba);
averageGreen += Color.green(rgba);
averageBlue += Color.blue(rgba);
averageAlpha += Color.alpha(rgba);
}
}
long pixels = width * height;
return Color.argb((int) (averageAlpha / pixels), (int) (averageRed / pixels), (int) (averageGreen / pixels), (int) (averageBlue / pixels));
}
public static int getAverageColor(Bitmap image, int fromX, int toX, int fromY, int toY) {
long averageRed = 0, averageGreen = 0, averageBlue = 0, averageAlpha = 0;
fromX = Math.max(0, fromX);
fromY = Math.max(0, fromY);
toX = Math.min(image.getWidth() - 1, toX);
toY = Math.min(image.getHeight() - 1, toY);
for (int x = fromX; x < toX; x++) {
for (int y = fromY; y < toY; y++) {
int rgba = image.getPixel(x, y);
averageRed += Color.red(rgba);
averageGreen += Color.green(rgba);
averageBlue += Color.blue(rgba);
averageAlpha += Color.alpha(rgba);
}
}
long pixels = (toX - fromX) * (toY - fromY);
if (pixels <= 0L) {
pixels = 1L;
}
return Color.argb((int) (averageAlpha / pixels), (int) (averageRed / pixels), (int) (averageGreen / pixels), (int) (averageBlue / pixels));
}
/**
* Returns a String representation of the given average color in HEX format, optionally with the alpha.
* @param rgb The rgb to visualize.
* @param useAlpha If alpha value should be visualized.
* @return A formatted HEX String visualizing the given rgb.
*/
public static String visualizeRGB(int rgb, boolean useAlpha) {
String output = "";
String curr;
if (useAlpha) {
curr = Integer.toHexString(Color.alpha(rgb)).toUpperCase();
if (curr.length() == 1) {
output += "0" + curr;
} else {
output += curr;
}
output += " ";
}
curr = Integer.toHexString(Color.red(rgb)).toUpperCase();
if (curr.length() == 1) {
output += "0" + curr;
} else {
output += curr;
}
output += " ";
curr = Integer.toHexString(Color.green(rgb)).toUpperCase();
if (curr.length() == 1) {
output += "0" + curr;
} else {
output += curr;
}
output += " ";
curr = Integer.toHexString(Color.blue(rgb)).toUpperCase();
if (curr.length() == 1) {
output += "0" + curr;
} else {
output += curr;
}
return output;
}
/**
* Calculates the brightness of the given rgb value using the alpha channel as
* a human would recognize it. Note that a fully transparent color would be considered to be bright.
* @param rgb The rgb value
* @return The brightness where 1 is very bright and 0 is very dark.
*/
public static double getBrightnessWithAlpha(int rgb) {
// formula: (255-alpha)/255 + alpha*(0.299*red+0.587*green+0.114*blue)/(255*255)
// white 255/255/255 is always considered to be very bright(=1), no matter the alpha
return 1. +((rgb >> 24) & 0xFF) * (-1./255. + 0.299/65025.0 * ((rgb >> 16) & 0xFF) + 0.587/65025.0 * ((rgb >> 8) & 0xFF) + 0.114/65025.0 * (rgb & 0xFF));
}
/**
* Calculates the brightness of the given rgb value as a human would recognize it.
* @param rgb The rgb value.
* @return The brightness where 1 is very bright and 0 is very dark.
*/
public static double getBrightnessNoAlpha(int rgb) {
// formula: (0.299*red+0.587*green+0.114*blue)/255
return 0.299/255. * ((rgb >> 16) & 0xFF) + 0.587/255. * ((rgb >> 8) & 0xFF) + 0.114/255. * (rgb & 0xFF);
}
/**
* Calculates the "greyness" of the given RGB color, which is a way
* to measure the distance from the grey colors with red=green=blue.
* @param red The red value.
* @param green The green value.
* @param blue The blue value.
* @return Greyness of 0 means that red=green=blue which is the most grey and 1 means
* the least grey, like (255,0,0).
*/
public static double getGreyness(int red, int green, int blue) {
double mean = (red + green + blue) / 3.0;
// square of the euclid distance from the straight line from 0/0/0 to 255/255/255 divided by the maximum possible distance
// which is (255/3-255)²+2*(255/3)² for a color in the corner of the color square
return ((mean - red) * (mean - red) + (mean - blue) * (mean - blue) + (mean - green) * (mean - green)) / 43350.;
}
}