JAI Tiff to JPEG convertion issue - java

I have an issue converting Tiff-Files to JPEGs with JAI. This is my Code:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
TIFFDecodeParam param = null;
ImageDecoder dec = ImageCodec.createImageDecoder("tiff", new FileSeekableStream(inPath), param);
RenderedImage op = dec.decodeAsRenderedImage(0);
JPEGEncodeParam jpgparam = new JPEGEncodeParam();
jpgparam.setQuality(67);
ImageEncoder en = ImageCodec.createImageEncoder("jpeg", baos, jpgparam);
en.encode(op);
Mostly this code works fine, but with some Images, I got the following error:
java.lang.RuntimeException: Only 1, or 3-band byte data may be written.
at com.sun.media.jai.codecimpl.JPEGImageEncoder.encode(JPEGImageEncoder.java:142)
I cant find any related Problems over here and i have no idea how to fix it. The Images who throw this error have a high Resolution (9000 x 7000 or more) and are mostly scans of old pictures.
Image with this ColorModel works:
ColorModel:
#pixelBits = 24
numComponents = 3
color space = java.awt.color.ICC_ColorSpace#21981a50
transparency = 1 has alpha = false
isAlphaPre = false
This not:
ColorModel:
#pixelBits = 16
numComponents = 1
color space = java.awt.color.ICC_ColorSpace#88a30ad
transparency = 1 has alpha = false
isAlphaPre = false

I tried reading the JPEG standard, and it is not readily clear whether this is a limitation of the JPEG format or just the Encoder.
The encoder provide with java only encodes 1 or 3 byte bands, so in your case there are 16bit gray scale images. One way to solve this, as it appears you have done, is to save the image using a PNG encoder. It would not support the compression quality parameter.
The other way to handle this would be to save your image as an 8bit gray scale image.
I made a simple example to test this w/out JAI.
public static void main(String[] args) throws Exception{
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_USHORT_GRAY);
Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix("jpg");
while( writers.hasNext() ){
ImageWriter writer = writers.next();
ImageOutputStream ios = ImageIO.createImageOutputStream( new File("junk.jpg") );
writer.setOutput(ios);
writer.write(img);
}
}
The simplest way I can see to convert it is to create a new image and draw to it with a graphics.
BufferedImage img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics g = img2.getGraphics();
g.drawImage(img, 0, 0);
g.dispose();
Then img2 can be saved as JPG.

Related

Exporting 8 bit-dpeth JPEG with ImageIO

