image - 如何持久化 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(...)方法。WritablePixelFormats
PixelFormat
WritablePixelFormat
有人对如何从这里继续前进有任何想法吗?
解决方案
您有几个选项可以获取 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 增强请求,但当然这需要一段时间,而且可能永远不会产生任何结果。