Saving colorspace in jpeg - java

I have a servlet to convert and cache smaller versions of photographs. It is implemented using java.awt.image + javax.imageio and a third party resample filter. The originals are all uploaded with an sRGB color profile. When I resample them and save them again they still are in sRGB however this is not recorded in the saved file.
How can I make sure this information is saved in the file?
In case you wondered it makes a difference, images without a profile are much more saturated on my screen (Safari + OSX + Calibrated screen) then when they have the correct sRGB profile. Also I'm sure it's the missing profile information and not the resampling algorithm.

Turns out it is enough to include an EXIF tag ColorSpace=1 that tells it should be processed as sRGB. Succeeded into doing this using Apache Commons Sanselan. This library is unfortunatly not complete so it can only be used to modify the EXIF after the file has been created.
Relevant code, based on Sanselan example:
public void addExifMetadata(File jpegImageFile, File dst)
throws IOException, ImageReadException, ImageWriteException {
OutputStream os = null;
try {
TiffOutputSet outputSet = new TiffOutputSet();
TiffOutputField colorspace = TiffOutputField.create(
TiffConstants.EXIF_TAG_COLOR_SPACE, outputSet.byteOrder, new Integer(1));
TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory();
exifDirectory.add(colorspace);
os = new FileOutputStream(dst);
os = new BufferedOutputStream(os);
new ExifRewriter().updateExifMetadataLossless(jpegImageFile, os, outputSet);
os.close();
os = null;
} finally {
if (os != null)
try {
os.close();
} catch (IOException e) {
}
}
}

Related

How to reduce size of a multipart file in java

