I'm trying to read a 256x256 image using ImageIO.read, transform it into a ByteArray, then into a BufferedImage, and back into an image file using ImageIO.write. It all seems to work as it should, but the final image is quite corrupted (although clearly still based on the original image. I can't find what's wrong in the process, I am suspicious of the scansize parameter, which I don't completely understand.
The idea is to manipulate pixels in between the reading and writing, but at the moment I can't even recreate the original image back into itself.
I attach the original image and the processed one below:
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import javax.imageio.ImageIO
fun main(args: Array<String>) {
val bImage = ImageIO.read(File("original.tiff"))
val bos = ByteArrayOutputStream()
ImageIO.write(bImage, "tiff", bos)
val data = bos.toByteArray()
val width = 256
val height = 256
val bytesPerPixel = 3
val len = width * height * bytesPerPixel
val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
val arr = IntArray(len)
for (i in 0 until len) arr[i] = data.get(i).toInt()
image.setRGB(0, 0, width, height, arr, 0, 256) // Seems like something is wrong here
try {
ImageIO.write(image, "jpg", File("converted-grayscale-002.jpg"))
} catch (e: IOException) {
System.err.println("IOException: $e")
}
}
This line is not returning the RGB image data:
val data = bos.toByteArray()
it is returning the compressed stream of the image in tiff format, surely it is not the correct image.
To get the pixels, use Image.getPixel(), alternatively you can get the buffer of the image directly, but to this you need to know what is the underlying buffer type - this is broad topic.
Take a look at this answer, it should give you idea how to do it: convert a RGB image to grayscale Image reducing the memory in java
Related
I need to compress a tif file that has several gray 16bit images (multi-page). I have tried working with ImageIO as here: Tiff compression using Java ImageIO Initially, each image that will be in the tif file comes from another tiff file. When I want to use the compressors, I have the following options:
CCITT RLE, CCITT T.4, CCITT T.6: They give me the error: "javax.imageio.IIOException: I/O error writing TIFF file!"
LZW. I cannot use it. My images are 16bit and LZW increases the size of 16bit images
JPEG. Not possible for 16bit images.
ZLIB. It only reduces 10% even if I specify setCompressionQuality(0.0f);
PackBits. Does not compress.
Deflate. Like ZLIB.
EXIF JPEG. It gives me the error: "javax.imageio.IIOException: Old JPEG compression not supported!"
Does any know any other alternative? I saw an apache imaging library but the tif compression only support the above or less options. Does anyone know about JPEG2000 compressor? Any other kind of alternative?
PNG compresses 16-bit images losslessly. Libraries and utilities are widely available. JPEG2000 has a lossy 16-bit mode, but you'd have to find some software that supports it. Open JPEG might.
However I'd have to ask: what are your criteria for when you have acceptable image quality and when you do not? If it is visual, then you likely end up at normal JPEG anyway, with a good bit less than 8 bits per pixel effective.
Reducing the image 16 bit to 8 bit. Consider that you have a byte[] variable plane16 where you have all the pixels of your image.
Note: My byte[] plane16 gets the data from a 16bit image but byte is 8bit=1byte. Therefore, 2 elements in row of this array are 2byte = 16bit. That is why I convert it to a short[] before operating. If you start from a short[], ommit "ByteBuffer.wrap(plane16).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);"
byte[] plane16; //Fill it with your image!!!
//Do stuff with imageIO. Set writer and compresion method
ImageIO.scanForPlugins();
TIFFImageWriterSpi tiffspi = new TIFFImageWriterSpi();
javax.imageio.ImageWriter writerIO = tiffspi.createWriterInstance();
ImageWriteParam param = writerIO.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("ZLib");
param.setCompressionQuality(0.5f);
File fOutputFile = new File(route+".tif");
ImageOutputStream ios = ImageIO.createImageOutputStream(fOutputFile);
writerIO.setOutput(ios);
//Reducing 16bit to 8bit
short[] shorts = new short[plane16.length/2];
ByteBuffer.wrap(plane16).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
int max = 0;
int min = 999999;
for (int v = 0; v<shorts.length;v++){
if (max<shorts[v]) max = shorts[v];
if (min>shorts[v]) min = shorts[v];
}
double range = 255./(max-min);
byte[] plane8 = new byte[shorts.length];
for (int v = 0; v<plane8.length;v++){
plane8[v] = (byte) ((shorts[v]+min)*range - 128);
}
//16bit:
/*BufferedImage convertedGrayscale = new BufferedImage(width,
heigth, BufferedImage.TYPE_USHORT_GRAY);
convertedGrayscale.getRaster().setDataElements(0, 0, width,
heigth, shorts);*/
//8bit:
BufferedImage convertedGrayscale = new BufferedImage(width,
heigth, BufferedImage.TYPE_BYTE_GRAY);
convertedGrayscale.getRaster().setDataElements(0, 0, width,
heigth, plane8);
//Save image
//If you have a stack of images in tiff, do this trick. "image" is the image number you are setting inside the tiff. If you only have 1 image, remove the if and take the expression from the else.
if (image!=0){
writerIO.writeInsert(image, new IIOImage(convertedGrayscale, null, null), param);
}else{
writerIO.write(null, new IIOImage(convertedGrayscale, null, null), param);
}
//do the next only after the last image to be saved
writerIO.dispose();
ios.flush();
ios.close();
I am trying to read a image with the following code, I wasn't able to figure it out why its happening. If there is anything I done wrong in the following code, please tell me.
System.out.println("Image Bytes ::"+imageBytes);
InputStream in = new ByteArrayInputStream(imageBytes);
BufferedImage img = ImageIO.read(in);
System.out.println("Buff Image :: "+img);
and the Output is as follows:
Image Bytes ::[B#4554617c
Buff Image :: null
Since the Source of your imageByte is unknown, it's would be hard to say what went wrong. But if your are creating that byteSource, then probably the below code will help you, because From the Javadocs for ImageIO.read()
Returns a BufferedImage as the result of decoding a supplied File with
an ImageReader chosen automatically from among those currently
registered. The File is wrapped in an ImageInputStream. If no
registered ImageReader claims to be able to read the resulting
stream, null is returned.
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
/**
* Created by ankur on 13/7/15.
* The Following program will read an image file.
* convert it into byte array, and then reuse the
* converted byte array, and convert it back to new BufferedImage
*
*/
public class ImageToBuf {
public static void main(String... strings) throws IOException {
byte[] imageInByte;
//read the image
BufferedImage originalImage = ImageIO.read(new File("/home/ankur/Pictures/BlpRb.png"));
//convert BufferedImage to byte array
ByteArrayOutputStream byteOutS = new ByteArrayOutputStream();
ImageIO.write(originalImage, "png", byteOutS);
byteOutS.flush();
imageInByte = byteOutS.toByteArray();
byteOutS.close();
//convert byte array back to BufferedImage
InputStream readedImage = new ByteArrayInputStream(imageInByte);
BufferedImage bfImage = ImageIO.read(readedImage);
System.out.println(bfImage);
}
}
OutPut(on my amchine):
BufferedImage#21b8d17c: type = 13 IndexColorModel: #pixelBits = 8 numComponents = 3 color space = java.awt.color.ICC_ColorSpace#6433a2 transparency = 1 transIndex = -1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 4959 height = 3505 #numDataElements 1 dataOff[0] = 0
I am very new to image encoding and would rather not learn a whole lot about it. Basically I'm taking greyscale byte array where each byte equals one pixel. I'm getting this data from mnist where I get 28x28 byte images. Anyway, bellow is my code, so you understand what I'm trying to accomplish.
private def getImages = {
val filePath = getClass.getResource("/mnist/train-images.idx3-ubyte").getPath
val fis = new FileInputStream(filePath)
var bytes = new Array[Byte](4)
fis.read(bytes)
println((ByteBuffer.wrap(bytes).getInt()))
fis.read(bytes)
println((ByteBuffer.wrap(bytes).getInt()))
fis.read(bytes)
var rows = ByteBuffer.wrap(bytes).getInt()
println("Number of rows: " + rows)
fis.read(bytes)
var cols = ByteBuffer.wrap(bytes).getInt()
println("Number of cols: " + cols)
var imageBytes = new Array[Byte](rows * cols)
fis.read(imageBytes)
imageBytes.foreach(println(_))
// I created a byte array input stream to feed into ImageIO
// which should create my image
val b = new ByteArrayInputStream(imageBytes)
// This is where your helpful answer would be placed
// What is the code to encode this into jpeg, gif, or whatever?
// This returns null because I have not encoded the bytes
// in the proper format
val img = ImageIO.read(b)
// Errors out because img is null
ImageIO.write(img, "gif", new File("/home/dev/woot.gif"))
}
The format is just consecutive pixel bytes laid next to each other. My question is what Java library or function is available to convert these raw bytes into jpeg, gif, or whatever format I need?
Before you write it out with ImageIO, create a BufferedImage first. It can be as simple as using the setRGB methods, and has the added benefit of allowing you to observe the image before writing it out.
I'm trying to send a BufferedImage over socket, I do this by converting the image to byte[] and then send it over after encoding it in Base64. I'm sending over 2 BufferedImages, one of them is "full", the other one is about 50% transparent. The problem I'm having, is that when they arrive, the second image is still visually transparent, but when I get the data array via Raster, it has been changed.
I made a small test code to demonstrate the problem;
BufferedImage levelBufferedOriginal = ...
BufferedImage backgroundBufferedOriginal = ...
byte[] levelDataOriginal = ((DataBufferByte) levelBufferedOriginal.getRaster().getDataBuffer()).getData();
byte[] backgroundDataOriginal = ((DataBufferByte) backgroundBufferedOriginal.getRaster().getDataBuffer()).getData();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] temp = null, temp2=null;
try {
ImageIO.write(levelBufferedOriginal, "png", baos);
baos.flush();
temp = baos.toByteArray();
baos.close();
baos=new ByteArrayOutputStream();
ImageIO.write(backgroundBufferedOriginal, "png", baos);
baos.flush();
temp2 = baos.toByteArray();
baos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
BufferedImage levelBufferedNew = null;
BufferedImage backgroundBufferedNew = null;
try {
levelBufferedNew = ImageIO.read(new ByteArrayInputStream(temp));
backgroundBufferedNew = ImageIO.read(new ByteArrayInputStream(temp2));
} catch (IOException e) {
e.printStackTrace();
}
byte[] levelDataNew = ((DataBufferByte) levelBufferedNew.getRaster().getDataBuffer()).getData();
byte[] backgroundDataNew = ((DataBufferByte) backgroundBufferedNew.getRaster().getDataBuffer()).getData();
System.out.println("LEVEL: " + Arrays.equals(levelDataOriginal, levelDataNew));
System.out.println("BACKGROUND: " + Arrays.equals(backgroundDataOriginal, backgroundDataNew));
All I do here, is simply transform the BufferedImage to byte[], then back, and compare the data I get from DataBufferByte. The output is
LEVEL: false
BACKGROUND: true
Background is the "full" image, and Level is the one with some transparent pixels.
If the general idea is wrong, I would like to hear another, all I want is to be able to exactly recreate 2 bufferedImages.
edit: What we have established so far:
The images (both before and after) are TYPE_BYTE_INDEXED (13) with IndexColorModel (color map)
The before image has a transparent color in the color map, at index 255 (which is the value -1 in the byte array, as Java uses signed bytes). The after image has a different value at this index, that is not transparent.
The images are serialized/deserialized in PNG format, using ImageIO
The images are visually equal, but the raw pixel data (the byte array) differs
Which leads to the conclusion that the ImageIO PNGImageWriter re-arranges the entries in the color map when writing, resulting in different pixel data/color map.
This basically leaves us with two options:
Serialize the image data in a different way, to assure the color map/pixel data is not modified. It is possible to send the pixel data array, along with the color map array and the height/width of the image, and then re-create the image exactly at the client. This is quite a bit of code, and is probably covered by other questions on SO.
Don't rely on the pixel data/color maps being the same. Use the value of ((IndexColorModel) levelBufferedNew.getColorModel()).getTransparentPixel() to test for/set transparency instead of the hardcoded value -1. This requires pretty much no other change in your code.
Note: These solutions will only work for TYPE_BYTE_INDEXED (13) images.
For a more generic (but possibly slower) approach, use the code in the original answer to set transparent parts, and use (levelBufferedNew.getRGB(x, y) >> 24) == 0 to test for transparency. This should work even for TYPE_INT_ARGB or TYPE_4BYTE_ABGR.
original answer:
Instead of fiddling with the image at byte array level, why not try using normal Java2D? ;-)
Something like:
Graphics2D g = levelBufferedNew.createGraphics();
try {
g.setComposite(AlphaComposite.Clear);
g.fillOval(x, y, w, h); // The area you want to make transparent
}
finally {
g.dispose();
}
...should work.
PS: As the images use IndexColorModel, you can use the getTransparentPixel() to get the transparent pixel index, instead of relying on it being at a certain index (-1/255). Then you can still manipulate at byte array level. ;-)
I'm trying to get a BufferedImage from raw samples, but I get exceptions about trying to read past the available data range which I just don't understand. What I'm trying to do is:
val datasize = image.width * image.height
val imgbytes = image.data.getIntArray(0, datasize)
val datamodel = new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, image.width, image.height, Array(image.red_mask.intValue, image.green_mask.intValue, image.blue_mask.intValue))
val buffer = datamodel.createDataBuffer
val raster = Raster.createRaster(datamodel, buffer, new Point(0,0))
datamodel.setPixels(0, 0, image.width, image.height, imgbytes, buffer)
val newimage = new BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
newimage.setData(raster)
Unfortunately I get:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 32784
at java.awt.image.SinglePixelPackedSampleModel.setPixels(SinglePixelPackedSampleModel.java:689)
at screenplayer.Main$.ximage_to_swt(Main.scala:40)
at screenplayer.Main$.main(Main.scala:31)
at screenplayer.Main.main(Main.scala)
The data is standard RGB with 1 byte padding (so that 1 pixel == 4 bytes) and the image size is 1366x24 px.
I finally got the code to run with the suggestion below. The final code is:
val datasize = image.width * image.height
val imgbytes = image.data.getIntArray(0, datasize)
val raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, image.width, image.height, 3, 8, null)
raster.setDataElements(0, 0, image.width, image.height, imgbytes)
val newimage = new BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
newimage.setData(raster)
If it can be improved, I'm open to suggestions of course, but in general it works as expected.
setPixels assumes that the image data is not packed. So it's looking for an input of length image.width*image.height*3, and running off the end of the array.
Here are three options for how to fix the problem.
(1) Unpack imgbytes so it is 3x longer, and do it the same way as above.
(2) Manually load the buffer from imgbytes instead of using setPixels:
var i=0
while (i < imgbytes.length) {
buffer.setElem(i, imgbytes(i))
i += 1
}
(3) Don't use createDataBuffer; if you already know that your data has the proper formatting you can create the appropriate buffer yourself (in this case, a DataBufferInt):
val buffer = new DataBufferInt(imgbytes, imgbytes.length)
(you may need to do imgbytes.clone if your original copy could get mutated by something else).