首页 > 解决方案 > 如何持久化 JavaFX 图像?

问题描述

想象一下,我们有一个用 JavaFX 8(很快迁移到 11)编写的绘画工具,用户可以在其中在背景图像上绘制形状。支持多种类型的图像 (PNG/JPG/GIF) 作为背景。

最近我们的客户开始使用越来越大的背景图像,因此我们在图形内存问题上苦苦挣扎,因为我们还没有使用任何平铺原则或类似的东西。

为了实现一些小的性能提升,当用户想要加载项目时,我们创建了自己的二进制图像格式,而不是以原始格式存储图像。

当我们保存图像时,我们总是使用所有 4 个通道(r、g、b 和 alpha),尽管在大多数情况下使用 JPG 时不需要这样做,当涉及到大(15000x10000 像素)背景图像时:

public class ImageExample extends Application
{
  public static void save( final OutputStream out, final Image image )
      throws IllegalArgumentException, IOException
  {
    if ( image.getProgress() != 1.0 )
    {
      throw new IllegalArgumentException( "Image still loading in Background." );
    }

    final int width = (int) image.getWidth();
    final int height = (int) image.getHeight();
    final PixelReader pixelReader = image.getPixelReader();

    if ( width <= 0 || height <= 0 )
    {
      throw new IllegalArgumentException(
          MessageFormat.format( "Width and Height have to be > 0 [ w{0}, h{1} ].", width, height ) );
    }

    System.out.println( "Pixelreader knows the image format: " + image.getPixelReader().getPixelFormat() );

    //Here it would be great, if we could choose the number of channels and format ourselfs,
    //but we failed to extends WritablePixelFormat... BYTE_RGB is the only one with 3 Channels, but
    //we can't use it in getPixels(...) since its not extending WritablePixelFormat.
    final int pixelChannels = 4;
    final WritablePixelFormat<ByteBuffer> pixelFormat = PixelFormat.getByteBgraInstance();

    final byte[] widthXheight = ByteBuffer.allocate( 8 )
        .putInt( width )
        .putInt( height )
        .array();
    out.write( widthXheight );

    final byte[] pixelBuffer = new byte[width * pixelChannels];
    for ( int row = 0; row < height; ++row )
    {
      //But we can only use WritablePixelFormat here, so we can't write with PIXELFORMAT BYTE_RGB here...
      pixelReader.getPixels( 0, row, width, 1, pixelFormat, pixelBuffer, 0, pixelBuffer.length );
      out.write( pixelBuffer );
    }

    out.flush();
  }


  @Override
  public void start( final Stage primaryStage ) throws Exception
  {
    final Image image = new Image( "image.jpg" );

    final Path tempFile = Files.createTempFile( "raw_image.", ".data" );
    tempFile.toFile().deleteOnExit();

    try (
        final OutputStream fileOut = Files.newOutputStream( tempFile );
        final CheckedOutputStream hashOut = new CheckedOutputStream( fileOut, new CRC32() ); )
    {
      save( hashOut, image );
      System.out.println( "generated hash: " + hashOut.getChecksum().getValue() );
    }
  }

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

因此,为了减少 25% 的内存成本,我们想通过扩展WritablePixelFormat将PixelFormat减少到 3 个通道,以防我们处理 JPG,但不幸的是,它们 都有类保护的构造函数,所以我无法编写自己的实现。已经存在的ByteRGB 格式是唯一实现的,但没有扩展,所以我也不能使用它来将它传递给pixelreader.getPixels(...)方法。WritablePixelFormatsPixelFormatWritablePixelFormat

有人对如何从这里继续前进有任何想法吗?

标签: imagejavafxpixelformat

解决方案


您有几个选项可以获取 3 Bytes-Per-Pixel 数据:

处理你得到的每个像素有 4 个字节的数组,以丢弃 alpha 通道并将剩余的 RGB 字节打包在一起,然后再写入。

或者

使用 Swing 互操作性类转换为 AWT BufferedImage:

BufferedImage bimg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
BufferedImage res = SwingFXUtils.fromFXImage(image, bimg);

但是,由于内存问题是您关心的问题,因此创建一个全新的图像可能不是最佳选择。我只会吸收性能损失并将您的 4 BPP 阵列处理到 3 BPP。

您也可以提交 JavaFX 增强请求,但当然这需要一段时间,而且可能永远不会产生任何结果。


推荐阅读