如何在不使用 Swing API 的情况下保存 JAVA FX 图表? [英] How to save a JAVA FX Chart without using Swing API?

查看:28
本文介绍了如何在不使用 Swing API 的情况下保存 JAVA FX 图表?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下功能来保存 LineChart 图像:

I have the following function to save a LineChart Image:

@FXML
    public void saveAsPng() {
        String timeStamp = new SimpleDateFormat("HHmmss_yyyyMMdd").format(Calendar.getInstance().getTime());            
        chart.setAnimated(false);               
        System.out.println("Saving . . .");
        WritableImage image = chart.snapshot(new SnapshotParameters(), null);
        File file = new File("chart"+timeStamp+".png");

        try {
            ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);
        } catch (IOException e) {
             Logger.getLogger(SampleController.class.getName()).log(Level.SEVERE, null, e);
             System.out.println("Error");
        }           
    }

遗憾的是,用于 ARM 的 JDK 不支持 Swing API.使用时出现错误:

Sadly, JDK for ARM dosent support Swing API. I get an error while using:

ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);

我在 WritableImage 图像 变量中有渲染图像.还有其他方法可以保存图表吗?

I have the rendered image in WritableImage image variable. Is there any other way in which I can save the Chart?

推荐答案

建议方法

PNG 的编码非常简单.谷歌搜索一些基于 Java 的 png 实现并修改它们的源代码以使用 JavaFX PixelReader 来输入像素数据而不是 awt 图像.

PNG is pretty simple to encode. Google some Java based png implementations and modify their source to use a JavaFX PixelReader for pixel data input rather than awt images.

这是您可以修改的示例项目:

Here is a sample project you could modify:

http://catcode.com/pngencoder/

请务必检查您修改的实施的任何许可要求.

Just be sure to check any licensing requirements for the implementation you modify.

示例解决方案

我想我会尝试一下,但它最终实现起来非常简单(感谢我在之前找到并链接的 catcode 上的优秀 png 编码源).

I thought I'd give this a try and it ended up pretty trivial to implement (thanks to the great png encoding source at catcode which I found and linked earlier).

下面的代码绝对没有断言它会做你想做的事.我所做的只是删除 AWT 图像和像素采集器引用,并用 JavaFX 图像和像素读取器引用替换它们.但对于我尝试过的有限测试用例来说,它似乎工作得很好.

The code below is provided with absolutely no assertion that it will do what you want. All I did was rip out the AWT image and pixel grabber references and replace them with JavaFX image and pixel reader references. But it seems to work fine for the limited test case I tried.

样本输出

左边的图像是原始图表.右侧的图像是导出的 PNG 编码图表,加载回 JavaFX 并显示在 ImageView 中.

The image on the left is the original chart. The image on the right is the exported PNG encoded chart loaded back into JavaFX and displayed in an ImageView.

左边的图像更清晰一些,大概是因为我在 Retina mac 上进行开发,并且有必要以双像素分辨率保存图像,以便在导出的图像中获得与源图像相同的保真度.

The image on the left is a bit sharper, presumably because I was doing development on a retina mac and it would be necessary to save the image at a double pixel resolution to get the same fidelity in the exported image as the source.

场景根有叶子的背景图像.图表使用半透明的彩色背景,以便背景叶子显示出来 - 这确保 alpha 编码适用于生成的 png 文件.

The scene root has a background image of leaves. A translucent colored background is used for the chart so that the background leaves show through - this ensures that alpha encoding is working for the generated png file.

PngEncoderFX.java - JavaFX 图像的 Png 编码器

import javafx.scene.image.Image;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

