# Java Swing Anatomy Image Animation with Frame Time Limit
Here's a complete Java Swing application that demonstrates how to create an anatomy image animation with frame time limits:
```java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import javax.imageio.*;
public class AnatomyAnimation extends JFrame {
private AnimationPanel animationPanel;
private ArrayList<BufferedImage> anatomyLayers = new ArrayList<>();
private int currentLayerIndex = 0;
private Timer animationTimer;
private int frameDelay = 100; // milliseconds between frames
private int totalFrames = 10; // Number of frames per layer
private int currentFrame = 0;
private JSlider frameSlider, speedSlider;
private JButton loadButton, playButton, saveButton;
public AnatomyAnimation() {
setTitle("Anatomy Image Animation");
setSize(1000, 800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// Create animation panel
animationPanel = new AnimationPanel();
add(animationPanel, BorderLayout.CENTER);
// Create control panel
JPanel controlPanel = new JPanel(new GridLayout(1, 5));
// Frame limit control
frameSlider = new JSlider(1, 30, totalFrames);
frameSlider.setMajorTickSpacing(5);
frameSlider.setMinorTickSpacing(1);
frameSlider.setPaintTicks(true);
frameSlider.setPaintLabels(true);
frameSlider.addChangeListener(e -> {
totalFrames = frameSlider.getValue();
});
// Animation speed control
speedSlider = new JSlider(10, 500, frameDelay);
speedSlider.setInverted(true);
speedSlider.setMajorTickSpacing(100);
speedSlider.setMinorTickSpacing(50);
speedSlider.setPaintTicks(true);
speedSlider.setPaintLabels(true);
speedSlider.addChangeListener(e -> {
frameDelay = speedSlider.getValue();
if (animationTimer != null) {
animationTimer.setDelay(frameDelay);
}
});
// Load button
loadButton = new JButton("Load Layers");
loadButton.addActionListener(e -> loadAnatomyLayers());
// Play button
playButton = new JButton("Play");
playButton.addActionListener(e -> toggleAnimation());
// Save button
saveButton = new JButton("Save Animation");
saveButton.addActionListener(e -> saveAnimation());
controlPanel.add(new JLabel("Frames per layer:"));
controlPanel.add(frameSlider);
controlPanel.add(new JLabel("Speed:"));
controlPanel.add(speedSlider);
controlPanel.add(loadButton);
controlPanel.add(playButton);
controlPanel.add(saveButton);
add(controlPanel, BorderLayout.SOUTH);
// Initialize timer
animationTimer = new Timer(frameDelay, e -> {
currentFrame++;
if (currentFrame >= totalFrames) {
currentFrame = 0;
currentLayerIndex = (currentLayerIndex + 1) % anatomyLayers.size();
}
animationPanel.repaint();
});
}
private void loadAnatomyLayers() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setMultiSelectionEnabled(true);
fileChooser.setDialogTitle("Select Anatomy Layer Images");
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
anatomyLayers.clear();
for (File file : fileChooser.getSelectedFiles()) {
try {
BufferedImage img = ImageIO.read(file);
anatomyLayers.add(img);
} catch (IOException ex) {
JOptionPane.showMessageDialog(this, "Error loading image: " + ex.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
if (!anatomyLayers.isEmpty()) {
currentLayerIndex = 0;
currentFrame = 0;
animationPanel.repaint();
}
}
}
private void toggleAnimation() {
if (anatomyLayers.isEmpty()) {
JOptionPane.showMessageDialog(this, "Please load anatomy layers first");
return;
}
if (animationTimer.isRunning()) {
animationTimer.stop();
playButton.setText("Play");
} else {
animationTimer.start();
playButton.setText("Stop");
}
}
private void saveAnimation() {
if (anatomyLayers.isEmpty()) {
JOptionPane.showMessageDialog(this, "No animation to save");
return;
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Save Animation as GIF");
fileChooser.setSelectedFile(new File("anatomy_animation.gif"));
if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
File outputFile = fileChooser.getSelectedFile();
try {
// Create a GIF writer
ImageWriter writer = ImageIO.getImageWritersByFormatName("gif").next();
ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile);
writer.setOutput(ios);
// Configure GIF metadata
writer.prepareWriteSequence(null);
// Create each frame
for (int i = 0; i < anatomyLayers.size(); i++) {
BufferedImage frame = createFrame(i);
// Write frame with delay
IIOMetadataNode node = new IIOMetadataNode("javax_imageio_gif_image_1.0");
IIOMetadataNode extension = new IIOMetadataNode("GraphicControlExtension");
extension.setAttribute("delayTime", String.valueOf(frameDelay / 10)); // in 1/100ths of a second
extension.setAttribute("disposalMethod", "none");
extension.setAttribute("userInputFlag", "FALSE");
extension.setAttribute("transparentColorIndex", "0");
node.appendChild(extension);
IIOMetadata metadata = writer.getDefaultImageMetadata(new ImageTypeSpecifier(frame), null);
metadata.mergeTree("javax_imageio_gif_image_1.0", node);
writer.writeToSequence(new IIOImage(frame, null, metadata), null);
}
writer.endWriteSequence();
ios.close();
JOptionPane.showMessageDialog(this, "Animation saved successfully!");
} catch (IOException ex) {
JOptionPane.showMessageDialog(this, "Error saving animation: " + ex.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
}
private BufferedImage createFrame(int layerIndex) {
BufferedImage frame = new BufferedImage(
animationPanel.getWidth(), animationPanel.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = frame.createGraphics();
// Draw all layers up to the current one
for (int i = 0; i <= layerIndex; i++) {
BufferedImage layer = anatomyLayers.get(i);
// Calculate alpha (transparency) based on position in sequence
float alpha = (i == layerIndex) ? 1.0f : 0.7f;
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
// Draw layer centered
int x = (frame.getWidth() - layer.getWidth()) / 2;
int y = (frame.getHeight() - layer.getHeight()) / 2;
g2d.drawImage(layer, x, y, null);
}
g2d.dispose();
return frame;
}
class AnimationPanel extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// Draw background
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, getWidth(), getHeight());
if (!anatomyLayers.isEmpty()) {
// Calculate current alpha based on frame progress
float alpha = (float) currentFrame / totalFrames;
// Draw all layers up to current one
for (int i = 0; i <= currentLayerIndex; i++) {
BufferedImage layer = anatomyLayers.get(i);
// Set transparency (full for previous layers, animated for current)
float layerAlpha = (i < currentLayerIndex) ? 0.7f : alpha;
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, layerAlpha));
// Draw layer centered
int x = (getWidth() - layer.getWidth()) / 2;
int y = (getHeight() - layer.getHeight()) / 2;
g2d.drawImage(layer, x, y, null);
}
// Draw frame info
g2d.setColor(Color.BLACK);
g2d.drawString("Layer: " + (currentLayerIndex + 1) + "/" + anatomyLayers.size(), 20, 30);
g2d.drawString("Frame: " + currentFrame + "/" + totalFrames, 20, 50);
} else {
// Show instructions if no layers loaded
g2d.setColor(Color.BLACK);
g2d.drawString("Click 'Load Layers' to add anatomy images", getWidth() / 2 - 150, getHeight() / 2);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
AnatomyAnimation animation = new AnatomyAnimation();
animation.setVisible(true);
});
}
}
```
## Key Features of This Implementation:
1. **Layered Anatomy Visualization**:
- Load multiple anatomy layer images (e.g., skeletal, muscular, nervous systems)
- Smooth transitions between layers with alpha blending
2. **Frame Time Control**:
- Adjustable frames per layer (1-30)
- Configurable animation speed (10-500ms delay between frames)
- Real-time preview of animation
3. **Animation Export**:
- Save animation as GIF with proper frame timing
- Preserves transparency and layer ordering
4. **User Interface**:
- Clean visualization panel with centered images
- Control panel with intuitive sliders
- Status display showing current layer and frame
5. **Image Handling**:
- Supports standard image formats (PNG, JPG, etc.)
- Maintains aspect ratio of original images
- Handles different image sizes gracefully
## How to Enhance This Application:
1. **Add Layer Labeling**:
```java
// Create a class to hold both image and label
class AnatomyLayer {
BufferedImage image;
String label;
public AnatomyLayer(BufferedImage image, String label) {
this.image = image;
this.label = label;
}
}
// Then modify the drawing code to display labels
g2d.drawString(layer.label, x, y - 10);
```
2. **Add Zoom/Pan Functionality**:
```java
// Add these fields to AnimationPanel
private double scale = 1.0;
private Point2D.Double viewPosition = new Point2D.Double(0, 0);
// Modify drawing code to account for zoom/pan
AffineTransform tx = new AffineTransform();
tx.translate(viewPosition.x, viewPosition.y);
tx.scale(scale, scale);
g2d.drawImage(layer, tx, null);
// Add mouse listeners for panning and zooming
addMouseListener(new MouseAdapter() {
// Implement panning
});
addMouseWheelListener(e -> {
// Implement zooming
});
```
3. **Add Annotation Tools**:
```java
// Create annotation tools for marking up anatomy
private String currentTool = "pointer"; // or "text", "arrow", etc.
// Add mouse listeners to handle annotations
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (currentTool.equals("text")) {
// Add text annotation
} else if (currentTool.equals("arrow")) {
// Start drawing arrow
}
}
});
```
4. **Add Layer Reordering**:
```java
// Add buttons to move layers up/down in the stack
JButton upButton = new JButton("Move Up");
upButton.addActionListener(e -> {
if (currentLayerIndex > 0) {
Collections.swap(anatomyLayers, currentLayerIndex, currentLayerIndex - 1);
repaint();
}
});
```
5. **Add Cross-Section Views**:
```java
// Implement cross-section views by processing images
private BufferedImage createCrossSection(BufferedImage original, int slicePosition) {
// Create a cross-section view of the anatomy
BufferedImage crossSection = new BufferedImage(
original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
// Process pixels to show only the slice
for (int y = 0; y < original.getHeight(); y++) {
for (int x = 0; x < original.getWidth(); x++) {
if (Math.abs(y - slicePosition) < 2) { // Show thin slice
crossSection.setRGB(x, y, original.getRGB(x, y));
}
}
}
return crossSection;
}
```
This implementation provides a solid foundation for creating anatomy animations with frame time limits in Java Swing. The architecture is designed to be easily extensible with additional features like annotations, cross-sections, and more sophisticated layer management.