Converting 8 bit to 4 bit image - java

Can anyone see what the issue is when I try to convert my 8 bit image into an 4 bit image?
I am testing using the 8 bit image found here: http://laurashoe.com/2011/08/09/8-versus-16-bit-what-does-it-really-mean/
You can tell how the 4 bit image should look like but mine is almost purely black.
// get color of the image and convert to grayscale
for(int x = 0; x <img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
int rgb = img.getRGB(x, y);
int r = (rgb >> 16) & 0xF;
int g = (rgb >> 8) & 0xF;
int b = (rgb & 0xF);
int grayLevel = (int) (0.299*r+0.587*g+0.114*b);
int gray = (grayLevel << 16) + (grayLevel << 8) + grayLevel;
img.setRGB(x,y,gray);
}
}

You should use 0xFF not 0xF,as 0xF means only last four bits, wchich will tell you almost nothing about the color, since in RGB an color is 8 bit.
try if this work:
// get color of the image and convert to grayscale
for(int x = 0; x <img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
int rgb = img.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb & 0xFF);
int grayLevel = (int) (0.299*r+0.587*g+0.114*b);
int gray = (grayLevel << 16) + (grayLevel << 8) + grayLevel;
img.setRGB(x,y,gray);
}
}

Since the code has been edited out from the question, here it is with the confirmed solution from the comments:
// get color of the image and convert to grayscale
for(int x = 0; x <img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
int rgb = img.getRGB(x, y);
// get the upper 4 bits from each color component
int r = (rgb >> 20) & 0xF;
int g = (rgb >> 12) & 0xF;
int b = (rgb >> 4) & 0xF;
int grayLevel = (int) (0.299*r+0.587*g+0.114*b);
// use grayLevel value as the upper 4 bits of each color component of the new color
int gray = (grayLevel << 20) + (grayLevel << 12) + (grayLevel << 4);
img.setRGB(x,y,gray);
}
}
Note that the resulting image only looks like 4-bit grayscale, but still uses int as the RGB value.

8 bit image values are in range [0, 255] because pow(2, 8) = 256
To get 4 bit image values which will be in range [0, 15] as pow(2, 4) = 16,
we need to divide each pixel value by 16 -> range [0, 255] / 16 = range [0, 15].
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("crowd.jpeg")
#Convert the image to grayscale
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray_img, cmap='gray')
Grayscale image
bit_4 = np.divide(gray_img, 16).astype('uint8')
plt.imshow(bit_4, cmap='gray')
Bit4 image

Related

get average image of a set of images in java

I have a set of meteorological RGB type BufferedImages. I want to get average image of them. By that, I mean get average value of each pixel and make a new image out of those values. What I tried is this:
public void getWaveImage(BufferedImage input1, BufferedImage input2){
// images are of same size that's why i'll use first one's width and height
int width = input1.getWidth(), height = input1.getHeight();
BufferedImage output = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
int[] rgb1 = input1.getRGB(0, 0, width, height, new int[width * height], 0, width);
int[] rgb2 = input2.getRGB(0, 0, width, height, new int[width * height], 0, width);
for(int i=0; i<width; i++){
for(int j=0; j<height; j++){
int rgbIndex = i * width + j;
rgb1[rgbIndex] = (rgb1[rgbIndex] + rgb2[rgbIndex]) / 2;
}
}
output.setRGB(0, 0, width, height, rgb1, 0, width);
return output;
}
What am I doing wrong? Thank you in advance.
input1:
input2:
output:
You want the average of each component of the colour, average red, average green, average blue.
Instead you are averaging the whole int.
Color c1 = new Color(rgb1[rgbIndex]);
Color c2 = new Color(rgb2[rgbIndex]);
Color cA = new Color((c1.getRed() + c2.getRed())/2,
(c1.getGreen() + c2.getGreen())/2,
(c1.getBlue() + c2.getBlue())/2);
rgb1[rgbIndex] = cA.getRGB();
This may not be the most efficient due to creating so many objects, so a more direct approach is like so:
public static int average(int argb1, int argb2){
return (((argb1 & 0xFF) + (argb2 & 0xFF)) >> 1) | //b
(((argb1 >> 8 & 0xFF) + (argb2 >> 8 & 0xFF)) >> 1) << 8 | //g
(((argb1 >> 16 & 0xFF) + (argb2 >> 16 & 0xFF)) >> 1) << 16 | //r
(((argb1 >> 24 & 0xFF) + (argb2 >> 24 & 0xFF)) >> 1) << 24; //a
}
Usage:
rgb1[rgbIndex] = average(rgb1[rgbIndex], rgb2[rgbIndex]);
If you have:
int rgb1, rgb2; //the rgb value of a pixel in image 1 and 2 respectively
The "average" color would be:
int r = (r(rgb1) + r(rgb2)) / 2;
int g = (g(rgb1) + g(rgb2)) / 2;
int b = (b(rgb1) + b(rgb2)) / 2;
int rgb = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0);
with the following "helper" methods:
private static int r(int rgb) { return (rgb >> 16) & 0xFF; }
private static int g(int rgb) { return (rgb >> 8) & 0xFF; }
private static int b(int rgb) { return (rgb >> 0) & 0xFF; }
Alternatively you can use the Color class if you don't want to deal with bitwise operations.
another solution can be to replace
rgb1[rgbIndex] = (rgb1[rgbIndex] + rgb2[rgbIndex]) / 2;
with
rgb1[rgbIndex] = ((rgb1[rgbIndex]>>1)&0x7f7f7f7f)+((rgb2[rgbIndex]>>1)&0x7f7f7f7f)+(rgb1[rgbIndex]&rgb2[rgbIndex]&0x01010101);
binary right shift to divide by 2, last member of the sum to handle the case of two odd numbers.