/**
 * PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file.
 * The Image is presumed to use the DirectColorModel.
 *
 * <p>Thanks to Jay Denny at KeyPoint Software
 *    http://www.keypoint.com/
 * who let me develop this code on company time.</p>
 *
 * <p>You may contact me with (probably very-much-needed) improvements,
 * comments, and bug fixes at:</p>
 *
 *   <p><code>david@catcode.com</code></p>
 *
 * <p>This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.</p>
 *
 * <p>This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.</p>
 *
 * <p>You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * A copy of the GNU LGPL may be found at
 * <code>http://www.gnu.org/copyleft/lesser.html</code></p>
 *
 * @author J. David Eisenberg
 * @version 1.5, 19 Oct 2003
 *
 * CHANGES:
 * --------
 * 30-Jav-2015 : Hacked source to work with JavaFX images instead of AWT images (by Jewelsea for StackOverflow).
 * 19-Nov-2002 : CODING STYLE CHANGES ONLY (by David Gilbert for Object Refinery Limited);
 * 19-Sep-2003 : Fix for platforms using EBCDIC (contributed by Paulo Soares);
 * 19-Oct-2003 : Change private fields to protected fields so that
 *               PngEncoderB can inherit them (JDE)
 *               Fixed bug with calculation of nRows
 */

public class PngEncoderFX extends Object {

    /** Constant specifying that alpha channel should be encoded. */
    public static final boolean ENCODE_ALPHA = true;

    /** Constant specifying that alpha channel should not be encoded. */
    public static final boolean NO_ALPHA = false;

    /** Constants for filter (NONE) */
    public static final int FILTER_NONE = 0;

    /** Constants for filter (SUB) */
    public static final int FILTER_SUB = 1;

    /** Constants for filter (UP) */
    public static final int FILTER_UP = 2;

    /** Constants for filter (LAST) */
    public static final int FILTER_LAST = 2;

    /** IHDR tag. */
    protected static final byte IHDR[] = {73, 72, 68, 82};

    /** IDAT tag. */
    protected static final byte IDAT[] = {73, 68, 65, 84};

    /** IEND tag. */
    protected static final byte IEND[] = {73, 69, 78, 68};

    /** The png bytes. */
    protected byte[] pngBytes;

    /** The prior row. */
    protected byte[] priorRow;

    /** The left bytes. */
    protected byte[] leftBytes;

    /** The image. */
    protected Image image;

    /** The width. */
    protected int width, height;

    /** The byte position. */
    protected int bytePos, maxPos;

    /** CRC. */
    protected CRC32 crc = new CRC32();

    /** The CRC value. */
    protected long crcValue;

    /** Encode alpha? */
    protected boolean encodeAlpha;

    /** The filter type. */
    protected int filter;

    /** The bytes-per-pixel. */
    protected int bytesPerPixel;

    /** The compression level. */
    protected int compressionLevel;

    /**
     * Class constructor
     */
    public PngEncoderFX() {
        this(null, false, FILTER_NONE, 0);
    }

    /**
     * Class constructor specifying Image to encode, with no alpha channel encoding.
     *
     * @param image A Java Image object which uses the DirectColorModel
     * @see java.awt.Image
     */
    public PngEncoderFX(Image image) {
        this(image, false, FILTER_NONE, 0);
    }

    /**
     * Class constructor specifying Image to encode, and whether to encode alpha.
     *
     * @param image A Java Image object which uses the DirectColorModel
     * @param encodeAlpha Encode the alpha channel? false=no; true=yes
     * @see java.awt.Image
     */
    public PngEncoderFX(Image image, boolean encodeAlpha) {
        this(image, encodeAlpha, FILTER_NONE, 0);
    }

    /**
     * Class constructor specifying Image to encode, whether to encode alpha, and filter to use.
     *
     * @param image A Java Image object which uses the DirectColorModel
     * @param encodeAlpha Encode the alpha channel? false=no; true=yes
     * @param whichFilter 0=none, 1=sub, 2=up
     * @see java.awt.Image
     */
    public PngEncoderFX(Image image, boolean encodeAlpha, int whichFilter) {
        this(image, encodeAlpha, whichFilter, 0);
    }


