如何在JavaFX中显示图像的特定部分

5

我的图片

我有这张图片(所有效果都在一个.png文件中),我想要显示例如第二张图片,我该如何在JavaFX中使用Image和ImageView来展示这张图片的特定部分呢?谢谢。


2
如果你使用 BufferedImage,那就非常简单了,但是 JavaFX 非要有自己的 Image 类... - MadProgrammer
我该如何使用BufferedImage实现这个? - user2768684
BufferedImage#subImage,问题是,它与JavaFX不兼容。 - MadProgrammer
3个回答

7

这个答案有点过头了。但是,考虑到你在问题中提供的精美图片,也许过度设计是需要的 :-)

基本设计与Uluk相同,只是调整ImageView的视口而不是设置裁剪,但概念相同。

注意 => 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();
    }
}

whack

简单的例子

这里展示了与上述游戏相同的概念,但仅使用动画图像的较简单系统,该系统可以通过方法调用而不是用户鼠标点击图像来进行控制。

动画图片类似于Sprite。下面的代码并不意味着是一种生产质量的Sprite系统(对于游戏,真正的Sprite系统可能会有更多的功能和特性),它只演示了基于视口显示简单动画图像的非常简单的方法。

simple image

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();
    }
}

1
请注意,不是每个人都会使用Java 8 ;) - MadProgrammer
3
经过一些实验后,我发现使用单独的图像(setImage())比使用setViewport()方法具有更好的性能,性能提升超过100%,同时也具有更好的垃圾回收性能。尽管setViewport()方法更加简洁,但我同意这个结论。 - Waverick

6
您可以利用 Node 的剪辑属性和 ImageView 的 x 属性。下面是一个演示,显示时间线上的图片部分,就像 gif 动画图片一样:
@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();

}

谢谢,但是我该如何在第一帧中显示每个图像呢(我的意思是,如果我有6个图像部分,就像我顶部的图像一样,我要在第一个图像中显示所有图像)? - user2768684
手动移动图像上的遮罩的方法是什么?(不使用关键帧重复执行) - Dennis

3

作为一个新手,我对JavaFX不太熟悉,但我有同样的问题。这个问题的要求比上面的答案要求少得多。我们只是想手动选择并在ImageView中显示图像的一部分,而不需要动画效果。这里是最简单的解决方案,用户输入图像编号,然后按下按钮来显示所需的图像部分。


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();
        }
    }
}

Image 4 selected


1
更加耗费资源的替代策略是使用PixelReader创建一个新的裁剪图像,如此处所建议的:https://dev59.com/IW7Xa4cB1Zd3GeqPoU0v - pakpe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接