How do I stop java from setting my BufferedImage to fully transparent?

I am trying to create an image editor in java, but when I run the code, the output image is fully transparent.
Here is my code for Main.java:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Main {
public static void main(String[] args) {
BufferedImage image = null;
try {
image = ImageIO.read(new File("strawberry.png"));
} catch (IOException e) {
System.out.println(e);
}
new Negative(image);
File outputfile = new File("saved.png");
try {
ImageIO.write(image, "png", outputfile);
} catch (IOException e) {
System.out.println(e);
}
}
}
And here is my code for Negative.java:
import java.awt.image.BufferedImage;
public class Negative {
public Negative(BufferedImage img) {
for (int x = 0; x < img.getWidth(); ++x) {
for (int y = 0; y < img.getHeight(); ++y) {
int rgb = img.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb & 0xFF);
r = 255 - r;
g = 255 - g;
b = 255 - b;
int newColour = (r << 16) + (g << 8) + (b << 4);
img.setRGB(x, y, newColour);
}
}
}
}
If anyone could help I would be very grateful.
Problem
What they call RGB color is in fact ARGB, 8 bits for each.
Alpha is given in the highest 8 bits, 0 for transparent to 255 for fully opaque.
This is what TYPE_INT_ARGB means in the javadoc for BufferedImage.setRGB() :
The pixel is assumed to be in the default RGB color model, TYPE_INT_ARGB, and default sRGB color space.
Solution
For a fully opaque image, add a 255 alpha value:
int newColour = (0xff << 24) + (r << 16) + (g << 8) + (b << 4);
Alternatively, you can take the original alpha of your image if you extract it too:
int rgb = img.getRGB(x, y);
int alpha = (rgb >>> 24);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb & 0xFF);
int newColour = (alpha << 24) + (r << 16) + (g << 8) + (b << 4);
There is one more component of a color: alpha channel. It is stored in 24-31 bits of a number. If it is set to 0, the image is transparent. So you need to set 24-31 bits of newColor to 1 to make it opaque.

How to compress a Pixmap / use cim files in android?

How can I compress a LibGDX Pixmap? I want to save it on the disk, but it uses about 4MB, which is way to much and takes like forever to save it.
final Pixmap pixmap = ScreenUtils.getFrameBufferPixmap(x, y, w, h);
FileHandle screenshot = Gdx.files.local("something.png");
PixmapIO.writePNG(screenshot, pixmap);
I saw that there is a PixmapIO.writeCIM, which is quite fast and the output is quite small.
Am I able to display the something.cim file in Android? The docu says, that cim should only be used within libgdx. Arguing that this is the old documentation, maybe there is something new?
http://badlogicgames.com/forum/viewtopic.php?f=11&t=8947
int w = p.getWidth();
int h = p.getHeight();
int[] pixels = new int[w * h];
for (int y=0; y<h; y++) {
for (int x=0; x<w; x++) {
//convert RGBA to RGB
int value = p.getPixel(x, y);
int R = ((value & 0xff000000) >>> 24);
int G = ((value & 0x00ff0000) >>> 16);
int B = ((value & 0x0000ff00) >>> 8);
int A = ((value & 0x000000ff));
int i = x + (y * w);
pixels[ i ] = (A << 24) | (R << 16) | (G << 8) | B;
}
}
Bitmap b = Bitmap.createBitmap(pixels, w, h, Config.ARGB_8888);
b.compress(CompressFormat.JPEG, quality, handle.write(false));

Toggle between RGB channels

