How to create an animated Image from still frames? - java

Given a list of true-color full frames in BufferedImage and a list of frame durations, how can I create an Image losslessly, that when put on a JLabel, will animate?
From what I can find, I could create an ImageWriter wrapping a ByteArrayOutputStream, write IIOImage frames to it, then Toolkit.getDefaultToolkit().createImage the stream into a ToolkitImage.
There are two problems with this attempt.
ImageWriter can only be instantiated with one of the known image encoders, and there is none for a lossless true-color animated image format (e.g. MNG),
It encodes (compresses) the image, then decompresses it again, becoming an unnecessary performance hazard.
[Edit]
Some more concise constraints and requirements. Please don't come up with anything that bends these rules.
What I don't want:
Making an animation thread and painting/updating each frame of the animation myself,
Using any kind of 3rd party library,
Borrowing any external process, for example a web browser,
Display it in some kind of video player object or 3D-accelerated scene (OpenGL/etc),
Work directly with classes from the sun.* packages
What I do want:
Frame size can be as large as monitor size. Please don't worry about performance. I'll worry about that. You'll just worry about correctness.
Frames all have the same size,
an Image subclass. I should be able to draw the image like g.drawImage(ani, 0, 0, this) and it would animate, or wrap it in an ImageIcon and display it on a JLabel/JButton/etc and it would animate,
Each frame can each have a different delay, from 10ms up to a second,
Animation can loop or can end, and this is defined once per animation (just like GIF),
I can use anything packaged with Oracle Java 8 (e.g. JavaFX),
Whatever happens, it should integrate with SWING
Optional:
Frames can have transparency. If needed, I can opaquify my images beforehand as the animation will be shown on a known background (single color) anyway.
I don't care if I have to subclass Image myself and add an animation thread in there that will cooperate with the ImageObserver, or write my own InputStreamImageSource, but I don't know how.
If I can somehow display a JavaFX scene with some HTML and CSS code that animates my images, then that's fine too. BUT as long as it's all encapsulated in a single SWING-compatible object that I can pass around.

You're right that ImageIO isn't an option, as the only animated format for which support is guaranteed is GIF.
You say you don't want to make an animation thread, but what about a JavaFX Animation object, like a Timeline?
public JComponent createAnimationComponent(List<BufferedImage> images,
List<Long> durations) {
Objects.requireNonNull(images, "Image list cannot be null");
Objects.requireNonNull(durations, "Duration list cannot be null");
if (new ArrayList<Object>(images).contains(null)) {
throw new IllegalArgumentException("Null image not permitted");
}
if (new ArrayList<Object>(durations).contains(null)) {
throw new IllegalArgumentException("Null duration not permitted");
}
int count = images.size();
if (count != durations.size()) {
throw new IllegalArgumentException(
"Lists must have the same number of elements");
}
ImageView view = new ImageView();
ObjectProperty<Image> imageProperty = view.imageProperty();
Rectangle imageSize = new Rectangle();
KeyFrame[] frames = new KeyFrame[count];
long time = 0;
for (int i = 0; i < count; i++) {
Duration duration = Duration.millis(time);
time += durations.get(i);
BufferedImage bufImg = images.get(i);
imageSize.add(bufImg.getWidth(), bufImg.getHeight());
Image image = SwingFXUtils.toFXImage(bufImg, null);
KeyValue imageValue = new KeyValue(imageProperty, image,
Interpolator.DISCRETE);
frames[i] = new KeyFrame(duration, imageValue);
}
Timeline timeline = new Timeline(frames);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
JFXPanel panel = new JFXPanel();
panel.setScene(new Scene(new Group(view)));
panel.setPreferredSize(imageSize.getSize());
return panel;
}
(I don't know why it's necessary to set the JFXPanel's preferred size explicitly, but it is. Probably a bug.)
Note that, like all JavaFX code, it has to be run in the JavaFX Application Thread. If you're using it from a Swing application, you can do something like this:
public JComponent createAnimationComponentFromAWTThread(
final List<BufferedImage> images,
final List<Long> durations)
throws InterruptedException {
final JComponent[] componentHolder = { null };
Platform.runLater(new Runnable() {
#Override
public void run() {
synchronized (componentHolder) {
componentHolder[0] =
createAnimationComponent(images, durations);
componentHolder.notifyAll();
}
}
});
synchronized (componentHolder) {
while (componentHolder[0] == null) {
componentHolder.wait();
}
return componentHolder[0];
}
}
But that's still not quite enough. You first have to initialize JavaFX by calling Application.launch, either with an explicit method call, or implicitly by specifying your Application subclass as the main class.

something like
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(getImageForCurrentTime(), 3, 4, this);
}

