I am trying to reduce a size of PDF using iText. In my code I resize the existing images. I found an example of the code at http://itextpdf.com/examples/iia.php?id=286 and modified it. It's reducing the size but the images are returned as jpg, so they have a white background. Obviously JPG does not support transparency. I've changed the code to write back as png image and some other things (like DCTDECODE filter to FLATEDECODE and BufferedImage.TYPE_INT_RGB to ARGB). But it's not working. Pdf can't be open (Insufficient data for an image). Any ideas what am I missing? Here is my code:
float FACTOR = 0.5f;
PdfReader reader = new PdfReader(src);
int n = reader.getXrefSize();
PdfObject object;
PRStream stream;
for (int i = 0; i < n; i++) {
object = reader.getPdfObject(i);
if (object == null || !object.isStream())
continue;
stream = (PRStream)object;
PdfObject pdfsubtype = stream.get(PdfName.SUBTYPE);
if (pdfsubtype != null && pdfsubtype.toString().equals(PdfName.IMAGE.toString())) {
PdfImageObject image = new PdfImageObject(stream);
BufferedImage bi = image.getBufferedImage();
if (bi == null) continue;
int width = (int)(bi.getWidth() * FACTOR);
int height = (int)(bi.getHeight() * FACTOR);
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = AffineTransform.getScaleInstance(FACTOR, FACTOR);
Graphics2D g = img.createGraphics();
g.drawRenderedImage(bi, at);
ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
ImageIO.write(img, "png", imgBytes);
g.dispose();
stream.clear();
stream.setData(imgBytes.toByteArray(), false, PRStream.BEST_COMPRESSION);
stream.put(PdfName.TYPE, PdfName.XOBJECT);
stream.put(PdfName.SUBTYPE, PdfName.IMAGE);
stream.put(PdfName.FILTER, PdfName.FLATEDECODE);
stream.put(PdfName.WIDTH, new PdfNumber(width));
stream.put(PdfName.HEIGHT, new PdfNumber(height));
stream.put(PdfName.BITSPERCOMPONENT, new PdfNumber(8));
stream.put(PdfName.COLORSPACE, PdfName.DEVICERGB);
imgBytes.flush();
imgBytes.close();
}
}
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
stamper.close();
reader.close();
Related
I'm converting an image to a pdf document by using iText.
I'm having trouble when I give the image a custom size that's bigger then the document itself.
Before I do doc.open(), I set the margins like this
doc.setMargins(marginLeft, marginRight, marginTop, marginBottom);
When I use my fit method, this works like a charm:
// scale
scaledAwtImage = new BufferedImage(scaledWidth, scaledHeight, bufferedImageType);
// instantiate
image = instantiateImage(awtImage, scaledAwtImage, scaledWidth, scaledHeight);
setAutoRotate(image);
// scaleWithMargins
float targetWidth = doc.getPageSize().getWidth() - mmToUnit(Float.parseFloat(marginLeft) + Float.parseFloat(marginRight));
float targetHeight = doc.getPageSize().getHeight() - mmToUnit(Float.parseFloat(marginBottom) + Float.parseFloat(marginTop));
image.scaleToFit(targetWidth, targetHeight);
Probably because of image.scaleToFit(). The following is the code I'm having trouble with. As you can see the right margin will get overwritten by the image. I can't use image.scaleToFit() here because I want to keep the custom sizes. Anyone got an idea?
// printWidth
if(printWidth != null && !printWidth.isEmpty() ){
scaledWidth = (int) mmToUnit(printWidth);
}
// printHeight
if(printHeight != null && !printHeight.isEmpty() ){
scaledHeight = (int) mmToUnit(printHeight);
}
scaledAwtImage = new BufferedImage(scaledWidth, scaledHeight, bufferedImageType);
//instantiate
image = instantiateImage(awtImage,scaledAwtImage, scaledWidth, scaledHeight);
EDIT
Forgot to post my instantiateImage() method for more information.
private Image instantiateImage(BufferedImage awtImage, BufferedImage scaledAwtImage, int scaledWidth, int scaledHeight) throws IOException, BadElementException {
Graphics2D g = scaledAwtImage.createGraphics();
g.drawImage(awtImage, 0, 0, scaledWidth, scaledHeight, null);
g.dispose();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ImageIO.write(scaledAwtImage, "jpeg", bout);
byte[] imageBytes = bout.toByteArray();
Image image = Image.getInstance(imageBytes);
return image;
}
I have a little problem here if someone could help me I will be really glad.
I'm trying to make the next operations
Read an BMP image
Convert the image into a byte[]
Rotate the image with 90 degree(the byte array)
And write a new image in some folder
My problem is... In the moment when I'm trying to write the new image I have some problem with BMP header and I don't know why. Please give me some advice if anyone know the answear.
Convert the image into byte[]
private static byte[] convertAnImageToPixelsArray(File file) throws FileNotFoundException {
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
try {
for (int readNum; (readNum = fis.read(buf)) != -1; ) {
bos.write(buf, 0, readNum);
}
} catch (IOException ex) {
Logger.getLogger(ConvertImage.class.getName()).log(Level.SEVERE, null, ex);
}
return bos.toByteArray();
}
Rotate
private static byte[] rotate(double angle, byte[] pixels, int width, int height) {
final double radians = Math.toRadians(angle), cos = Math.cos(radians), sin = Math.sin(radians);
final byte[] pixels2 = new byte[pixels.length];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int
centerx = width / 2,
centery = height / 2,
m = x - centerx,
n = y - centery,
j = ((int) (m * cos + n * sin)) + centerx,
k = ((int) (n * cos - m * sin)) + centery;
if (j >= 0 && j < width && k >= 0 && k < height)
pixels2[(y * width + x)] = pixels[(k * width + j)];
}
}
arraycopy(pixels2, 0, pixels, 0, pixels.length);
return pixels2;
}
Convert the byte[] into image
private static void convertArrayPixelsIntoImage(byte[] bytes) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
Iterator<?> readers = ImageIO.getImageReadersByFormatName("bmp");
ImageReader reader = (ImageReader) readers.next();
Object source = bis;
ImageInputStream iis = ImageIO.createImageInputStream(source);
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
Image image = reader.read(0, param);
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bufferedImage.createGraphics();
g2.drawImage(image, null, null);
File imageFile = new File("Images/Output.bmp");
ImageIO.write(bufferedImage, "bmp", imageFile);
}
Main:
public static void main(String[] args) throws IOException {
File file = new File("Images/Input-1.bmp");
Image img = ImageIO.read(file);
convertArrayPixelsIntoImage(rotate(90,convertAnImageToPixelsArray(file),img.getWidth(null),img.getHeight(null)));
}
Here it's the error message:
Exception in thread "main" javax.imageio.IIOException: Unable to read the image header.
Any suggestions?
The problem is that you're not taking into account the structure of the BMP file when you are rotating the image.
You're just reading a byte[] from the file, as you could from any file - it's just a stream of bytes.
But in your rotate method, you're assuming that the pixel values are:
1 byte per pixel;
Starting at the start of the array.
This isn't the case. Aside from the fact that each pixel will almost certainly be encoded by multiple bytes, the BMP file format starts with a header, and other metadata.
Whilst you obviously could work out how to decode the data correctly, I would strongly discourage it. You're already reading the image using ImageIO (Image img = ImageIO.read(file);), so you've got no need to reinvent the wheel: just use Java's existing image manipulation functionality.
You missed that the BMP image has a header like any other format. When you try to rotate the image, you are changing the bytes sequance, so the header loses in other place instead, of beginning. Try to extract first 54 bytes to another array, rotate others and then write to file header at first and ather bytes at second
Input image
Output Image
I am merging a group of tiff images into one and at the same time want to resize/rescale the final tiff image in java . Is there a way to do that . Below was my logic for merging . I am successfully in merging but can't increase the height and width .
ByteArrayOutputStream baos=new ByteArrayOutputStream();
//ImageEncoder encoder = ImageCodec.createImageEncoder("tiff", baos, params);
List<BufferedImage> imageList = new ArrayList<BufferedImage>();
for (int i = 1; i < images.size(); i++)
{
imageList.add(resizedImage);
}
params.setCompression(params.COMPRESSION_NONE);
params.setExtraImages(imageList.iterator());
TIFFField[] extras = new TIFFField[2];
extras[0] = new TIFFField(282, TIFFField.TIFF_RATIONAL, 1, (Object) new long[][] { { (long) 300, (long) 1 },
{ (long) 0, (long) 0 } });
extras[1] = new TIFFField(283, TIFFField.TIFF_RATIONAL, 1, (Object) new long[][] { { (long) 300, (long) 1 },
{ (long) 0, (long) 0 } });
params.setExtraFields(extras);
ImageEncoder encoder = ImageCodec.createImageEncoder("tiff", baos, params);
encoder.encode(images.get(0));
baos does holds the bytes for the final image which i am converting into a tiff
Final steps of creating a combined tiff image
FileOutputStream fos = new FileOutputStream (new File(inputDir + "\\combined.tiff"));
baos.writeTo(fos);
But how to increase the height and width . Please make a note that i had done everything in the java code and all at the run time
I'll give it a try since I don't understand what you are doing with the tiffs:
List<BufferedImage> imageList = new ArrayList<BufferedImage>();
for (int i = 0; i < images.size(); i++)
{
BufferedImage b=new BufferedImage(1500, 1500, BufferedImage.TYPE_INT_RGB);
Graphics g=b.getGraphics();
g.drawImage(images.get(i), 0, 0, b.getWidth(), b.getHeight(), null);
imageList.add(b);
}
This will convert the images from the list images to size 1500, 1500 and store them in the list imageList, which will then use to create the tiffs.
Well, in the proccess of converting an image from TIFF to PNG format, I really want the final image to be the smallest possible so I am setting the bit depth to 1 (black&white), I am working with images of signatures. My problem is that I am "losing" some pixels because in the original image were "too white" and the process of conversion is ignoring them.
What I want is a way to tell the PNG encoder to apply a threshold in the image, for example if the gray level is greater than 240 then it is white, if not is black. Or maybe any other way in wich the image don't lose much of the pixels, because the image is a signature and I have to show it in a browser. Here is a sample:
This is the code I use to convert a image from TIFF to PNG format:
public byte[]tiffToPng(byte[]tiffBytes) throws IOException {
SeekableStream stream = new ByteArraySeekableStream(tiffBytes);
ImageDecoder decoder = ImageCodec.createImageDecoder("tiff", stream, null);
RenderedImage renderedImage = decoder.decodeAsRenderedImage(0);
PNGEncodeParam pngEncodeParam = PNGEncodeParam.getDefaultEncodeParam(renderedImage);
pngEncodeParam.setBitDepth(1);
ByteArrayOutputStream pngBytesStream = new ByteArrayOutputStream();
ImageEncoder encoder = ImageCodec.createImageEncoder("png", pngBytesStream, pngEncodeParam);
encoder.encode(renderedImage);
pngBytesStream.flush();
return pngBytesStream.toByteArray();
}
Well, after some time, I first made the conversion of the RenderedImage to a BufferedImage, then a loop over the RGB values and the thresholding is done. Finally, I encoded the resulting image into a PNG (bit depth=1). This is it:
public byte[]tiffToPng(byte[]tiffBytes) throws IOException {
SeekableStream stream = new ByteArraySeekableStream(tiffBytes);
ImageDecoder decoder = ImageCodec.createImageDecoder("tiff", stream, null);
RenderedImage tiffRenderedImage = decoder.decodeAsRenderedImage(0);
BufferedImage tiffBufferedImage = toBufferedImage(tiffRenderedImage);
for (int y = 0; y < tiffBufferedImage.getHeight(); y++) {
for (int x = 0; x < tiffBufferedImage.getWidth(); x++) {
int rgb = tiffBufferedImage.getRGB(x, y);
int a = (rgb>>24)&0xFF;
int r = (rgb>>16)&0xFF;
int g = (rgb>>8)&0xFF;
int b = (rgb>>0)&0xFF;
if(a == 0xFF && r == g && g == b) {
if(r < 254) {
tiffBufferedImage.setRGB(x, y, 0xFF000000);
} else {
tiffBufferedImage.setRGB(x, y, 0xFFFFFFFF);
}
}
}
}
PNGEncodeParam pngEncodeParam = PNGEncodeParam.getDefaultEncodeParam(tiffBufferedImage);
pngEncodeParam.setBitDepth(1);
ByteArrayOutputStream pngBytesStream = new ByteArrayOutputStream();
ImageEncoder encoder = ImageCodec.createImageEncoder("png", pngBytesStream, pngEncodeParam);
encoder.encode(tiffBufferedImage);
pngBytesStream.flush();
return pngBytesStream.toByteArray();
}
public BufferedImage toBufferedImage(RenderedImage img) {
if (img instanceof BufferedImage) {
return (BufferedImage) img;
}
ColorModel cm = img.getColorModel();
int width = img.getWidth();
int height = img.getHeight();
WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
Hashtable properties = new Hashtable();
String[] keys = img.getPropertyNames();
if (keys != null) {
for (int i = 0; i < keys.length; i++) {
properties.put(keys[i], img.getProperty(keys[i]));
}
}
BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties);
img.copyData(raster);
return result;
}
And this is the final image:
What you want to do is more a image preprocessing thing, I wouldn't expect an encoder to allow for that. Just preprocess the image (brightness/contrast and/or gamma) and save.
I am a newbie to graphics. I've been using this code to make thumbnails of image files. When i use small files(~100KB) like wall papers, it works fine but when i use an image file(a photo) of size ~5MB, it produces just a few bytes(~1-8KB) of file which shows up as black image. It does not matter what Width and Height i give it. What could be going wrong here? Is it a difference between image types or the camera that produces the images? I'm sure the problem images are from a different camera than the non problematic ones. I am giving quality param as 100 to not miss out any detail that way...
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
int dx = thumbWidth, dy = thumbHeight;
Image image = Toolkit.getDefaultToolkit().createImage(file);
MediaTracker mediaTracker = new MediaTracker(new Container());
mediaTracker.addImage(image, 0);
mediaTracker.waitForID(0);
double thumbRatio = (double)thumbWidth / (double)thumbHeight;
int imageWidth = image.getWidth(null);
int imageHeight = image.getHeight(null);
double imageRatio = (double)imageWidth / (double)imageHeight;
if (thumbRatio < imageRatio) {
thumbHeight = (int)(thumbWidth / imageRatio);
} else {
thumbWidth = (int)(thumbHeight * imageRatio);
}
if(thumbWidth > dx) {
thumbWidth = dx;
thumbHeight = (int)(thumbWidth / imageRatio);
}
if(thumbHeight > dy)
{
thumbHeight = dy;
thumbWidth = (int) (thumbHeight*imageRatio);
}
log.debug("X="+thumbWidth+" Y="+thumbHeight);
BufferedImage thumbImage = new BufferedImage(thumbWidth, thumbHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = thumbImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics2D.drawImage(image, 0, 0, thumbWidth, thumbHeight, null);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(thumbImage);
quality = Math.max(0, Math.min(quality, 100));
param.setQuality((float)quality / 100.0f, false);
encoder.setJPEGEncodeParam(param);
encoder.encode(thumbImage);
log.debug("ThumbLength"+out.toByteArray().length);
FileOutputStream fos = new FileOutputStream("/root/testx.jpg");
fos.write(out.toByteArray());
fos.close();
} catch(Exception e) { log.debug(e.getMessage());}
return out.toByteArray();
You might try BufferedImage.TYPE_INT_ARGB, as shown here.
Also, your MediaTracker is waiting on the same thread; ImageIO.read() might be simpler.
Addendum: Also consider AffineTransformOp, although the src and dst must be different.