    /**
     * Class constructor specifying Image source to encode, whether to encode alpha, filter to use,
     * and compression level.
     *
     * @param image A Java Image object
     * @param encodeAlpha Encode the alpha channel? false=no; true=yes
     * @param whichFilter 0=none, 1=sub, 2=up
     * @param compLevel 0..9
     * @see java.awt.Image
     */
    public PngEncoderFX(Image image, boolean encodeAlpha, int whichFilter, int compLevel) {
        this.image = image;
        this.encodeAlpha = encodeAlpha;
        setFilter(whichFilter);
        if (compLevel >= 0 && compLevel <= 9) {
            this.compressionLevel = compLevel;
        }
    }

    /**
     * Set the image to be encoded
     *
     * @param image A Java Image object which uses the DirectColorModel
     * @see java.awt.Image
     * @see java.awt.image.DirectColorModel
     */
    public void setImage(Image image) {
        this.image = image;
        pngBytes = null;
    }

    /**
     * Creates an array of bytes that is the PNG equivalent of the current image, specifying
     * whether to encode alpha or not.
     *
     * @param encodeAlpha boolean false=no alpha, true=encode alpha
     * @return an array of bytes, or null if there was a problem
     */
    public byte[] pngEncode(boolean encodeAlpha) {
        byte[]  pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10};

        if (image == null) {
            return null;
        }
        width = (int) image.getWidth();
        height = (int) image.getHeight();

        /*
         * start with an array that is big enough to hold all the pixels
         * (plus filter bytes), and an extra 200 bytes for header info
         */
        pngBytes = new byte[((width + 1) * height * 3) + 200];

        /*
         * keep track of largest byte written to the array
         */
        maxPos = 0;

