Java FX MVC multiple views - java

I am using Java FX (required for a class project), and I am struggling to format my MVC so that I can load multiple views onto the stage (various scenes). I have a Controller as seen below that has 2 instances of View1.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.animation.AnimationTimer;
public class Controller extends Application {
public int page = 0;
private View1 view2;
private View1 view1;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage theStage) {
view1 = new View1(theStage);
view2 = new View1(theStage);
new AnimationTimer() {
public void handle(long currentNanoTime)
{
switch (page){
case 0:
view1.update();
break;
case 1:
view2.update();
break;
default:
page = 0;
}
try {
Thread.sleep(33);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
theStage.show();
}
}
The problem occurs at the line view2 = new View1(theStage);. Without this line, the output is a large canvas with a background image. With that line, however, there is no background image it is just a blank canvas. Below is my View I simplified it as much as possible, just one background image to determine if it's loaded correctly. (I left the imports out in order to keep it concise, if needed I can add them back in)
public class View1 {
Stage theStage;
Group root;
Scene theScene;
Canvas canvas;
// value of the height and width of screen
int canvasWidth = 1000;
int canvasHeight = 800;
GraphicsContext gc;
Image background;
//View1 constructor initialize the starting position for the image
//Called in controller
public View1(Stage theStage) {
this.theStage = theStage;
this.theStage.setTitle("Estuary Game");
root = new Group();
theScene = new Scene(root);
this.theStage.setScene(theScene);
canvas = new Canvas(canvasWidth, canvasHeight);
root.getChildren().add(canvas);
gc = canvas.getGraphicsContext2D();
background = createImage("assets/mini-game-1.png");
}
//Read image from file and return
private Image createImage(String image_file) {
Image img = new Image(image_file);
return img;
}
//method used to repaint on the image and called in controller
public void update() {
// draw background and sharkMove such like current
gc.drawImage(background, 0, 0);
}
}
I am not sure I am tackling the multiple views correctly, my intentions are to just have multiple scenes but I wasn't sure how to structure it.

Try the following structure (the following code is a one-file mre. Copy-paste it into SwitchScene.java and run) :
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
public class SwitchScene extends Application {
public int page = 0;
#Override
public void start(Stage theStage) {
new Controller(theStage);
theStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class Controller {
private final View1 view1, view2;
private final Stage stage;
private static final String[] images = {
"https://findicons.com/files/icons/345/summer/128/cake.png",
"http://icons.iconarchive.com/icons/atyourservice/service-categories/128/Sweets-icon.png"
};
Controller(Stage stage) {
this.stage = stage;
view1 = new View1(images[0]);
view2 = new View1(images[1]);
swapScenes();
}
void swapScenes(){
new AnimationTimer() {
int page = 2;
#Override
public void handle(long currentNanoTime){
switch (page){
case 2:
page = 1;
stage.setScene(new Scene(new Group(view1)));
break;
case 1:
page = 2;
stage.setScene(new Scene(new Group(view2)));
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
class View1 extends ImageView {
public View1(String imagePath) {
super(imagePath);
}
}
Edit: a version of View1 using canvas:
class View1 extends Group {
public View1(String imagePath) {
Image img = new Image(imagePath);
Canvas canvas = new Canvas(img.getWidth(), img.getHeight());
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
getChildren().add(canvas);
}
}

Related

Accessing elements inside a GraphicsContext in JavaFX canvas

I'm implementing a simple hockey game following an MVC pattern. I'm having trouble refreshing the player's position, which I have created inside a canvas using the GraphicsContext.drawImage() method. I'm inside an AnimationTimer anonymous class, inside the handle method.
The positions and boundaries are all reflected to the backend,so I don't really need to do any particular logic here, I just want to refresh the player's position on every frame, based on its position calculated inside the model, but I can't access the image I've drawn before in any way. Here's the code:
DefaultController controller;
#FXML
public void initialize() {
controller = new DefaultController(800, 400);
double playerX = controller.getField().getPlayer().getX();
double playerY = controller.getField().getPlayer().getY();
double enemyX = controller.getField().getEnemy().getX();
double enemyY = controller.getField().getEnemy().getY();
context.drawImage(new Image("player.png"),playerX, playerY);
context.drawImage(new Image("enemy.png"),enemyX, enemyY);
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
pane.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (key) -> {
if (key.getCode() == KeyCode.UP) {
controller.getField().getPlayer().setLocation(playerX,playerY-0.1)
// how do I update the player image position inside the canvas?
}
});
}
};
timer.start();
}
You are following a completely wrong approach. You cannot update anything in a Canvas once it is drawn. You can just erase it and redraw it again. For what you are trying to do the scene graph is much better suited.
The following is an mre demonstrating how to move an image on a canvas using the UP key:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.*;
import javafx.scene.image.Image;
import javafx.scene.input.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
private static final int SIZE = 200;
private static final String FISH_IMAGE = "https://www.shareicon.net/data/128x128/2015/03/28/14104_animal_256x256.png";
private Image image;
private Canvas canvas;
private final double xPos = 250;
private double yPos = 150;
private static final double MOVE = 0.8;
#Override
public void start(Stage primaryStage) {
image = new Image(FISH_IMAGE,SIZE,SIZE,false,false);
canvas = new Canvas(SIZE*3, SIZE*3);
draw();
Scene scene = new Scene(new StackPane(canvas));
scene.addEventHandler(KeyEvent.KEY_PRESSED, (key) -> animate(key));
primaryStage.setScene(scene);
primaryStage.show();
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
//to avoid multiple handlers do not add handler here
draw();
}
};
timer.start();
}
private void animate(KeyEvent key){
if(key.getCode()== KeyCode.UP) {
yPos -= MOVE;
}
//todo add down, left , right
//for low rate animation, like response to key-press, invoke draw() here instead of using AnimationTimer
}
private void draw(){
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());//clear previous
gc.drawImage(image, xPos, yPos);
}
public static void main(String[] args) {
launch(args);
}
}

Lack of run() method?

So I am reading 'Thinking in java' book and I came across ColorBox program, changing color of boxes at random. However I noticed that there is a problem in running this code as if there wasn't run() method.
I highlighted it below with "/HERE/" :)
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.*;
import java.util.*;
import static sun.misc.PostVMInitHook.run;
class CBox extends JPanel implements Runnable {
private int pause;
private static Random rand = new Random();
private Color color = new Color(0);
public void paintComponent(Graphics g) {
g.setColor(color);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
public CBox(int pause) { this.pause = pause; }
public void run() {
try {
while(!Thread.interrupted()) {
color = new Color(rand.nextInt(0xFFFFFF));
repaint(); // Asynchronously request a paint()
TimeUnit.MILLISECONDS.sleep(pause);
}
} catch(InterruptedException e) {
// Acceptable way to exit
}
}
}
public class ColorBoxes extends JFrame {
private int grid = 12;
private int pause = 50;
private static ExecutorService exec =
Executors.newCachedThreadPool();
public void setUp() {
setLayout(new GridLayout(grid, grid));
for(int i = 0; i < grid * grid; i++) {
CBox cb = new CBox(pause);
add(cb);
exec.execute(cb);
}
}
public static void main(String[] args) {
ColorBoxes boxes = new ColorBoxes();
if(args.length > 0)
boxes.grid = new Integer(args[0]);
if(args.length > 1)
boxes.pause = new Integer(args[1]);
boxes.setUp();
/**HERE**/ run(boxes, 500,400);
}
}
I did change nothing, it is the exact code from book. They wanted to improve previous version including JApplet and there was method something like this:
public static void run(JApplet applet, int width, int height) {
....
}
The devil is in the details
import static sun.misc.PostVMInitHook.run;
That will allow you to call the run() as it's given.
It's not very nice though, since it's using sun.* packages, and you don't need to do it like that to get your program running. Probably a leftover from the past.
The more commonly used style would be something like
SwingUtilities.invokeLater(() -> {
boxes.setSize(500, 400);
boxes.setVisible(true);
});

Javafx KeyEvent and MouseEvent

I am trying to learn javafx. I did most of the code but I am having trouble with the start method.
What I wanted to do was add spots to the screen by clicking on it. And if I press either 1 or 0 future spots that will be added will change to some different color. Therefore, I know that I must use setOnMouseClicked
and setOnKeyPressed methods but there isn't much on the internet on it.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class Spots extends Application {
public static final int SIZE = 500;
public static final int SPOT_RADIUS = 20;
private LinkedList<Spot> spotList;
private Color color;
public static void main(String...args) {
launch(args);
}
public void start(Stage stage) {
stage.setTitle("Spots");
dotList = new SinglyLinkedList<>();
Group root = new Group();
Scene scene = new Scene(root, 500, 500, Color.BLACK);
Spot r;
// ...
stage.show();
}
private class Spot extends Circle {
public Spot(double xPos, double yPos) {
super(xPos, yPos, SPOT_RADIUS);
setFill(color);
}
public boolean contains(double xPos, double yPos) {
double dx = xPos - getCenterX();
double dy = yPos - getCenterY();
double distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
return distance <= SPOT_RADIUS;
}
}
}
The reason the circle is not accepting is that it is not focused. For nodes to respond to key events they should be focusTraversable. You can do that by
calling setFocusTraversable(true) on the node. I edited your start() method and here is the code I ended up with.
public void start(Stage primaryStage) throws Exception {
Pane pane = new Pane();
final Scene scene = new Scene(pane, 500, 500);
final Circle circle = new Circle(250, 250, 20);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
pane.getChildren().add(circle);
circle.setFocusTraversable(true);
circle.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
if ((e.getCode() == KeyCode.UP) && (circle.getCenterY() >= 5)) {
circle.setCenterY(circle.getCenterY() - 5);
}
else if ((e.getCode() == KeyCode.DOWN && (circle.getCenterY() <= scene.getHeight() - 5))) {
circle.setCenterY(circle.getCenterY() + 5);
}
else if ((e.getCode() == KeyCode.RIGHT) && (circle.getCenterX() <= scene.getWidth() - 5)) {
circle.setCenterX(circle.getCenterX() + 5);
}
else if ((e.getCode() == KeyCode.LEFT && (circle.getCenterX() >= 5))) {
circle.setCenterX(circle.getCenterX()-5);
}
}
});
//creates new spots by clicking anywhere on the pane
pane.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
double newX = event.getX(); //getting the x-coordinate of the clicked area
double newY = event.getY(); //getting the y-coordinate of the clicked area
Circle newSpot = new Circle(newX, newY,20);
newSpot.setFill(Color.WHITE);
newSpot.setStroke(Color.BLACK);
pane.getChildren().add(newSpot);
}
});
primaryStage.setTitle("Move the circle");
primaryStage.setScene(scene);
primaryStage.show();
}
Also take look at the answers for the following links:
Handling keyboard events
Cannot listen to KeyEvent in
JavaFX
Solution Approach
You can monitor the scene for key typed events and switch the color mode based on that. You can place an mouse event handler on your scene root pane and add a circle (of the appropriate color for the prevailing color mode) to the scene when the user clicks anywhere in the pane.
Sample Code
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
// Java 8+ code.
public class Spots extends Application {
private static final int SIZE = 500;
private static final int SPOT_RADIUS = 20;
private Color color = Color.BLUE;
public void start(Stage stage) {
Pane root = new Pane();
root.setOnMouseClicked(event ->
root.getChildren().add(
new Spot(
event.getX(),
event.getY(),
color
)
)
);
Scene scene = new Scene(root, SIZE, SIZE, Color.BLACK);
scene.setOnKeyTyped(event -> {
switch (event.getCharacter()) {
case "0":
color = Color.BLUE;
break;
case "1":
color = Color.RED;
break;
}
});
stage.setScene(scene);
stage.show();
}
private class Spot extends Circle {
public Spot(double xPos, double yPos, Color color) {
super(xPos, yPos, SPOT_RADIUS);
setFill(color);
}
}
public static void main(String... args) {
launch(args);
}
}
Further Info
For detailed information on event handling in JavaFX, see the Oracle JavaFX event tutorial.
Generally, you'd use setOnAction as shown in the Oracle tutorials.
Example:
btn.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
System.out.println("Hello World");
}
});
If the particular Node you're trying to use does not have a clickHandler method, try doing something like this (on Group for example):
group.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("Hello!");
}
});