I need to toggle on/off RGB channels of an image, but I am stuck and my code is buggy.
Can you help me figure out how to do this the right way? This is my code:
The function channels is called when 1 of 3 checkboxes has changed its state and provides the arguments which are true == selected
public void channels(boolean red, boolean green, boolean blue) {
if (this.img != null) {// checks if the image is set
char r = 0xFF, g = 0xFF, b = 0xFF;
if (red == false) {
r = 0x00;
}
if (green == false) {
g = 0x00;
}
if (blue == false) {
b = 0x00;
}
BufferedImage tmp = new BufferedImage(
img.getWidth(),
img.getHeight(),
BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < img.getWidth(); i++) {
for (int j = 0; j < img.getHeight(); j++) {
int rgb = img.getRGB(i, j);
int red = (rgb >> 16) & r;
int green = (rgb >> 8) & g;
int blue = (rgb >> 0) & b;
int gbr = (red << 16) | (green << 8) | blue;// EDITED
tmp.setRGB(i, j, gbr);
}
}
img = tmp;
repaint();
} else {
//show error
}
}
Thank you for your help!
How about this optimized version, with a lot less bit shifting?
public void channels(boolean showRed, boolean showGreen, boolean showBlue) {
if (this.origImg!= null) {// checks if the image is set
int channelMask = 0xff << 24 | (showRed ? 0xff : 0) << 16 | (showGreen ? 0xff : 0) << 8 | (showBlue ? 0xff : 0);
BufferedImage tmp = new BufferedImage(origImg.getWidth(), origImg.getHeight(), BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < origImg.getWidth(); i++) {
for (int j = 0; j < origImg.getHeight(); j++) {
int rgb = origImg.getRGB(i, j);
tmp.setRGB(i, j, rgb & channelMask);
}
}
img = tmp;
repaint();
} else {
//show error
}
}
A faster approach yet, would probably be to use a channeled Raster, or at least a Raster configuration that allows band sub-sampling (see Raster.createChild(...) method, especially the last parameter).
LookupOp, as mentioned by #trashgod is also a good idea, and probably faster than the getRGB()/setRGB() approach.
It looks like you're shifting in the bits wrong. Shouldn't it be: int gbr = (red << 16) | (green << 8) | blue;? You basically want to shift back in the same order as how you shifted out to begin with.
Also, once you have cleared the corresponding colour, there's no way for you to get it back. You'll need to store a copy of the original image somewhere. When it's time to turn the channel back on, simply copy the original pixel from the original image back.
Assuming that you have the original image stored somewhere as origImg, I would modify your for loop so that if the channel is toggled on, copy from the original image.
for (int i = 0; i < img.getWidth(); i++) {
for (int j = 0; j < img.getHeight(); j++) {
int rgb = img.getRGB(i, j);
int origRGB = origImg.getRGB(i, j);
int redPixel = red ? (origRGB >> 16) & r : (rgb >> 16) & r;
int greenPixel = green ? (origRGB >> 8) & g : (rgb >> 8) & g;
int bluePixel = blue ? origRGB & b : rgb & b;
int gbr = (redPixel << 16) | (greenPixel << 8) | bluePixel;
tmp.setRGB(i, j, gbr);
}
}

Java BufferedImage Not Registering Transparent Pixels?

I have used the method ImageIO.read(File file); to read a PNG image file. However, when I use the getRGB(int x, int y) method on it to extract the alpha it always returns 255 whether the pixel is transparent or not. How do I remedy this inconvenience?
When converting packed int colors to Color objects, you need to tell it if it should calculate the alpha value or not.
new Color(image.getRGB(x, y), true).getAlpha();
See Color(int, boolean) for more details
Just wanted to point out that using the method getRGB(x,y) is extremely inefficient. If you want to get the pixels of an image you could extract the colours from each individual pixel and then store the pixel in an int array. Credit also to mota for explaining why this is inefficient see his post . Example below:
/**===============================================================================================
* Method that extracts pixel data from an image
* #return a 2d array representing the pixels of the image
=================================================================================================*/
public static int[][] getImageData(BufferedImage img) {
int height = img.getHeight();
int width = img.getWidth();
final byte[] imgPixels = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
final boolean is_Alpha_Present = img.getAlphaRaster() != null;
int[][] imgArr = new int[height][width];
if (is_Alpha_Present) {
final int pixelLength = 4; //number of bytes used to represent a pixel if alpha value present
for (int pixel = 0, row = 0, col = 0; pixel < imgPixels.length; pixel = pixel + pixelLength) {
int argb = 0;
argb += (((int) imgPixels[pixel] & 0xff) << 24); //getting the alpha for the pixel
argb += ((int) imgPixels[pixel + 1] & 0xff); //getting the blue colour
argb += (((int) imgPixels[pixel + 2] & 0xff) << 8); //getting the green colour
argb += (((int) imgPixels[pixel + 3] & 0xff) << 16); //getting the red colour
imgArr[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
}
else {
final int pixelLength = 3;
for (int pixel = 0, row = 0, col = 0; pixel < imgPixels.length; pixel = pixel + pixelLength) {
int argb = 0;
argb += Integer.MIN_VALUE;
argb += ((int) imgPixels[pixel] & 0xff); //getting the blue colour
argb += (((int) imgPixels[pixel+1] & 0xff) << 8); //getting the green colour
argb += (((int) imgPixels[pixel+2] & 0xff) << 16); //getting the red colour
imgArr[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
}
return imgArr;
}

Categories

Resources