        bytePos = writeBytes(pngIdBytes, 0);
        //hdrPos = bytePos;
        writeHeader();
        //dataPos = bytePos;
        if (writeImageData()) {
            writeEnd();
            pngBytes = resizeByteArray(pngBytes, maxPos);
        }
        else {
            pngBytes = null;
        }
        return pngBytes;
    }

    /**
     * Creates an array of bytes that is the PNG equivalent of the current image.
     * Alpha encoding is determined by its setting in the constructor.
     *
     * @return an array of bytes, or null if there was a problem
     */
    public byte[] pngEncode() {
        return pngEncode(encodeAlpha);
    }

    /**
     * Set the alpha encoding on or off.
     *
     * @param encodeAlpha  false=no, true=yes
     */
    public void setEncodeAlpha(boolean encodeAlpha) {
        this.encodeAlpha = encodeAlpha;
    }

    /**
     * Retrieve alpha encoding status.
     *
     * @return boolean false=no, true=yes
     */
    public boolean getEncodeAlpha() {
        return encodeAlpha;
    }

    /**
     * Set the filter to use
     *
     * @param whichFilter from constant list
     */
    public void setFilter(int whichFilter) {
        this.filter = FILTER_NONE;
        if (whichFilter <= FILTER_LAST) {
            this.filter = whichFilter;
        }
    }

    /**
     * Retrieve filtering scheme
     *
     * @return int (see constant list)
     */
    public int getFilter() {
        return filter;
    }

    /**
     * Set the compression level to use
     *
     * @param level 0 through 9
     */
    public void setCompressionLevel(int level) {
        if (level >= 0 && level <= 9) {
            this.compressionLevel = level;
        }
    }

    /**
     * Retrieve compression level
     *
     * @return int in range 0-9
     */
    public int getCompressionLevel() {
        return compressionLevel;
    }

    /**
     * Increase or decrease the length of a byte array.
     *
     * @param array The original array.
     * @param newLength The length you wish the new array to have.
     * @return Array of newly desired length. If shorter than the
     *         original, the trailing elements are truncated.
     */
    protected byte[] resizeByteArray(byte[] array, int newLength) {
        byte[]  newArray = new byte[newLength];
        int     oldLength = array.length;

        System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength));
        return newArray;
    }

    /**
     * Write an array of bytes into the pngBytes array.
     * Note: This routine has the side effect of updating
     * maxPos, the largest element written in the array.
     * The array is resized by 1000 bytes or the length
     * of the data to be written, whichever is larger.
     *
     * @param data The data to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeBytes(byte[] data, int offset) {
        maxPos = Math.max(maxPos, offset + data.length);
        if (data.length + offset > pngBytes.length) {
            pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, data.length));
        }
        System.arraycopy(data, 0, pngBytes, offset, data.length);
        return offset + data.length;
    }

    /**
     * Write an array of bytes into the pngBytes array, specifying number of bytes to write.
     * Note: This routine has the side effect of updating
     * maxPos, the largest element written in the array.
     * The array is resized by 1000 bytes or the length
     * of the data to be written, whichever is larger.
     *
     * @param data The data to be written into pngBytes.
     * @param nBytes The number of bytes to be written.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeBytes(byte[] data, int nBytes, int offset) {
        maxPos = Math.max(maxPos, offset + nBytes);
        if (nBytes + offset > pngBytes.length) {
            pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, nBytes));
        }
        System.arraycopy(data, 0, pngBytes, offset, nBytes);
        return offset + nBytes;
    }

    /**
     * Write a two-byte integer into the pngBytes array at a given position.
     *
     * @param n The integer to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeInt2(int n, int offset) {
        byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)};
        return writeBytes(temp, offset);
    }

    /**
     * Write a four-byte integer into the pngBytes array at a given position.
     *
     * @param n The integer to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeInt4(int n, int offset) {
        byte[] temp = {(byte) ((n >> 24) & 0xff),
                       (byte) ((n >> 16) & 0xff),
                       (byte) ((n >> 8) & 0xff),
                       (byte) (n & 0xff)};
        return writeBytes(temp, offset);
    }

    /**
     * Write a single byte into the pngBytes array at a given position.
     *
     * @param b The integer to be written into pngBytes.
     * @param offset The starting point to write to.
     * @return The next place to be written to in the pngBytes array.
     */
    protected int writeByte(int b, int offset) {
        byte[] temp = {(byte) b};
        return writeBytes(temp, offset);
    }

    /**
     * Write a PNG "IHDR" chunk into the pngBytes array.
     */
    protected void writeHeader() {
        int startPos;

        startPos = bytePos = writeInt4(13, bytePos);
        bytePos = writeBytes(IHDR, bytePos);
        width = (int) image.getWidth();
        height = (int) image.getHeight();
        bytePos = writeInt4(width, bytePos);
        bytePos = writeInt4(height, bytePos);
        bytePos = writeByte(8, bytePos); // bit depth
        bytePos = writeByte((encodeAlpha) ? 6 : 2, bytePos); // direct model
        bytePos = writeByte(0, bytePos); // compression method
        bytePos = writeByte(0, bytePos); // filter method
        bytePos = writeByte(0, bytePos); // no interlace
        crc.reset();
        crc.update(pngBytes, startPos, bytePos - startPos);
        crcValue = crc.getValue();
        bytePos = writeInt4((int) crcValue, bytePos);
    }

    /**
     * Perform "sub" filtering on the given row.
     * Uses temporary array leftBytes to store the original values
     * of the previous pixels.  The array is 16 bytes long, which
     * will easily hold two-byte samples plus two-byte alpha.
     *
     * @param pixels The array holding the scan lines being built
     * @param startPos Starting position within pixels of bytes to be filtered.
     * @param width Width of a scanline in pixels.
     */
    protected void filterSub(byte[] pixels, int startPos, int width) {
        int i;
        int offset = bytesPerPixel;
        int actualStart = startPos + offset;
        int nBytes = width * bytesPerPixel;
        int leftInsert = offset;
        int leftExtract = 0;

        for (i = actualStart; i < startPos + nBytes; i++) {
            leftBytes[leftInsert] =  pixels[i];
            pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256);
            leftInsert = (leftInsert + 1) % 0x0f;
            leftExtract = (leftExtract + 1) % 0x0f;
        }
    }

    /**
     * Perform "up" filtering on the given row.
     * Side effect: refills the prior row with current row
     *
     * @param pixels The array holding the scan lines being built
     * @param startPos Starting position within pixels of bytes to be filtered.
     * @param width Width of a scanline in pixels.
     */
    protected void filterUp(byte[] pixels, int startPos, int width) {
        int     i, nBytes;
        byte    currentByte;

        nBytes = width * bytesPerPixel;

        for (i = 0; i < nBytes; i++) {
            currentByte = pixels[startPos + i];
            pixels[startPos + i] = (byte) ((pixels[startPos  + i] - priorRow[i]) % 256);
            priorRow[i] = currentByte;
        }
    }

    /**
     * Write the image data into the pngBytes array.
     * This will write one or more PNG "IDAT" chunks. In order
     * to conserve memory, this method grabs as many rows as will
     * fit into 32K bytes, or the whole image; whichever is less.
     *
     *
     * @return true if no errors; false if error grabbing pixels
     */
    protected boolean writeImageData() {
        int rowsLeft = height;  // number of rows remaining to write
        int startRow = 0;       // starting row to process this time through
        int nRows;              // how many rows to grab at a time

        byte[] scanLines;       // the scan lines to be compressed
        int scanPos;            // where we are in the scan lines
        int startPos;           // where this line's actual pixels start (used for filtering)

        byte[] compressedLines; // the resultant compressed lines
        int nCompressed;        // how big is the compressed area?

        //int depth;              // color depth ( handle only 8 or 32 )

        PixelReader pg = image.getPixelReader();

        bytesPerPixel = (encodeAlpha) ? 4 : 3;

        Deflater scrunch = new Deflater(compressionLevel);
        ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024);

        DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch);
        try {
            while (rowsLeft > 0) {
                nRows = Math.min(32767 / (width * (bytesPerPixel + 1)), rowsLeft);
                nRows = Math.max( nRows, 1 );

                int[] pixels = new int[width * nRows];

                pg.getPixels(0, startRow, width, nRows, PixelFormat.getIntArgbInstance(), pixels, 0, width);

                /*
                 * Create a data chunk. scanLines adds "nRows" for
                 * the filter bytes.
                 */
                scanLines = new byte[width * nRows * bytesPerPixel +  nRows];

                if (filter == FILTER_SUB) {
                    leftBytes = new byte[16];
                }
                if (filter == FILTER_UP) {
                    priorRow = new byte[width * bytesPerPixel];
                }

                scanPos = 0;
                startPos = 1;
                for (int i = 0; i < width * nRows; i++) {
                    if (i % width == 0) {
                        scanLines[scanPos++] = (byte) filter;
                        startPos = scanPos;
                    }
                    scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff);
                    scanLines[scanPos++] = (byte) ((pixels[i] >>  8) & 0xff);
                    scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff);
                    if (encodeAlpha) {
                        scanLines[scanPos++] = (byte) ((pixels[i] >> 24) & 0xff);
                    }
                    if ((i % width == width - 1) && (filter != FILTER_NONE)) {
                        if (filter == FILTER_SUB) {
                            filterSub(scanLines, startPos, width);
                        }
                        if (filter == FILTER_UP) {
                            filterUp(scanLines, startPos, width);
                        }
                    }
                }

                /*
                 * Write these lines to the output area
                 */
                compBytes.write(scanLines, 0, scanPos);

                startRow += nRows;
                rowsLeft -= nRows;
            }
            compBytes.close();

            /*
             * Write the compressed bytes
             */
            compressedLines = outBytes.toByteArray();
            nCompressed = compressedLines.length;

            crc.reset();
            bytePos = writeInt4(nCompressed, bytePos);
            bytePos = writeBytes(IDAT, bytePos);
            crc.update(IDAT);
            bytePos = writeBytes(compressedLines, nCompressed, bytePos);
            crc.update(compressedLines, 0, nCompressed);

            crcValue = crc.getValue();
            bytePos = writeInt4((int) crcValue, bytePos);
            scrunch.finish();
            return true;
        }
        catch (IOException e) {
            System.err.println(e.toString());
            return false;
        }
    }

    /**
     * Write a PNG "IEND" chunk into the pngBytes array.
     */
    protected void writeEnd() {
        bytePos = writeInt4(0, bytePos);
        bytePos = writeBytes(IEND, bytePos);
        crc.reset();
        crc.update(IEND);
        crcValue = crc.getValue();
        bytePos = writeInt4((int) crcValue, bytePos);
    }

}

