Mulithreading Example: Data Decomposition in Picture Processing - java

Currently I am programming an example for a java program which uses basic Multithreading. I want to show the advantage of the data composition principle in an image processing application that resembles a monochromatic filter. I load a picture in a buffer and then spawn four Threads which all compute different parts of the Image.
The image is divided into four slices and the computation time alone is lowered according to my expectations (3 to 4 times faster on a quadcore).
The problem is i try to avoid Race Conditions by giving each Thread a Buffer where the computed picture is saved. After the execution each Thread saves it's part of the picture in a jpg.
My question here is if there is a pattern i can use to save the different slices as one picture again while avoiding the dangers of the Shared mutable state variables.
If i use a static Variable in my Processing Class to reassemble the picture I get times that are even worse than the singlethreaded solutions.
How can i make the effiency that multithreading offers appearant in this example?
Thats my Code for the Multithreaded application:
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
public class Main {
static int threadCount=4;
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedImage original=null;
BufferedImage greyscale=null;
//loading Picture into Buffer
try{
original = ImageIO.read(new File("Desert.jpg"));
}
catch (IOException e)
{
e.printStackTrace();
}
ArrayList<Thread> threads = new ArrayList<Thread>();
//Creating Threads, each Thread gets a Copy of the Image
for( int i=0; i < threadCount; i++)
{
final int threadNumber =i;
threads.add(new Thread(new Processor2(deepCopy(original),threadNumber,threadCount)));}
//threads gets started
long start = System.currentTimeMillis();
for( int i=0; i < threadCount; i++){
threads.get(i).start();
}
for( int i=0; i < threadCount; i++){
try {
threads.get(i).join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
long seconds = (end-start);
System.out.println("Dauer: "+seconds+" ms.");
}
//Method that copies the Image
static BufferedImage deepCopy(BufferedImage bi) {
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(null);
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
}
The Threads that get spawned Execute this code:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Processor2 implements Runnable {
BufferedImage greyscale;
BufferedImage original;
int threadNumber, threadCount;
//The Constructor gets the Image, the total number of Threads and the own Threadnumber.
Processor2(BufferedImage image, int x, int y){
this.original=image;
threadNumber=x;
threadCount=y;
}
Object lock =new Object();
#Override
// The run Method goes through the pixel of the assignes slide of the picture, renders it monochromatic and then saves it in an ImageBuffer. This image is saved after the loop has reached all pixels.
public void run() {
// TODO Auto-generated method stub
int part =original.getWidth()/threadCount;
greyscale = new BufferedImage(part, original.getHeight(),BufferedImage.TYPE_BYTE_GRAY);
int x=0;
try{for(int i=threadNumber*part; i<part*(threadNumber+1); i++)
{
for(int j=0; j<original.getHeight(); j++)
{
// Get pixels
int rgb = original.getRGB(i, j);
int a = (rgb>>24)&0xff;
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb & 0xFF);
int avg = (r + g + b) / 3;
int gray = colorToRGB(a, avg, avg, avg);
greyscale.setRGB(x, j, gray);
} x++;
}}
finally{
saveImage(greyscale, threadNumber);
}
}
public void start(){
}
private static int colorToRGB(int alpha, int red, int green, int blue) {
int newPixel = 0;
newPixel += alpha;
newPixel = newPixel << 8;
newPixel += red; newPixel = newPixel << 8;
newPixel += green; newPixel = newPixel << 8;
newPixel += blue;
return newPixel;
}
static void saveImage(BufferedImage r,int threadNumber){
try {
ImageIO.write(r, "png",new File("blackwhiteimageshared"+threadNumber+".png") );
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
This Code is around two to three times faster than going through every pixel in a loop in one thread.
Would the Producer Consumer Pattern be a good method to change the Code to a state that after the execution i have one monochromatic image instead of 4 parts in a reasonable time?

Related

Why no speedup with more than one thread?

I'm creating a toy program in java using synchronized block. I have n "Pixelator" threads which pick a random pixel in a 1000x1000 image and assign it to the color of the Pixelator. Each pixel can only be assigned once. I write to a bufferedImage using a wrapper class that uses a synchronized method to write to the image. However, when I test with more than 1 thread, I do not see a speedup. Do you have a hint as to why that would be?
Relavant Code:
import java.awt.Color;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import java.util.ArrayList;
import java.util.Random;
public class q2 {
// The image constructed
public static BufferedImage img;
// Image dimensions; you could also retrieve these from the img object.
public static int width;
public static int height;
// simplified method for stack overflow example
public static int rgbFromN(int n) {
return -16755216;
}
public static void main(String[] args) {
Random r = new Random();
try {
// arg 0 is the width
width = 1000;
// arg 1 is the height
height = 1000;
// arg 2 is the number of threads
int nt = 1;
// create an image and initialize it to all 0's
img = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
synchronizedIMG simg = new synchronizedIMG(img);
for (int i=0;i<width;i++) {
for (int j=0;j<height;j++) {
img.setRGB(i,j,0);
}
}
Thread[] threads = new Thread[nt];
long startTime = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Pixelator(rgbFromN(i),width,height,((width*height)/nt),simg));
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
long endTime = System.currentTimeMillis();
System.out.println("Time(ms): " + (endTime-startTime));
// Write out the image
File outputfile = new File("outputimage.png");
ImageIO.write(img, "png", outputfile);
} catch (Exception e) {
System.out.println("ERROR " +e);
e.printStackTrace();
}
}
}
class Pixelator implements Runnable {
int color;
int width;
int height;
int numPixels;
int currentPixels = 0;
synchronizedIMG simg;
public Pixelator(int color, int width, int height,int numPixels, synchronizedIMG simg){
this.color = color;
this.width = width;
this.height = height;
this.numPixels = numPixels;
this.simg = simg;
}
public void run() {
int randomX = 0;
int randomY = 0;
boolean success = false;
while(currentPixels < numPixels){
randomX = 0 + (int)(Math.random() * (width));
randomY = 0 + (int)(Math.random() * (height));
success = simg.setColor(color, randomX, randomY);
if(success){
currentPixels++;
}
}
return;
}
}
class synchronizedIMG{
BufferedImage img;
public synchronizedIMG(BufferedImage img){
this.img = img;
}
public synchronized boolean setColor(int color, int x, int y){
if(img.getRGB(x, y) == 0){
img.setRGB(x, y, color);
return true;
} else{
return false;
}
}
}
It requires a certain amount of time to the machine to manage the threads. In image processing, use two threads instead of one, does not reduce the processing time by 50%, but between 30 % to 40 according to the processing (empirical estimation with the multi-threaded classes of my own java library).
Moreover in your case, you don't do any major processing just simple computation. So it's longer to manage the threads than doing the processing on a single thread. Try to do a big convolution.
The biggest problem you face is that adding more threads will not increase the memory bandwidth of your system.
Your threads do nothing except compute random numbers and write them out to memory. Adding more threads potentially increases the speed with which you can compute the random numbers, but that probably was pretty fast to begin with. Math.random() is not a crypto-quality random number generator. It probably is very fast.
Unfortunately, your job isn't done until all of the bytes have been written out to memory. Your system has only one memory bus, and it can only go so fast. All of the threads have to contend for that resource.

ImageWriter creates smaller picture ( in kb size ) [duplicate]

This question already has answers here:
Hiding message in JPG image
(2 answers)
Closed 6 years ago.
I have the following problem, I want to create simple steganography "program" by coding message in LSB.
I extract ARGB from picture ( each in it's own array ), encode message in LSB of blue color, and try to create new image using a those new values ( I join ARGB arrays back in int array ).
The obvious problem I have is when I change LSB and try to write them to picture , I can see that ImageWriter is creating picture that is much smaller in kb and I can't extract my message anymore.
This is the code :
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Steganography {
int [][] alpha;
int [][] red;
int [][] green;
int [][] blue;
public int [][] readPixels (String image) throws IOException {
//load image into img buffer
BufferedImage img = ImageIO.read(new File(image));
//make matrix according to picture height and width
int [][] pixels = new int[img.getHeight()][img.getWidth()];
// load matrix with image pixels
for(int i=0;i<pixels.length;i++) {
for (int j = 0; j < pixels[0].length; j++) {
pixels[i][j]=(img.getRGB(j, i));
}
}
/* reminder to myself
values will be negative because of packing the 4 byte values into a 4-byte
The getRGB method returns an int whose 4 bytes are the alpha, red, green, and blue components in that order.
Assuming that the pixel is not transparent, the alpha is 255 (0xFF).
It's the most significant byte in the int, and the first bit is set in that value.
Because in Java int values are signed according to Two's Complement,
the value is actually negative because that first bit is on.
*/
return pixels ;
}
// extracts colors and alpha into their own matrix so we can reconstruct image later
public void extractColors(int [][] pixel){
this.alpha = new int[pixel.length][pixel[0].length];
this.red = new int[pixel.length][pixel[0].length];
this.green = new int[pixel.length][pixel[0].length];
this.blue = new int[pixel.length][pixel[0].length];
for(int i=0;i<pixel.length;i++) {
for(int j=0;j<pixel[i].length;j++){
int clr = pixel[i][j];
alpha[i][j] = (clr & 0xff000000) >> 24;
red[i][j] = (clr & 0x00ff0000) >> 16;
green[i][j] = (clr & 0x0000ff00) >> 8;
blue [i][j] = clr & 0x000000ff;
}
}
} // closed method
//reconstruct image
// need to make 32 bit integer again in correct order
public void reconstructImage () throws IOException{
int height = alpha.length;
int width= alpha[0].length;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < width; y++) {
for (int x = 0; x < height; x++) {
int rgb= red[x][y];
rgb = (rgb << 8) + green[x][y];
rgb = (rgb << 8) + blue[x][y];
image.setRGB(y, x, rgb);
}
}
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // Needed see javadoc
param.setCompressionQuality(1.0F); // Highest quality
File file = new File("output.jpg");
ImageOutputStream ios = ImageIO.createImageOutputStream(file);
writer.setOutput(ios);
writer.write(image);
}
public void codeMessage (String message){
//first turn string into binary representation
// each character should have 7 bits
// ASCII uses 7 bit
message="START"+message.length()+message+"STOP";
String binaryMessage ="";
for(int i =0;i<message.length();i++){
//adding zeros if string has less than 8 characters
String binaryString= Integer.toBinaryString(message.charAt(i));
while (binaryString.length() !=7)
binaryString = "0"+binaryString;
binaryMessage+=binaryString;
}
//binaryMessage is binary representation of string
// change value of LSB in blue color according to binaryMessage
//actually coding message into LSB is done here
int k=0;
for (int i = 0; i < blue.length; i++) {
for (int j = 0; j < blue[i].length; j++) {
if(k>=binaryMessage.length())
break;
else if (binaryMessage.charAt(k) == '0') {
blue[i][j] = blue[i][j] & 0b1111110;
k++;
}
else {
blue[i][j] = blue[i][j] | 0b0000001;
k++;
}
}
}
} //closed codeMessage
public void readMessage(){
String LSB ="";
char charLSB;
String messageBinary ="";
for(int i=0;i<blue.length;i++){
for(int j=0;j<blue[i].length;j++){
LSB = Integer.toBinaryString(blue[i][j]);
charLSB = LSB.charAt(LSB.length()-1);
messageBinary+=charLSB;
}
}
char ArrayOfChars [] = new char [blue[0].length*blue.length];
int k =0;
for(int i=0;i<messageBinary.length()-7;i+=7){
String letter=(messageBinary.substring(i,i+7));
int valueOfASCIIcharacter = Integer.parseInt(letter,2);
char c = (char)(valueOfASCIIcharacter);
System.out.println(c);
ArrayOfChars[k]=c;
k++;
}
}
}
I have also tried to use ARGB instead of RGB for BufferedImage, without luck (only messes up colors, picture gets kinda pink ).
This is how I call function in main class
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException{
Steganography img = new Steganography();
int pixels [][] =img.readPixels("image.jpg");
img.extractColors(pixels);
img.codeMessage("Some message");
img.reconstructImage();
/*reading message from here on */
int pixels2 [][] = img.readPixels("output.jpg");
img.extractColors(pixels2);
img.readMessage();
}
}
Original picture has 83,3 kb ,and recreated picture has only 24,3 kb.
I have found solution.
For anyone having same problem as me and possible searching for solution in future:
This algorithm can't survive .jpg extension. Changed picture to bmp, takes bit longer but everything works as expected.
If you want to use steganography on jpg you have to use something else than LSB.

generating design with java

i am trying to generate following given sateen design with help of my java code but this code is not creating png file named sateen
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
public class Sateen {
BufferedImage image;
int width;
int height;
int red,green,blue;
public Sateen() {
try {
File input = new File("n.png");
image = ImageIO.read(input);
width = image.getWidth();
height = image.getHeight();
//n is png file with only white pixels
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
Color p = new Color(image.getRGB(j, i));
Color g = new Color(image.getRGB(j+5, i));
//getting (j,i) coordinate pixel value and then comparing it to next 5th pixel
if(p.getRed()==255&&p.getBlue()==255&&p.getGreen()==255&&g.getRed()==255&&
g.getBlue()==255&&g.getGreen()==255)
{ red=0;
blue=0;
green=0; }
//if both pixel value is white then setting 5th pixel value to black
Color newColor = new Color(red,green,blue);
image.setRGB(j+5,i,newColor.getRGB());
j=j+5;
}
}
File ouptut = new File("Sateen.png");
ImageIO.write(image, "png", ouptut);
//creating sateen png file
} catch (Exception e) {}
}
static public void main(String args[]) throws Exception
{
Sateen obj = new Sateen();
}
}
Seems to actually create a .png file (make sure the filepath is correct?). However this code will not generate a sateen pattern, rather draws black pixels at column 6, 12, 18 etc.
The following code fixes some issues in your code. Although it produces an output file but the output file does not look like what you are trying to produce. Perhaps you need to tweak your algorithm?
Anyway the main thing to learn here is that if you are looping over arrays and incrementing the index within the loop then you need to take care of the situation where your index might go out of bounds of the array i.e. your j+5 can be anything outside the image.
Also, make sure that your input file is in the correct location which will depend on where you run the program from. If you run it from the debugger/IDE then it should be inside the project folder next to the src folder. It would be better if you specify full path to your file i.e. C:\SomeFolder\n.png to avoid input file missing error.
Finally, use something like e.printStackTrace(); in your catch block to find out where and why your code is failing.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
public class Sateen {
BufferedImage image;
int width;
int height;
int red, green, blue;
public Sateen() {
try {
File input = new File("n.png");
image = ImageIO.read(input);
width = image.getWidth();
height = image.getHeight();
// n is png file with only white pixels
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) { // may not need j++ as you are doing j+5?
Color p = new Color(image.getRGB(j, i));
int jPlus5 = j + 5;
if (jPlus5 < width) {
Color g = new Color(image.getRGB(jPlus5, i));
// getting (j,i) coordinate pixel value and then
// comparing
// it to next 5th pixel
if (p.getRed() == 255 && p.getBlue() == 255 && p.getGreen() == 255 && g.getRed() == 255 &&
g.getBlue() == 255 && g.getGreen() == 255)
{
red = 0;
blue = 0;
green = 0;
}
// if both pixel value is white then setting 5th pixel
// value
// to black
Color newColor = new Color(red, green, blue);
image.setRGB(jPlus5, i, newColor.getRGB());
}
j = jPlus5;
}
}
File ouptut = new File("Sateen.png");
ImageIO.write(image, "png", ouptut);
// creating sateen png file
} catch (Exception e) {
e.printStackTrace();
}
}
static public void main(String args[]) throws Exception
{
Sateen obj = new Sateen();
}
}

