JavaFX load image memory problems - java

When I load an 15Mb image in JavaFX, it takes like 250Mb of RAM!
Image imageColored = new Image("file:C:\\Users\\user\\Desktop\\portret.jpg");
ImageViewResizable imageView = new ImageViewResizable(imageColored);
And copying it takes 10 seconds and increases RAM usage up to 1Gb.
WritableImage imageBlack;
int width = (int) imageColored.getWidth();
int height = (int) imageColored.getHeight();
//make img black and white;
imageBlack = new WritableImage(width, height);
PixelReader pixelReader = imageColored.getPixelReader();
PixelWriter pixelWriter = imageBlack.getPixelWriter();
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++) {
Color color = pixelReader.getColor(x, y);
double grey = (color.getBlue() + color.getGreen() + color.getRed()) / 3;
pixelWriter.setColor(x, y, new Color(grey, grey, grey, color.getOpacity()));
}
How can I decrease RAM usage and copy image effectively?

This is expected behaviour and has already been discussed in the JavaFX bug system. To overcome this you need to provide the size of the image to which the image should be scaled, in the Image()
constructor.
According to one of the comments by a JavaFX lead developer Kevin Rushforth :
The png image is encoded in a way that it needs to be decoded before it can be used or displayed. When you construct an Image, it has to create that buffer with W*H pixels. Each pixel takes up 4 bytes in memory. As specified the default Image constructor takes the width and height specified in the file as the width and height of an image. This is a 5000*5000 image meaning 25,000,000 pixels. At 4 bytes each, that takes 100 Mbytes in memory. The only way to reduce the memory is to scale the image on the way in by specifying a smaller width and height to which the image is scaled.
Although, he talks about PNG, creating buffer with W*H must not be very different for JPEG images.
For more information visit - Huge memory consumption when loading a large Image

Related

Android, change Bitmap aspect ratio programmatically

I found a lot of code about Bitmaps in Android. Change size, change size preserving aspect ratio, compress etc. etc.
However I need to change a Bitmap aspect ratio to fit my needs (5:8), programmatically.
I have no code to show because any search I made return how to preserve aspect ratio, not to change it.
As for example, 5:8 should be width:500 height:800
Thanks to #SergeyGlotov comment I simply calculated the new size this way:
private Bitmap bitmapChangeAspectRatio(Bitmap bitmap){
int width, height;
width = bitmap.getWidth() / 5;
height = width * 8;
return Bitmap.createScaledBitmap(bitmap, bitmap.getWidth(), height, true);
}
I do not delete the question due there is not a clear example of this on the web.

Quickly Render Bytes to Canvas with a Color Palette

