Related
This question already has answers here:
What causes a java.lang.ArrayIndexOutOfBoundsException and how do I prevent it?
(26 answers)
Closed 6 years ago.
Hello fellow hydrogen based life form. I learning how to make 3-Dimensional gaems from a top secret website (www.youtube.com) and I was learning from a very nice Youtuber. My project looks like this:
There are 3 classes: Main, Screen and Render
I get this exception :( :
Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 65600
at Render.Draw(Render.java:20)
at Screen.render(Screen.java:19)
at Main.render(Main.java:74)
at Main.run(Main.java:59)
at java.lang.Thread.run(Unknown Source)
I shall upload my code here too below this. Please help me become good at this.
Main class:
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
public class Main extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static final String TITLE = "Nexus Overload";
private Thread t;
private boolean Running = false;
#SuppressWarnings("unused")
private Render ren;
private Screen s;
private BufferedImage img;
private BufferStrategy bs;
private int[] pixels;
public Main() {
s = new Screen(WIDTH, HEIGHT);
img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
}
private void start() {
if (Running)
return;
Running = true;
t = new Thread(this);
t.start();
}
#SuppressWarnings("unused")
private void stop() {
if (!Running)
return;
Running = false;
try {
t.join();
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
public void run() {
while (Running) {
tick();
render();
}
}
private void tick() {
}
private void render() {
bs = this.getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
s.render();
for (int i = 0; i < WIDTH * HEIGHT; i++) {
pixels[i] = s.pixels[i];
}
Graphics g = bs.getDrawGraphics();
g.drawImage(img, WIDTH, HEIGHT, null);
g.dispose();
bs.show();
}
public static void main(String[] args) {
Main m = new Main();
JFrame frame = new JFrame();
frame.getContentPane().add(m);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(350, 100, WIDTH, HEIGHT);
frame.setTitle(TITLE);
frame.setVisible(true);
m.start();
}
}
Render class:
public class Render {
public final int width;
public final int height;
public final int[] pixels;
public Render(int width, int height) {
this.width = width;
this.height = height;
this.pixels = new int[width * height];
}
public void Draw (Render ren, int xOffset, int yOffset) {
for (int y = 0; y < ren.height; y++) {
int yPix = y + yOffset;
for (int x = 0; x < ren.width; x++) {
int xPix = x + xOffset;
pixels[xPix + yPix * width] = ren.pixels[xPix + yPix * width];
}
}
}
}
Screen class:
import java.util.Random;
public class Screen extends Render{
private Render ren;
public Screen(int width, int height) {
super(width, height);
Random r = new Random();
ren = new Render(256, 256);
for (int i = 0; i < 256 * 256; i++) {
ren.pixels[i] = r.nextInt();
}
}
public void render() {
Draw(ren, 0, 0);
}
}
For those who want image with eclipse debugging, here:
With variables tab selected:
Your exception is pointing us to the Draw method in the Render class (NB: Java conventions state that method names should be lower case so this method should actually be called draw(Render ren, int xOffset, int yOffset). I would first try and set the int y in the outer for loop to 1 to see if that helps, you may also been to do the same for int x in the inner for loop...
I'm currently working on a screen recorder application that uses JNA to capture the frames of the video. I want to insert a semi-transparent marker onto the captured hbitmap before adding the cursor to better highlight the location of the cursor in the video. After much searching I haven't been able to find an example of how to get the pixel data out of, say, a BufferedImage and write it manually to the object pointed to by hbitmap. Here is my best attempt:
GuiTest.java:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
public class GuiTest extends JFrame
{
private static final long serialVersionUID = 1L;
private JLabel lblNewLabel;
public GuiTest()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initGui();
}
private void initGui()
{
setPreferredSize(new Dimension(400, 400));
Rectangle area = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
BufferedImage bi = new BufferedImage(area.width, area.height, BufferedImage.TYPE_INT_ARGB);
int[] ia = new int[area.width * area.height];
bi.getRGB(0, 0, area.width, area.height, ia, 0, area.width);
MemoryImageSource mis = new MemoryImageSource(area.width, area.height, ia, 0, area.width);
mis.setAnimated(true);
mis.newPixels(captureScreen(area), ColorModel.getRGBdefault(), 0, area.width);
lblNewLabel = new JLabel("");
lblNewLabel.setIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(mis)));
getContentPane().add(lblNewLabel, BorderLayout.CENTER);
}
public int[] captureScreen(Rectangle recordArea)
{ // This code captures the desktop. I wish I could say I understood it.
Pointer handle = User32.INSTANCE.GetDesktopWindow();
Pointer hdcSrc = User32.INSTANCE.GetWindowDC(handle);
Pointer hdcDest = Gdi32.INSTANCE.CreateCompatibleDC(hdcSrc);
Pointer hBitmap = Gdi32.INSTANCE.CreateCompatibleBitmap(hdcSrc, recordArea.width, recordArea.height);
Pointer hOld = Gdi32.INSTANCE.SelectObject(hdcDest, hBitmap);
Gdi32.INSTANCE.BitBlt(hdcDest, 0, 0, recordArea.width, recordArea.height, hdcSrc, 0, 0, Gdi32.SOURCE_COPY | Gdi32.CAPTURE_BLT);
try
{ // Start broken code:
BufferedImage MOUSE_SHADE = ImageIO.read(GuiTest.class.getResource("mouse_shade48.png"));
Point cursorPosition = MouseInfo.getPointerInfo().getLocation();
int[] pixels = MOUSE_SHADE.getRGB(0, 0, 48, 48, new int[48 * 48], 0, 48);
Memory shadeMem = new Memory(recordArea.width * recordArea.height * 4);
shadeMem.write(0, copyToOffsetCentered(pixels, new Rectangle(48, 48),
new int[recordArea.width * recordArea.height], new Rectangle(recordArea.width, recordArea.height),
cursorPosition.x, cursorPosition.y), 0, recordArea.width * recordArea.height);
BitmapInfo shadebmi = new BitmapInfo(1);
shadebmi.bmiHeader.biWidth = recordArea.width;
shadebmi.bmiHeader.biHeight = -recordArea.height;
shadebmi.bmiHeader.biPlanes = 1;
shadebmi.bmiHeader.biBitCount = 32;
shadebmi.bmiHeader.biCompression = Gdi32.BI_RGB;
Pointer hdc = User32.INSTANCE.GetDC(null);
Pointer hdcDest2 = Gdi32.INSTANCE.CreateCompatibleDC(hdc);
Pointer hBitmap2 = Gdi32.INSTANCE.CreateCompatibleBitmap(hdc, recordArea.width, recordArea.height);
Gdi32.INSTANCE.SetDIBits(hdc, hBitmap2, 0, recordArea.height, shadeMem, shadebmi, Gdi32.DIB_RGB_COLORS);
Msimg32.INSTANCE.TransparentBlt(hdcDest, 0, 0, recordArea.width, recordArea.height, hdc, 0, 0, recordArea.width, recordArea.height, new BlendFunction());
// I have also tried Gdi32.BitBlt
}
catch (IOException e1)
{
e1.printStackTrace();
} // End broken code
try
{
CursorData cursorData = new CursorData();
cursorData.drawCursorToHandle(hdcDest);
cursorData.close();
}
catch (Exception e)
{
e.printStackTrace();
}
Gdi32.INSTANCE.SelectObject(hdcDest, hOld);
Gdi32.INSTANCE.DeleteDC(hdcDest);
int nBits = recordArea.width * recordArea.height * 4;
BitmapInfo bmi = new BitmapInfo(1);
bmi.bmiHeader.biWidth = recordArea.width;
bmi.bmiHeader.biHeight = -recordArea.height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = Gdi32.BI_RGB;
Memory colorBitsMem = new Memory(nBits);
Gdi32.INSTANCE.GetDIBits(hdcSrc, hBitmap, 0, recordArea.height, colorBitsMem, bmi, Gdi32.DIB_RGB_COLORS);
int[] colorBits = colorBitsMem.getIntArray(0, recordArea.width * recordArea.height);
User32.INSTANCE.ReleaseDC(handle, hdcSrc);
Gdi32.INSTANCE.DeleteObject(hBitmap);
return colorBits;
}
private int[] copyToOffsetCentered(int[] src, Rectangle srcDim, int[] dest, Rectangle destDim, int dx, int dy)
{
int startx = dx - srcDim.width / 2;
int endx = startx + srcDim.width;
int starty = dy - srcDim.height / 2;
int endy = starty + srcDim.height;
for (int x = Math.max(startx, 0); x < Math.min(endx, destDim.width); x++)
{
for (int y = Math.max(starty, 0); y < Math.min(endy, destDim.height); y++)
{
int xSrc = x - startx;
int ySrc = y - starty;
dest[y * destDim.width + x] = src[ySrc * srcDim.width + xSrc];
}
}
return dest;
}
/**
* Launch the application.
*/
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
try
{
GuiTest window = new GuiTest();
window.pack();
window.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
CursorData.java:
import java.awt.MouseInfo;
import java.awt.Point;
import com.sun.jna.Pointer;
public class CursorData
{
public boolean isVisible;
public Pointer iconHandle;
public Point position;
// private static final HBITMAP MOUSE_SHADE;
// static
// {
// BufferedImage bi = new BufferedImage(0, 0, 0);
// try
// {
// bi = ImageIO.read(Images.class.getResource("/icons/com/valsphere/mouse_shade48.png"));
// }
// catch (IOException e)
// {
// e.printStackTrace();
// }
// int[] pixels = bi.getRaster().getPixels(0, 0, 48, 48, new int[48 * 48]);
//
// }
public CursorData()
{
updateCursorData();
}
public void updateCursorData()
{
CursorInfo cursorInfo = new CursorInfo();
if (User32.INSTANCE.GetCursorInfo(cursorInfo))
{
isVisible = cursorInfo.flags.intValue() == User32.CURSOR_SHOWING;
if (isVisible)
{
iconHandle = User32.INSTANCE.CopyIcon(cursorInfo.hCursor.getPointer());
IconInfo iconInfo = new IconInfo();
if (User32.INSTANCE.GetIconInfo(iconHandle, iconInfo))
{
Point cursorPosition = MouseInfo.getPointerInfo().getLocation();
position = new Point(cursorPosition.x - iconInfo.xHotspot.intValue(), cursorPosition.y - iconInfo.yHotspot.intValue());
if (iconInfo.hbmMask != null)
{
Gdi32.INSTANCE.DeleteObject(iconInfo.hbmMask.getPointer());
}
if (iconInfo.hbmColor != null)
{
Gdi32.INSTANCE.DeleteObject(iconInfo.hbmColor.getPointer());
}
}
}
}
}
public void drawCursorToHandle(Pointer hdcDest)
{
drawCursorToHandle(hdcDest, new Point());
}
public void drawCursorToHandle(Pointer hdcDest, Point cursorOffset)
{
if (iconHandle != null)
{
Point drawPosition = new Point(position.x - cursorOffset.x, position.y - cursorOffset.y);
User32.INSTANCE.DrawIconEx(hdcDest, drawPosition.x, drawPosition.y, iconHandle, 0, 0, 0, null, User32.DI_NORMAL);
}
}
public void close() throws Exception
{
if (iconHandle != null)
{
User32.INSTANCE.DestroyIcon(iconHandle);
iconHandle = null;
}
}
}
mouse_shade48.png:
I have verified that my int[] is being created and populated correctly, I just don't know how to get that data into an hbitmap and properly alpha blend it with the captured screen hbitmap that I already have. What I'm currently seeing is that the image is showing up as though the broken code is being skipped. Any help is much appreciated.
Also, I realize that the code above is missing the Msimg32, Gdi32, and User32 JNA interfaces I'm using, so if you need them I can create a gist and put all the pertinent code there.
I'm making an rpg with a custom pixel engine, and when I try to run it I just get a NullPointerException and I know why, but when I try to initialize that variable I get another one in a different location and I fix that and now it won't run at all and I still get a NullPointerException. This is the class that gets an error, and the console tells me that the error is on the line that says screen.render();
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import sprites.SpriteSheetLoader;
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static final int HEIGHT = 120;
public static final int WIDTH = 120;
public static final int SCALE = 3;
public static final String NAME = "TypicalRPG";
public SpriteSheetLoader loader;
private BufferedImage img = new BufferedImage(WIDTH * SCALE, HEIGHT * SCALE, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
private boolean running = false;
private Screen screen = new Screen(WIDTH, HEIGHT, loader);
Random random = new Random();
public void start(){
running = true;
new Thread(this).start();
}
public static void main(String args[]){
Game game = new Game();
game.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
game.setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
JFrame jf = new JFrame(NAME);
jf.add(game);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
jf.setResizable(true);
game.start();
}
public void run(){
while(running){
tick();
render();
try{
Thread.sleep(5);
}
catch(InterruptedException e){ e.printStackTrace(); }
}
}
public void stop(){ running = false; }
public void tick(){
screen.render(0, 0, 0, 16, 16);
}
public void render(){
BufferStrategy bs = getBufferStrategy();
if(bs == null){
createBufferStrategy(3);
requestFocus();
return;
}
for(int i = 0; i < screen.pixels.length; i++){
pixels[i] = screen.pixels[i];
}
Graphics g = bs.getDrawGraphics();
g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
g.dispose();
bs.show();
}
public void init(){
BufferedImage sheet = null;
try {
sheet = ImageIO.read(Game.class.getResourceAsStream("res/tiles.png"));
}
catch (IOException e) {
e.printStackTrace();
}
loader = new SpriteSheetLoader(sheet);
screen = new Screen(WIDTH, HEIGHT, loader);
}
}
and this is the class that has render():
import sprites.SpriteSheetLoader;
public class Screen{
public int[] pixels;
private SpriteSheetLoader loader;
private int w, h;
int xoff = 0, yoff = 0;
public Screen(int w, int h, SpriteSheetLoader loader){
this.loader = loader;
this.w = w;
this.h = h;
pixels = new int[w * h];
}
public void render(int xpos, int ypos, int tile, int width, int height){
loader.grabTile(tile, width, height);
xpos -= xoff;
ypos -= yoff;
for(int y = 0; y < height; y++){
if(ypos + y < 0 || ypos + y >= h) continue;
for(int x = 0; x < width; x++){
if(xpos + x < 0 || xpos + x >= w) continue;
int col = loader.pixels[x + (y * height)];
if(col != -65281) pixels[(x + xpos) + (y + ypos)] = col;
}
}
}
}
Seems like you never initiate the loader in your first class.
You define your variables like this:
public SpriteSheetLoader loader;
private Screen screen = new Screen(WIDTH, HEIGHT, loader);
Hence, you pass a null-object to your Screen. And from there you try to call a method on that null-object:
loader.grabTile(tile, width, height);
You have only declared loader, but never initiated it.
public SpriteSheetLoader loader;
i am stuck on how to go ahead on creating a simple game for my project, it is simply a puzzle game where i have a image on the background and boxes will be covering the image, the boxes are to be matched and when matched they will remove themselves to reveal part of the image, currently what i have done is to draw the grids but how can i make them stacked against each other? do i make each generated grid a imageitem?
The canvas of generating the blocks
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.Sprite;
public class blockcanvas extends Canvas
{
private Image pokeballImage;
private int screenW,screenH,imageW,imageH;
public blockcanvas()
{
//get screen size
screenW = getWidth();
screenH = getHeight();
try {
pokeballImage = Image.createImage("/pokeball.png");
imageW = pokeballImage.getWidth();
imageH = pokeballImage.getHeight();
} catch (IOException e) {
e.printStackTrace();
}
}
protected void paint(Graphics g) {
g.setColor(255,255,255);
g.fillRect(0,0,getWidth(),getHeight());
drawPokeBall(g);
}
public void drawPokeBall(Graphics g)
{
int maxNumberOfBlockX = screenW / imageW;
int maxNumberOfBlockY = screenH / imageH;
Sprite pokeball = new Sprite(pokeballImage);
pokeball.defineCollisionRectangle(0,0,20,20);
pokeball.defineReferencePixel(imageW/2,imageH/2);
for (int i = 0; i <maxNumberOfBlockX; i++)
{
pokeball.setRefPixelPosition(i*20,0);
pokeball.paint(g);
}
for (int i = 0; i <maxNumberOfBlockY; i++)
{
pokeball.setRefPixelPosition(0,i*20);
pokeball.paint(g);
}
System.out.println(maxNumberOfBlockX);
}
}
The canvas for the image to be covered by blocks
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class gameCanvas extends Canvas
{
private Image image;
private int screenW,screenH;
blockcanvas block;
public gameCanvas()
{
block = new blockcanvas();
//get screen size
screenW = getWidth()/2;
screenH = getHeight()/2;
try {
image = Image.createImage("/gby001.gif");
} catch (IOException e) {
e.printStackTrace();
}
}
protected void paint(Graphics g) {
g.setColor(255,255,255);
g.fillRect(0,0,getWidth(),getHeight());
g.drawImage(resizeImage(image), 10, 10, Graphics.TOP | Graphics.LEFT);
block.drawPokeBall(g);
}
private Image resizeImage(Image src) {
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
Image tmp = Image.createImage(screenW, srcHeight);
Graphics g = tmp.getGraphics();
int ratio = (srcWidth << 16) / screenW;
int pos = ratio/2;
//Horizontal Resize
for (int x = 0; x < screenW; x++) {
g.setClip(x, 0, 1, srcHeight);
g.drawImage(src, x - (pos >> 16), 0, Graphics.LEFT | Graphics.TOP);
pos += ratio;
}
Image resizedImage = Image.createImage(screenW, screenH);
g = resizedImage.getGraphics();
ratio = (srcHeight << 16) / screenH;
pos = ratio/2;
//Vertical resize
for (int y = 0; y < screenH; y++) {
g.setClip(0, y, screenW, 1);
g.drawImage(tmp, 0, y - (pos >> 16), Graphics.LEFT | Graphics.TOP);
pos += ratio;
}
return resizedImage;
}//resize image
}
The main app
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
import javax.microedition.lcdui.Canvas;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
/**
* #author Lotus
*/
public class game extends MIDlet implements CommandListener{
//Variables Declartion
private Display theDisp;
private Command cmdExit,cmdGuess,cmdReturn;
private gameCanvas canvas1;
private blockcanvas canvas2;
private Image gameImage;
public game()
{
theDisp = Display.getDisplay(this);
cmdExit = new Command("Exit",Command.EXIT,1);
cmdGuess = new Command("Guess",Command.SCREEN,2);
cmdReturn = new Command("Back",Command.BACK,2);
canvas1 = new gameCanvas();
canvas2 = new blockcanvas();
canvas1.addCommand(cmdExit);
canvas1.addCommand(cmdGuess);
canvas1.setCommandListener(this);
}
public void startApp() {
theDisp.setCurrent(canvas1);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable d) {
if(c == cmdExit)
{
destroyApp(true);
notifyDestroyed();
}
else if(c== cmdGuess)
{
}
}
}
You may want to change
g.fillRect(i, i, 20, 20);
to something like
g.fillRect(i*20, i*20, 20, 20);
I'm trying to determine how much heap any given TYPE_INT_ARGB BufferedImage will use so that, for a program which is doing some image processing, I can set a reasonable max heap based on the size of image we feed it.
I wrote the following program as a test, which I then used to determine the least maximum heap under which it would run without an OutOfMemoryError:
import java.awt.image.BufferedImage;
public class Test {
public static void main(String[] args) {
final int w = Integer.parseInt(args[0]);
final int h = Integer.parseInt(args[1]);
final BufferedImage img =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
System.out.println((4*w*h) >> 20);
}
}
(The printed value is the expected size of the int[] in which the BufferedImage's pixel data is stored.) What I expected to find was that the required max heap is something like x + c, where x is the size of the data array and c is a constant consisting of the sizes of the classes which are loaded, the BufferedImage object, etc. This is what I found instead (all values are in MB):
4*w*h min max heap
----- ------------
5 -
10 15
20 31
40 61
80 121
160 241
1.5x is a good fit for the observations. (Note that I found no minimum for the 5MB image.) I don't understand what I'm seeing. What are these extra bytes?
There seem to be a bug in Oracle's VM introduced somewhere between 1.6.0_16 and 1.6.0_20. You can even reduce the problem to allocating an int array, as the problem is not only related to BufferedImage.
With 1.6.0_16, I need at least 413 MB heap to allocate an int array with 100,000,000 elements, which seem reasonable. With 1.6.0_20, the same operation requires at least 573 MB heap space, although only appr 400,000,000 bytes are actually used after allocating the array.
On further investigation, the problem appears to be that the Old Generation in the heap is not able to expand sufficiently to accommodate the image's data array, despite that there is enough free memory in the heap at large.
For further details about how to expand the Old Generation, see this question.
The problem is that the BufferedImage object stores the image in the memory in uncompresed format. There is an effective solution for this: you can store the image on the hard drive and you don't have to worry about the heap size or the physical memory limit. It can store maximum 2,147,483,647 pixels (or 46,340 x 46,340 pixels). BigBufferedImage solves this problem.
Create az empty a BigBufferedImage:
BigBufferedImage image = BigBufferedImage.create(
tempDir, width, height, type);
Load an existent image to BigBufferedImage:
BigBufferedImage image = BigBufferedImage.create(
imagePath, tempDir, type);
Render the part of the image:
part = image.getSubimage(x, y, width, height);
The implementation of BigBufferedImage:
package com.pulispace.mc.ui.panorama.util;
/*
* This class is part of MCFS (Mission Control - Flight Software) a development
* of Team Puli Space, official Google Lunar XPRIZE contestant.
* This class is released under Creative Commons CC0.
* #author Zsolt Pocze, Dimitry Polivaev
* Please like us on facebook, and/or join our Small Step Club.
* http://www.pulispace.com
* https://www.facebook.com/pulispace
* http://nyomdmegteis.hu/en/
*/
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import sun.nio.ch.DirectBuffer;
public class BigBufferedImage extends BufferedImage {
private static final String TMP_DIR = System.getProperty("java.io.tmpdir");
public static final int MAX_PIXELS_IN_MEMORY = 1024 * 1024;
public static BufferedImage create(int width, int height, int imageType) {
if (width * height > MAX_PIXELS_IN_MEMORY) {
try {
final File tempDir = new File(TMP_DIR);
return createBigBufferedImage(tempDir, width, height, imageType);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
return new BufferedImage(width, height, imageType);
}
}
public static BufferedImage create(File inputFile, int imageType) throws IOException {
try (ImageInputStream stream = ImageIO.createImageInputStream(inputFile);) {
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if (readers.hasNext()) {
try {
ImageReader reader = readers.next();
reader.setInput(stream, true, true);
int width = reader.getWidth(reader.getMinIndex());
int height = reader.getHeight(reader.getMinIndex());
BufferedImage image = create(width, height, imageType);
int cores = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
int block = Math.min(MAX_PIXELS_IN_MEMORY / cores / width, (int) (Math.ceil(height / (double) cores)));
ExecutorService generalExecutor = Executors.newFixedThreadPool(cores);
List<Callable<ImagePartLoader>> partLoaders = new ArrayList<>();
for (int y = 0; y < height; y += block) {
partLoaders.add(new ImagePartLoader(
y, width, Math.min(block, height - y), inputFile, image));
}
generalExecutor.invokeAll(partLoaders);
generalExecutor.shutdown();
return image;
} catch (InterruptedException ex) {
Logger.getLogger(BigBufferedImage.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return null;
}
private static BufferedImage createBigBufferedImage(File tempDir, int width, int height, int imageType)
throws FileNotFoundException, IOException {
FileDataBuffer buffer = new FileDataBuffer(tempDir, width * height, 4);
ColorModel colorModel = null;
BandedSampleModel sampleModel = null;
switch (imageType) {
case TYPE_INT_RGB:
colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[]{8, 8, 8, 0},
false,
false,
ComponentColorModel.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, 3);
break;
case TYPE_INT_ARGB:
colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[]{8, 8, 8, 8},
true,
false,
ComponentColorModel.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, 4);
break;
default:
throw new IllegalArgumentException("Unsupported image type: " + imageType);
}
SimpleRaster raster = new SimpleRaster(sampleModel, buffer, new Point(0, 0));
BigBufferedImage image = new BigBufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
return image;
}
private static class ImagePartLoader implements Callable<ImagePartLoader> {
private final int y;
private final BufferedImage image;
private final Rectangle region;
private final File file;
public ImagePartLoader(int y, int width, int height, File file, BufferedImage image) {
this.y = y;
this.image = image;
this.file = file;
region = new Rectangle(0, y, width, height);
}
#Override
public ImagePartLoader call() throws Exception {
Thread.currentThread().setPriority((Thread.MIN_PRIORITY + Thread.NORM_PRIORITY) / 2);
try (ImageInputStream stream = ImageIO.createImageInputStream(file);) {
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.setInput(stream, true, true);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(region);
BufferedImage part = reader.read(0, param);
Raster source = part.getRaster();
WritableRaster target = image.getRaster();
target.setRect(0, y, source);
}
}
return ImagePartLoader.this;
}
}
private BigBufferedImage(ColorModel cm, SimpleRaster raster, boolean isRasterPremultiplied, Hashtable<?, ?> properties) {
super(cm, raster, isRasterPremultiplied, properties);
}
public void dispose() {
((SimpleRaster) getRaster()).dispose();
}
public static void dispose(RenderedImage image) {
if (image instanceof BigBufferedImage) {
((BigBufferedImage) image).dispose();
}
}
private static class SimpleRaster extends WritableRaster {
public SimpleRaster(SampleModel sampleModel, FileDataBuffer dataBuffer, Point origin) {
super(sampleModel, dataBuffer, origin);
}
public void dispose() {
((FileDataBuffer) getDataBuffer()).dispose();
}
}
private static final class FileDataBufferDeleterHook extends Thread {
static {
Runtime.getRuntime().addShutdownHook(new FileDataBufferDeleterHook());
}
private static final HashSet<FileDataBuffer> undisposedBuffers = new HashSet<>();
#Override
public void run() {
final FileDataBuffer[] buffers = undisposedBuffers.toArray(new FileDataBuffer[0]);
for (FileDataBuffer b : buffers) {
b.disposeNow();
}
}
}
private static class FileDataBuffer extends DataBuffer {
private final String id = "buffer-" + System.currentTimeMillis() + "-" + ((int) (Math.random() * 1000));
private File dir;
private String path;
private File[] files;
private RandomAccessFile[] accessFiles;
private MappedByteBuffer[] buffer;
public FileDataBuffer(File dir, int size) throws FileNotFoundException, IOException {
super(TYPE_BYTE, size);
this.dir = dir;
init();
}
public FileDataBuffer(File dir, int size, int numBanks) throws FileNotFoundException, IOException {
super(TYPE_BYTE, size, numBanks);
this.dir = dir;
init();
}
private void init() throws FileNotFoundException, IOException {
FileDataBufferDeleterHook.undisposedBuffers.add(this);
if (dir == null) {
dir = new File(".");
}
if (!dir.exists()) {
throw new RuntimeException("FileDataBuffer constructor parameter dir does not exist: " + dir);
}
if (!dir.isDirectory()) {
throw new RuntimeException("FileDataBuffer constructor parameter dir is not a directory: " + dir);
}
path = dir.getPath() + "/" + id;
File subDir = new File(path);
subDir.mkdir();
buffer = new MappedByteBuffer[banks];
accessFiles = new RandomAccessFile[banks];
files = new File[banks];
for (int i = 0; i < banks; i++) {
File file = files[i] = new File(path + "/bank" + i + ".dat");
final RandomAccessFile randomAccessFile = accessFiles[i] = new RandomAccessFile(file, "rw");
buffer[i] = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, getSize());
}
}
#Override
public int getElem(int bank, int i) {
return buffer[bank].get(i) & 0xff;
}
#Override
public void setElem(int bank, int i, int val) {
buffer[bank].put(i, (byte) val);
}
#Override
protected void finalize() throws Throwable {
dispose();
}
private void disposeNow() {
final MappedByteBuffer[] disposedBuffer = this.buffer;
this.buffer = null;
disposeNow(disposedBuffer);
}
public void dispose() {
final MappedByteBuffer[] disposedBuffer = this.buffer;
this.buffer = null;
new Thread() {
#Override
public void run() {
disposeNow(disposedBuffer);
}
}.start();
}
private void disposeNow(final MappedByteBuffer[] disposedBuffer) {
FileDataBufferDeleterHook.undisposedBuffers.remove(this);
if (disposedBuffer != null) {
for (MappedByteBuffer b : disposedBuffer) {
((DirectBuffer) b).cleaner().clean();
}
}
if (accessFiles != null) {
for (RandomAccessFile file : accessFiles) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
accessFiles = null;
}
if (files != null) {
for (File file : files) {
file.delete();
}
files = null;
}
if (path != null) {
new File(path).delete();
path = null;
}
}
}
}