Related

Why is the animation not showing and is slowing down the rest?

The animation isn't showing on the Pane background and it slows down the other animation already present. I pass on the Pane background as a parameter so I don't know maybe that is the cause but I have other methods doing that and it's not causing any problems.
public static void fireAnimation(Pane animatedBackground) {
AnimationTimer animationTimer = new AnimationTimer(){
#Override
public void handle (long now){
listFireParticles.addAll(addFireParticles(200,200));
for(Iterator<FireParticles> iteratorFirePart = listFireParticles.iterator(); iteratorFirePart.hasNext();){
FireParticles fireParticle = iteratorFirePart.next();
fireParticle.particlesUpdate();
if(!fireParticle.isAlive()){
iteratorFirePart.remove();
continue;
}
fireParticle.render(graphicsContext);
}
Canvas canvas = new Canvas (400, 400);
graphicsContext = canvas.getGraphicsContext2D();
animatedBackground.getChildren().add(canvas);
}
};
animationTimer.start();
System.out.println("ANIMATION");
}
You are creating a new canvas for each animation frame, rather than reusing an existing one.
You take a Pane and you add a new Canvas to it.
You do this an AnimationTimer handle method.
The timer method is called each pulse.
Be default, JavaFX generates a pulse sixty times a second.
In one second, your pane will contain sixty canvases.
In a minute there will be 3600 canvases.
In a day there will be five million one hundred and eighty four thousand canvases.
At some point before that, something is going to slow down and break.
FAQ
How would I make the canvas stop being created so many times? I can't figure it out.
Remove the lines which create a canvas and add it to the scene graph from the animation timer handle method.
Create the canvas and add it to the scene graph (e.g. a pane) only once, outside of the handle method.
Store a reference to the canvas and access the reference in the handle method.
You likely have other issues in code not shown, so don't expect a simple fix as outlined above to just make your application work as you expect.
Perhaps something roughly like this:
final int W = 200;
final in H = 200;
List<FireParticle> fireParticles = createFireParticles(W, H);
Canvas canvas = new Canvas (W, H);
animatedBackground.getChildren().add(canvas);
AnimationTimer animationTimer = new AnimationTimer() {
#Override
public void handle (long now){
GraphicsContext2D graphicsContext = canvas.getGraphicsContext2D();
graphicsContext.clearRect(0, 0, W, H);
if (fireParticles.isEmpty()) {
this.stop();
animatedBackground.getChildren().remove(canvas);
return;
}
for (Iterator<FireParticle> iteratorFirePart = fireParticles.iterator(); iteratorFirePart.hasNext();) {
FireParticle fireParticle = iteratorFirePart.next();
fireParticle.particlesUpdate();
if (!fireParticle.isAlive()){
iteratorFirePart.remove();
continue;
}
fireParticle.render(graphicsContext);
}
}
};
animationTimer.start();
Important: the above code is indicative only. I make no assertion it will exhibit the behaviour you wish. It is untested and won't work stand-alone, I didn't even try to compile it. Without the requested minimal example, that is the best that can be provided.

OpenGL and AWT/Swing User Interface