PieChartPngEncoder.java - 测试工具

import javafx.application.Application;
import javafx.collections.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.Stage;
import javafx.scene.chart.*;

import java.io.IOException;
import java.nio.file.*;
import java.util.logging.*;

/**
 * Demonstrates encoding a JavaFX node to a PNG image using a custom PNG encoder and no AWT/Swing classes.
 */
public class PieChartPngEncoder extends Application {
    private static final Logger logger = Logger.getLogger(PieChartPngEncoder.class.getName());

    @Override public void start(Stage stage) throws IOException {
        final PieChart chart = createChart();
        chart.getStylesheets().add(getClass().getResource(
                "chart.css"
        ).toExternalForm());
        chart.getStyleClass().add("translucent-background");

        Path imagePath = Files.createTempFile("png-test", ".png");
        exportPngSnapshot(
                chart,
                imagePath,
                Color.TRANSPARENT
        );

        Image chartImage = new Image(
                imagePath.toUri().toURL().toExternalForm()
        );

        Label exportLocation = new Label("Exported to " + imagePath);
        exportLocation.setStyle("");
        exportLocation.getStyleClass().add("overlay-label");

        VBox layout = new VBox(
                10,
                new HBox(10,
                        chart,
                        new ImageView(chartImage)
                ),
                exportLocation
        );
        layout.setAlignment(Pos.BASELINE_RIGHT);
        layout.setPadding(new Insets(10));

        Scene scene = new Scene(layout);
        scene.getStylesheets().add(getClass().getResource(
                "encoder-app.css"
        ).toExternalForm());

        stage.setScene(scene);
        stage.show();

        logger.log(Level.INFO, "Wrote: " + imagePath);
    }