Using Java ImageIO, is it possible to export a jpeg image that has a bit-depth of 8? How would I do this? Even when exporting a BufferedImage of TYPE_BYTE_BINARY, which is a grayscale image, the result is a JPEG with bit-depth of 24.
This is what I have so far.
public void testJpegBitDepth() throws Exception{
Path pIn = Paths.get("testing/jpg/box1.jpg"), pOut;
BufferedImage bi;
//*******************************************
//Write 8 bit jpg
//Init ImageWriter
Iterator<ImageWriter> it = ImageIO.getImageWritersByFormatName("jpg");
ImageWriter writer = null;
while(it.hasNext()) {
try {
writer = it.next();
//Read input
bi = ImageIO.read(pIn.toFile());
if(bi == null)
throw new Exception("Failed to read input file: " + pIn);
//Convert to gray
bi = AWTImaging.convertToGray(bi);
log.debug("Num bands from the image raster: " + bi.getRaster().getNumBands());
pOut = test.outputDir.resolve("jpegBitDepth-8-"
+ pIn.getFileName().toString() + ".jpg");
//Init ImageTypeSpecifier
ImageTypeSpecifier imageType = ImageTypeSpecifier.createGrayscale(
8, //8 bits per pixel
DataBuffer.TYPE_BYTE, //stored in a byte
false); //unsigned
//Init WriteParam
ImageWriteParam param = writer.getDefaultWriteParam();
param.setDestinationType(imageType);
//Not sure if this is required or not, but the same Exception occurs either way
//param.setSourceBands(new int[] {0});
//Init meta
IIOMetadata meta = writer.getDefaultImageMetadata(imageType, param);
String metadataFormat = "javax_imageio_jpeg_image_1.0";
IIOMetadataNode root = new IIOMetadataNode(metadataFormat);
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
//I think we want app0JFIF metadata here, as it can specify a grayscale image https://docs.oracle.com/javase/10/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
root.appendChild(jpegVariety);
root.appendChild(markerSequence);
jpegVariety.appendChild(app0JFIF);
meta.mergeTree(metadataFormat, root);
//Export jpg
Files.deleteIfExists(pOut);
ImageOutputStream ios = ImageIO.createImageOutputStream(pOut.toFile());
writer.setOutput(ios);
writer.write(meta, new IIOImage(bi, null, meta), param);
log.debug("Succeded writing jpeg with writer: " + writer.getClass().toString());
break;
}catch(Exception e) {
log.error("Failed writing jpeg with writer: " + (writer != null ? writer.getClass().toString():"null"));
log.error("Ex: " + e);
}
}
}
I'm getting an Exception thrown from JpegImageWriter, here is the relevant stack trace:
Ex: javax.imageio.IIOException: Metadata components != number of destination bands
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=checkSOFBands,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=writeOnThread,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=write,Line=-1
Also I know that the Buffered Image is a TYPE_BYTE_BINARY, and the raster has 1 band (I printed this in a debug message above). So the Exception message would make me think that I need to define in the app0JFIF metadata that we are exporting 1 band. I don't know how to define this though, does anyone have any experience with this? This metadata is difficult to work with, or is it just me?
Thanks in advance.
You are correct about needing one band. Here’s how I did it:
if (bi.getSampleModel().getNumBands() != 1) {
ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false,
Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
BufferedImage oneBandedImage = new BufferedImage(colorModel,
colorModel.createCompatibleWritableRaster(
bi.getWidth(), bi.getHeight()),
false, new Properties());
Graphics g = oneBandedImage.createGraphics();
g.drawImage(bi, 0, 0, null);
g.dispose();
bi = oneBandedImage;
}
After doing that, I didn’t need to directly obtain an ImageWriter and I didn’t need to set any metadata; ImageIO.write(bi, "JPEG", file) was sufficient.
I ran /usr/bin/file on the result, and got this:
JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 315x180, components 1
I assume the components 1 part means that it has only one channel.

Change in color while using ImageIO.read()