I am currently developing 3d viewer with OpenGL (LWJGL) for an embedded system.
Without going into much detail, there is an application written on Java/Swing that currently has fully implemented UI and logic on Java+Swing. But it was decided that instead of 2d top-down picture it'd be cool to have 3d view as well, and that's where I come in.
I will say that I have to use GLES 3.0 or Opengl 2.1, and I prefer GLES because it has more features compared to 2.1
I also have a standalone application implemented with GLFW windowing system (a default for LWJGL) that runs fine on the device - it doesn't lag and gives decent FPS. Again, when running in a GLFW window.
But now I need to attach it to JFrame preferably, and that's where my problems start. Basically, what I need is to have my 3D view render as a background, and then have Swing buttons/panels and additional windows (for example with options menu) display on top of it.
I've implemented a basic algorhythm of simply reading the FrameBuffer and drawing it on a canvas as a raster image. But that's SLOW. I get like 10 FPS with that.
private void render()
{
logic.Render(window); //GLFW window
window.update();
ByteBuffer nativeBuffer = BufferUtils.createByteBuffer(GlobalSettings.WINDOW_WIDTH*GlobalSettings.WINDOW_HEIGHT*4);
BufferedImage image = new BufferedImage(GlobalSettings.WINDOW_WIDTH,GlobalSettings.WINDOW_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
GLES30.glReadPixels(0, 0, GlobalSettings.WINDOW_WIDTH,GlobalSettings.WINDOW_HEIGHT, GLES20.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, nativeBuffer);
byte[] imgData = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
nativeBuffer.get(imgData);
Graphics g = canvas.getGraphics();
g.drawImage(image, 0,0, GlobalSettings.WINDOW_WIDTH,GlobalSettings.WINDOW_HEIGHT, null);
}
The other thing I've tried is using this library https://github.com/LWJGLX/lwjgl3-awt that seems to be recommended a lot. The problem is, it uses some OpenGL 3.1 functionality, and getting it to work AT ALL under 2.1 context was a huge chore. But as for GLES - I couldn't make it work at all, and I obviously can't have it create context for 2.1 and then use GLES functionality further on, so it basically breaks my entire rendering code.
Maybe I'm just not doing it right, but anyway - I couldn't make it work for me.
And that's pretty much where I stand. I thought I'd ask for help here. To me, at this point, it seems likely that I'll have to write the entire interface for OpenGL/GLFW entirely, and forgo Swing completely. Which is not ideal at all - I even wonder if we have such time at all.
Please, point me in the right direction if there are ways to make my thing work with AWT.
I will post an answer myself. It works well both on Linux desktop and on the embedded system, with good performance so far as I can see.
The idea is taking an EGL context and GLES, and setting AWT canvas surface as a direct target for the context. You don't need to read buffer and then paint it on the canvas.
Below is my Window class initialization.
private Canvas canvas;
private JAWTDrawingSurface ds;
public long display;
private long eglDisplay;
public long drawable;
private long surface;
public static final JAWT awt;
static {
awt = JAWT.calloc();
awt.version(JAWT_VERSION_1_4);
if (!JAWT_GetAWT(awt))
throw new AssertionError("GetAWT failed");
}
public void lock() throws AWTException {
int lock = JAWT_DrawingSurface_Lock(ds, ds.Lock());
if ((lock & JAWT_LOCK_ERROR) != 0)
throw new AWTException("JAWT_DrawingSurface_Lock() failed");
}
public void unlock() throws AWTException {
JAWT_DrawingSurface_Unlock(ds, ds.Unlock());
}
public void Init2()
{
frame = new JFrame("AWT test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.setPreferredSize(new Dimension(width, height));
canvas = new Canvas();
frame.add(canvas);
frame.pack();
frame.setVisible(true);
frame.transferFocus();
int error;
System.out.println("Window init2() started");
this.ds = JAWT_GetDrawingSurface(canvas, awt.GetDrawingSurface());
//JAWTDrawingSurface ds = JAWT_GetDrawingSurface(canvas, awt.GetDrawingSurface());
try
{
lock();
try
{
JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo());
JAWTX11DrawingSurfaceInfo dsiWin = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo());
int depth = dsiWin.depth();
this.display = dsiWin.display();
this.drawable = dsiWin.drawable();
System.out.printf("EGL Display %d, drawable: \n", display, drawable);
eglDisplay = eglGetDisplay(display);
EGLCapabilities egl;
try (MemoryStack stack = stackPush()) {
IntBuffer major = stack.mallocInt(1);
IntBuffer minor = stack.mallocInt(1);
if (!eglInitialize(eglDisplay, major, minor)) {
throw new IllegalStateException(String.format("Failed to initialize EGL [0x%X]", eglGetError()));
}
egl = EGL.createDisplayCapabilities(eglDisplay, major.get(0), minor.get(0));
}
System.out.println("EGL caps created");
IntBuffer attrib_list = BufferUtils.createIntBuffer(18);
attrib_list.put(EGL_CONFORMANT).put(EGL_OPENGL_ES2_BIT);
//attrib_list.put(EGL_ALPHA_MASK_SIZE).put(4);
//attrib_list.put(EGL_ALPHA_SIZE).put(4);
//attrib_list.put(EGL_RED_SIZE).put(5);
//attrib_list.put(EGL_GREEN_SIZE).put(6);
//attrib_list.put(EGL_BLUE_SIZE).put(5);
//attrib_list.put(EGL_DEPTH_SIZE).put(4);
//attrib_list.put(EGL_SURFACE_TYPE).put(EGL_WINDOW_BIT);
attrib_list.put(EGL_NONE);
attrib_list.flip();
PointerBuffer fbConfigs = BufferUtils.createPointerBuffer(1);
IntBuffer numConfigs = BufferUtils.createIntBuffer(1);
boolean test2 = eglChooseConfig(eglDisplay, attrib_list, fbConfigs,numConfigs);
if (fbConfigs == null || fbConfigs.capacity() == 0) {
// No framebuffer configurations supported!
System.out.println("No supported framebuffer configurations found");
}
long test = numConfigs.get(0);
IntBuffer context_attrib_list = BufferUtils.createIntBuffer(18);
context_attrib_list.put(EGL_CONTEXT_MAJOR_VERSION).put(3);
context_attrib_list.put(EGL_CONTEXT_MINOR_VERSION).put(0);
context_attrib_list.put(EGL_NONE);
context_attrib_list.flip();
long context = eglCreateContext(eglDisplay,fbConfigs.get(0),EGL_NO_CONTEXT,context_attrib_list);
error = eglGetError();
surface = eglCreateWindowSurface(eglDisplay,fbConfigs.get(0),drawable,(int[])null);
error = eglGetError();
eglMakeCurrent(eglDisplay,surface,surface,context);
error = eglGetError();
GLESCapabilities gles = GLES.createCapabilities();
System.out.println("CLES caps created");
}
finally
{
unlock();
}
}
catch (Exception e)
{
System.out.println("JAWT Failed");
}
// Render with OpenGL ES
glClearColor(0f,0f,0f,1f);
glfwSwapInterval(vSync);
glEnable(GL_DEPTH_TEST);
int[] range = new int[2];
int[] precision = new int[2];
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, range, precision);
System.out.println("Window context Initialized");
}
Note that attribute lists can be whatever you want or nothing at all. I actually ran into GLES problem though when I didn't specify 3.0 as version compatibility for the context - it tried to use OpenGl ES-CL 1.1 for some reason and failed. Anyway, simply specifying minor and major context version fixed it.
Also note that it's the initialization only. JFrame and Canvas are created elsewhere and Canvas then passed to the Window class constructor.
Another important thing is the lock() / unlock() functions. You will get lots of XCB errors and crashes without them - because other threads will try to update display while you're drawing, causing conflicts and XCB will crash.
Also, you need to lock and unlock when you swap buffers (i.e. when you actually draw the framebuffer.
public void update()
{
try
{
lock();
eglSwapBuffers(eglDisplay, surface);
unlock();
}
catch (Exception e)
{
System.out.println("Swap buffers failed");
}
}
Don't mind that my current exception handling is lacking - I am editing my answer as soon as I found the solution lest my previous version confuses people.
I would also like to give credit to lwjgl-awt project, for giving me ideas. It does not support EGL as is, so I had to modify it a bit, but I took parts from there, so credit where credit is due.
https://github.com/LWJGLX/lwjgl3-awt
Just for comparison sake, here is the GLFW version. It's the initialization of the same class, basically, but it simply does other things. Here, however, the window is created directly inside the method.
public void Init()
{
System.out.println("Window init() started");
GLFWErrorCallback.createPrint().set();
if (!glfwInit()) {
throw new IllegalStateException("Unable to initialize glfw");
}
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
// GLFW setup for EGL & OpenGL ES
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
windowHandle = glfwCreateWindow(width, height, title, NULL, NULL);
if (windowHandle == NULL) {
throw new RuntimeException("Failed to create the GLFW window");
}
System.out.printf("Window handle created %d\n", windowHandle);
SetCallbacks();
// EGL capabilities
displayHandle = glfwGetEGLDisplay();
System.out.printf("EGL DisplayHandle %d\n", displayHandle);
try (MemoryStack stack = stackPush()) {
IntBuffer major = stack.mallocInt(1);
IntBuffer minor = stack.mallocInt(1);
if (!eglInitialize(displayHandle, major, minor)) {
throw new IllegalStateException(String.format("Failed to initialize EGL [0x%X]", eglGetError()));
}
EGLCapabilities egl = EGL.createDisplayCapabilities(displayHandle, major.get(0), minor.get(0));
}
System.out.println("EGL caps created");
// OpenGL ES capabilities
glfwMakeContextCurrent(windowHandle);
System.out.printf("Current context: %d.%d rev %d, Client_Context: %d\n",glfwGetWindowAttrib(windowHandle,GLFW_CONTEXT_VERSION_MAJOR),
glfwGetWindowAttrib(windowHandle,GLFW_CONTEXT_VERSION_MINOR), glfwGetWindowAttrib(windowHandle,GLFW_CONTEXT_REVISION),
glfwGetWindowAttrib(windowHandle,GLFW_CLIENT_API));
GLESCapabilities gles = GLES.createCapabilities();
System.out.println("CLES caps created");
// Render with OpenGL ES
//glfwShowWindow(windowHandle);
glClearColor(0f,0f,0f,1f);
glfwSwapInterval(vSync);
glEnable(GL_DEPTH_TEST);
int[] range = new int[2];
int[] precision = new int[2];
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, range, precision);
System.out.printf("Current context: %d.%d\n",glfwGetWindowAttrib(windowHandle,GLFW_CONTEXT_VERSION_MAJOR),
glfwGetWindowAttrib(windowHandle,GLFW_CONTEXT_VERSION_MINOR));
}

