I'm writing an application which reads and displays images as ImageIcons (within a JLabel), the application needs to be able to support jpegs and bitmaps.
For jpegs I find that passing the filename directly to the ImageIcon constructor works fine (even for displaying two large jpegs), however if I use ImageIO.read to get the image and then pass the image to the ImageIcon constructor, I get an OutOfMemoryError( Java Heap Space ) when the second image is read (using the same images as before).
For bitmaps, if I try to read by passing the filename to ImageIcon, nothing is displayed, however by reading the image with ImageIO.read and then using this image in the ImageIcon constructor works fine.
I understand from reading other forum posts that the reason that the two methods don't work the same for the different formats is down to java's compatability issues with bitmaps, however is there a way around my problem so that I can use the same method for both bitmaps and jpegs without an OutOfMemoryError?
(I would like to avoid having to increase the heap size if possible!)
The OutOfMemoryError is triggered by this line:
img = getFileContentsAsImage(file);
and the method definition is:
public static BufferedImage getFileContentsAsImage(File file) throws FileNotFoundException {
BufferedImage img = null;
try {
ImageIO.setUseCache(false);
img = ImageIO.read(file);
img.flush();
} catch (IOException ex) {
//log error
}
return img;
}
The stack trace is:
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
at java.awt.image.DataBufferByte.<init>(DataBufferByte.java:58)
at java.awt.image.ComponentSampleModel.createDataBuffer(ComponentSampleModel.java:397)
at java.awt.image.Raster.createWritableRaster(Raster.java:938)
at javax.imageio.ImageTypeSpecifier.createBufferedImage(ImageTypeSpecifier.java:1056)
at javax.imageio.ImageReader.getDestination(ImageReader.java:2879)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:925)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:897)
at javax.imageio.ImageIO.read(ImageIO.java:1422)
at javax.imageio.ImageIO.read(ImageIO.java:1282)
at framework.FileUtils.getFileContentsAsImage(FileUtils.java:33)
You are running out of memory because ImageIO.read() returns an uncompressed BufferedImage which is very large and is retained in the heap because it is referenced by the ImageIcon. However, the images returned by Toolkit.createImage remain in their compressed format (using the private ByteArrayImageSource class.)
You cannot read a BMP using Toolkit.createImage (and even if you could it would still remain uncompressed in memory and you would probably run out of heap space again) but what you can do is read the uncompressed image and save it in a byte array in compressed form, e.g.
public static ImageIcon getPNGIconFromFile(File file) throws IOException {
BufferedImage bitmap = ImageIO.read(file);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ImageIO.write(bitmap, "PNG", bytes);
return new ImageIcon(bytes.toByteArray());
}
That way the only time the uncompressed bitmap must be held in memory is when it is being loaded or rendered.
Have you tried this?
ImageIcon im = new ImageIcon(Toolkit.getDefaultToolkit().createImage("filename"));
It couldn't be that you indeed just run out of memory? I mean, does the error still occur if you run java with, say, -Xmx1g ?
Related
Firstly, I understand questions regarding java.lang.OutOfMemoryError and Bitmaps have already been asked numerous times before. I have also checked out the Displaying Bitmaps Efficiently page.
My use case:
I am storing two different sized Bitmaps as Strings in an SQLite database. The first size of the Bitmaps is 50% of the screen width, and the second size is 100% of the screen width.
I am using a RecyclerView which displays the images in ImageViews which are either 50% or 100% of the screen width, so the Bitmaps being loaded are no bigger than they need to be, and they are appropriately sized before the images are retrieved from the Database.
I am also loading the Bitmaps using an AsyncTask.
I have over 180 different items in the RecyclerView so I have a total of over 360 Bitmaps (i.e. numberOfimages * theDifferentSizesOfEachImage) being created. I am coverting the String versions of the images into byte arrays via this code: byte [] byteArray = Base64.decode(encodedString, Base64.DEFAULT);
The Problem
The Activity was able to load over around 170 different images without encurring the java.lang.OutOfMemoryError, unless I restarted the same Activity (e.g. load the Activity, then recreate the Activity by clicking on it again in the Navigation Drawer and then repeating that process) and incurred the java.lang.OutOfMemoryError whilst converting the Strings into byte arrays.
I am converting the byte array to a Bitmap using the Glide library using the following code:
Bitmap bitmap = Glide.with(context).load(byteArray).asBitmap()
.dontTransform().dontAnimate().skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE).into(-1, -1).get();
My Question in a nutshell
How do I avoid the java.lang.OutOfMemoryError occurring whilst am I converting the Strings into byte arrays?
Note
After creating a Bitmap using Glide I am calling recycle() on the given Bitmap and then setting it to null.
I am also already using android:largeHeap="true" in my Android Manifest
Thanks in advance
Edit
Here is how I am creating the Bitmap Strings:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.WEBP,100, baos);
byte [] b =baos.toByteArray();
String bitmapString = Base64.encodeToString(b, Base64.DEFAULT);
What i would suggest you to drop your approach, this would just not work with large set of image and you are already putting to much work on your thread to handle. One should never store image like that in the sqllite.
You should just convert your bitmap to a file having unique name or could be same (depends upon your use case) then you can just save this file inside the app directory and save the file path in database. Here is some code to help you.
File pictureFile = getOutputMediaFile(getActivity(), MEDIA_TYPE_IMAGE);
if (pictureFile == null) {
return;
}
Bitmap bitmap =BitmapFactory.decodeByteArray(data,0,data.length);
FileOutputStream fos = new FileOutputStream(pictureFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
private File getOutputMediaFile(Context context, int m) {
File mediaStorageDir = context.getFilesDir();
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.d("Fade", "failed to create directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
.format(new Date());
File mediaFile=new File(mediaStorageDir.getPath()+File.separator
+ "IMG_" + timeStamp + ".JPG");
return mediaFile;
}
Now you have the file and now you can just store the file path in the database and when its needed you can always get your file from the storage using glide. This would also make your database fast to queries.
This way you wont need any changes in gradle or anywhere else. Try this.
I am reading a gif image from internet url.
// URL of a sample animated gif, needs to be wrapped in try-catch block
URL imageUrl = new Url("http://4.bp.blogspot.com/-CTUfMbxRZWg/URi_3Sp-vKI/AAAAAAAAAa4/a2n_9dUd2Hg/s1600/Kei_Run.gif");
// reads the image from url and stores in BufferedImage object.
BufferedImage bImage = ImageIO.read(imageUrl);
// creates a new `java.io.File` object with image name
File imageFile = new File("download.gif");
// ImageIO writes BufferedImage into File Object
ImageIO.write(bImage, "gif", imageFile);
The code executes successfully. But, the saved image is not animated as the source image is.
I have looked at many of the stack-overflow questions/answers, but i am not able to get through this. Most of them do it by BufferedImage frame by frame which alters frame-rate. I don't want changes to the source image. I want to download it as it is with same size, same resolution and same frame-rate.
Please keep in mind that i want to avoid using streams and unofficial-libraries as much as i can(if it can't be done without them, i will use them).
If there is an alternative to ImageIO or the way i read image from url and it gets the thing done, please point me in that direction.
There is no need to decode the image and then re-encode it.
Just read the bytes of the image, and write the bytes, as is, to the file:
try (InputStream in = imageUrl.openStream()) {
Files.copy(in, new File("download.gif").toPath());
}
I'm creating a Chat in java for a university project, and one of the requirements is each user must have an image associate, this can be done through registration windows and data modification windows, in registration everything works great, but on the modification window, the program throws an exception when i try to send big files, both codes (registration and modification) are basiccally the same, changing only variables and path, but still gives my problem only in modification
Here is my code:
Client:
BufferedImage image = ImageIO.read(new File(usuario.getImagen().getCanonicalPath()));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", byteArrayOutputStream);
byte[] size = ByteBuffer.allocate(4).putInt(byteArrayOutputStream.size()).array();
salida.write(size);
salida.write(byteArrayOutputStream.toByteArray());
salida.flush();
Server:
dir = new File ("." + "/Documentos/Imagenes de Verificacion/" +
usuarioRegistro.getNombreDeUsuario() + ".jpg");
sizeAr = new byte[4];
entrada.read(sizeAr);
size = ByteBuffer.wrap(sizeAr).asIntBuffer().get();
imageAr = new byte[size];
entrada.readFully(imageAr);
image = ImageIO.read(new ByteArrayInputStream(imageAr));
ImageIO.write(image, "jpg", new File(dir.getCanonicalPath()));
usuarioRegistro.setImagen(dir.getCanonicalFile());
And the exception is:
Exception in thread "Thread-0" java.lang.IllegalArgumentException: image == null!
at javax.imageio.ImageTypeSpecifier.createFromRenderedImage(ImageTypeSpecifier.java:925)
at javax.imageio.ImageIO.getWriter(ImageIO.java:1591)
at javax.imageio.ImageIO.write(ImageIO.java:1520)
at com.ucab.javachat.Servidor.model.ServidorModel.run(ServidorModel.java:198)
The line ServiorModel.java:198 is: ImageIO.write(image, "jpg", new File(dir.getCanonicalPath()));
In my tests i can send images of 20, 30, 80, 200 Kb, but when i try to send the 2.1mb file gives the error.
I think this i related with some data loose on the byteArray (maybe header data?) but what i dont know is how to fix it, my register window method uses the same sockets and OutputStream to send data and i succesfully send a 24mb image.
As per the documentation:
Returns a BufferedImage as the result of decoding a supplied File with
an ImageReader chosen automatically from among those currently
registered. The File is wrapped in an ImageInputStream. If no
registered ImageReader claims to be able to read the resulting stream,
null is returned. The current cache settings from getUseCacheand
getCacheDirectory will be used to control caching in the
ImageInputStream that is created.
Note that there is no read method that takes a filename as a String;
use this method instead after creating a File from the filename.
This method does not attempt to locate ImageReaders that can read
directly from a File; that may be accomplished using IIORegistry and
ImageReaderSpi.
Make sure you register an ImageReader or wrap your file on a FileInputStream, but since your implementation works I bet it's the image causing issues therefore,
Make sure that your image is of type: GIF, PNG, JPEG, BMP, and WBMP for these are the types supported by the class.
I am trying to create an mxgraph and a image from the created mxgraph in JAVA. Below is the code to create the image from mxgraph.
BufferedImage image = mxCellRenderer.createBufferedImage(graph,
null, 1, Color.WHITE, graphComponent.isAntiAlias(), null,
graphComponent.getCanvas());
// Creates the URL-encoded XML data
mxCodec codec = new mxCodec();
String xml = URLEncoder.encode(mxXmlUtils.getXml(codec.encode(graph.getModel())), "UTF-8");
mxPngEncodeParam param = mxPngEncodeParam.getDefaultEncodeParam(image);
param.setCompressedText(new String[] { "mxGraphModel", xml });
//Saves as a PNG file
outputStream = new FileOutputStream(new File("graph.jpg"));
ImageIO.write(image, "jpg", outputStream);
outputStream.close();
image = null;
I am using hierarchical layout in the graph.
But I am getting the out of memory error on creating the image for larger graph.
How can i get rid of this memory issue (apart from increasing the heap size)?
Is there any other alternate way to solve this problem (apart from increasing the heap size)?
See this post here:
http://forum.jgraph.com/questions/5408/save-as-png-detect-out-of-memory
especially the bottom part. There's a check in JGraphX which determines if there's enough memory. That one is wrong. There may not be enough memory because the GC hasn't run yet. If the GC runs, then memory would be freed and the createBufferedImage method could be successful. So instead of checking for the free memory, the memory should have just been allocated in a try { ... } catch( Error err} { ... } block.
I am using a BufferedImage in java after capturing the image out of a JFrame. I need some way to sharpen the image such that when it's enlarged it doesn't look so pixelated. Here's the thing it has to keep the same image size.
Here's the code I'm using to capture the image.
private void grabScreenShot() throws Exception
{
BufferedImage image = (BufferedImage)createImage(getSize().width, getSize().height);
paint(image.getGraphics());
try{
//ImageIO.write(image, "jpg", new File(TimeTable.path+"\\TimeLine.jpg"));
ImageIO.write(image, "jpg", new File("C:\\Users\\"+TimeTable.user+"\\AppData\\TimeLineMacroProgram\\TimeLine.jpg"));
//ImageIO.write(image, "jpg", new File("C:\\ProgramData\\TimeLineMacroProgram\\TimeLine.jpg"));
System.out.println("Image was created");
}
catch (IOException e){
System.out.println("Had trouble writing the image.");
throw e;
}
}
And here's the image it creates
JPG is ill suited for screenshots. It's designed for complex and colorful pictures wherein information loss during compression is nearly negligible, such as photos. For screenshots you should rather be using GIF or, better, PNG.
ImageIO.write(image, "png", new File("C:\\Users\\"+TimeTable.user+"\\AppData\\TimeLineMacroProgram\\TimeLine.png"));
You only end up with a bigger file, but you get pixelperfect sharpness and detail back, which is simply impossible with JPG.
I think it's because you're writing it out as a JPEG file.
I'd change the format to something non-lossy, or else force the writer to not use compression by accessing and changing its ImageWriteParam.