While working on a Java application which requires rendering sprites, I thought that, instead of loading a .png or .jpg file as an Image or BufferedImage, I could load up a byte[] array containing indices for a color palette(16 colors per palette, so two pixels per byte), then render that.
The method I currently have generates a BufferedImage from the byte[] array and color palette while initializing, taking extra time to initialize but running smoothly after that, which works fine, but there are only 4 sprites in the program so far. I'm worried that when there are 100+ sprites, storing all of them as BufferedImages will be too taxing on the memory. And not only would that mean 1 BufferedImage per sprite, but actually 1 image for each sprite/palette combination I'd want to use.
This function creates the BufferedImage:
protected BufferedImage genImage(ColorPalette cp, int width, int height){ //Function to generate BufferedImage to render from the byte[]
BufferedImage ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); //Create the Image to return
for(int j=0; j<height; j++){ //Run a for loop for each pixel
for(int i=0; i<width; i++){
int index = (j * width + i)/2; //Get the index of the needed byte
int value = image[index] & 0x00ff; //Convert to "unsigned byte", or int
byte thing; //declare actual color index as byte
if(i % 2 == 0)thing = (byte)((value & 0b11110000) >>> 4); //If it's an even index(since it starts with 0, this includes the 1st one), get the first 4 bits of the value
else thing = (byte)(value & 0b00001111); //If it's odd, get the last four bits
ret.setRGB(i, j, cp.getColor(thing & 0x00ff).getRGB()); //Set the pixel in the image to the value in the Color Palette
}
}
return ret;
}
And this one actually renders it to the screen:
public void render(Graphics g, int x, int y){ //Graphics to render to and x/y coords
g.drawImage(texture, x, y, TILE_WIDTH, TILE_HEIGHT, null); //Render it
}
I've experimented with another method that renders from the byte[] directly w/o the need for a BufferedImage, which should theoretically succeed in saving memory by avoiding use of a BufferedImage for each sprite, but it ended up being very, very slow. It took several seconds to render each frame w/ at most 25 sprites to render on the screen! Note that g is a Graphics object.
private void drawSquare(int x, int y, int scale, Color c){ //Draw each "pixel" to scale
if(g == null){ //If null, quit
return;
}
g.setColor(c); //Set the color
for(int i=x; i<x+scale; i++){ //Loop through each pixel
if(i<0)continue;
for(int j=y; j<y+scale; j++){
if(j<0)continue;
g.fillRect(x, y, scale, scale); //Fill the rect to make the "pixel"
}
}
}
public void drawBytes(byte[] image, int x, int y, int width, int height, int scale, ColorPalette palette){ //Draw a byte[] image with given byte[], x/y coords, width/height, scale, and color palette
if(image.length < width * height / 2){ //If the image is too small, exit
return;
}
for(int j=0; j<height; j++){ //Loop through each pixel
for(int i=0; i<width; i++){
int index = (j * width + i)/2; //Get index
int value = image[index]; //get the byte
byte thing; //get the high or low value depending on even/odd
if(i % 2 == 0)thing = (byte)((value & 0b11110000) >>> 4);
else thing = (byte)(value & 0b00001111);
drawSquare((int)(x + scale * i), (int)(y + scale * j), scale, palette.getColor(thing)); //draw the pixel
}
}
}
So is there a more efficient way to render these byte[] arrays w/o the need for BufferedImage's? Or will it really not be problematic to have several hundred BufferdImage's loaded into memory?
EDIT: I've also tried doing the no-BufferedImage methods, but with g as the one large BufferedImage to which everything is rendered, and is then rendered to the Canvas. The primary difference is that g.fillRect(... is changed to g.setRGB(... in that method, but it was similarly slow.
EDIT: The images I'm dealing with are 16x16 and 32x32 pixels.
If memory usage is your main concern, I'd use BufferedImages with IndexColorModel (TYPE_BYTE_BINARY). This would perfectly reflect your byte[] image and ColorPalette, and waste very little memory. They will also be reasonably fast to draw.
This approach will use about 1/8th of the memory used by the initial use of TYPE_INT_RGB BufferedImages, because we retain the 4 bits per pixel, instead of 32 bits (an int is 32 bits) per pixel (plus some overhead for the palette, of course).
public static void main(String[] args) {
byte[] palette = new byte[16 * 3]; // 16 color palette without alpha
byte[] pixels = new byte[(16 * 16 * 4) / 8]; // 16 * 16 * 4 bit
Random random = new Random(); // For test purposes, just fill arrays with random data
random.nextBytes(palette);
random.nextBytes(pixels);
// Create ColorModel & Raster from palette and pixels
IndexColorModel cm = new IndexColorModel(4, 16, palette, 0, false, -1); // -1 for no transparency
DataBufferByte buffer = new DataBufferByte(pixels, pixels.length);
WritableRaster raster = Raster.createPackedRaster(buffer, 16, 16, 4, null);
// Create BufferedImage from CM and Raster
final BufferedImage image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
System.out.println("image: " + image); // "image: BufferedImage#...: type = 12 ..."
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Foo");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(new JLabel(new ImageIcon(image)));
frame.pack();
frame.setVisible(true);
}
});
}
The above code will create fully opaque (Transparency.OPAQUE) images, that will occupy the entire 16 x 16 pixel block.
If you want bitmask (Transparency.BITMASK) transparency, where all pixels are either fully opaque or fulle transparent, just change the last parameter in the IndexColorModel to the palette index you want to be fully transparent.
int transparentIndex = ...;
IndexColorModel cm = new IndexColorModel(4, 16, palette, 0, false, transparentIndex);
// ...everything else as above
This will allow your sprites to have any shape you want.
If you want translucent pixels (Transparency.TRANSLUCENT), where pixels can be semi-transparent, you can also have that. You will then have to change the palette array to 16 * 4 entries, and include a sample for the alpha value as the 4th sample for each entry (quadruple). Then invoke the IndexColorModel constructor with the last parameter set to true (hasAlpha):
byte[] palette = new byte[16 * 4]; // 16 color palette with alpha (translucency)
// ...
IndexColorModel cm = new IndexColorModel(4, 16, palette, 0, true); // true for palette with alpha samples
// ...everything else as above
This will allow smoother gradients between the transparent and non-transparent parts of the sprites. But with only 16 colors in the palette, you won't have many entries available for transparency.
Note that it is possible to re-use the Rasters and IndexColorModels here, in all of the above examples, to save further memory for images using the same palette, or even images using the same image data with different palettes. There's one caveat though, that is the images sharing rasters will be "live views" of each other, so if you make any changes to one, you will change them all. But if your images are never changed, you could exploit this fact.
That said, the above really is a compromise between saving memory and having "reasonable" performance. If performance (ie. frames per second) is more important, just ignore the memory usage, and create BufferedImages that are compatible with your graphics card's/the OS's native pixel layout. You can do this, by using component.createCompatibleImage(...) (where component is a JComponent subclass) or gfxConfig.createCompatibleImage(...) (where gfxConfig is a GraphicsConfiguration obtained from the local GraphicsEnvironment).