Can't stop flickering on JPanel

I have class that creates a new thread.
`
public ScreenG(JPanel PanelR)
{
Panel = PanelR;
RenderImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
FPS = 25;
Hide = false;
(new Thread()
{
public void run()
{
while(true)
{
if(Hide == false)
{
Timer = System.currentTimeMillis() + (1000/FPS);
if(!DrawRendering)
{
Graphics g = Panel.getGraphics();
g.drawImage(RenderImage, 0, 0, null);
}
DrawRendering = false;
while(System.currentTimeMillis() <= Timer) try { Thread.sleep(1); } catch (InterruptedException e) {Thread.currentThread().interrupt();}
}
}
}
}).start();
}
public void draw(BufferedImage ImageR)
{
DrawRendering = true;
RenderImage = ImageR;
Graphics g = Panel.getGraphics();
g.drawImage(RenderImage, 0, 0, null);
}`
In my main I create a new instance of ScreenG. This will start a new thread that draws a bufferedImage onto a JPanel with a consistent FPS.
In the main I would then call draw with the image that I created. Sometimes it works but sometimes the image on the panel flickers. I try variations like the draw function taking over the drawing. Non of them work. I could only REDUCE the flickering.
Not possible by design. Swing does not synchronize to the bitmap raster DMA that's actually sending the screen data to your monitor, so it always possible that the screen buffer is read by the DMA while you're busy rendering to it (possible exception is Fullscreen mode).
To at least minimize flickering follow the recommended method of custom Swing painting: https://docs.oracle.com/javase/tutorial/uiswing/painting/
You can easily trigger periodic repaints on the EDT using a Swing timer, or SwingUtilities.invokeAndWait/invokeLater from another thread (whatever works best in your design).
The flickering can be because the rendering isn't fast enough from an update.
Now I do recommend you use Swings paintComponent (Graphics g) when rendering its components. That being said. To solve the flickering for you add a BufferStrategy in your JFrame
Without that code avaible I can only provide a general solution.
JFrame jframe = new JFrame ();
...
BufferStrategy bufferstrategy = jframe.getBufferStrategy ();
if (bufferstrategy == null) {
jframe.createBufferStrategy(3);
return;
}
g.dispose();
bufferstrategy.show();
To read more about BufferStrategy I recommend a read over at the documentation.
Small Note
There is no reason in your code to either store JPanel PanelR or BufferedImage ImageR. You can instead directly invoke methods directly on PanelR resp. ImageR.
Thank you for the answers. I read Oracle tutorial that you recommended and get my paintComponent() function working correctly on the main thread. To do that I am calling JPanel().repaint() from the draw() function. I will learn about using BufferStrategy next.