How show specific part of an image in javafx

i have this picture(all of these effects are in one .png file ) i want to display for example second picture how can i use Image and ImageView in javafx to display specific part of this image ? thanks
This answer is overkill. But with a nice set of images like you have in your question, maybe overkill is what is called for :-)
The fundamental design is the same as Uluk's, it just adjusts the Viewport of the ImageView rather than setting a clip, but the concept is the same.
Beware => Java 8
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
class ExploadableImageView extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Duration FRAME_TIME = Duration.seconds(.5);
public ExploadableImageView(Image explosionImage, int numCells) {
this.numCells = numCells;
double cellWidth = explosionImage.getWidth() / numCells;
double cellHeight = explosionImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(explosionImage);
setViewport(cellClips[0]);
}
public void explode(EventHandler<ActionEvent> onFinished) {
final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
Timeline kaboom = new Timeline(
new KeyFrame(FRAME_TIME, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
kaboom.setCycleCount(numCells);
kaboom.setOnFinished(onFinished);
kaboom.play();
}
}
class ExplodableItem extends StackPane {
public ExplodableItem(Image objectImage, Image explosionImage, int numCells) {
ImageView objectView = new ImageView(objectImage);
ExploadableImageView explosionView = new ExploadableImageView(
explosionImage, numCells
);
setMinSize(
Math.max(
objectImage.getWidth(),
explosionView.getViewport().getWidth()
),
Math.max(
objectImage.getHeight(),
explosionView.getViewport().getHeight()
)
);
objectView.setPickOnBounds(false);
objectView.setOnMouseClicked(event -> {
getChildren().setAll(explosionView);
explosionView.explode(complete -> getChildren().setAll(objectView));
});
DropShadow drop = new DropShadow(10, Color.GOLD);
drop.setInput(new Glow());
objectView.setOnMouseEntered(event -> objectView.setEffect(drop));
objectView.setOnMouseExited(event -> objectView.setEffect(null));
getChildren().setAll(objectView);
}
}
public class CatWhack extends Application {
public static void main(String[] args) {
launch(args);
}
private static final int NUM_CELLS_PER_EXPLOSION = 6;
#Override
public void start(Stage stage) {
Image objectImage = new Image("http://icons.iconarchive.com/icons/iconka/meow/96/cat-box-icon.png"); // cat icon linkware: backlink to http://www.iconka.com required
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(CatWhack.class.getResourceAsStream("QMqbQ.png"));
Image explosionImage = new Image("http://i.stack.imgur.com/QMqbQ.png");
TilePane tiles = new TilePane();
tiles.setPrefColumns(4);
for (int i = 0; i <16; i++) {
tiles.getChildren().add(
new ExplodableItem(objectImage, explosionImage, NUM_CELLS_PER_EXPLOSION)
);
}
tiles.setMinSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
stage.setTitle("Cat Whack - Click a cat to whack it!");
stage.setScene(new Scene(tiles));
stage.show();
}
}
Simpler example
Here is the same concept as demonstrated in the above game, but just with a simpler system of an animated image which can be controlled via method calls rather than user mouse clicks on the image.
The animated image is similar to a Sprite. The code below is not meant to be a production quality Sprite system (probably a true Sprite system for a game would have more functions and features), it just demonstrates very simple display of an animated image based upon a Viewport.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
class Sprite extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Timeline timeline;
private final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
public Sprite(Image animationImage, int numCells, Duration frameTime) {
this.numCells = numCells;
double cellWidth = animationImage.getWidth() / numCells;
double cellHeight = animationImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(animationImage);
setViewport(cellClips[0]);
timeline = new Timeline(
new KeyFrame(frameTime, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
}
public void playOnce() {
frameCounter.set(0);
timeline.setCycleCount(numCells);
timeline.stop();
timeline.playFromStart();
}
public void playContinuously() {
frameCounter.set(0);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.stop();
timeline.playFromStart();
}
public void stop() {
frameCounter.set(0);
setViewport(cellClips[frameCounter.get()]);
timeline.stop();
}
}
public class SpriteSample extends Application {
private static final int NUM_CELLS_PER_ANIMATION = 6;
private static final Duration FRAME_TIME = Duration.seconds(.5);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(Sprite.class.getResourceAsStream("QMqbQ.png"));
Image tilesheetImage = new Image(SpriteSample.class.getResourceAsStream("QMqbQ.png"));
Sprite sprite = new Sprite(tilesheetImage, NUM_CELLS_PER_ANIMATION, FRAME_TIME);
ToggleButton animationControl = new ToggleButton("Animate");
animationControl.setOnAction(event -> {
if (animationControl.isSelected()) {
animationControl.setText("Stop");
sprite.playContinuously();
} else {
animationControl.setText("Animate");
sprite.stop();
}
});
VBox layout = new VBox(10, sprite, animationControl);
layout.setPadding(new Insets(10));
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
}
You can utilize the clip property of the Node, along with x property of the ImageView. Below is a demo showing the part of image in timeline, like a gif animated picture:
#Override
public void start(Stage stage) {
Group root = new Group();
Image image = new Image(this.getClass().getResource("your.png").toExternalForm());
final int numberOfFrames = 6; // in image
double frameWidth = image.getWidth() / numberOfFrames;
Scene scene = new Scene(root, frameWidth, image.getHeight());
final ImageView view = new ImageView(image);
Rectangle mask = new Rectangle(frameWidth, image.getHeight());
view.setClip(mask);
Timeline timeline = new Timeline();
for (int i = 0; i <= numberOfFrames; i++) {
KeyFrame kf = new KeyFrame(Duration.seconds(i), new KeyValue(view.xProperty(), -frameWidth * i, Interpolator.DISCRETE));
timeline.getKeyFrames().add(kf);
}
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
root.getChildren().add(view);
stage.setScene(scene);
stage.show();
}
As someone who is new to JavaFX, I had the same question. The question asks for much less than the above answers provide. We just want to manually select and display a portion of the image in an ImageView without animation. Here is the simplest solution wherein the user enters the image number and presses a button to display the desired image part.
package com.pakzaban;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class Controller {
#FXML
public ImageView imageView;
public TextField numberField;
private final int IMAGE_WIDTH = 50;
private final int IMAGE_HEIGHT = 80;
private int imageNumber = 1;
public void onSetImagePressed(){
try {
imageNumber = Integer.parseInt(numberField.getText());
//SET THE WHOLE IMAGE INTO IMAGEVIEW
Image wholeImage = new Image("com/pakzaban/wholePicture.png");
imageView.setImage(wholeImage);
//SET THE VIEWPORT TO DESIRED PART OF THE IMAGE
Rectangle2D imagePart = new Rectangle2D((imageNumber - 1) * IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
imageView.setViewport(imagePart);
}
catch (Exception e){
e.printStackTrace();
}
}
}

Other ways of subreferencing images?

The only way I've found of drawing part of an image is with this drawImage function:
public abstract boolean drawImage(Image img,int dx1,int dy1,int dx2,int dy2,int sx1,int sy1,int sx2,int sy2,ImageObserver observer)
Is there a way of drawing a section of an image which only requires destination coordinates rather than a rectangle? I want to specify my image cut and then draw it at a coordinate rather than having to worry that the destination rectangle matches the size of my cut. I find this has more room for error as if you get it wrong it squeezes/stretches your image to fit etc. Plus when you have images moving around on screen it means moving two sets of points rather than one simple top left coordinate point.
(I'm sure I've seen a video where someone does this I just can't remember what the function is)
You can create a sub-image from your Image and then draw that.
Please check out the BufferedImage API, the getSubImage() method. This will return a cut out image from the big image as a BufferedImage object. Then you can draw this with a simpler drawImage(...) overload that just takes the position.
For example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SubImageEg extends JPanel {
// images from Wikimedia Commons
// http://commons.wikimedia.org/wiki/Main_Page
public static final String MAIN_IMG_PATH = "http://upload.wikimedia.org/wikipedia/commons/" +
"thumb/4/43/Sarcophilus_harrisii_taranna.jpg/800px-Sarcophilus_harrisii_taranna.jpg";
public static final String SECOND_IMG_PATH = "http://upload.wikimedia.org/wikipedia/commons/" +
"thumb/f/f8/Soldering_a_0805.jpg/800px-Soldering_a_0805.jpg";
public static final String[] IMAGE_PATHS = {MAIN_IMG_PATH, SECOND_IMG_PATH};
private static final int SUB_X = 520;
private static final int SUB_Y = 340;
private static final int SUB_W = 150;
private static final int SUB_H = 150;
private static final int SEC_SUB_X = 400;
private static final int SEC_SUB_Y = 200;
private BufferedImage[] images = new BufferedImage[IMAGE_PATHS.length];
private BufferedImage secondImgSubImg;
public SubImageEg() {
try {
for (int i = 0; i < IMAGE_PATHS.length; i++) {
images[i] = ImageIO.read(new URL(IMAGE_PATHS[i]));
}
secondImgSubImg = images[1].getSubimage(SUB_X, SUB_Y, SUB_W, SUB_H);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (images[0] != null) {
g.drawImage(images[0], 0, 0, this);
}
if (secondImgSubImg != null) {
g.drawImage(secondImgSubImg, SEC_SUB_X, SEC_SUB_Y, this);
}
}
#Override
public Dimension getPreferredSize() {
if (images[0] != null) {
return new Dimension(images[0].getWidth(), images[0].getHeight());
} else {
return super.getPreferredSize();
}
}
private static void createAndShowGui() {
SubImageEg mainPanel = new SubImageEg();
JFrame frame = new JFrame("SubImageEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Categories