OutOfMemoryError: Jave heap space when jtable saved as Image

Currently I am saving a jtable as jpeg using the below method, when the dimension of the jtable became 2590, 126181, java.lang.OutOfMemoryError: Java heap space exception occurs at "BufferedImage constructor", when the size of the table is small the image gets saved successfully.
public BufferedImage saveComponentAsJPEG(JTable table, String filename) {
Dimension size = table.getSize();
BufferedImage myImage =
new BufferedImage(size.width, size.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = myImage.createGraphics();
table.paint(g2);
return myImage;
}
How to save a jtable with bigger size in pdf or jpeg image?
Updated Info:
You asked how to "split the JTable into different small images":
As you go through my code below please read my comments, they help explain what is happening and will help you grasp a better understanding of how a JTable/JComponent can be painted to lots of small images. At the heart my code is similar to yours, but there are two key points:
1) Rather than create a single large BufferedImage, I create a single small image that is then used multiple times, therefore leaving a very small memory footprint.
2) With the single image, I use Graphics.translate() to paint a small part of the JTable each time.
The following code was tested with a large JComponents (2590 x 126181) and a tile size of 200x200, and the whole process did not exceed 60mb of memory:
//width = width of tile in pixels, for minimal memory usage try 200
//height = height of tile in pixels, for minimal memory usage try 200
//saveFileLocation = folder to save image tiles
//component = The JComponent to save as tiles
public static boolean saveComponentTiles(int width, int height, String saveFileLocation, JComponent component)
{
try
{
//Calculate tile sizes
int componentWidth = component.getWidth();
int componentHeight = component.getHeight();
int horizontalTiles = (int) Math.ceil((double)componentWidth / width); //use (double) so Math.ceil works correctly.
int verticalTiles = (int) Math.ceil((double)componentHeight / height); //use (double) so Math.ceil works correctly.
System.out.println("Tiles Required (H, W): "+horizontalTiles+", verticalTiles: "+verticalTiles);
//preset arguments
BufferedImage image;
//Loop through vertical and horizontal tiles
//Draw part of the component to the image
//Save image to file
for (int h = 0; h < verticalTiles; h++)
{
for (int w = 0; w < horizontalTiles; w++)
{
//check tile size, if area to paint is smaller than image then shrink image
int imageHeight = height;
int imageWidth = width;
if (h + 1 == verticalTiles)
{
imageHeight = componentHeight - (h * height);
}
if (w + 1 == horizontalTiles)
{
imageWidth = componentWidth - (w * width);
}
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
//translate image graphics so that only the correct part of the component is panted to the image
g.translate(-(w * width), -(h * height));
component.paint(g);
//In my example I am saving the image to file, however you could throw your PDF processing code here
//Files are named as "Image.[h].[w]"
//Example: Image 8 down and 2 accross would save as "Image.8.2.png"
ImageIO.write(image, "png", new File(saveFileLocation + "Image." + h +"."+ w + ".png"));
//tidy up
g.dispose();
}
}
return true;
}
catch (IOException ex)
{
return false;
}
}
Just call it like so:
boolean result = saveComponentTiles(200, 200, saveFileLocation, jTable1);
Also if you haven't done it already, you should only call the method from a different thread because it will hang your application when dealing with large components.
If you have not picked a PDF library yet, then I highly recommend looking at iText.
Original Post:
The process you are looking for is quite simple, however it may take some work.
You were on the right track thinking about parts, but as David
mentioned you shouldn't mess with the jTable, instead you will need a
to make use of the TiledImage class, or do something yourself with
RenderedImage and Rasters.
This sort of method basically uses HDD space instead of memory and
lets you create a large image in lots of smaller parts/tiles, then
when its done you can save it all to a single image file.
This answer may also help: https://stackoverflow.com/a/14069551/1270000

