ColorAdjust 与 SwingX 的 ImageView?

CollorAdjust with SwingX's ImageView?

我有一个关于调整从 swingx 库加载到 jXImageView 的图像的对比度、饱和度和色调的问题。

我有 ColorAdjust 方法。

ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setContrast(0.3);
colorAdjust.setHue(-0.03);
colorAdjust.setBrightness(0.2);
colorAdjust.setSaturation(0.2);

当用户点击 "Enhancement" 按钮时,图像应该会发生一点变化,但如何做到这一点呢?记住:我使用的是 jXImageView。

我已经使用以下代码增加了对比度:

    float brightenFactor = 1.5f;
    BufferedImage imagem = (BufferedImage) jXImageView2.getImage();
    RescaleOp op = new RescaleOp(brightenFactor, 0, null);
    imagem = op.filter(imagem, imagem);
    jXImageView2.updateUI();

编辑

我试过:

BufferedImage imagem = (BufferedImage) jXImageView2.getImage();
Image image = SwingFXUtils.toFXImage(imagem, null);//<--ERROR on that line (incompatible types: writable image cannot be converted to Image)
ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setContrast(0.3);
colorAdjust.setHue(-0.03);
colorAdjust.setBrightness(0.2);
colorAdjust.setSaturation(0.2);
ImageView imageView = new ImageView(image);//<--ERROR on taht line no suitable constructor for ImageView(java.awt.Image)
imageView.setFitWidth(imagem.getWidth());
imageView.setPreserveRatio(true);
imagem = SwingFXUtils.fromFXImage(imageView.snapshot(null, null), null);
jXImageView2.setImage(imagem);

...但没有成功。

您需要将 BufferedImage 转换为 javafx.scene.image.Image,您可以使用诸如...

Image image = SwingFXUtils.toFXImage(imagem, null);

然后你可以申请 ColorAdjust...

ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setContrast(0.1);
colorAdjust.setHue(-0.05);
colorAdjust.setBrightness(0.1);
colorAdjust.setSaturation(0.2);

ImageView imageView = new ImageView(image);
imageView.setFitWidth(image.getWidth());
imageView.setPreserveRatio(true);
imageView.setEffect(colorAdjust);

然后再转换回来...

imagem = SwingFXUtils.fromFXImage(imageView.snapshot(null, null), null);

这个想法是从 jewelsea / SaveAdjustedImage.java 偷来的。我不知道的是,如果 ImageView 需要首先在屏幕上实现,那么...

已更新

请注意,您正在跨越两个不同的 UI 框架,就像他们在电影中所说的那样,"don't cross the streams!"

JavaFX 比 Swing 有更严格的控制要求,这既是好事也是坏事。

您必须做的是在事件线程中将 JavaFX 代码获取到 运行。这比听起来更棘手(而且似乎需要如此),例如...

原创 |颜色调整(取自JavaDocs example)|单色...

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import javax.imageio.ImageIO;

public class Test extends Application {

    public static void main(String[] args) {
        Application.launch();
    }

    @Override
    public void start(Stage stage) throws Exception {

        try {
            System.out.println("Load image...");
            BufferedImage imagem = ImageIO.read(new File("..."));
            Image image = SwingFXUtils.toFXImage(imagem, null);

            ColorAdjust colorAdjust = new ColorAdjust();
            colorAdjust.setHue(0);
            colorAdjust.setSaturation(-1);
            colorAdjust.setBrightness(0);
            colorAdjust.setContrast(0);
//          colorAdjust.setHue(-0.05);
//          colorAdjust.setSaturation(0.2);
//          colorAdjust.setBrightness(0.1);
//          colorAdjust.setContrast(0.1);

            ImageView imageView = new ImageView(image);
            imageView.setFitWidth(image.getWidth());
            imageView.setPreserveRatio(true);
            imageView.setEffect(colorAdjust);

            System.out.println("Convert and save...");
            imagem = SwingFXUtils.fromFXImage(imageView.snapshot(null, null), null);
            ImageIO.write(imagem, "png", new File("ColorAdjusted.png"));
        } catch (IOException exp) {
            exp.printStackTrace();
        } finally {
            Platform.exit();
        }
    }

}

接下来要弄清楚如何让它作为实用程序工作class...

示例解决方案

  • 左图为原图。
  • 右边的图片是调整后的图片(已降低颜色饱和度,使图片成为单色)。

此解决方案适用于:

  1. 正在转换 Swing/AWT BufferedImage into a JavaFX Image
  2. 使用JavaFX ColorAdjust效果修改图片。
  3. A snapshot 颜色调整后的图像被用来创建一个新的 JavaFX 图像。
  4. 新的 JavaFX 图像 converted 回到新的 Swing/AWT BufferedImage。

由于该解决方案混合了两个不同的工具包,因此在创建它时考虑了以下注意事项:

  1. 注意用于确保给定工具包调用使用正确 class 的导入;例如,JavaFX 和 Swing/AWT 都有 ColorImage class,因此有必要确保使用给定工具包的完全限定 class在正确的上下文中 - 将 Swing Image 直接传递给 JavaFX API 是错误的,反之亦然。

  2. 注意线程规则。 JavaFX 场景的快照必须在 JavaFX 应用程序线程上创建。必须在 Swing 事件调度线程上执行 Swing APIs。各个工具包的各种实用程序(例如 SwingUtilities and the JavaFX Platform class)用于确保满足给定工具包的线程约束。

  3. JavaFX 工具包必须先初始化才能使用。通常这是在您的应用程序扩展 JavaFX 时隐式完成的 Application class. However Swing applications do not extend the JavaFX application class. So, perhaps somewhat counter-intuitively and poorly documented, a JFXPanel 必须在使用工具包之前实例化以初始化 JavaFX 工具包。