I have Java Spring MVC application in which there is an option to upload an image and save to the server. i have the following method:
#RequestMapping(value = "/uploaddocimagecontentsubmit", method = RequestMethod.POST)
public String createUpdateFileImageContentSubmit(#RequestParam("file") MultipartFile file, ModelMap model)
{
//methods to handle file upload
}
I am now trying to reduce the size of the image refering the following:
increasing-resolution-and-reducing-size-of-an-image-in-java and decrease-image-resolution-in-java
The problem I am facing is that in the above examples, we are dealing with java.io.File Objects which are saved to a specified location. I dont want to save the image. Is there any way that I can use something similar to compress my Multipart Image file and continue with the upload.
Why don't you resize it on the client before upload? That will save some bandwidth
BlueImp JQuery Upload can do this
It was my first time taking a deep dive into the ImageIO package. I came across the MemoryCacheImageOutputStream, which allows you to write an image output stream to an output stream, i.e. ByteArrayOutputStream. From there, The data can be retrieved using toByteArray() and toString(), after compression. I used toByteArray, as I am storing images to postgresql and it stores the images as a byte array. I know this is late, but I hope it helps someone.
private byte[] compressImage(MultipartFile mpFile) {
float quality = 0.3f;
String imageName = mpFile.getOriginalFilename();
String imageExtension = imageName.substring(imageName.lastIndexOf(".") + 1);
// Returns an Iterator containing all currently registered ImageWriters that claim to be able to encode the named format.
// You don't have to register one yourself; some are provided.
ImageWriter imageWriter = ImageIO.getImageWritersByFormatName(imageExtension).next();
ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // Check the api value that suites your needs.
// A compression quality setting of 0.0 is most generically interpreted as "high compression is important,"
// while a setting of 1.0 is most generically interpreted as "high image quality is important."
imageWriteParam.setCompressionQuality(quality);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// MemoryCacheImageOutputStream: An implementation of ImageOutputStream that writes its output to a regular
// OutputStream, i.e. the ByteArrayOutputStream.
ImageOutputStream imageOutputStream = new MemoryCacheImageOutputStream(baos);
// Sets the destination to the given ImageOutputStream or other Object.
imageWriter.setOutput(imageOutputStream);
BufferedImage originalImage = null;
try (InputStream inputStream = mpFile.getInputStream()) {
originalImage = ImageIO.read(inputStream);
} catch (IOException e) {
String info = String.format("compressImage - bufferedImage (file %s)- IOException - message: %s ", imageName, e.getMessage());
logger.error(info);
return baos.toByteArray();
}
IIOImage image = new IIOImage(originalImage, null, null);
try {
imageWriter.write(null, image, imageWriteParam);
} catch (IOException e) {
String info = String.format("compressImage - imageWriter (file %s)- IOException - message: %s ", imageName, e.getMessage());
logger.error(info);
} finally {
imageWriter.dispose();
}
return baos.toByteArray();
}

Zxing Format Exception in Scanning PDF and converting to Buffered Image to Decode QR

I'm having problem with getting continuous successful QR decoding after PDF conversion. I keep getting,
"Exception in thread "main" com.google.zxing.FormatException."
My conversion attempts were done in:
PDFBox
public static BufferedImage convertPDFtoBufferedImageType2(String PDFPath) throws IOException{
PDDocument document = null;
try {
document = PDDocument.load(PDFPath);
PDPage firstPage = (PDPage) document.getDocumentCatalog().getAllPages().get(0);
return firstPage.convertToImage();
} catch (IOException ex) {
Logger.getLogger(PDF_Utility.class.getName()).log(Level.SEVERE, null, ex);
return null;
} finally {
if(document != null)
document.close();
}
}
Second Attempt with ghost4j
public static BufferedImage convertPDFtoBufferedImage(String PDFPath) throws IOException, RendererException, DocumentException{
System.setProperty("jna.library.path", "C:\\Program Files\\gs\\gs9.16\\bin\\");
PDFDocument document = new PDFDocument();
document.load(new File(PDFPath));
SimpleRenderer renderer = new SimpleRenderer();
renderer.setResolution(300);
List<Image> imgs = renderer.render(document);
Image im = imgs.get(0);
BufferedImage bi = new BufferedImage
(im.getWidth(null),im.getHeight(null),BufferedImage.TYPE_INT_RGB);
Graphics bg = bi.getGraphics();
bg.drawImage(im, 0, 0, null);
bg.dispose();
return bi;
}
My QR Decoder is:
public static String readQRCode(BufferedImage image, String charset, Map hintMap)
throws FileNotFoundException, IOException, NotFoundException, ChecksumException, FormatException {
Result qrCodeResult = null;
BinaryBitmap binaryBitmap = new BinaryBitmap(
new HybridBinarizer(new BufferedImageLuminanceSource(image)));
try{
qrCodeResult = new com.google.zxing.qrcode.QRCodeReader().decode(binaryBitmap,hintMap);
}catch(NotFoundException | FormatException e){ //attempt without hints
qrCodeResult = new com.google.zxing.qrcode.QRCodeReader().decode(binaryBitmap);
}
return qrCodeResult.getText();
}
And the reason why I called decode twice was because sometimes the "try harder"
hintMap.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
actually didn't catch the QR code, but the default did.
Anyways, these code snippets do catch most of my QR scans from a pile of documents, but there are times where it does not catch it at all. I even attempted to write it out as an image and then re-read it in:
ImageIO.write((RenderedImage) im, "png", new File("/path/to/my/img.png"));
Interestingly, http://zxing.org/w/decode.jspx does decode that output image, but my code couldn't.
I also tried different charset:
CHAR_SET = "UTF-8"; and CHAR_SET = "ISO-8859-1";
By getting Format Exceptions, the code was found, but "did not conform to the barcode's format rules. This could have been due to a mis-detection."
Apology for the messy code, but those attempts have gained majority of successful scans. 9/10 rate? Interestingly, sometimes another scanned copy of the same doc worked. Any help/advice/crazy voodoo combination is appreciated! Thanks!
EDIT: I got a sample (after whiting out the contents around. The real image has contents! Zxing website was able to catch this QR code too (with and without contents! (My program already ignored the other 1Ds at this same format and those with contents).
#Tilman Hausherr pointed out for the PDFBox default rendering size as low so I changed the default to 300dpi as he suggested. Overall, it worked for my case but definitely slowed down the speed. Will need to tweak my algorithm to run both a fast and this slower one as a backup.
return firstPage.convertToImage(BufferedImage.TYPE_4BYTE_ABGR, 300);
EDIT: Increased the success rate of catching barcodes, but did not successfully catch all. Increasing the dpi does not help.

Image size getting decreased after converting it into byte[] using BufferedImage and ImageIO

I am converting an Image into byte[] using following code.
public static byte[] extractBytes (String ImageName) throws IOException {
ByteArrayOutputStream baos=new ByteArrayOutputStream();
BufferedImage img=ImageIO.read(new File(ImageName));
ImageIO.write(img, "jpg", baos);
return baos.toByteArray();
}
Now when I am testing my code:
public static void main(String[] args) throws IOException {
String filepath = "image_old.jpg";
File outp=new File(filepath);
System.out.println("Size of original image="+outp.length());
byte[] data = extractBytes(filepath);
System.out.println("size of byte[] data="+data.length);
BufferedImage img = ImageIO.read(new ByteArrayInputStream(data));
//converting the byte[] array into image again
File outputfile = new File("image_new.jpg");
ImageIO.write(img, "jpeg", outputfile);
System.out.println("size of converted image="+outputfile.length());
}
I am getting very strange results:
Size of original image=78620
size of byte[] data=20280
size of converted image=20244
After converting image into byte[], its size getting decreased by around 1/4th and also when I am converting byte[] back to image its size alters.But output image is successfully getting created in the desired location. I can see the slight difference in quality of the original image and new image after doing 500-600 % zoom in. New image is little blurred after zoom in.
Here is the image on which I am doing the testing http://pbrd.co/1BrOVbf
Please explain the reason of this change in size and also I want to know any method to get the same size after this.
The image you have is compressed with maximum quality setting ("100%" or 1.0 in ImageIO terms). JPEG compression isn't very effective at such high settings, and is thus quite a bit larger than usual. When using ImageIO.write(..., "JPEG", ...) the default quality setting will be used. This default is 0.75 (the exact meaning of such a value is encoder dependent though, and isn't exact science), and thus lower quality, resulting in a smaller file size.
(Another likely cause for such a significant decrease in file size between the original and the re-compressed image, is the removal of meta data. When reading using ImageIO.read(file) you are effectively stripping away any meta data in the JPEG file, like XMP, Exif or ICC profiles. In extreme cases (yes, I'm talking mainly about Photoshop here ;-)) this meta data can take up more space than the image data itself (ie. megabytes of meta data is possible). This is however, not the case for your file.)
As you can see from the second re-compression (from byte[] to final output file), the output is just slightly smaller than the input. This is because the quality setting (unspecified, so still using default) will be the same in both cases (also, any metadata would also be lost in this step, so not adding to the file size). The minor difference is likely due to some small losses (rounding errors etc) in the JPEG decompression/re-compression.
While slightly counter-intuitive, the least data-loss (in terms of change from the original image, not in file size) when re-compression a JPEG, is always achieved by re-compression with the same quality setting (using the exact same tables should be virtually lossless, but small rounding errors might still occur) as the original. Increasing the quality setting will make the file output larger, but the quality will actually degrade.
The only way to be 100% sure to not lose any data or image quality, is by not decoding/encoding the image in the first place, but rather just copy the file byte by byte, for instance like this:
File in = ...;
File out = ...;
InputStream input = new FileInputStream(in);
try {
OutputStream output = new FileOutputStream(out);
try {
copy(input, output);
}
finally {
output.close();
}
}
finally {
input.close();
}
And the copy method:
public void copy(final InputStream in, final OutputStream out) {
byte[] buffer = new byte[1024];
int count;
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
// Flush out stream, to write any remaining buffered data
out.flush();
}
When you call ImageIO.write(img, "jpeg", outputfile); the ImageIO library writes a jpeg image, using its own compression parameters. The output image appears to be more compressed than the input image. You can adjust the level of compression by changing the parameter in the call to jpegParams.setCompressionQuality below. The resulting file may be bigger or smaller than the original depending on the relative compression levels in each.
public static ImageWriter getImageWriter() throws IOException {
IIORegistry registry = IIORegistry.getDefaultInstance();
Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class, (provider) -> {
if (provider instanceof ImageWriterSpi) {
return Arrays.stream(((ImageWriterSpi) provider).getFormatNames()).anyMatch(formatName -> formatName.equalsIgnoreCase("JPEG"));
}
return false;
}, true);
ImageWriterSpi writerSpi = services.next();
ImageWriter writer = writerSpi.createWriterInstance();
return writer;
}
public static void main(String[] args) throws IOException {
String filepath = "old.jpg";
File outp = new File(filepath);
System.out.println("Size of original image=" + outp.length());
byte[] data = extractBytes(filepath);
System.out.println("size of byte[] data=" + data.length);
BufferedImage img = ImageIO.read(new ByteArrayInputStream(data));
File outputfile = new File("new.jpg");
JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1f);
ImageWriter writer = getImageWriter();
outputfile.delete();
try (final ImageOutputStream stream = createImageOutputStream(outputfile)) {
writer.setOutput(stream);
try {
writer.write(null, new IIOImage(img, null, null), jpegParams);
} finally {
writer.dispose();
stream.flush();
}
}
System.out.println("size of converted image=" + outputfile.length());
}
This solution is adapted from the answer by JeanValjean given here Setting jpg compression level with ImageIO in Java

BufferedImage get resized with different colors

I am resizing many jpeg images using Apache Sanselan which also deals with CMYK colors.
I have a problem when trying to convert jpeg images that has an alpha channel... when doing it the result is an image with different colors, and i guess that java somehow handles these type of images as a different color format.
As i said, the RGB resizing works fine as well as CMYK. ARGB images turn out with different colors.
An example:
Any suggestions? Can i force somehow ignore the alpha channel and handle the image as an RGB image? or convert it to be an RGB image without losing the real colors?
The code that handles this image is:
ImageInputStream stream = ImageIO.createImageInputStream(file);
Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
while (iter.hasNext()) {
ImageReader reader = iter.next();
reader.setInput(stream);
BufferedImage image = null;
ICC_Profile profile = null;
try {
image = reader.read(0);
} catch (IIOException e) {
... (CMYK conversion if needed)
}
return image;
}
return null;
Thanks in advance
I found a good solution here (first solution worked great):
problem using ImageIO.write jpg file
Edit:
There is a new open source library which supports CMYK processing.
All you need to do is to add the dependency to your project and a new reader will be added to the list of readers (while the known JPEGImageReader can't deal with CMYK).
You will probably want to iterate over these readers and read the image using the first reader which doesn't throw exception.
This package is a release candidate, but i am using it and it solved a huge problem that we had hard time dealing with.
http://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-jpeg/3.0-rc5
You can do the iteration this way to get the BufferedImage, and after you got that, the rest is easy (you can use any existing image converting package to save it as another format):
try (ImageInputStream input = ImageIO.createImageInputStream(source)) {
// Find potential readers
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
// For each reader: try to read
while (readers != null && readers.hasNext()) {
ImageReader reader = readers.next();
try {
reader.setInput(input);
BufferedImage image = reader.read(0);
return image;
} catch (IIOException e) {
// Try next reader, ignore.
} catch (Exception e) {
// Unexpected exception. do not continue
throw e;
} finally {
// Close reader resources
reader.dispose();
}
}
// Couldn't resize with any of the readers
throw new IIOException("Unable to resize image");
}

ImageIO.read illegal argument exception - raster bands/colour space components?

Apologies for the somewhat vague title, I can't work out what the keywords are here. The setup's quite simple, I'm opening an image with
ImageIO.read(new File(filename));
This works for most files, however for one I get an IllegalArgumentException with the detail: "numbers of source Raster bands and source color space components do not match". This image was obtained via wget on a valid Flickr URL, and I've used other images obtained this way, so the method for obtaining images seems sound in principle. I'm not sure what's causing the exception.
A workaround would be more than acceptable - I'm not fussed with using ImageIO in particular, and the image looks fine visually. I just need to get it being read without Java freaking out!
Here's the image in question, in case it's of any use:
So I was having this same issue and found that the image was gray-scale and that the default ImageIO.read implementation was not figuring that out because the image metadata wasn't quite as expected. I wrote a work around that retries the load as 'BufferedImage.TYPE_BYTE_GRAY' if it fails the main load.
Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
Exception lastException = null;
while (iter.hasNext()) {
ImageReader reader = null;
try {
reader = (ImageReader)iter.next();
ImageReadParam param = reader.getDefaultReadParam();
reader.setInput(stream, true, true);
Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
while (imageTypes.hasNext()) {
ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();
int bufferedImageType = imageTypeSpecifier.getBufferedImageType();
if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {
param.setDestinationType(imageTypeSpecifier);
break;
}
}
bufferedImage = reader.read(0, param);
if (null != bufferedImage) break;
} catch (Exception e) {
lastException = e;
} finally {
if (null != reader) reader.dispose();
}
}
// If you don't have an image at the end of all readers
if (null == bufferedImage) {
if (null != lastException) {
throw lastException;
}
}
The error message is informative and indicates that the number of raster bands, as mentioned in the ICC color profile, seems to be incorrect. I used ImageMagick to strip the ICC profile from the image. ImageIO subsequently has no problems reading the images (~1k bad images). Hope that helps.
It is possible to read this image using twelvemonkeys ImageIO, which is a more robust and forgiving replacement for the original ImageIO provided by the JRE.
See https://github.com/haraldk/TwelveMonkeys/
I found this solution in the PDF Box Jira https://issues.apache.org/jira/browse/PDFBOX-3637
In order to use twelvemonkeys, it is sufficient to add it as a maven dependency. It then registers itself before the default image processor.
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.3.2</version> <!-- Alternatively, build your own version -->
</dependency>

Categories