Write swing component to large TIFF image using JAI - java

I have a large swing component to write to TIFF. The component is too large to load the TIFF in memory, so I either need to make a big BufferedImage which is backed by a disk-based WritableRaster (as mentioned here) or use JAI.
JAI seems like the better answer, aside from the utter confusion of the project.
Given that, can someone outline steps for writing my swing component to a tiled TIFF without running out of Memory?
Image size will be maybe 10000x700
Ideally I would create some sort of disk-based image, and write parts of the component to it, each write being flushed to disk.
EDIT
I think I could do this with an ImageWriter, however I'm getting a NoSuchElementException when I call:
ImageWriter imageWriter = ImageIO.getImageWritersByFormatName("tif").next();
I have the jai_code.jar and jai_core.jar jars on my classpath, is there something else I need to do?
EDIT
I can create a very large TIFF using JAI, but JAI doesn't support TIFF compression, so the file is 92 MB.
If I install JAI-ImageIO, I can create a compressed TIFF Using an ImageWriter, but only from a Raster or BufferedImage, which I don't have enough memory for.
Is there some way to do a two-step approach, use JAI to create the large TIFF, then compress the large TIFF without loading the whole thing into memory?

I had to load and store a large tiff (59392x40192px) with JAI. My solution is: TiledImages.
I have used a TiledImage because I need tiles and subimages.
To use the TiledImage efficient you should construct it with your prefered tile size. JAI uses a TileCache so not the whole Image will be in memory, when it's not needed.
To write the TiledImage in a File use the option "writeTiled" (avoid OutOfMemory because it writes tile by tile):
public void storeImage(TiledImage img, String filepath) {
TIFFEncodeParam tep = new TIFFEncodeParam();
//important to avoid OutOfMemory
tep.setTileSize(256, 256);
tep.setWriteTiled(true);
//fast compression
tep.setCompression(TIFFEncodeParam.COMPRESSION_PACKBITS);
//write file
JAI.create("filestore", img, filepath, "TIFF", tep);
}
It works fine with images up to 690mb (compressed), for larger images i haven't tested yet.
But if you are working on WinXP 32-bit you may not able to have more as 1280m HeapSpace size, this is still a limit of Java VM.
My TiledImage is build with a IndexedColorModel from my image-source data:
//here you create a ColorModel for your Image
ColorModel cm = source.createColorModel();
//then create a compatible SampleModel, with the tilesize
SampleModel sm = cm.createCompatibleSampleModel(tileWidth,tileHeight);
TiledImage image = new TiledImage(0, 0, imageWidth, imageHeight, 0, 0, sm, cm);

I had the same situation and I used these steps:
Load as BufferedImage with JAI
Resize BufferedImage size to preferable size (600x600px) maintaining aspect-ratio using Image#getScaledInstance(int w, int h, Image.SCALE_SMOOTH)
Draw image using Graphics2d.drawImage(..) method in JComponent#paintComponent(java.awt.Graphics) method
That helped me with showing and manipulating TIFF images ~50MB (5000x5000px).

Related

JAVA: How to create "png" image from BufferedImage. Image has to be openable in DevIL library