备注

  1. 这个解决方案是为满足问题的特定要求而设计的(这是一个需要进行一些颜色调整的 Swing 应用程序)。如果您只想从 JavaFX 中调整图像颜色而不使用 Swing,那么 more straight-forward solutions 存在并且是首选。

  2. 调用 System.exit 通常足以关闭 JavaFX 工具包。示例应用程序调用 Platform.exit 以显式关闭 JavaFX 工具包,但在这种情况下,可能不需要显式调用 Platform.exit

这意味着解决方案中的 ColorAdjuster 可以从 Swing 程序使用,而无需 Swing 程序显式导入任何 JavaFX classes(尽管在内部,ColorAdjuster 将导入那些 classes并且系统必须满足 运行 Swing 和 JavaFX 工具包的正常最低要求)。在可能的情况下减少每个 class 对单个工具包的混合导入是可取的,因为对于混合的 JavaFX/Swing 应用程序,在单个 class 中混合导入是一个很好的乏味错误来源,由于潜在的名称冲突和线程相关的问题。

ColorAdjuster.java

图像颜色调整实用程序。

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.SnapshotParameters;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;

import javax.swing.SwingUtilities;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/** Uses JavaFX to adjust the color of an AWT/Swing BufferedImage */
public class ColorAdjuster {
    // Instantiation of a JFXPanel is necessary otherwise the JavaFX toolkit is not initialized.
    // The JFXPanel doesn't actually need to be used, instantiating it in the constructor is enough to trigger toolkit initialization.
    private final JFXPanel fxPanel;

    public ColorAdjuster() {
        // perhaps this check is not necessary, but I feel a bit more comfortable if it is there.
        if (!SwingUtilities.isEventDispatchThread()) {
            throw new IllegalArgumentException(
                    "A ColorAdjuster must be created on the Swing Event Dispatch thread.  " +
                            "Current thread is " + Thread.currentThread()
            );
        }

        fxPanel = new JFXPanel();
    }

    /** 
     * Color adjustments to the buffered image are performed with parameters in the range -1.0 to 1.0
     * 
     * @return a new BufferedImage which has colors adjusted from the original image.
     **/
    public BufferedImage adjustColor(
            BufferedImage originalImage,
            double hue,
            double saturation,
            double brightness,
            double contrast
    ) throws ExecutionException, InterruptedException {
        // This task will be executed on the JavaFX thread.
        FutureTask<BufferedImage> conversionTask = new FutureTask<>(() -> {
            // create a JavaFX color adjust effect.
            final ColorAdjust monochrome = new ColorAdjust(0, -1, 0, 0);

            // convert the input buffered image to a JavaFX image and load it into a JavaFX ImageView.
            final ImageView imageView = new ImageView(
                    SwingFXUtils.toFXImage(
                            originalImage, null
                    )
            );

            // apply the color adjustment.
            imageView.setEffect(monochrome);

            // snapshot the color adjusted JavaFX image, convert it back to a Swing buffered image and return it.
            SnapshotParameters snapshotParameters = new SnapshotParameters();
            snapshotParameters.setFill(Color.TRANSPARENT);

            return SwingFXUtils.fromFXImage(
                    imageView.snapshot(
                            snapshotParameters,
                            null
                    ),
                    null
            );
        });

        Platform.runLater(conversionTask);

        return conversionTask.get();
    }
}

ColorAdjustingSwingAppUsingJavaFX.java

测试工具:

import javafx.application.Platform;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutionException;

public class ColorAdjustingSwingAppUsingJavaFX {

    private static void initAndShowGUI() {
        try {
            // This method is invoked on Swing thread
            JFrame frame = new JFrame();

            // read the original image from a URL.
            URL url = new URL(
                    IMAGE_LOC
            );
            BufferedImage originalImage   = ImageIO.read(url);

            // use JavaFX to convert the original image to monochrome.
            ColorAdjuster colorAdjuster = new ColorAdjuster();
            BufferedImage monochromeImage = colorAdjuster.adjustColor(
                    originalImage,
                    0, -1, 0, 0
            );

            // add the original image and the converted image to the Swing frame.
            frame.getContentPane().setLayout(new FlowLayout());
            frame.getContentPane().add(
                    new JLabel(
                            new ImageIcon(originalImage)
                    )
            );
            frame.getContentPane().add(
                    new JLabel(
                            new ImageIcon(monochromeImage)
                    )
            );

            // set a handler to close the application on request.
            frame.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    // shutdown the JavaFX runtime.
                    Platform.exit();

                    // exit the application.
                    System.exit(0);
                }
            });

            // display the Swing frame.
            frame.pack();
            frame.setLocation(400, 300);
            frame.setVisible(true);
        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(
                ColorAdjustingSwingAppUsingJavaFX::initAndShowGUI
        );
    }


    // icon source: http://www.iconarchive.com/artist/aha-soft.html
    // icon license: Free for non-commercial use, commercial usage: Not allowed
    private static final String IMAGE_LOC =
            "http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Pizza-icon.png";
}