I have a servlet based application that is serving images from files stored locally. I have added logic that will allow the application to load the image file to a BufferedImage and then resize the image, add watermark text over the top of the image, or both.
I would like to set the content length before writing out the image. Apart from writing the image to a temporary file or byte array, is there a way to find the size of the BufferedImage?
All files are being written as jpg if that helps in calculating the size.
BufferedImage img = = new BufferedImage(500, 300, BufferedImage.TYPE_INT_RGB);
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
ImageIO.write(img, "png", tmp);
tmp.close();
Integer contentLength = tmp.size();
response.setContentType("image/png");
response.setHeader("Content-Length",contentLength.toString());
OutputStream out = response.getOutputStream();
out.write(tmp.toByteArray());
out.close();
No, you must write the file in memory or to a temporary file.
The reason is that it's impossible to predict how the JPEG encoding will affect file size.
Also, it's not good enough to "guess" at the file size; the Content-Length header has to be spot-on.
Well, the BufferedImage doesn't know that it's being written as a JPEG - as far as it's concerned, it could be PNG or GIF or TGA or TIFF or BMP... and all of those have different file sizes. So I don't believe there's any way for the BufferedImage to give you a file size directly. You'll just have to write it out and count the bytes.
You can calculate the size of a BufferedImage in memory very easily. This is because it is a wrapper for a WritableRaster that uses a DataBuffer for it's backing. If you want to calculate it's size in memory you can get a copy of the image's raster using getData() and then measuring the size of the data buffer in the raster.
DataBuffer dataBuffer = bufImg.getData().getDataBuffer();
// Each bank element in the data buffer is a 32-bit integer
long sizeBytes = ((long) dataBuffer.getSize()) * 4l;
long sizeMB = sizeBytes / (1024l * 1024l);`
Unless it is a very small image file, prefer to use chunked encoding over specifying a content length.
It was noted in one or two recent stackoverflow podcasts that HTTP proxies often report that they only support HTTP/1.0, which may be an issue.
Before you load the image file as a BufferedImage make a reference to the image file via the File object.
File imgObj = new File("your Image file path");
int imgLength = (int) imgObj.length();
imgLength would be your approximate image size though it my vary after resizing and then any operations you perform on it.
Related
I'm looking to read in the RGB values of a bitmap (or the hex colour codes, either work).
I have tried both this code :
File image = serverConfig.get(map.bmp);
BufferedImage buffer = ImageIO.read(image);
dimX = buffer.getWidth();
dimY = buffer.getHeight();
byte[] pixlesB = (byte[]) buffer.getRaster().getDataElements(0, 0, buffer.getWidth(), buffer.getHeight(), null);
and this code :
File image = serverConfig.get(map.bmp);
BufferedImage buffer = ImageIO.read(image);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(buffer, "bmp", baos );
baos.flush();
byte[] pixlesB = baos.toByteArray();
baos.close();
The both work fine for a small bitmap, but when I load a large bitmap, the data gets compressed and the array returns a bunch of semi random numbers.
for example:
A green pixel will read 2,2,2 instead of 0,255,0
A red pixel will read 5,5,5 instead of 255,0,0
A yellow pixel will read 8,8,8 instead of 255,255,0
The bitmaps I'm using only include the colours red, yellow and green.
My problem is I have no way of knowing what colour 2,2,2 relates to without checking it manually (which I cannot do since it changes with each bitmap)
I know that there is some metadata in the bitmap that specifies 2 is green, but I don't know how to access it or use it to turn 2 back into 0,255,0
And this is not a duplicate of Java - get pixel array from image since that doesn't mention compressed files.
And while I did ask this question a while back, it was just redirected to the above site.
Thanks in advance!
EDIT: Just thought this might make the question a bit clearer. I believe the file is being read correctly, it is just compressed. How do I decompress it?
If you want to read an image as a bitmap or as rgb values, you need to transform the image's format first.
Jpeg is a compressed image format, you need to use a tool or library in order to read as rgb.
check this answer:
How to get the rgb values of a jpeg image in TYPE_3BYTE_BGR?
Hope this helps
So I have an image, called square.png, that is about 3.7 kB in size. I read it into a BufferedImage as so:
BufferedImage img = ImageIO.read("square.png");
At this point I tried writing several different objects to the disk.
ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream("square_out.data"));
I tried the image wrapped in an ImageIcon: stream.writeObject(new ImageIcon(img));
I tried a 2D array of the same size as the pixel dimension of the image (800x600).
I tried wrapping that array in a custom class that implements Serializable and writing that.
All the above techniques resulted in a square_out.data with a size of about 1.9 MB. That's huge considering the original image was only a handful of kilobytes. It's also odd because the size was exactly the same for each. Is there any reasonable explanation for this/is there a way around it? I'd like to store lots of these, so the absurd file size is bothersome.
Because the BufferedImage stores the image in uncompressed format internally (and that is what gets serialized).
Use ImageIO.write to save the image in compressed format.
I am trying to reduce the size of the image by reszing its length and width but it stays the same size in megabytes sometimes its even larger even though its only half as big.
String compressPath = Environment.getExternalStorageDirectory()+"/test2.jpg";
FileInputStream in = new FileInputStream(path);
Bitmap fullSizeBitmap = BitmapFactory.decodeStream(in);
Bitmap resized = Bitmap.createScaledBitmap(
fullSizeBitmap,
(int)(fullSizeBitmap.getWidth()*0.5),
(int)(fullSizeBitmap.getHeight()*0.5),
true
);
FileOutputStream mOutputStream = new FileOutputStream(compressPath);
resized.compress(Bitmap.CompressFormat.PNG, 100, mOutputStream);
What's the problem? Do I have to lower the quality?
Per Bitmap's Android doc:
quality Hint to the compressor, 0-100. 0 meaning compress for small
size, 100 meaning compress for max quality. Some formats, like PNG
which is lossless, will ignore the quality setting
Your original input is JPG, but you are outputting it to PNG. Since JPG will always have quality degradation that introduce artifacts, making the generated PNG files larger when converted. Try saving it as JPG with 80-100 quality instead.
Also, in my tests compressing to PNG take 2-5x more time than compressing to JPG, due to PNG compression is done in software and JPG in hardware. So for most images it's probably better to compress to JPG.
Looking for something like djpeg which uses O(1) RAM to resize by sub sampling, but in java and able to handle jpg, png, gif, bmp, etc. Maybe some implementation already exists.
How to resize an image in a stream (using minimal RAM)?
The FileImageInputStream doesn't know anything about specific image formats, it's just convenience for reading ints, shorts, bytes, byte arrays, etc, from a file-backed input. File format support is handled by the various ImageReader implementations.
The short answer to your question is: You can't really resize an image without loading it.
From the description of djpeg:
djpeg decompresses the named JPEG file [...]
(Emphasis is mine)
However, you can subsample images, wich is really fast (for most formats), and will uses less memory. Have a look at the ImageReadParam.setSourceSubSampling method and the ImageReader.read(int, ImageReadParam) method. This will create a resized image, quite similar to the "nearest neighbour" or "point sampling" algorithms (ie. the results won't necessary look good).
It's possible to combine subsampling first, with better quality resizing afterwards, to save memory, and possibly get acceptable results. It all depends on what quality you expect/need.
If you really, really want to resize images without loading them into heap memory (perhaps your images are huge), I've written some classes that use memory mapped files you can look at, but they are painfully slow.
The twelvemonkeys jars enable reading of non RGB jpegs
setSourceSubSampling and setSourceRegion permit resizing images in a stream.
Progressive jpegs are not supported
Large output files are not supported
Example:
File javaStreamSubsample(File inFile, int s, Rectangle sourceRegion) throws IOException {
File outFile = File.createTempFile("img", null);;
ImageInputStream input = ImageIO.createImageInputStream(inFile);
try {
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
ImageReader reader = readers.next();
try {
reader.setInput(input);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceSubsampling(s, s, 0, 0);
if(sourceRegion!=null){
param.setSourceRegion(sourceRegion);
}
BufferedImage image = reader.read(0, param);
ImageIO.write(image, "jpg", outFile);
}finally {
reader.dispose();
}
} finally {
input.close();
}
return outFile;
}
I am trying to convert a BufferedImage to a byte array. I have two conditions. 1. I should not lose the quality of the image. 2. The size of the byte array should be the same as the actual image. I tried a couple of options.
Option 1:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
baos.flush();
byte[] imageBytes = baos.toByteArray();
baos.close();
Option 2:
WritableRaster raster = image.getRaster();
DataBufferByte data = (DataBufferByte) raster.getDataBuffer();
byte[] imageBytes = data.getData();
Both these options increase the size of the image (more than twofold for large images).
Appreciate any help. Thanks!
The PNG format uses lossless data compression, so you shouldn't need to worry about loosing the quality of the image with your option 1.
How are you seeing an increase in the size of the image? I'm sure you know what imageBytes.length is. What are you comparing this to that you're thinking this is twice as large as it should be? (I'm thinking your assumption may be incorrect.)
It is possible that your original source file is just using a higher compression setting than Java is re-compressing it with. You may need to pass some additional parameters into your writer. See how to compress a PNG image using Java for some additional details - specifically the link to http://www.exampledepot.com/egs/javax.imageio/JpegWrite.html.