Getting low quality thumbnail from a high quality image in Java - java

I have tried the below code to generate the high quality thumbnail image but got the thumb nail blurred and not that much clarity.
BufferedImage thumbImage = new BufferedImage(thumbWidth, thumbHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = thumbImage.createGraphics();
graphics2D.setBackground(Color.WHITE);
graphics2D.setPaint(Color.WHITE);
graphics2D.fillRect(0, 0, thumbWidth, thumbHeight);
graphics2D.setComposite(AlphaComposite.Src);
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.drawImage(image, 0, 0, thumbWidth, thumbHeight, null);
graphics2D.dispose();
File file = new File(thumbnailFile);
if (javax.imageio.ImageIO.write(thumbImage, "JPG", file))
return file;
}
The original image is a high quality image. Why am I getting the thumbnail image distorted and low in quality?

There are two possibilities from looking at the code:
The original image is very large, and the thumbnail is very small, resulting in image quality being degraded by the simple bilinear interpolation.
The compression artifacts resulting from the JPEG encoding is degrading the quality of the thumbnail.
Using a simple bilinear interpolation can be adequate if the original image and the thumbnail doesn't differ in size by a whole lot, for example, going from a 200x200 to 100x100.
However, when it comes to resizing images that are large (such as 1600x1200) to a thumbnail size image, bilinear interpolation (and bicubic interpolation, for that matter), so alternative techniques such as multi-step resizing should be used.
The article The Perils of Image.getScaledInstance() by Chris Campbell goes into more details about how and why downscaling large images can lead to degradation of image quality.
The book Filthy Rich Clients by Chet Haase and Romain Guy also goes into some details about creating high-quality thumbnails.
I maintain a thumbnail generation library called Thumbnailator, which uses techniques such as multi-step resizing to create high-quality thumbnails with an easy-to-use API.
For example, your example code could be written using Thumbnailator like this:
Thumbnails.of(image)
.size(thumbWidth, thumbHeight)
.outputFormat("JPG")
.toFile(file);
It's also possible to specify the compression quality settings if compression artifacts are causing image quality degradation:
Thumbnails.of(image)
.size(thumbWidth, thumbHeight)
.outputFormat("JPG")
.outoutQuality(0.9)
.toFile(file);

Thumbnails4j (I'm a maintainer, but it's owned by Elastic) is a java library that can be used to create thumbnails from image files, as well as from other file types.
File input = new File("/path/to/my_file.jpeg");
Thumbnailer thumbnailer = new ImageThumbnailer("png"); // or "jpg", whichever output format you want
List<Dimensions> outputDimensions = Collections.singletonList(new Dimensions(100, 100));
BufferedImage output = thumbnailer.getThumbnails(input, outputDimensions).get(0);
One of the approaches it takes is to iteratively resize the image, instead of jumping straight to the desired size, as this helps to preserve image quality. But this library allows you to avoid having to worry about the details of the image processing algorithm.

Related

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();
}

Reduce image size (bytes) in ITextPDF

I'm using the itext PDF library to build a very image-intensive PDF document in Java. Each page has a dozen images on it. The original source images are very high resolution, and I'm using scaleToFit to render the image to the size I need.
The problem I have is that the PDF document is still very large. My understanding is that the entire original high resolution image is being included, and the scaling I'm using only affects the actual rendering, not the size of the image that's included in the file.
I've verified this by removing the scaling — the pages were rendered with the high resolution images overlapping each other and the edge of pages, and the PDF was the same size as when the scaling was in place.
So, here's the question — how can I reduce the size of the PDF file by scaling down each image? If I lose a little bit of image quality that's ok. Rescaling the source images manually will be difficult.
So I've found a way to do it. I now load the image into a BufferedImage, and then scale that using the hints found here: how do I scale a BufferedImage.
This gives me a BufferedImage — I then convert this into an iText image using
Image returnedImage = Image.getInstance ( pcb, bufferedImage, quality );
Where quality is currently 0.6. That's acceptable for the work I'm doing.

Can I change the compression algorithm used by Java's ImageWriter when creating a JPEG?

