I have following code, which creates grayscale BufferedImage and then sets random colors of each pixel.
import java.awt.image.BufferedImage;
public class Main {
public static void main(String[] args) {
BufferedImage right = new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_GRAY);
int correct = 0, error = 0;
for (int i = 0; i < right.getWidth(); i++) {
for (int j = 0; j < right.getHeight(); j++) {
int average = (int) (Math.random() * 255);
int color = (0xff << 24) | (average << 16) | (average << 8) | average;
right.setRGB(i, j, color);
if(color != right.getRGB(i, j)) {
error++;
} else {
correct++;
}
}
}
System.out.println(correct + ", " + error);
}
}
In approximately 25-30% pixels occurs weird behaviour, where I set color and right afterwards it has different value than was previously set. Am I setting colors the wrong way?
Here is your solution: ban getRGB and use the Raster (faster and easier than getRGB) or even better DataBuffer (fastest but you have to handle the encoding):
import java.awt.image.BufferedImage;
public class Main
{
public static void main(String[] args)
{
BufferedImage right = new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_GRAY);
int correct = 0, error = 0;
for (int x=0 ; x < right.getWidth(); x++)
for (int j = 0; j < right.getHeight(); j++)
{
int average = (int) (Math.random() * 255) ;
right.getRaster().setSample(x, y, 0, average) ;
if ( average != right.getRaster().getSample(x, y, 0) ) error++ ;
else correct++;
}
System.out.println(correct + ", " + error);
}
}
In your case getRGB is terrible, because the encoding is an array of byte (8 bits), and you have to manipulate RGB values with getRGB. The raster does all the work of conversion for you.
I think your issue has to do with the image type (third parameter for BufferedImage constructor). If you change the type to BufferedImage.TYPE_INT_ARGB, then you will get 100% correct results.
Looking at the documentation for BufferedImage.getRGB(int,int) there is some conversion when you get RGB that is not the default color space
Returns an integer pixel in the default RGB color model (TYPE_INT_ARGB) and default sRGB colorspace. Color conversion takes place if this default model does not match the image ColorModel.
So you're probably seeing the mismatches due to the conversion.
Wild guess:
Remove (0xff << 24) | which is the alpha channel, how intransparent/opaque the color is. Given yes/no transparent and average < or >= 128 application of transparency, 25% could be the wrong color mapping (very wild guess).
Related
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.
I'm building an application that uses OCR to read text from an image (using Tess4J for Google's Tesseract), but I want to ignore the tan-colored text and only read the grey.
In the image below, for instance, I only want to read "Ricki" and ignore "AOA".
http://i.imgur.com/daCuTbB.png
To accomplish this, I figured removing the tan color from the image before performing OCR was my best option.
/* Remove RGB Value for Group Tag */
int width = image.getWidth();
int height = image.getHeight();
int[] pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);
for (int i = 0; i < pixels.length; i++) {
//If pixel is between dark-tan value and light-tan value
if (pixels[i] > 0xFF57513b && pixels[i] < 0xFF6b6145) {
// Set pixel to black
System.out.println("pixel found");
pixels[i] = 0xFF000000;
}
}
image.setRGB(0, 0, width, height, pixels, 0, width);
But this code removes almost all of the grey text as well. You aren't able to simply compare hex color values for a range of values the way I have. Is there another way to approach detecting a range of colors? Or a better different approach to this problem?
haraldK pointed me in the right direction by mentioning converting RGB. Bit shifting to get individual r, g, and b int values from the hex value allowed me to compare the color within a range and black out a range of colors from the image.
int baser = 108; //base red
int baseg = 96; //base green
int baseb = 68; //base blue
int range = 10; //threshold + and - from base values
/* Set all pixels within +- range of base RGB to black */
for (int i = 0; i < pixels.length; i++) {
int a = (pixels[i]>>24) &0xFF; //alpha
int r = (pixels[i]>>16) &0xFF; //red
int g = (pixels[i]>>8) &0xFF; //green
int b = (pixels[i]>>0) &0xFF; //blue
if ( (r > baser-range && r < baser+range) &&
(g > baseg-range && g < baseg+range) &&
(b > baseb-range && b < baseb+range) ) {
pixels[i] = 0xFF000000; //Set to black
}
}
Here i'm trying to do a fastest method to save 3 matrix(R, G and B) into a BufferedImage.
I've found this method here at StackExchange, but it doesn't work for me because the image it's being saved in a grayscale color.
If I'm doing something wrong or if there's a way of doing this faster than bufferimage.setRGB(), please help me. Thanks!
public static BufferedImage array_rasterToBuffer(int[][] imgR,
int[][]imgG, int[][] imgB) {
final int width = imgR[0].length;
final int height = imgR.length;
int numBandas = 3;
int[] pixels = new int[width*height*numBandas];
int cont=0;
System.out.println("max: "+width*height*3);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
for (int band = 0; band < numBandas; band++) {
pixels[(((i*width)+j)*numBandas +band)] =Math.abs(( (imgR[i][j] & 0xff) >> 16 | (imgG[i][j] & 0xff) >> 8 | (imgB[i][j] & 0xff)));
cont+=1;
}
}
}
BufferedImage bufferImg = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
WritableRaster rast = (WritableRaster) bufferImg.getData();
rast.setPixels(0, 0, width, height, pixels);
bufferImg.setData(rast);
return bufferImg;
}
I think you are getting grey because the expression
Math.abs(( (imgR[i][j] & 0xff) >> 16 | (imgG[i][j] & 0xff) >> 8 | (imgB[i][j] & 0xff)));
does not depend on band, so your rgb values are all the same.
The expression looks dodgy anyway because you normally use the left shift operator << when packing rgb values into a single int.
I don't know for sure, as I'm not familiar with the classes you are using, but I'm guessing something like this might work
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
pixels[(((i*width)+j)*numBandas)] = imgR[i][j] & 0xFF;
pixels[(((i*width)+j)*numBandas + 1)] = imgG[i][j] & 0xFF;
pixels[(((i*width)+j)*numBandas + 2)] = imgB[i][j] & 0xFF;
}
}
If you want a faster approach, you need to get the "live" WritableRaster from the BufferedImage and set pixels in the "native" format of the image, which is "pixel packed" for TYPE_INT_RGB. This will save you multiple (at least two) array copies and some data conversion. It will also save you 2/3rds of the memory used for the conversion, as we only need a single array component per pixel.
The below method should be quite a bit faster:
public static BufferedImage array_rasterToBuffer(int[][] imgR, int[][] imgG, int[][] imgB) {
final int width = imgR[0].length;
final int height = imgR.length;
// The bands are "packed" for TYPE_INT_RGB Raster,
// so we need only one array component per pixel
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// "Pack" RGB values to native TYPE_INT_RGB format
// (NOTE: Do not use Math.abs on these values, and without alpha there won't be negative values)
pixels[((y * width) + x)] = ((imgR[y][x] & 0xff) << 16 | (imgG[y][x] & 0xff) << 8 | (imgB[y][x] & 0xff));
}
}
BufferedImage bufferImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// NOTE: getRaster rather than getData for "live" view
WritableRaster rast = bufferImg.getRaster();
// NOTE: setDataElements rather than setPixels to avoid conversion
// This requires pixels to be in "native" packed RGB format (as above)
rast.setDataElements(0, 0, width, height, pixels);
// No need for setData as we were already working on the live data
// thus saving at least two expensive array copies
return bufferImg;
}
// Test method, displaying red/green/blue stripes
public static void main(String[] args) {
int[][] fooR = new int[99][99];
int[][] fooG = new int[99][99];
int[][] fooB = new int[99][99];
for (int i = 0; i < 33; i++) {
Arrays.fill(fooR[i], 0xff);
Arrays.fill(fooG[i + 33], 0xff);
Arrays.fill(fooB[i + 66], 0xff);
}
BufferedImage image = array_rasterToBuffer(fooR, fooG, fooB);
showIt(image);
}
// For demonstration only
private static void showIt(final BufferedImage image) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("JPEGTest");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JScrollPane scroll = new JScrollPane(new JLabel(new ImageIcon(image)));
scroll.setBorder(BorderFactory.createEmptyBorder());
frame.add(scroll);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
It is possible to optimize this further, if you don't need a "managed" (possible hardware accelerated for display) image. The trick is to create the image directly "around" your pixels array, thus saving one more array allocation and array copy in setDataElements. The downside is that in some cases the image will be a little slower to draw onto the screen. This is mainly a concern for games or smooth animations though.
Replace the lines from BufferedImage bufferImg = new BufferedImage... until the return statement, with the following code:
DataBufferInt buffer = new DataBufferInt(pixels, pixels.length);
int[] bandMasks = {0xFF0000, 0xFF00, 0xFF}; // RGB (no alpha)
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, width, bandMasks, null);
ColorModel cm = new DirectColorModel(32,
0x00ff0000, // Red
0x0000ff00, // Green
0x000000ff, // Blue
0x00000000 // No Alpha
);
BufferedImage bufferImg = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
PS: Note that I also changed the shifts inside the x/y loop, from right to left shifts. Might have been just a minor typo. :-)
I'm attempting to work out the area of a greyscale image, I'm aware that I could use getRGB() if it was a buffered image, but i'm using a toolkit so it is therefore a int image. I just want to ask how I can get the pixel value? I've included my code below
import iptoolkit.*;
public class FindArea {
public static void main(String[] args) {
String imageDir = "C:/Users/John/Dropbox/finalYear/Project/Leaves/";
MainWindow mw = new MainWindow();
int area = 0;
IntImage src = new IntImage(imageDir + "bg7.jpg", 256, 256);
src.displayImage(); //displays the image in a window
for (int row = 0; row <= src.getRows(); row++)
{
for (int col=0; col <= src.getCols(); col++)
{
//if(src.pixels[row][col] >= 0)
area++;
}
}
System.out.print("The area of the leaf is:" +area);
}
int pixel = src.pixels[row][col];
int red = (pixel & 0x00ff0000) >> 16;
int green = (pixel & 0x0000ff00) >> 8;
int blue = pixel & 0x000000ff;
// and the Java Color is ...
Color color = new Color(red,green,blue);
based on this for BufferedImage, but principle is same.
I believe to remember that bits in an RGB value are ordered like that:
8 bit of R | 8 bits of G | 8 bits of B
But it also depends on the type of image that you are using.
Use some bit operators like shift << and >> and mask the value with a and operator &.
I making App in netbeans platform using java Swing and JAI. In this i want to do image processing. I capture .tiff black and white image using X-Ray gun. after that i want to plot histogram of that Black and White image. so, for plot to histogram , first we have to get gray or black and white image pixel value. then we can plot histogram using this pixel value.so, how can i get this pixel value of black and white image?
This should work if you use java.awt.image.BufferedImage.
Since you want to create a histogram, I suppose you will loop through all the pixels. There is the method for returning a single pixel value.
int getRGB(int x, int y)
However, since looping will take place I suppose you'd want to use this one:
int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
When you get the array, use:
int alpha = (pixels[i] >> 24) & 0x000000FF;
int red = (pixels[i] >> 16) & 0x000000FF;
int green = (pixels[i] >>8 ) & 0x000000FF;
int blue = pixels[i] & 0x000000FF;
To extract the channel data. Not sure if the variables can be declared as byte (we are using only one byte of the integer in the array, although byte is signed and different arithmetic takes place - two's complement form), but you can declare them as short.
Then preform some maths on these values, for example:
int average = (red + green + blue) / 3;
This will return the average for the pixel, giving you a point you can use in a simple luminosity histogram.
EDIT:
Regarding histogram creation, I have used this class. It takes the image you want the histogram of as an argument to its setImage(BufferedImage image) method. Use updateHistogram() for array populating. The drawing data is in paintComponent(Graphics g). I must admit, it is sloppy, especially when calculating the offsets, but it can be easily simplified.
Here is the whole class:
class HistogramCtrl extends JComponent
{
BufferedImage m_image;
int[] m_histogramArray = new int[256]; //What drives our histogram
int m_maximumPixels;
public HistogramCtrl(){
m_maximumPixels = 0;
for(short i = 0; i<256; i++){
m_histogramArray[i] = 0;
}
}
void setImage(BufferedImage image){
m_image = image;
updateHistogram();
repaint();
}
void updateHistogram(){
if(m_image == null) return;
int[] pixels = m_image.getRGB(0, 0, m_image.getWidth(), m_image.getHeight(), null, 0, m_image.getWidth());
short currentValue = 0;
int red,green,blue;
for(int i = 0; i<pixels.length; i++){
red = (pixels[i] >> 16) & 0x000000FF;
green = (pixels[i] >>8 ) & 0x000000FF;
blue = pixels[i] & 0x000000FF;
currentValue = (short)((red + green + blue) / 3); //Current value gives the average //Disregard the alpha
assert(currentValue >= 0 && currentValue <= 255); //Something is awfully wrong if this goes off...
m_histogramArray[currentValue] += 1; //Increment the specific value of the array
}
m_maximumPixels = 0; //We need to have their number in order to scale the histogram properly
for(int i = 0; i < m_histogramArray.length;i++){ //Loop through the elements
if(m_histogramArray[i] > m_maximumPixels){ //And find the bigges value
m_maximumPixels = m_histogramArray[i];
}
}
}
protected void paintComponent(Graphics g){
assert(m_maximumPixels != 0);
Rectangle rect = g.getClipBounds();
Color oldColor = g.getColor();
g.setColor(new Color(210,210,210));
g.fillRect((int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight());
g.setColor(oldColor);
String zero = "0";
String thff = "255";
final short ctrlWidth = (short)rect.getWidth();
final short ctrlHeight = (short)rect.getHeight();
final short activeWidth = 256;
final short activeHeight = 200;
final short widthSpacing = (short)((ctrlWidth - activeWidth)/2);
final short heightSpacing = (short)((ctrlHeight - activeHeight)/2);
Point startingPoint = new Point();
final int substraction = -1;
startingPoint.x = widthSpacing-substraction;
startingPoint.y = heightSpacing+activeHeight-substraction;
g.drawString(zero,widthSpacing-substraction - 2,heightSpacing+activeHeight-substraction + 15);
g.drawString(thff,widthSpacing+activeWidth-substraction-12,heightSpacing+activeHeight-substraction + 15);
g.drawLine(startingPoint.x, startingPoint.y, widthSpacing+activeWidth-substraction, heightSpacing+activeHeight-substraction);
g.drawLine(startingPoint.x,startingPoint.y,startingPoint.x,heightSpacing-substraction);
double factorHeight = (double)activeHeight / m_maximumPixels; //The height divided by the number of pixels is the factor of multiplication for the other dots
Point usingPoint = new Point(startingPoint.x,startingPoint.y);
usingPoint.x+=2; //I want to move this two points in order to be able to draw the pixels with value 0 a bit away from the limit
Point tempPoint = new Point();
for(short i = 0; i<256; i++){
tempPoint.x = usingPoint.x;
tempPoint.y = (int)((heightSpacing+activeHeight-substraction) - (m_histogramArray[i] * factorHeight));
if((i!=0 && (i % 20 == 0)) || i == 255){
oldColor = g.getColor();
g.setColor(oldColor.brighter());
//Draw horizontal ruler sections
tempPoint.x = widthSpacing + i;
tempPoint.y = heightSpacing+activeHeight-substraction+4;
g.drawLine(tempPoint.x,tempPoint.y,widthSpacing + i,heightSpacing+activeHeight-substraction-4);
if(i <= 200){
//Draw vertical ruler sections
tempPoint.x = widthSpacing - substraction - 3;
tempPoint.y = heightSpacing+activeHeight-substraction-i;
g.drawLine(tempPoint.x,tempPoint.y,widthSpacing - substraction + 4, heightSpacing+activeHeight-substraction-i);
}
tempPoint.x = usingPoint.x;
tempPoint.y = usingPoint.y;
g.setColor(oldColor);
}
g.drawLine(usingPoint.x, usingPoint.y, tempPoint.x, tempPoint.y);
usingPoint.x++; //Set this to the next point
}
}
}