I need to save BufferedImage as png image. At next step,I will need to open the image in another program(Avisynth). This program is able to open images, which I have drawn in ms-paint, but images created by my java program it is not able to open. Images from my program and from ms-paint are type of png, and in windows seems good. It means I am able to open it, and image contains all what I have drawn.
In external program it throws following error:
Avisynth open failure:
ImageReader error 'Could not open file' in DevIL library.
reading image"C:\Images\mask\mirror.png"
DevIL version 166.
(C:\User\admin\Documents\4555.avs)
here is code. I tried it even with commented line. I found something on google. This message is typical for bad image format. But I do not know how to do the same image like ms-paint does.
BufferedImage img = new BufferedImage(video.getWidth(), video.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) img.getGraphics();
img.getGraphics().setColor(Color.white);
c.paintAll(img.getGraphics());
File f = new File(path + ".png");
opencv_core.IplImage imgs = IplImage.createFrom(img);
opencv_highgui.cvSaveImage(f.getPath(), imgs);
//ImageIO.write(img, "png", f);
In code:
c is JComponent, whitch contains the image, whitch I want to save, and I used JavaCL library
I've looked at your attached images, and I they both seem perfectly valid PNG files, and open fine in any viewer/library that I have tried. They both use standard deflate compression, adaptive filtering and are non-interlaced.
However, there's one important difference, and that is the one written from Java has transparency (alpha channel), while the one from Paint does not. None of the images have transparent pixels, so you could try to just throw away the alpha channel, and see if that helps (only change the first line of your code):
int w = video.getWidth();
int h = video.getHeight();
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
TYPE_3BYTE_BGR is what the ImageIO PNGReader returns for an opaque PNG, so it's probably faster, but you could just as well use TYPE_INT_RGB I think.
This small change in code, should produce an image that is closer to what Paint creates. I'm quite confident that this change should produce a PNG that DevIL/Avisynth can read.
(There's also another small difference between the images, that is the Paint image explicitly contains an sRGB chunk (and some other auxiliary chunks), but that shouldn't matter).
Other than that, I see that DevIL is using LibPNG (which is probably the most standard and widely used PNG library out there) to read PNGs, so I do find it kind of strange that it cannot read this particular PNG. But, it could be something about this version, the way its built or something (I'm no C/C++ programmer, nor do I know the library in depth). You should probably talk to (file an issue with) the developers of the library/program about this.
To read/write a BufferedImage, you can use javax.imageio.ImageIO. You can also add the TwelveMonkeys extension which improves the I/O operations with imageio.
An other solution is to use JAI, but it has a memory leak issues when reading images.
The png format is a standard, supported by almost all the libraries/softwares. If Avisynth cannot open an image created with your java software, it is certainly because you saved it into a rare format.

Efficient way to send an image over socket in Java

I'm a bit of a Java noob, and I have read some basics about sockets and I can successfully send images over socket using ImageIO, but I want to reduce the amount of data that is sent. Ultimately I want the image (screen capture) to be send as fast as possible with the smallest possible file size.
Right now, I have imageIO set up as such;
DataInputStream in=new DataInputStream(client.getInputStream());
DataOutputStream out = new DataOutputStream(client.getOutputStream());
ImageIO.write(captureImg(),"JPG",client.getOutputStream());
And the receiver:
BufferedImage img=ImageIO.read(ImageIO.createImageInputStream(server.getInputStream()));
File outputfile = new File("Screen"+(date.toString())+".jpg");
ImageIO.write(img, "jpg", outputfile);
In case you're wondering, this is my method that is used to take the image.
Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
BufferedImage capture = new Robot().createScreenCapture(screenRect);
I have heard about Byte arrays, where you can send the bytes then draw the image at the other end. However I'm not sure if this is more efficient.
Any help would be greatly appreciated, please ask if you would like me to add any extra info or code for the byte array!
Thanks.
EDIT: Patrick:
ByteArrayOutputStream bScrn = new ByteArrayOutputStream();
ImageIO.write(captureImg(), "JPG", bScrn);
byte imgBytes[] = bScrn.toByteArray();
out.write((Integer.toString(imgBytes.length)).getBytes());
out.write(imgBytes,0,imgBytes.length);
There already has been an extensive discussion in the comments, but to summarize a few points that I find important:
You have a trade-off between several criteria:
Minimize network traffic
Minimize CPU load
Maximize image quality
You can reduce the network traffic with a high image compression. But this will increase the CPU load and might reduce the image quality.
Whether it reduces the image quality depends on the compression type: For JPG, you can make the image arbitrarily small, but the quality of the image will then be ... well, arbitrarily bad. For PNG, the image quality will stay the same (since it is a lossless compression), but the CPU load and the resulting image size may be greater.
The option of ZIPping the image data was also mentioned. It is true that ZIPping the JPG or PNG data of an image will hardly reduce the amount of data (because the data already is compressed). But compressing the raw image data can be a feasible option, as an alternative to JPG or PNG.
Which compression technique (JPG, PNG or ZIP) is appropriate also depends on the image content: JPG is more suitable for "natural" images, like photos or rendered images. These can withstand a high compression without causing artefacts. For artifical images (like line drawings), it will quickly cause undesirable artefacts, particularly at sharp edges or when the image contains texts. In contrast to that: When the image contains large areas with a single color, then a compression like PNG (or ZIP) can reduce the image size due to the "run length compression" nature of these compression methods.
I already made some experiments for such an image transfer quite a while ago, and implemented it in a way that easily allowed tweaking and tuning these parameters and switching between the different methods, and comparing the speed for different application cases. But from the tip of my head, I can not give a profound summary of the results.
BTW: Depending on what you actually want to transfer, you could consider obtaining the image data with a different technique than Robot#createScreenCapture(Rectangle). This method is well-known to be distressingly slow. For example, when you want to transfer a Swing application, you could let your application directly paint into an image. Roughly with a pattern like
BufferedImage image = new BufferedImage(w,h,type);
Graphics g = image.getGraphics();
myMainFrame.paint(g);
g.dispose();
(This is only a sketch, to show the basic idea!)
Additionally, you could consider further options for increasing the "percieved speed" of such an image transfer. For example, you could divide your image into tiles, and transfer these tiles one after another. The receiver will possibly appreciate it if the image would at least be partially visible as quickly as possible. This idea could be extended further. For example, by detecting which tiles have really changed between two frames, and only transfer these changed tiles. (This approach could be extended and implemented in a rather sophisticated way, by detecting the "minimum regions" that have to be transferred)
However, for the case that you first want to play around with the most obvious tuning parameter: Here is a method that allows writing a JPG image with a quality value between 0.0 and 1.0 into an output stream:
public static void writeJPG(
BufferedImage bufferedImage,
OutputStream outputStream,
float quality) throws IOException
{
Iterator<ImageWriter> iterator =
ImageIO.getImageWritersByFormatName("jpg");
ImageWriter imageWriter = iterator.next();
ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
imageWriteParam.setCompressionQuality(quality);
ImageOutputStream imageOutputStream =
new MemoryCacheImageOutputStream(outputStream);
imageWriter.setOutput(imageOutputStream);
IIOImage iioimage = new IIOImage(bufferedImage, null, null);
imageWriter.write(null, iioimage, imageWriteParam);
imageOutputStream.flush();
}

Programmatically Reducing JPEG file size

Apologies for any ignorance, but I have never worked with jpeg images (let alone any types of images) in Java before.
Supposing I want to send a jpeg image from a web service to a client. Is there any way that I can reduce the jpeg file size by manipulating the colour profile of the image in some way?
I have already been able to reduce the image size by scaling it using a neat tool for BufferedImages called imgscalr. See here.
I would also like a jpeg that has less colours than a high quality jpeg image. For example, I would like to be able to use 8bit colour in my jpeg instead of say 16bit colour.
What exactly would I need to change if I have a BufferedImage from Java's 2D package?
Another way to reduce image size is to change compression level. You can do that using ImageWriter.
ImageWriter writer = null;
Iterator<ImageWriter> iwi = ImageIO.getImageWritersByFormatName("jpg");
if (!iwi.hasNext())
return;
writer = (ImageWriter) iwi.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
iwp.setCompressionQuality(compressionQuality);
writer.setOutput(...);
writer.write(null, image, iwp);
The easiest way to do this is to decompress the byte stream into a Java Image, optionally resize it (which makes it smaller) and then regenerate a JPEG image from this with the desired quality setting.
This new image is then what is sent to the client.
Have a look at the ImageIO class. As for reducing file size: since the image would already be a JPEG the only things you could do is reduce the quality or the image size.
Another thing to keep in mind: if the image is a CMYK jpeg it might be bigger. Unfortunately ImageIO can't handle those, but you can try JAI ImageIO to convert from CMYK to RGB (which should be much smaller).
Two of the possible solutions are downscaling the image, here's how you'd do it:
BufferedImage original = //your image here
scaled = original.getScaledInstance(finalWidth, finalHeight, Image.SCALE_SMOOTH); // scale the image to a smaller one
BufferedImage result = new BufferedImage(finalWidth, finalHeight, original.getType());
Graphics2D g = result.createGraphics();
g.drawImage(scaled, 0, 0, null); //draw the smaller image
g.dispose();
Obviously, you have to calculate the scaled width and height so the image stays by the same aspect ratio.
Once you have drawn it smaller, you can now turn this image into a JPEG file:
BufferedImage image = // this is the final scaled down image
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(output);
JPEGEncodeParam jpegEncodeParam = jpegEncoder.getDefaultJPEGEncodeParam(image);
jpegEncodeParam.setDensityUnit(JPEGEncodeParam.DENSITY_UNIT_DOTS_INCH);
jpegEncodeParam.setXDensity(92);
jpegEncodeParam.setYDensity(92);
jpegEncodeParam.setQuality( 0.8F , false);
jpegEncoder.encode(image, jpegEncodeParam);
These classes are from the JAI package (more exactly com.sun.image.codec.jpeg) and the JVM might complain that they should not be used directly, but you can ignore that.
You can possibly download JAI from here, if it does not work I have github mirrors setup for the two libraries, JAI core and JAI ImageIO.

How to save optimized png images with java's ImageIO?

I am generating lots of images in java and saving them through the ImageIO.write method like this:
final BufferedImage img = createSomeImage();
ImageIO.write( img, "png", new File( "/some/file.png" );
I was happy with the results until Google's firefox addon 'Page Speed' told me that i can save up to 60% of the size if i optimize the images. The images are QR codes, their size is around 900B each and the firefox-plugin optimized versions are around 300B.
I'd like to save such optimized 300B Images directly from java.
So here my question again: How to save optimized png images with java's ImageIO?
Use PngEncoderB to convert your BufferedImage into a PNG encoded byte array.
You can apply a filter to it, which helps prepare the image for better optimization. This is what OptiPNG does, only OptiPNG calculates which filter will get you the best compression.
You might have to try applying each filter to see which one is consistently better for you. With 2 bit color, I think the only filter that might help is "up", so I'm guessing that's the one to use.
Once you get the image to a PNG encoded byte array, you can write that directly to a file.

How can I create a JAI PlanarImage from a byte array raster?

I have an application that uses JAI to process images. It takes TIFF files, reads them, wraps the content into a SeekableStream then uses JAI.create to create the PlanarImage.
What I want to do is change the source from TIFF files to a component, which already exists, and which serves me the image data in the form of a raster stored in a byte array. I know the format of the raster data (width, height, bits per sample etc.).But I can't find a way to create a PlanarImage for JAI in the same way as before.
My only idea is to create a java.awt.Image from the raster and use that with JAI.create. Is this OK, or is there a better solution?
You can use PlanarImage.wrap(bufferedImage) method.
Note: you can create bufferedImage from raster.
Furkan

Categories

Resources