Or, alternatively, is there a better library to use to handle compression?
Let me preface this with what I already understand: (1) JPEG is lossy--it won't look the same as the input file. (2) I can adjust the compression quality setting to something between 0.0 and 1.0, as I've done in the code below.
I am taking a BufferedImage and converting it to a JPEG and am noticing that Java's ImageWriter's .write() method produces sub-par results for JPEG images (as compared to Photoshop "Save for Web", as an example).
My code looks a bit like this right now:
// img is a BufferedImage, here
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(.75f);
IIOImage image = new IIOImage(img, null, null);
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
writer.setOutput(ImageIO.createImageOutputStream(byteArrayOut));
writer.write(null, image, iwp);
writer.dispose();
Playing with the compression quality setting produces different quality outputs, but even at setting "1.0", they don't look as nice as what I can get with other tools when creating a JPEG.
Since I'm a new user and can't post images yet... here's a webpage that demos the differences. Hopefully I can get them in here permanently at some point for future users who may have a similar question.
Obviously, this particular image is not the best candidate for JPEG compression (the PNG is much smaller and lossless), but it allows the compression artifacts to be seen more easily. The actual images will be mostly photographic in nature. At the very least, this is more to question the algorithm and quality of Java's JPEG compression versus others out there that produce images that look closer to the original with fewer bytes.
"Java's ImageWriter's .write() method produces sub-par results for JPEG
images (as compared to Photoshop "Save for Web", as an example)."
There are more than one reasons why this happens and it is unfair to compare image quality produced by Java imageio with a professional image software as photoshop.
Anyway, let's see what the most probable cause is for the artifacts in your image: usually for a software to save images as JPEG, it will allow user to specify a parameter either as compression or quality, one is the inverse of the other. This parameter is used to scale the quantization tables used in the quantization process which is the most significant contributor to JPEG lossiness. Different encoder may use different quantization tables which partially accounts for the image quality difference.
But there could be other factors which affect the compression and image quality among which is chroma subsampling (or downsampling) which actually happens before the quantization process. Chroma subsampling is the process whereby the color information in the image is sampled at a lower resolution than the original. For better explanation read this article.
Calvin Hass provides an excellent JPEG damping tool called JPEGSnoop which can be downloaded from http://www.impulseadventure.com. Using this tool on the ps75.jpg image you provided, I found the following output which pertains to chroma subsampling:
Component[1]: ID=0x01, Samp Fac=0x11 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y)
Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 1 x 1), Quant Tbl Sel=0x01 (Chrom: Cb)
Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 1 x 1), Quant Tbl Sel=0x01 (Chrom: Cr)
which means there is no subsampling done on the color components. On the other hand, the subsamping part from both 100.jpg and 75.jpg are the same:
Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y)
Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb)
Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr)
which means subsamping has been done on both horizontal and vertical directions for color components by taking the average of two consecutive pixels.
The effect of chroma subsampling on image quality will be most notable when the original images are composed of strip and/or squares as in your case and you've already noticed the artifacts are easier to be seen here.
So, IMO, for this special case, the problem is more from the chroma subsampling than from the quality factor setting. Maybe I haven't dug deep enough, but I could not find a way to set sampling factors for imageio or the ImageWriter behind it (which is most probably com.sun.imageio.plugins.jpeg.JPEGImageWriter) though it seems possible to set quantization and Huffman tables to be used by ImageWriter.
Therefore, it is unlikely that you could change the compression algorithm used by Java's ImageWriter unless you write your own ImageWriter plugin for imageio or as a standalone one. But both are non-trivial given the complexity of JEPG compression algorithm. There is one relatively easy to follow Java JpegEncoder implementation written by James R. Weeks which by default doesn't do chroma subsampling. It used to be free but you can find the original versions by searching the web.
Another interesting thing is: from the output of JPEGSnoop, the actual quality factor for the 75% JPEG image saved by photoshop is actually shown as around 92%. And the following quoted text from Calvin Hass's website will answer the question why photoshop using no subsampling in your case:
As an aside, note that Photoshop CS2 uses different chroma subsampling levels depending on the Save JPEG Quality settings:
Photoshop Save As Quality 0-6 - 2x2 Chroma Subsampling
Photoshop Save As Quality 7-12 - 1x1 No Chroma Subsampling
Photoshop Save For Web Quality 0-50 - 2x2 Chroma Subsampling
Photoshop Save For Web Quality 51-100 - 1x1 No Chroma Subsampling
Check out this Java image library which can write JPEG image as well.

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.

Resized image degrades in quality

I resized an image using Java2D Graphics class. But it doesn't look right.
BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
g.dispose();
Is it possible to scale an image without introducing artifacts?
Bitmap graphics do not scale well, generally speaking. Degradation is particularly notable when you increase the size of the image, but even scaling down can introduce undesirable artifacts especially if not scaling by integral factors.
The best solution, if you need multiple sizes of a single image for display, is to either use vector* graphics or take the highest fidelity bitmap you have and scale down, and by integral factors.
*Note that vector graphics aren't an option for photographs and the like.
This article by Chris Campbell has lots of detailed information on scaling images with Java2D.
There are a number of options you can use regarding the quality of the scaling, where generally the better the quality the longer the scaling will take (performance versus quality tradeoff).
The information in the article will probably help your scaling look better, but as #Kevin says in his answer, at the end of the day no scaling is going to be absolutely perfect.
You could look into java-image-scaling library. With a quick test it created a better quality down scaled image than using standard Java2D/AWT tools.

Categories

Resources