Android Saving/Displaying Drawn Images In View

I'm fairly new to the android platform and I overrode a CustomView's onDraw method with canvas.drawPath(path, paint) in order to produce drawings on the page.
However, I attempted to save this drawing to be used in another activity (final page) where multiple saved drawings can be displayed side by side, but I ran into frequent issues with no images appearing on my final page.
I was wondering how to handle this scenario. I read posts on SO about storing the drawing as a bitmap and reconstructing this later. So I used Convert view to bitmap on Android
public static Bitmap getBitmapFromView(View view) { ...
to extract a bitmap from my custom view containing the original drawing. Then, I created a canvas with the extracted bit map using
Bitmap map = getBitmapFromView(cView);
Canvas canvas = new Canvas(map);
and appended this canvas to a static list of Canvas objects.
In the activity used to display all drawings side by side, I had
RelativeLayout layout = (RelativeLayout) findViewById(R.id.activity_display);
for (int x=0; x < canvasList.size(); x++) {
Canvas currCanvas = canvasList.get(x)
View view = new View(this);
view.draw(currCanvas);
layout.addView(canvasList.get(x));
}
There is definitely something wrong with my approach, but I have failed to identify it.
So, have you tried displaying the bitmapRecieved in getBitmapFromView to verify that it works?
Then the next step would be to store it in a static class, and try to display it in another acitvity to ensure storing works as planned.
And on your last Code Snippet: your are creating a View, drawing onto its canvas and then adding the canvas to the layout? without further research to logical thing to do, seems to add the view to the layout or am i wrong?
Try using Bundle savedInstance. Instead of using canvasList, I will use a bitmap array:
#Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
state.putSerializable("starttime", startTime);
for(int i=0; i<bmp.size; i++){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp[i].compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
state.putByteArray("img"+i, byte[] value);
}
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
if ((bundle != null)
for(int i=0; bundle.getByteArray("img"+i) != null; i++) {
//Load byteArray back to bitmap array
}
}
I haven't tested the code, so there might be some issues. There is a lot of ways to achieve what you want, you can also save in a drawable or save as a picture at user's client.
Good luck, let me know if it works!

How do you Scale and display specific sections of an image using Java

So I took it upon myself to learn Java.
A decision I regret more with every crash of Elipse.
Fortunately I have actually managed to get this block of my self inflicted project to actually 'work' but obviously being self taught I am quite sure I have made a lot of errors in my layout and
In total, the program will create a JFrame then stick a JscrollPane inside that into which it inserts a a JPanel (wrapPage). It then loops through a process that generates an array of TMTile Objects which are extended JPanels containing the tile images which are drawn from a source folder of jpg images. Once that has finished it places that array as a grid using the GridBagLayout within the wrapPage Jpanel resulting in a nice little perfect maze.
That all works perfectly, but the big let down is that the size of the image used to create the tiles is dictating everything. I can't for the life of me figure out how to scale the image and efforts to find a suitable process have only got me methods of creating new image files or alternating between stretching and tiling images to fit a within their containing component or suggestions I just couldn't follow to save my life.
Fortunately. The image handling is part of the TMTile class file! This means I can show you the entire relevant bit of script;
The following are imported for use in this file
from java.awt: Color, GridBagConstraints, GridBagLayout, Insets
from javax.swing: ImageIcon, JLabel, JPanel
public class TMTile extends JPanel
{
private static final long serialVersionUID = 1L;
private int paths; // values 0 to 15, uses bitwise & | to set or check open paths
private JLabel tileWrap; // to contain the ImageIcon
private ImageIcon tileImg; // the image to be used
GridBagConstraints bag;
public TMTile( int inDir ) // called by maze constructor
{
paths = inDir;
this.setBackground( Color.RED ); // so I can tell if the image didn't load right
this.setLayout( new GridBagLayout() ); // GridBagLayout is probably overkill but it what I am most familiar with now.
bag = new GridBagConstraints();
bag.insets = new Insets( 0, 0, 0, 0 );
tileImg = tileImage( paths );
tileWrap = new JLabel( "", tileImg, JLabel.CENTER );
this.add( tileWrap, bag );
}
public void open( int inDir ) // called by maze constructor when tile value changes resulting from the perfect maze backtrack method
{
paths = paths | inDir;
tileImg = tileImage( paths );
tileWrap.setIcon( tileImg );
}
private ImageIcon tileImage( int paths ) // created to cut down on duplicate code and make updating easier
{
String inEnd;
if(paths < 10)
{
inEnd = "0"+ paths;
}
else
{
inEnd = ""+ paths;
}
ImageIcon tileImg = new ImageIcon( "imgs/MAZE_"+ inEnd +".jpg" );
System.out.println( "imgs/MAZE_"+ inEnd +".jpg" );
Image newimg = tileImg.getImage().getScaledInstance( 40, 40, java.awt.Image.SCALE_DEFAULT );
tileImg = new ImageIcon( newimg );
return tileImg;
}
public int getOpen()
{
return paths;
}
}
Thanks to nachokk and MadProgrammer I now once again have a working maze program and the maze tiles are scalable. That just leaves the final goal of doing away with individual tile .jpgs and switching to a single image file with all 16 stored within in.
What I would love to have is the ability to utilize a single large image file which is divided into 16 sections, 1 section for each tile value. I started out working toward this goal but had to abandon it fairly quickly as I couldn’t figure out how to only display the section of the image needed which would also need to be scaled in the way described above.
Since I am very much still learning Java advice on any alternatives is welcome but ideally I would love to know how to accomplish this as planned.

Categories

Resources