Refresh BufferedImage periodically

I have BufferedImage displayed in JFrame which I want to refresh periodically with raw R, G, B data I receive through Socket(byte[] buffer). Sequence of actions should look something like this:
Receive byte[1280 * 480] array of pure RGB data(one byte per component) -> this part works flawless
Iterate through byte array and call BufferedImage.setRgb(x, y, RGB) for every pixel
I have no problem with receiving and displaying one frame, but when I wrap code which does steps 1. and 2. I receive data regularly but not a single frame is ever shown. My guess was that receiving data is significantly faster than displaying images, in other words my received image gets somehow overwritten by new image etc.
My next idea was to hand over buffer with image to another background thread and block main thread which does network communication until background thread has done 'displaying' image.
Then I heard it can easily be done with SwingWorker here: Can a progress bar be used in a class outside main? but it does exactly the same thing as if I was still doing everything on one thread: no image was ever shown. Here is my code:
public class ConnectionManager {
public static final String serverIp = "192.168.1.10";
public static final int tcpPort = 7;
public static final int bufferSize = 1280;
private Socket client;
private BufferedInputStream networkReader;
private PrintStream printStream;
byte[] buffer;
public ConnectionManager(){}
public void connect() throws IOException{
int dataRead;
while(true){
client = new Socket(serverIp, tcpPort);
printStream = new PrintStream(client.getOutputStream());
networkReader = new BufferedInputStream(client.getInputStream());
dataRead = 0;
buffer = new byte[1280 * 480];
printStream.println(""); // CR is code to server to send data
while(dataRead < (1280 * 480)){
dataRead += networkReader.read(buffer, dataRead, (1280 * 480) - dataRead);
}
DrawBack d = new DrawBack();
d.execute();
try {
d.get(); // here im trying to block main thread purposely
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
private class DrawBack extends SwingWorker<Void, Void>{
#Override
protected Void doInBackground() throws Exception {
byte Y, U, V;
int R, G, B, RGB, Yi, Ui, Vi;
boolean alternate = false;
for(int i = 0; i < 480; ++i){
for(int j = 1; j < 1280; j += 2){
if(alternate){
Y = buffer[i * 1280 + j];
V = buffer[i * 1280 + j -1];
U = buffer[i * 1280 + j -3];
} else {
Y = buffer[i * 1280 + j];
U = buffer[i * 1280 + j -1];
V = buffer[i * 1280 + j +1];
}
Yi = Y & 0xFF;
Ui = U & 0xFF;
Vi = V & 0xFF;
R = (int)(Yi + 1.402 * (Vi - 128));
G = (int)(Yi - 0.34414 * (Ui - 128) - 0.71414 * (Vi - 128));
B = (int)(Yi + 1.772 * (Ui - 128));
RGB = R;
RGB = (RGB << 8) + G;
RGB = (RGB << 8) + B;
alternate = !alternate;
Masapp.window.getImage().setRGB(j/2, i, RGB);// reference to buffered image on JFrame
if((i == 100) && (j == 479)){
System.out.println(Yi + " " + Ui + " " + Vi);
}
}
}
return null;
}
}
}
I even tried to wait for completion with while:
DrawBack d = new DrawBack(); // DrawBack extends SwingWorker<Void, Void>
d.execute();
while(!d.isDone());
but it makes no improvement. I tried with calling BufferedImage.flush() and JFrame.invalidate() when I set all pixels.
My question basically is: How to refresh and display buffered image periodically?
Your implementation is incorrectly synchronized in that it updates the GUI from the worker's background thread rather than the event dispatch thread. The resulting behavior is unpredictable. Instead, define a SwingWorker<BufferedImage, BufferedImage> and publish() the image for later rendering in your implementation of process(). For improved liveness, publish portions of the image as they are ready, e.g. publish() a BufferedImage containing one row at a time. Compare the example cited with this related example to see the approach.

Getting the most common color of an image

I'd like to get the most common color from an image. I use Java and I want to have the most predominant color. Are there any cbir java library to make this?
Thanks
How accurate do you want this to be? You can use Bozhos's approach and loop over the entire image but this could be slow for large images. There are 16777216 possible RGB values and keeping counters for them in a Map is not very efficient.
An alternative is to resample the image using getScaledInstance to scale it down to a smaller version e.g. a 1x1 image and then use getRGB to get the colour of that pixel. You can experiment with different resampling algorithms such as SCALE_REPLICATE and SCALE_AREA_AVERAGING to see what works best for you.
Thanks for the answers. Here is a practical example of Bozho's method. It also filters out white/grays/black.
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
public class ImageTester {
public static void main(String args[]) throws Exception {
File file = new File("C:\\Users\\Andrew\\Desktop\\myImage.gif");
ImageInputStream is = ImageIO.createImageInputStream(file);
Iterator iter = ImageIO.getImageReaders(is);
if (!iter.hasNext())
{
System.out.println("Cannot load the specified file "+ file);
System.exit(1);
}
ImageReader imageReader = (ImageReader)iter.next();
imageReader.setInput(is);
BufferedImage image = imageReader.read(0);
int height = image.getHeight();
int width = image.getWidth();
Map m = new HashMap();
for(int i=0; i < width ; i++)
{
for(int j=0; j < height ; j++)
{
int rgb = image.getRGB(i, j);
int[] rgbArr = getRGBArr(rgb);
// Filter out grays....
if (!isGray(rgbArr)) {
Integer counter = (Integer) m.get(rgb);
if (counter == null)
counter = 0;
counter++;
m.put(rgb, counter);
}
}
}
String colourHex = getMostCommonColour(m);
System.out.println(colourHex);
}
public static String getMostCommonColour(Map map) {
List list = new LinkedList(map.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return ((Comparable) ((Map.Entry) (o1)).getValue())
.compareTo(((Map.Entry) (o2)).getValue());
}
});
Map.Entry me = (Map.Entry )list.get(list.size()-1);
int[] rgb= getRGBArr((Integer)me.getKey());
return Integer.toHexString(rgb[0])+" "+Integer.toHexString(rgb[1])+" "+Integer.toHexString(rgb[2]);
}
public static int[] getRGBArr(int pixel) {
int alpha = (pixel >> 24) & 0xff;
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel) & 0xff;
return new int[]{red,green,blue};
}
public static boolean isGray(int[] rgbArr) {
int rgDiff = rgbArr[0] - rgbArr[1];
int rbDiff = rgbArr[0] - rgbArr[2];
// Filter out black, white and grays...... (tolerance within 10 pixels)
int tolerance = 10;
if (rgDiff > tolerance || rgDiff < -tolerance)
if (rbDiff > tolerance || rbDiff < -tolerance) {
return false;
}
return true;
}
}
What if you consider your image as a big linear array of pixels, and after that all what you have to do is just sort it? When you have it sorted, you can count the longest part of same values.
Depending on how exact you need the color value to be, you might want to consider "color buckets" collecting similar colors to avoid memory issues. This would mean to partition the color space into "intervals" of colors, where all colors which are similar (i.e. close together) enough are counted as the same color. By changing the interval size you have a means of directly manipulating the trade-off between accuracy and memory consumption.
Edit: What you want is basically a histogram (go look that up). There are most probably well established standard solutions for efficiently calculating one of those.
You can loop the BufferedImage (two loops - one from 0 to width, and one from 0 to height), and get the call getRgb(x, y). Then count each different value. You can use a Map for that (key = color, value = number of occurences).
I would calculate the hue of each pixel and then the cardinality of each hue (creates a histogram). Perhaps weighting by saturation. Then, apply a low-pass filter, and find the maximum. Finally convert from hue back to RGB.
This assumes that if you had just the red plane of an image, you'd want the result to be "red", not some shade of pink.
Andrew Dyster code is working fine, Quick response in android
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import android.graphics.Bitmap;
public class ImageTester {
public interface ImageColor {
void onImageColor(int r, int g, int b);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
public static void getMostCommonColour(final Bitmap image,
final ImageColor heColor) {
new Thread(new Runnable() {
private int rgb;
#Override
public void run() {
int height = image.getHeight();
int width = image.getWidth();
Map m = new HashMap();
int boderWid = width / 4;
int borderHeigh = height / 4;
for (int i = boderWid; i < width - boderWid;) {
for (int j = borderHeigh; j < height - borderHeigh;) {
try {
rgb = image.getPixel(i, j);
} catch (Exception e) {
continue;
}finally{
i += 20;
j += 20;
}
int[] rgbArr = getRGBArr(rgb);
// Filter out grays....
if (!isGray(rgbArr)) {
Integer counter = (Integer) m.get(rgb);
if (counter == null)
counter = 0;
counter++;
m.put(rgb, counter);
}
}
}
List list = new LinkedList(m.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return ((Comparable) ((Map.Entry) (o1)).getValue())
.compareTo(((Map.Entry) (o2)).getValue());
}
});
Map.Entry me = (Map.Entry) list.get(list.size() - 1);
int[] rgb = getRGBArr((Integer) me.getKey());
heColor.onImageColor(rgb[0], rgb[1], rgb[2]);
}
}).start();
}
public static int[] getRGBArr(int pixel) {
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel) & 0xff;
return new int[] { red, green, blue };
}
public static boolean isGray(int[] rgbArr) {
int rgDiff = rgbArr[0] - rgbArr[1];
int rbDiff = rgbArr[0] - rgbArr[2];
int tolerance = 10;
if (rgDiff > tolerance || rgDiff < -tolerance)
if (rbDiff > tolerance || rbDiff < -tolerance) {
return false;
}
return true;
}
}

Categories