I have a few (38000) picture/video files in a folder. Approximately 40% of these are duplicates which I'm trying to get rid of. My question is, how can I tell if 2 files are identical? So far I tried to use a SHA1 of the files but it turns out that many duplicates files had different hashes. This is the code I was using:
public static String getHash(File doc) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA1");
FileInputStream inStream = new FileInputStream(doc);
DigestInputStream dis = new DigestInputStream(inStream, md);
BufferedInputStream bis = new BufferedInputStream(dis);
while (true) {
int b = bis.read();
if (b == -1)
break;
}
inStream.close();
dis.close();
bis.close();
} catch (NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
}
BigInteger bi = new BigInteger(md.digest());
return bi.toString(16);
}
Can I modify this in any way? Or will I have to use a different method?
As outlined above duplicate detection can be based on a hash. However, if you want to have near duplicate detection, which means that you are searching for images that basically show the same things, but have been scaled, rotated, etc. you might need a content based image retrieval approach. There's LIRE (https://code.google.com/p/lire/), a Java library for that, and you'll find the "SimpleApplication" in the Download section. What you then can do is to
Index the first image
go to the next image I
Search for I in the index
If there are results with a score below a threshold, then mark them as duplicate
Index I
Go to (2)
Students of mine did it, it worked well, but I don't have the source code at hand. But rest assured, it's just a few lines and the simple application will get you started.
Besides using hash, if your duplicates have different sizes (because they were resized), you could compare pixel by pixel (maybe not the entire image but a sub-section of the image).
This may depend on the image format but you could compare by comparing the height and width and then go pixel by pixel using the RGB code. To make it more efficient you can decide a threshold of comparison. For example:
public class Main {
public static void main(String[] args) throws IOException {
ImageChecker i = new ImageChecker();
BufferedImage one = ImageIO.read(new File("D:/Images/460249177.jpg"));
BufferedImage two = ImageIO.read(new File("D:/Images/460249177a.jpg"));
if(one.getWidth() + one.getHeight() >= two.getWidth() + two.getHeight()) {
i.setOne(one);
i.setTwo(two);
} else {
i.setOne(two);
i.setTwo(one);
}
System.out.println(i.compareImages());
}
}
public class ImageChecker {
private BufferedImage one;
private BufferedImage two;
private double difference = 0;
private int x = 0;
private int y = 0;
public ImageChecker() {
}
public boolean compareImages() {
int f = 20;
int w1 = Math.min(50, one.getWidth() - two.getWidth());
int h1 = Math.min(50, one.getHeight() - two.getHeight());
int w2 = Math.min(5, one.getWidth() - two.getWidth());
int h2 = Math.min(5, one.getHeight() - two.getHeight());
for (int i = 0; i <= one.getWidth() - two.getWidth(); i += f) {
for (int j = 0; j <= one.getHeight() - two.getHeight(); j += f) {
compareSubset(i, j, f);
}
}
one = one.getSubimage(Math.max(0, x - w1), Math.max(0, y - h1),
Math.min(two.getWidth() + w1, one.getWidth() - x + w1),
Math.min(two.getHeight() + h1, one.getHeight() - y + h1));
x = 0;
y = 0;
difference = 0;
f = 5;
for (int i = 0; i <= one.getWidth() - two.getWidth(); i += f) {
for (int j = 0; j <= one.getHeight() - two.getHeight(); j += f) {
compareSubset(i, j, f);
}
}
one = one.getSubimage(Math.max(0, x - w2), Math.max(0, y - h2),
Math.min(two.getWidth() + w2, one.getWidth() - x + w2),
Math.min(two.getHeight() + h2, one.getHeight() - y + h2));
f = 1;
for (int i = 0; i <= one.getWidth() - two.getWidth(); i += f) {
for (int j = 0; j <= one.getHeight() - two.getHeight(); j += f) {
compareSubset(i, j, f);
}
}
System.out.println(difference);
return difference < 0.1;
}
public void compareSubset(int a, int b, int f) {
double diff = 0;
for (int i = 0; i < two.getWidth(); i += f) {
for (int j = 0; j < two.getHeight(); j += f) {
int onepx = one.getRGB(i + a, j + b);
int twopx = two.getRGB(i, j);
int r1 = (onepx >> 16);
int g1 = (onepx >> 8) & 0xff;
int b1 = (onepx) & 0xff;
int r2 = (twopx >> 16);
int g2 = (twopx >> 8) & 0xff;
int b2 = (twopx) & 0xff;
diff += (Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1
- b2)) / 3.0 / 255.0;
}
}
double percentDiff = diff * f * f / (two.getWidth() * two.getHeight());
if (percentDiff < difference || difference == 0) {
difference = percentDiff;
x = a;
y = b;
}
}
public BufferedImage getOne() {
return one;
}
public void setOne(BufferedImage one) {
this.one = one;
}
public BufferedImage getTwo() {
return two;
}
public void setTwo(BufferedImage two) {
this.two = two;
}
}
You need to use aHash, pHash and best of both dHash algorithm for this.
I wrote a pure java library just for this few days back. You can feed it with directory path(includes sub-directory), and it will list the duplicate images in list with absolute path which you want to delete. Alternatively, you can use it to find all unique images in a directory too.
It used awt api internally, so can't be used for Android though. Since, imageIO has problem reading alot of new types of images, i am using twelve monkeys jar which is internally used.
https://github.com/srch07/Duplicate-Image-Finder-API
Jar with dependencies bundled internally can be downloaded from, https://github.com/srch07/Duplicate-Image-Finder-API/blob/master/archives/duplicate_image_finder_1.0.jar
The api can find duplicates among images of different sizes too.
You could convert your files with e.g. imagemagick convert to a format which has a canonical representation and as little metadata as possible. I guess I'd use PNM. So try something like this:
convert input.png pnm:- | md5sum -
If this does yield the same result for two files which compared different before, then metadata is in fact the source of your problem, and you can either use some command line approach like this, or update your code to read the image and compute the hash from the raw uncompressed data.
If, on the other hand, different files still compare different, then you have some changes to the actual image data. One possible cause might be the addition or removal of an alpha channel, particularly if you are dealing with PNG here. With JPEG, on the other hand, you'll likely have images uncompressed and then recompressed again, which will lead to slight modifications and data loss. JPEG is an inherently lossy codec, and any two images will likely differ unless they were created using the same application (or library), with the same settings and from the same input data. In that case you'll need to perform a fuzzy image matching. Tools like Geeqie can perform such things. If you want to do this yourself, you'll have a lot of work ahead of you, and should do some research up front.
It's been a long time so I should probably explain how I finally solved my problem. The real trick was to not use hashes to begin with and instead just compare the timestamps in the exif data. Given that these pictures were taken either by me of my wife it would have been quite unlikely for different files to have the same timestamp, hence this simpler solution was actually much more reliable.
You can check different percentage of two images through below method and if different percentage os below 10 then you can call it identical image:
private static double getDifferencePercent(BufferedImage img1, BufferedImage img2) {
int width = img1.getWidth();
int height = img1.getHeight();
int width2 = img2.getWidth();
int height2 = img2.getHeight();
if (width != width2 || height != height2) {
throw new IllegalArgumentException(String.format("Images must have the same dimensions: (%d,%d) vs. (%d,%d)", width, height, width2, height2));
}
long diff = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y));
}
}
long maxDiff = 3L * 255 * width * height;
return 100.0 * diff / maxDiff;
}
private static int pixelDiff(int rgb1, int rgb2) {
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
}
// covert image to Buffered image through this method
public static BufferedImage toBufferedImage(Image img)
{
if (img instanceof BufferedImage)
{
return (BufferedImage) img;
}
// Create a buffered image with transparency
BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
// Draw the image on to the buffered image
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(img, 0, 0, null);
bGr.dispose();
// Return the buffered image
return bimage;
}
Get insight idea from this site : https://rosettacode.org/wiki/Percentage_difference_between_images#Kotlin
The question was asked long time ago. I have found the following link very useful, it has codes for all languages. https://rosettacode.org/wiki/Percentage_difference_between_images#Kotlin
Here is the code for Kotlin taken from the link
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import kotlin.math.abs
fun getDifferencePercent(img1: BufferedImage, img2: BufferedImage): Double {
val width = img1.width
val height = img1.height
val width2 = img2.width
val height2 = img2.height
if (width != width2 || height != height2) {
val f = "(%d,%d) vs. (%d,%d)".format(width, height, width2, height2)
throw IllegalArgumentException("Images must have the same dimensions: $f")
}
var diff = 0L
for (y in 0 until height) {
for (x in 0 until width) {
diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y))
}
}
val maxDiff = 3L * 255 * width * height
return 100.0 * diff / maxDiff
}
fun pixelDiff(rgb1: Int, rgb2: Int): Int {
val r1 = (rgb1 shr 16) and 0xff
val g1 = (rgb1 shr 8) and 0xff
val b1 = rgb1 and 0xff
val r2 = (rgb2 shr 16) and 0xff
val g2 = (rgb2 shr 8) and 0xff
val b2 = rgb2 and 0xff
return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2)
}
fun main(args: Array<String>) {
val img1 = ImageIO.read(File("Lenna50.jpg"))
val img2 = ImageIO.read(File("Lenna100.jpg"))
val p = getDifferencePercent(img1, img2)
println("The percentage difference is ${"%.6f".format(p)}%")
}
Related
I've been trying to implement a BC1 (DXT1) decompression algorithm in Java. Everything seems to work pretty precise but I've ran into problem with some blocks around transparent ones. I've been trying to resolve it for a few hours without success.
In short, after decompressing all blocks everything looks good except for the blocks whose are around transparent ones. During the development I've been checking results with results from DirectXTex (texconv) which is written in C++.
This is my result compared to DirectXTex one:
Here is the code I'm using:
BufferedImage decompress(byte[] buffer, int width, int height)
and implementation:
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
int[] scanline = new int[4 * width]; //stores 4 horizontal lines (width/4 blocks)
RGBA[] blockPalette = new RGBA[4]; //stores RGBA values of current block
int bufferOffset = 0;
for (int row = 0; row < height / 4; row++) {
for (int col = 0; col < width / 4; col++) {
short rgb0 = Short.reverseBytes(Bytes.getShort(buffer, bufferOffset));
short rgb1 = Short.reverseBytes(Bytes.getShort(buffer, bufferOffset + 2));
int bitmap = Integer.reverseBytes(Bytes.getInt(buffer, bufferOffset + 4));
bufferOffset += 8;
blockPalette[0] = R5G6B5.decode(rgb0);
blockPalette[1] = R5G6B5.decode(rgb1);
if(rgb0 <= rgb1) {
int c2r = (blockPalette[0].getRed() + blockPalette[1].getRed()) / 2;
int c2g = (blockPalette[0].getGreen() + blockPalette[1].getGreen()) / 2;
int c2b = (blockPalette[0].getBlue() + blockPalette[1].getBlue()) / 2;
blockPalette[2] = new RGBA(c2r, c2g, c2b, 255);
blockPalette[3] = new RGBA(0, 0, 0, 0);
} else {
int c2r = (2 * blockPalette[0].getRed() + blockPalette[1].getRed()) / 3;
int c2g = (2 * blockPalette[0].getGreen() + blockPalette[1].getGreen()) / 3;
int c2b = (2 * blockPalette[0].getBlue() + blockPalette[1].getBlue()) / 3;
int c3r = (blockPalette[0].getRed() + 2 * blockPalette[1].getRed()) / 3;
int c3g = (blockPalette[0].getGreen() + 2 * blockPalette[1].getGreen()) / 3;
int c3b = (blockPalette[0].getBlue() + 2 * blockPalette[1].getBlue()) / 3;
blockPalette[2] = new RGBA(c2r, c2g, c2b, 255);
blockPalette[3] = new RGBA(c3r, c3g, c3b, 255);
}
for (int i = 0; i < 16; i++, bitmap >>= 2) {
int pi = (i / 4) * width + (col * 4 + i % 4);
int index = bitmap & 3;
scanline[pi] = A8R8G8B8.encode(blockPalette[index]);
}
}
//copy scanline to buffered image
result.setRGB(0, row * 4, width, 4, scanline, 0, width);
}
return result;
Does anyone have idea where is the problem? I've been doing exactly the same steps as specification says: Block Compression (Direct3D 10)
Is it that blockPalette[2].set(c2r, c2g, c2b); should be blockPalette[2].set(c2r, c2g, c2b, 255);? (in two locations)
For those who are interested, I've found that the problem was in comparing short values.
I've just changed:
if(rgb0 <= rgb1) {
to either
if(Short.compareUnsigned(rgb0, rgb1) <= 0) {
or
if((rgb0 & 0xffff) <= (rgb1 & 0xffff)) {
and this ensures that color values are compared as unsigned shorts (positive integers).
I am trying to implement window level functionality( To apply bone, brain, lung etc on CT) for DICOM images in my application and implemented formula as per the DICOM specification.
I am changing pixel values based on below formula and creating a new image, but images are becoming blank. What am doing wrong and is this correct way to do this. Please help :(:( Thanks
BufferedImage image = input image;
double w = 2500; // Window width
double c = 500; // window Center
double ymin = 0;
double ymax = 255;
double x = 0;
double y = 0;
double slope = dicomObject.get(Tag.RescaleSlope).getFloat(true);
double intercept = dicomObject.get(Tag.RescaleIntercept).getFloat(true);
int width = image.getWidth();
int height = image.getHeight();
double val = c - 0.5 - (w - 1) / 2;
double val2 = c - 0.5 + (w - 1) / 2;
for (int m = 0; m < height; m++) {
for (int n = 0; n < width; n++) {
int rgb = image.getRGB(n, m);
int valrgb = image.getRGB(n, m);
int a = (0xff000000 & valrgb) >>> 24;
int r = (0x00ff0000 & valrgb) >> 16;
int g = (0x0000ff00 & valrgb) >> 8;
int b = (0x000000ff & valrgb);
x = a + r + g + b;
if (x <= val)
y = ymin;
else if (x > val2)
y = ymax;
else {
y = ((x - (c - 0.5)) / (w - 1) + 0.5) * (ymax - ymin)+ ymin;
}
y = y * slope + intercept;
rgb = (int) y;
image.setRGB(n, m, rgb);
}
}
String filePath = "out put fileName";
ImageIO.write(image, "jpeg", new File(filePath));
First of all whats in your BufferedImage image ?
There are three steps you want to take from raw (decopressed) pixel data:
Get stored values - apply BitsAllocated, BitsStored, HighBit transformation. (I guess you image already passed that level)
Get modality values - thats your Slope, Intercept transformation. Ofter this transformation, your data will be in Hounsfield Units for CT.
Then you apply WW/WL (Value Of Interest) transformation, which will transform this window of walues into grayscale color space.
EDIT:
You've got to tell me where did you get "input image" from? After decompression pixel data should be in a byte array of size byte[width*height*2] (for CT Image BitsAllocated is always 16, thus *2). You can get stored values like this:
ushort code = (ushort)((pixel[0] + (pixel[1] << 8)) & (ushort)((1<<bitsStored) - 1));
int value = TwosComplementDecode(code);
This is how I am doing to convert from RGB to CMYK using the more "correct" way - i.e using an ICC color profile.
// Convert RGB to CMYK with level shift (minus 128)
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(JPEGWriter.class.getResourceAsStream(pathToCMYKProfile)));
float red, green, blue, cmyk[];
//
for(int i = 0, index = 0; i < imageHeight; i++) {
for(int j = 0; j < imageWidth; j++, index++) {
red = ((rgb[index] >> 16) & 0xff)/255.0f;
green = ((rgb[index] >> 8) & 0xff)/255.0f;
blue = (rgb[index] & 0xff)/255.0f;
cmyk = instance.fromRGB(new float[] {red, green, blue});
C[i][j] = cmyk[0]*255.0f - 128.0f;
M[i][j] = cmyk[1]*255.0f - 128.0f;
Y[i][j] = cmyk[2]*255.0f - 128.0f;
K[i][j] = cmyk[3]*255.0f - 128.0f;
}
}
}
My problem is: it's prohibitively slow given a large image. In one case, it took about 104s instead of the usual 2s for me to write the data as a JPEG image. It turns out the above transform is the most time-consuming part.
I am wondering if there is any way to make it faster. Note: I am not going to use the cheap conversion algorithm one can find form the web.
Update: following haraldK's suggestion, here is the revised version:
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
if(cmykColorSpace == null)
cmykColorSpace = new ICC_ColorSpace(ICC_Profile.getInstance(JPEGWriter.class.getResourceAsStream(pathToCMYKProfile)));
DataBuffer db = new DataBufferInt(rgb, rgb.length);
WritableRaster raster = Raster.createPackedRaster(db, imageWidth, imageHeight, imageWidth, new int[] {0x00ff0000, 0x0000ff00, 0x000000ff}, null);
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorConvertOp cco = new ColorConvertOp(sRGB, cmykColorSpace, null);
WritableRaster cmykRaster = cco.filter(raster, null);
byte[] o = (byte[])cmykRaster.getDataElements(0, 0, imageWidth, imageHeight, null);
for(int i = 0, index = 0; i < imageHeight; i++) {
for(int j = 0; j < imageWidth; j++) {
C[i][j] = (o[index++]&0xff) - 128.0f;
M[i][j] = (o[index++]&0xff) - 128.0f;
Y[i][j] = (o[index++]&0xff) - 128.0f;
K[i][j] = (o[index++]&0xff) - 128.0f;
}
}
}
Update: I also found out it's much faster to do filter on a BufferedImage instead of a Raster. See this post: ARGB int array to CMYKA byte array convertion
You should get rid of the memory allocation within the innermost loop. new is a prohibitively expensive operation. Also it might kick the garbage collector into action, which adds a further penality.
If you can affort the memory consumption, you could create a lookup table:
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
ColorSpace cs = new ICC_ColorSpace(...);
int[] lookup = createRGB2CMYKLookup(cs);
for(int y = 0, index = 0; y < imageHeight; y++) {
for(int x = 0; x < imageWidth; x++, index++) {
int cmyk = lookup[rgb[index]];
C[y][x] = ((cmyk >> 24) & 255) - 128F;
M[y][x] = ((cmyk >> 16) & 255) - 128F;
Y[y][x] = ((cmyk >> 8) & 255) - 128F;
K[y][x] = ((cmyk ) & 255) - 128F;
}
}
}
static int[] createRGB2CMYKLookup(ColorSpace cs) {
int[] lookup = new int[16 << 20]; // eats 16m times 4 bytes = 64mb
float[] frgb = new float[3];
float fcmyk[];
for (int rgb=0; rgb<lookup.length; ++rgb) {
frgb[0] = ((rgb >> 16) & 255) / 255F;
frgb[1] = ((rgb >> 8) & 255) / 255F;
frgb[2] = ((rgb ) & 255) / 255F;
fcmyk = cs.fromRGB(frgb);
int c = (int) (fcmyk[0] * 255F);
int m = (int) (fcmyk[1] * 255F);
int y = (int) (fcmyk[2] * 255F);
int k = (int) (fcmyk[3] * 255F);
int icmyk = (c << 24) | (m << 16) | (y << 8) | k;
}
return lookup;
}
Now this may actually worsen performance for small images as it is. It will only help if you can re-use the lookup table for multiple images, but as your example looks you're using actually the same ICC profile over and over. Thus you could cache the lookup table and pay its initialization cost only once:
static int[] lookup;
static {
ColorSpace cs = new ICC_ColorSpace(...);
lookup = createRGB2CMYKLookup(cs);
}
// convert always using (the same) lookup table
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
for(int y = 0, index = 0; y < imageHeight; y++) {
for(int x = 0; x < imageWidth; x++, index++) {
int cmyk = lookup[rgb[index]];
C[y][x] = ((cmyk >> 24) & 255) - 128F;
M[y][x] = ((cmyk >> 16) & 255) - 128F;
Y[y][x] = ((cmyk >> 8) & 255) - 128F;
K[y][x] = ((cmyk ) & 255) - 128F;
}
}
}
You should probably use ColorConvertOp. It uses optimized native code on most platforms, and supports ICC profile transforms.
Not sure how fast it will work when using float based Rasters, but it does the job.
Something like:
ICC_Profile cmyk = ...;
ICC_Profile sRGB = ...;
ColorConvertOp cco = new ColorConvertOp(sRGB, cmyk);
Raster rgbRaster = ...;
WritableRaster cmykRaster = cco.filter(rgbRaster, null);
// Or alternatively, if you have a BufferedImage input
BufferedImage rgbImage = ...;
BufferedImage cmykImage = cco.filter(rgbImage, null);
This question already has answers here:
Java: how to convert RGB color to CIE Lab
(6 answers)
Closed 6 years ago.
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;
public class ConvertRGBtoLAB {
public static void main(String[] args) {
//get input image
String fileName = "IMG_7990.jpg";
//read input image
BufferedImage image = null;
try
{
image = ImageIO.read(new File(fileName));
}
catch (IOException e)
{
e.printStackTrace();
}
//setup result image
int sizeX = image.getWidth();
int sizeY = image.getHeight();
float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
float ls, as, bs;
float eps = 216.f/24389.f;
float k = 24389.f/27.f;
float Xr = 0.964221f; // reference white D50
float Yr = 1.0f;
float Zr = 0.825211f;
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int c = image.getRGB(x,y);
int R= (c & 0x00ff0000) >> 16;
int G = (c & 0x0000ff00) >> 8;
int B = c & 0x000000ff;
r = R/255.f; //R 0..1
g = G/255.f; //G 0..1
b = B/255.f; //B 0..1
// assuming sRGB (D65)
if (r <= 0.04045)
r = r/12;
else
r = (float) Math.pow((r+0.055)/1.055,2.4);
if (g <= 0.04045)
g = g/12;
else
g = (float) Math.pow((g+0.055)/1.055,2.4);
if (b <= 0.04045)
b = b/12;
else
b = (float) Math.pow((b+0.055)/1.055,2.4);
X = 0.436052025f*r + 0.385081593f*g + 0.143087414f *b;
Y = 0.222491598f*r + 0.71688606f *g + 0.060621486f *b;
Z = 0.013929122f*r + 0.097097002f*g + 0.71418547f *b;
// XYZ to Lab
xr = X/Xr;
yr = Y/Yr;
zr = Z/Zr;
if ( xr > eps )
fx = (float) Math.pow(xr, 1/3.);
else
fx = (float) ((k * xr + 16.) / 116.);
if ( yr > eps )
fy = (float) Math.pow(yr, 1/3.);
else
fy = (float) ((k * yr + 16.) / 116.);
if ( zr > eps )
fz = (float) Math.pow(zr, 1/3.);
else
fz = (float) ((k * zr + 16.) / 116);
ls = ( 116 * fy ) - 16;
as = 500*(fx-fy);
bs = 200*(fy-fz);
int Ls = (int) (2.55* ls + .5);
int As = (int) (as + .5);
int Bs = (int) (bs + .5);
int lab = 0xFF000000 + (Ls << 16) + (As << 8) + Bs; // and reassign
image.setRGB(x, y, lab);
}
}
//write new image
File outputfile = new File("lab.png");
try {
// png is an image format (like gif or jpg)
ImageIO.write(image, "png", outputfile);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Hello,
I am trying to turn a RGB image into CIELAB colour space(LAB) I get an output but I have no idea what it is supposed to look like.
Can anyone point me to a already existing image converter or confirm that I have done this correctly?
Thanks!
I personally use this site as quick reference on conversion formulas between common color spaces.
OpenCV have functions for conversions between different color spaces. Look at my other answer here. This is in C, bout you can easily check you code.
You will want to ensure that Ls, As, and Bs are clamped to the range 0 to 255. The statement you have to combine them into a single int will allow an out-of-bounds value to affect the other values.
Heading
Is there a way to map an arbitrary string to a HEX COLOR code. I tried to compute the HEX number for string using string hashcode. Now I need to convert this hex number to six digits which are in HEX color code range. Any suggestions ?
String [] programs = {"XYZ", "TEST1", "TEST2", "TEST3", "SDFSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS"};
for(int i = 0; i < programs.length; i++) {
System.out.println( programs[i] + " -- " + Integer.toHexString(programs[i].hashCode()));
}
If you don't really care about the "meaning" of the color you can just split up the bits of the int (remove the first for just RGB instead of ARGB)
String [] programs = {"XYZ", "TEST1", "TEST2", "TEST3", "SDFSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS"};
for(int i = 0; i < programs.length; i++) {
System.out.println( programs[i] + " -- " + intToARGB(programs[i].hashCode()));
}
....
public static String intToARGB(int i){
return Integer.toHexString(((i>>24)&0xFF))+
Integer.toHexString(((i>>16)&0xFF))+
Integer.toHexString(((i>>8)&0xFF))+
Integer.toHexString((i&0xFF));
}
How about anding the hashcode with 0x00FFFFFF (or 0xFFFFFF if you want to default the alpha channel)? For example:
private String getColorCode(String inputString)
{
String colorCode = String.format("#%06x", 0xFFFFFF & inputString.hashCode());
}
I ran into this question while looking for a Ruby solution, so I thought I would add an answer for Ruby in case someone follows the same path I did. I ended up using the following method, which creates the same six digit hex code from a string by using String.hash and the optional base-specifying parameter of Fixnum.to_s. It slices from 1 rather than 0 to skip negative signs.
def color_from_string(query)
'#'+query.hash.to_s(16).slice(1,6)
end
In case anyone else is looking for a solution for Flutter/Dart:
Color _fromInt(int i) {
final a = (i >> 24) & 0xFF;
final r = (i >> 16) & 0xFF;
final g = (i >> 8) & 0xFF;
final b = i & 0xFF;
return Color.fromARGB(a, r, g, b);
}
It's also worth noting that with certain background colours e.g. black, it may be difficult to differentiate the colours.
To this end, I set the alpha channel to the max value of 255:
Color _fromInt(int i) {
const a = 255;
final r = (i >> 16) & 0xFF;
final g = (i >> 8) & 0xFF;
final b = i & 0xFF;
return Color.fromARGB(a, r, g, b);
}
The following class takes a String and converts it to a color.
It is a simplified Java port of the Color-Hash TypeScript project (MIT license): https://github.com/zenozeng/color-hash.
The original project contains some parameters to adjust the generated colours.
These were not included.
The advantage of the Color-Hash algorithm, compared using a hash value directly, is that the generated colours are more perceptually uniform.
A lot of copy/paste was going on here:
Mostly from here the Color-Hash project (MIT license): https://github.com/zenozeng/color-hash
Convert HSL to RGB: https://stackoverflow.com/a/33947547/2021763
Easy way to convert Color to hex string: https://stackoverflow.com/a/18194652/2021763
Result:
XYZ: #bf40b3
TEST1: #86432d
TEST2: #3a2dd2
TEST3: #bf4073
SDFSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS: #53ac8b
public class ColorHash
{
private static final double[] LigthnessArray = new double[] { 0.35, 0.5, 0.65 };
private static final double[] SaturationArray = new double[] { 0.35, 0.5, 0.65 };
public Color generateColor(String input) throws NoSuchAlgorithmException
{
// Easiest way would be String.hashCode()
// But "Test1" and "Test2" result in practically the same color
// Therefore another hash algorithm should be used
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(input.getBytes());
byte[] digest = md.digest();
int hash = Math.abs(ByteBuffer.wrap(digest).getInt());
double hue, saturation, lightness;
hue = hash % 359 / 359.; // note that 359 is a prime
hash = (int) Math.ceil(hash / 360);
saturation = SaturationArray[hash % SaturationArray.length];
hash = (int) Math.ceil(hash / SaturationArray.length);
lightness = LigthnessArray[hash % LigthnessArray.length];
return hslColor((float) hue, (float) saturation, (float) lightness);
}
public String generateColorHash(String input) throws NoSuchAlgorithmException
{
return "#" + Integer.toHexString(generateColor(input).getRGB()).substring(2);
}
private static Color hslColor(float h, float s, float l)
{
float q, p, r, g, b;
if (s == 0)
{
r = g = b = l; // achromatic
} else
{
q = l < 0.5 ? (l * (1 + s)) : (l + s - l * s);
p = 2 * l - q;
r = hue2rgb(p, q, h + 1.0f / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1.0f / 3);
}
return new Color(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255));
}
private static float hue2rgb(float p, float q, float h)
{
if (h < 0)
{
h += 1;
}
if (h > 1)
{
h -= 1;
}
if (6 * h < 1) { return p + ((q - p) * 6 * h); }
if (2 * h < 1) { return q; }
if (3 * h < 2) { return p + ((q - p) * 6 * ((2.0f / 3.0f) - h)); }
return p;
}
public static void main(String[] args) throws NoSuchAlgorithmException
{
String [] programs = {"XYZ", "TEST1", "TEST2", "TEST3", "SDFSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS"};
ColorHash gen = new ColorHash();
for(String p : programs) {
System.out.println(p + ": " + gen.generateColorHash(p));
}
}
}