java Android - working with image scaling/cropping programmatically

Well, all this thing tortures me for long weeks, I set an Image 227 pixels high in scales it to 170 pixels even if I want it to be wrap_content whenever I do.
Ok. Here I take My Image which is 1950 pixels long (I put here a part of it so you can understand how it should look like).
First, I want to scale it back to 227 pixels high because that's how it was designed and how it should be
Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(),R.drawable.ver_bottom_panel_tiled_long);
int width = bitmapOrg.getWidth();
int height = bitmapOrg.getHeight();
int newWidth = 200; //this should be parent's whdth later
int newHeight = 227;
// calculate the scale
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0,
width, height, matrix, true);
BitmapDrawable dmpDrwbl=new BitmapDrawable(resizedBitmap);
verbottompanelprayer.setBackgroundDrawable(dmpDrwbl);
so... it's not a cropped image at all - no, it's 1950 pixels pressed into 200 pixels.
But I want just cut anything besides this 200 pixels or whatever width I'll set - crop it and not press all this long image into 200 pixels area.
Also, BitmapDrawable(Bitmap bitmap); and imageView.setBackgroundDrawable(drawable); are deprecated - how can I change that?
according to what i see, you create a bitmap of the new size (200x227) , so i'm not sure what you expected. you've even written in the comments that you scale and no word on cropping...
what you can do is :
if the API is at least 10 (gingerbread) , you can use BitmapRegionDecoder , using decodeRegion :
if the API is too old, you need to decode the large bitmap, and then crop it into a new bitmap, using Bitmap.createBitmap
something like this:
final Rect rect =...
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD_MR1)
{
BitmapRegionDecoder decoder=BitmapRegionDecoder.newInstance(imageFilePath, true);
croppedBitmap= decoder.decodeRegion(rect, null);
decoder.recycle();
}
else
{
Bitmap bitmapOriginal=BitmapFactory.decodeFile(imageFilePath, null);
croppedBitmap=Bitmap.createBitmap(bitmapOriginal,rect.left,rect.top,rect.width(),rect.height());
}

java image crop

I am aware of BufferedImage.getSubimage However, it cant deal with cropping images that are smaller than the cropping size throwing the exception:
java.awt.image.RasterFormatException: (y + height) is outside raster
I want to be able to crop either a PNG/JPG/GIF to a certain size however if the image is smaller than the cropping area centre itself on a white background. Is there a call to do this? Or do I need to create an image manually to centre the image on if so, how would I go about this?
Thanks
You cannot crop an image larger, only smaller. So, you start with the goal dimension,let's say 100x100. And your BufferedImage (bi), let's say 150x50.
Create a rectangle of your goal:
Rectangle goal = new Rectangle(100, 100);
Then intersect it with the dimensions of your image:
Rectangle clip = goal.intersection(new Rectangle(bi.getWidth(), bi.getHeight());
Now, clip corresponds to the portion of bi that will fit within your goal. In this case 100 x50.
Now get the subImage using the value of clip.
BufferedImage clippedImg = bi.subImage(clip,1, clip.y, clip.width, clip.height);
Create a new BufferedImage (bi2), the size of goal:
BufferedImage bi2 = new BufferedImage(goal.width, goal.height);
Fill it with white (or whatever bg color you choose):
Graphics2D big2 = bi2.getGraphics();
big2.setColor(Color.white);
big2.fillRect(0, 0, goal.width, goal.height);
and draw the clipped image onto it.
int x = goal.width - (clip.width / 2);
int y = goal.height - (clip.height / 2);
big2.drawImage(x, y, clippedImg, null);

Categories