    private PieChart createChart() {
        ObservableList<PieChart.Data> pieChartData =
                FXCollections.observableArrayList(
                        new PieChart.Data("Grapefruit", 13),
                        new PieChart.Data("Oranges", 25),
                        new PieChart.Data("Plums", 10),
                        new PieChart.Data("Pears", 22),
                        new PieChart.Data("Apples", 30));
        final PieChart chart = new PieChart(pieChartData);
        chart.setTitle("Imported Fruits");
        return chart;
    }

    private void exportPngSnapshot(
            Node node,
            Path path,
            Paint backgroundFill
    ) throws IOException {
        if (node.getScene() == null) {
            Scene snapshotScene = new Scene(new Group(node));
        }

        SnapshotParameters params = new SnapshotParameters();
        params.setFill(backgroundFill);
        Image chartSnapshot = node.snapshot(params, null);
        PngEncoderFX encoder = new PngEncoderFX(chartSnapshot, true);
        byte[] bytes = encoder.pngEncode();
        Files.write(path, bytes);
    }

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

encoder-app.css - 测试工具的示例样式表

/** file: encoder-app.css **/

.root {
    -fx-background-image: url(http://pixdaus.com/files/items/pics/7/92/275792_de0efe881e1a7e5da077a26eacfa5ed2_large.jpg);
    -fx-background-size: cover;
}

.overlay-label {
    -fx-background-color: rgba(0, 80, 0, 0.7);
    -fx-text-fill: white;
}

chart.css - 测试工具的示例样式表

/** file: chart.css **/
.translucent-background {
    -fx-background-color: rgba(127, 127, 60, 0.7);
}

这篇关于如何在不使用 Swing API 的情况下保存 JAVA FX 图表?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