I am trying to read images from external directory and for that I am using
bufferedImage image=ImageIO.read(new File(imagefile));
jlabel.seticon(new imageicon(image));
and getting a drastic change in colors. I tried many other things like:
bufferedImage image=ImageIO.read(new File(imagefile));
bufferedImage img=new bufferedImage(image.getWidth(),image.getHeight(),bufferedImage.TYPE_INT_RGB);
and I tried:
img.setData(image.getData();
jlabel.seticon(new imageicon(image));
and I tried:
Iterator readers = ImageIO.getImageReadersByFormatName("JPEG");
ImageReader reader = null;
while(readers.hasNext()) {
reader = (ImageReader)readers.next();
if(reader.canReadRaster()) {
break;
}
}
ImageInputStream input = ImageIO.createImageInputStream(f);
reader.setInput(input);
Raster raster = reader.readRaster(0, null);
BufferedImage bi = new BufferedImage(raster.getWidth(), raster.getHeight(),
BufferedImage.TYPE_4BYTE_ABGR);
bi.getRaster().setRect(raster);
but result are still same
http://i.stack.imgur.com/jNVm0.jpg
Here is an example of the issue:
The minimal code for viewing is:
bufferedImage image=ImageIO.read(new File(imagefile));
jlabel.seticon(new imageicon(image));
lbitem.setIcon(im);
and for storing
File f = new File(s);
long size=f.length();
FileInputStream fis1=new FileInputStream(f);
FileOutputStream fos2=new FileOutputStream("src/image/"+tfpn.getText()+".jpg");
byte b[]=new byte[1000];
int r=0;
long count=0;
while(true)
{
r=fis1.read(b,0,1000);
fos2.write(b,0,1000);
count = count+r;
if(count==size)
break;
System.out.println(count);
}
What could be causing the bad colors?
This problem is cause by a mismatch between reading/writing (creating/using) an image
that contains alpha (transparency) but you are expecting it to contain no alpha (or the inverse).
For example, if your image is BufferedImage.TYPE_4BYTE_ABGR and you output it
to a file type that does not support alpha (transparency) , or you writer does not
support alpha, it will look like your sample after reading and displaying it.
Use type PNG (supports alpha channel) not JPG (does not support alpha channel)

JAVA : How to create .PNG image from a byte[]?

I have seen some code source, but I do not understand...
I use Java 7
Please, how to convert a RGB (Red,Green,Blue) Byte Array (or something similar) to a .PNG file format ?
Example from an array that could represent "a RGB pixel" :
byte[] aByteArray={0xa,0x2,0xf};
Important Aspect :
I try to generate a .PNG file only from a byte[] "not from a previous existing file"
is it possible with an existing API? ;)
Here my first code :
byte[] aByteArray={0xa,0x2,0xf};
ByteArrayInputStream bais = new ByteArrayInputStream(aByteArray);
File outputfile = new File("image.png");
ImageIO.write(bais, "png", outputfile);
....Error : No suitable Method Found
Here the other version modified from Jeremy but look similar :
byte[] aByteArray={0xa,0x2,0xf};
ByteArrayInputStream bais = new ByteArrayInputStream(aByteArray);
final BufferedImage bufferedImage = ImageIO.read(newByteArrayInputStream(aByteArray));
ImageIO.write(bufferedImage, "png", new File("image.png"));
....multiple Errors : image == null! ...... Sure ? Note : I do not search to use a source file
The Image I/O API deals with images, so you need to make an image from your byte array first before you write it out.
byte[] aByteArray = {0xa,0x2,0xf,(byte)0xff,(byte)0xff,(byte)0xff};
int width = 1;
int height = 2;
DataBuffer buffer = new DataBufferByte(aByteArray, aByteArray.length);
//3 bytes per pixel: red, green, blue
WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, 3 * width, 3, new int[] {0, 1, 2}, (Point)null);
ColorModel cm = new ComponentColorModel(ColorModel.getRGBdefault().getColorSpace(), false, true, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(cm, raster, true, null);
ImageIO.write(image, "png", new File("image.png"));
This assumes the byte array has three bytes per pixel (red, green then blue) and the range of values is 0-255.

antialiasing in java image scaling library

I am currently using http://code.google.com/p/java-image-scaling/ this library to generate scaled images for my web app.But when I scale down the image to about 100x100 size there are some leftover artifacts visible in some images. Is this an issue with antialiasing? And how do I use antialiasing with this library.The api documentation doesn't say any thing about it.
Here is the code
File f = new File("C:\\Users\\ad min\\Pictures\\30-whisky-3d-wallpaper-1152x864.jpg");
BufferedImage src = ImageIO.read(f);
//ResampleOp resampleOp = new ResampleOp(76, 76);
ResampleOp resampleOp = new ResampleOp(200,200);
resampleOp.setUnsharpenMask(AdvancedResizeOp.UnsharpenMask.VerySharp);
BufferedImage rescaled = resampleOp.filter(src, null);
ImageIO.write(rescaled, "JPG", new File(
"C:\\Users\\ad min\\Pictures\\scaleddown.jpg"));
what am I doing wrong?
I finally didn't need antialiasing I simply used this code given in the foloowing link and it worked :) whewww
http://www.universalwebservices.net/web-programming-resources/java/adjust-jpeg-image-
compression-quality-when-saving-images-in-java
Iterator<ImageWriter> iter = ImageIO
.getImageWritersByFormatName("jpeg");
ImageWriter writer = (ImageWriter) iter.next();
// instantiate an ImageWriteParam object with default compression
// options
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(1); // an integer between 0 and 1
// 1 specifies minimum compression and maximum quality
File file = new File("C:\\Users\\ad min\\Pictures\\scaleddown.jpg");
FileImageOutputStream output = new FileImageOutputStream(file);
writer.setOutput(output);
IIOImage image = new IIOImage(rescaled, null, null);
writer.write(null, image, iwp);
writer.dispose();

Insufficient Data For An Image (PDF File Generation)

I'm using PDFBox to generate PDF files, however when I try to draw an image which I receive from an array of bytes I get the following error:
Insufficient data for an image
This is the basic structure of my code:
public ByteArrayOutputStream generatePDF() {
.. Variable Declaration
// Creating Document
document = new PDDocument();
// Creating Pages
for(int i = 0; i < arrayVar.length; i++) {
// Adding page to document
page = new PDPage();
// Creating FONT Attributes
fontNormal = PDType1Font.HELVETICA;
fontBold = PDType1Font.HELVETICA_BOLD;
// Building Front & Back Invoice Images
singleImageMap = // Getting Map With Array Of Bytes from Web Service Call;
if(singleImageMap != null && !singleImageMap.isEmpty()) {
arrayFront = Utils.readImage((byte[]) singleImageMap.get(Constants.WS_IMAGE_FRONT));
arrayBack = Utils.readImage((byte[]) singleImageMap.get(Constants.WS_IMAGE_BACK));
fileFront = new ByteArrayInputStream(arrayFront);
fileBack = new ByteArrayInputStream(arrayBack);
bufferedImageFront = ImageIO.read(fileFront);
bufferedImageBack = ImageIO.read(fileBack);
rescaledFrontImg = Scalr.resize(bufferedImageFront, 500);
rescaledBackImg = Scalr.resize(bufferedImageBack, 500);
front = new PDJpeg(document, rescaledFrontImg);
back = new PDJpeg(document, rescaledBackImg);
}
// Next we start a new content stream which will "hold" the to be created content.
contentStream = new PDPageContentStream(document, page);
// Let's define the content stream
contentStream.beginText();
contentStream.setFont(fontNormal, 8);
contentStream.moveTextPositionByAmount(200, 740);
contentStream.drawString("NAME: " + arrayVar[i].getParameter(Constants.NAME));
contentStream.endText();
if(front != null && back != null) {
contentStream.drawImage(front, 55, 500);
contentStream.drawImage(back, 55, 260);
}
// Add Page
document.addPage(page);
// Let's close the content stream
contentStream.close();
}
// Let's create OutputStream object
output = new ByteArrayOutputStream();
// Finally Let's save the PDF
document.save(output);
document.close();
return output;
}
Since I receive a PNG file from the Web Service I do the conversion to JPG with the following method:
public static byte[] readImage(byte[] file) throws Exception {
ImageInputStream is = ImageIO.createImageInputStream(new ByteArrayInputStream(file));
BufferedImage originalImage = ImageIO.read(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "jpg", baos );
byte[] imageInByte = baos.toByteArray();
return imageInByte;
}
As per this link:
https://issues.apache.org/jira/browse/PDFBOX-849
It points out that the error is because the PDJepg object should be created before the creation of the contentStream, but that's what I do in my code.
I'm not sure if there is a problem with the structure of my code, or that maybe there is an error in the way I'm handling the image bytes I'm getting from the Web Service call.
Does anyone has an idea of what could be the problem?
UPDATE
I did what Zelter Ady and indeed the image that I'm getting from the Web Service is valid since I was able to generate a physical file with it, so the problem should be somewhere around the manipulation of the image, the thing is I don't know what I'm missing.
I've got the same problem. With some images, Acrobat failed to display pages with this message:
Insufficient data for an image
My problem came from the colorModel in some jpeg images.
To track which images weren't ok, i log the BufferedImage colorModel by log.warn(img.getColorModel());
[VisualLocatorServlet.doGet:142] ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace#4b7fce transparency = 1 has alpha = false isAlphaPre = false
[VisualLocatorServlet.doGet:142] ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace#4b7fce transparency = 1 has alpha = false isAlphaPre = false
[VisualLocatorServlet.doGet:142] ColorModel: #pixelBits = 8 numComponents = 1 color space = java.awt.color.ICC_ColorSpace#19ef899 transparency = 1 has alpha = false isAlphaPre = false
Obviously, failing images are 8-bits encoded.
To fix that, i did the following:
byte[] buffer = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
BufferedImage img = ImageIO.read(new URL(visual));
/* resample 8-bits to 24-bits if necessary to fix pdf corruption */
if(img.getColorModel().getNumColorComponents()==1){
log.warn("components #1"+img.getColorModel());
BufferedImage out = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g2 = out.createGraphics();
g2.setBackground(Color.WHITE);
g2.drawImage(i, 0, 0, null);
g2.dispose();
log.warn("redrawn image "+img.getColorModel());
}
ImageIO.write(img, "jpeg", out);
...
The main point is to recreate a BufferedImage in 24bits. (BufferedImage.TYPE_3BYTE_BGR).
This may be an issue on the Adobe viewer side rather than at creation time. There's a known issue with the latest Acrobat versions: “Insufficient data for an image” error after updating to 10.1.4 or 9.5.2:
http://blogs.adobe.com/dmcmahon/2012/08/21/acrobat-insufficient-data-for-an-image-error-after-updating-to-10-1-4-or-9-5-2/
Before the build of the pdf try to save the image in a file, just to see the image is complete and can be saved.
You may use something like this to test the received image:
System.IO.File.WriteAllBytes("c:\\tmp.png", (byte[]) singleImageMap.get(Constants.FRONT));
and then open the image in a imageviewer. If the image cannot be open, then u have an error here. If the image is ok.... at least you know that this part is ok!
Well after a lot of debugging I found that the problem was here:
front = new PDJpeg(document, rescaledFrontImg);
back = new PDJpeg(document, rescaledBackImg);
The PDJpeg class has two constructors:
PDJpeg(PDDocument doc, BufferedImage bi)
PDJpeg(PDDocument doc, InputStream is)
I was passing a BufferedImage and at some point that I still can't figure out, I assume all the bytes were not being completely sent thus I got the message "Insufficient Data For An Image".
Solution: I passed an InputStream instead of a BufferedImage.
I still don't know why I got that error using a BufferedImage maybe I needed to do some sort of .push()?
This code worked for me.
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import org.apache.commons.imaging.Imaging;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
public void generatePdfFromTifPbox(File sourceFile, String destinationPath) throws Exception {
//sourceFile is tiff file, destinationPath is pdf destination path with pdf file name
PDDocument doc = new PDDocument();
List<BufferedImage> bimages = Imaging.getAllBufferedImages(sourceFile);
for (BufferedImage bi : bimages) {
PDPage page = new PDPage();
doc.addPage(page);
PDPageContentStream contentStream = new PDPageContentStream(doc, page);
try {
// the .08F can be tweaked. Go up for better quality,
// but the size of the PDF will increase
PDImageXObject image = JPEGFactory.createFromImage(doc, bi, 0.08f);
Dimension scaledDim = getScaledDimension(new Dimension(image.getWidth(), image.getHeight()),
new Dimension((int) page.getMediaBox().getWidth(), (int) page.getMediaBox().getHeight()));
contentStream.drawImage(image, 1, 1, scaledDim.width, scaledDim.height);
} finally {
contentStream.close();
}
}
doc.save(destinationPath);
}
private Dimension getScaledDimension(Dimension imgSize, Dimension boundary) {
int original_width = imgSize.width;
int original_height = imgSize.height;
int bound_width = boundary.width;
int bound_height = boundary.height;
int new_width = original_width;
int new_height = original_height;
// first check if we need to scale width
if (original_width > bound_width) {
// scale width to fit
new_width = bound_width;
// scale height to maintain aspect ratio
new_height = (new_width * original_height) / original_width;
}
// then check if we need to scale even with the new height
if (new_height > bound_height) {
// scale height to fit instead
new_height = bound_height;
// scale width to maintain aspect ratio
new_width = (new_height * original_width) / original_height;
}
return new Dimension(new_width, new_height);
}
Reference/Courtesy: http://www.paulzepernick.com/java/java-apache-pdfbox-convert-multipage-tiff-to-pdf/
Maven dependency:
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-imaging</artifactId>
<version>1.0-alpha1</version>
</dependency>

Categories