在JavaFX中显示原始字节数组图像(不带SwingFXUtils)
我目前正在做一个项目,我必须处理真正的大图像>>100mb。 图像的格式为原始字节数组,现在仅用于灰度图像,以后彩色图像的每个通道都有一个字节数组 我想在JavaFXImageView中显示图像。因此,我必须在尽可能短的时间内将给定的原始图像数据转换为JavaFX图像 我尝试了很多解决方案,我在这里和stackoverflow以及其他来源找到了这些解决方案 秋千 最流行、最简单的解决方案是从原始数据构造一个BuffereImage,并使用SwingFXUtils.toFXImage将其转换为JavaFX映像。。。。 在一张100mb 8184*12000左右的图像上,我测量了以下时间 BuffereImage的创建时间约为20-40ms。 通过SwingFXUtils.toFXImage转换为JavaFX图像。。。需要超过700毫秒。这对我来说太过分了。 对图像进行编码并将其作为ByteArrayInputStream读取 我在这里发现的一种方法是使用OpenCVs功能将图像编码为类似*.bmp的格式,并直接从ByteArray构建JavaFX图像 这个解决方案需要更复杂的OpenCV或其他一些编码库/算法,编码似乎增加了额外的计算步骤 问题 所以我在寻找一种更有效的方法。大多数情况下,所有解决方案最终都会使用SwingfXutil,或者通过迭代所有像素来转换它们来解决问题,这是最慢的解决方案。 有没有一种方法可以实现比SwingFXUtils.toFXImage更高效的函数。。。或者直接从字节数组构造JavaFX映像。 也许还有一种方法,可以直接在JavaFX中绘制BuffereImage。因为JavaFX映像没有带来任何好处,它只会使事情变得复杂在JavaFX中显示原始字节数组图像(不带SwingFXUtils),java,arrays,image,performance,javafx,Java,Arrays,Image,Performance,Javafx,我目前正在做一个项目,我必须处理真正的大图像>>100mb。 图像的格式为原始字节数组,现在仅用于灰度图像,以后彩色图像的每个通道都有一个字节数组 我想在JavaFXImageView中显示图像。因此,我必须在尽可能短的时间内将给定的原始图像数据转换为JavaFX图像 我尝试了很多解决方案,我在这里和stackoverflow以及其他来源找到了这些解决方案 秋千 最流行、最简单的解决方案是从原始数据构造一个BuffereImage,并使用SwingFXUtils.toFXImage将其转换为Ja
感谢您的回复。跟进@James\u D在评论中的建议以及您对灰度图像的要求: 使用javafx.scene.image.PixelBuffer,但将javafx.scene.image.PixelFormat指定为 使用PixelFormat.createByteIndexedInstance或PixelFormat.CreateByteindexedEmultipleInstance将颜色i的调色板设置为RGB i、i、i或RGBA i、i、i、1.0
然后,您应该能够只输入字节灰度值的单通道数组。按照@James_D在评论中的建议和您对灰度图像的要求: 使用javafx.scene.image.PixelBuffer,但将javafx.scene.image.PixelFormat指定为 使用PixelFormat.createByteIndexedInstance或PixelFormat.CreateByteindexedEmultipleInstance将颜色i的调色板设置为RGB i、i、i或RGBA i、i、i、1.0
然后,您应该能够只输入字节灰度值的单通道数组。从注释中提取信息,似乎有两个可行的选项,都使用可写图像 在其中一种情况下,您可以使用PixelWriter设置图像中的像素,使用原始字节数据和字节索引的PixelFormat。下面的演示演示了这种方法。在我的系统上,生成实际字节数组数据需要约2.5秒;创建大的可写映像大约需要0.15秒,将数据绘制到映像大约需要0.12秒
import java.nio.ByteBuffer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
int width = 12000 ;
int height = 8184 ;
byte[] data = new byte[width*height];
int[] colors = new int[256];
for (int i = 0 ; i < colors.length ; i++) {
colors[i] = (255<<24) | (i << 16) | (i << 8) | i ;
}
PixelFormat<ByteBuffer> format = PixelFormat.createByteIndexedInstance(colors);
long start = System.nanoTime();
for (int y = 0 ; y < height ; y++) {
for (int x = 0 ; x < width; x++) {
long dist2 = (1L * x - width/2) * (x- width/2) + (y - height/2) * (y-height/2);
double dist = Math.sqrt(dist2);
double val = (1 + Math.cos(Math.PI * dist / 1000)) / 2;
data[x + y * width] = (byte)(val * 255);
}
}
long imageDataCreated = System.nanoTime();
WritableImage img = new WritableImage(width, height);
long imageCreated = System.nanoTime();
img.getPixelWriter().setPixels(0, 0, width, height, format, data, 0, width);
long imageDrawn = System.nanoTime() ;
ImageView imageView = new ImageView();
imageView.setPreserveRatio(true);
imageView.setImage(img);
long imageViewCreated = System.nanoTime();
BorderPane root = new BorderPane(imageView);
imageView.fitWidthProperty().bind(root.widthProperty());
imageView.fitHeightProperty().bind(root.heightProperty());
Scene scene = new Scene(root, 800, 800);
stage.setScene(scene);
stage.show();
long stageShowCalled = System.nanoTime();
double nanosPerMilli = 1_000_000.0 ;
System.out.printf(
"Data creation time: %.3f%n"
+ "Image Creation Time: %.3f%n"
+ "Image Drawing Time: %.3f%n"
+ "ImageView Creation Time: %.3f%n"
+ "Stage Show Time: %.3f%n",
(imageDataCreated-start)/nanosPerMilli,
(imageCreated-imageDataCreated)/nanosPerMilli,
(imageDrawn-imageCreated)/nanosPerMilli,
(imageViewCreated-imageDrawn)/nanosPerMilli,
(stageShowCalled-imageViewCreated)/nanosPerMilli);
}
public static void main(String[] args) {
launch();
}
}
另一种方法是使用像素缓冲区。PixelBuffer似乎不支持索引颜色,因此这里没有选项,只能将字节数组数据转换为表示ARGB值的数组数据。在这里,我使用字节缓冲符,其中rgb值作为字节重复,并且alpha始终设置为0xff:
显然,正如所写的那样,这种方法消耗了更多的内存。有一种方法可以创建ByteBuffer的自定义实现,该实现只需查看底层字节数组并生成正确的值,而无需冗余数据存储。根据具体的用例,例如,如果可以重用转换后的数据,这可能会更有效
从评论中提取信息,似乎有两个可行的选择,都使用可写映像 在其中一种情况下,您可以使用PixelWriter设置图像中的像素,使用原始字节数据和字节索引的PixelFormat。下面的演示演示了这种方法。在我的系统上,生成实际字节数组数据需要约2.5秒;创建大的可写映像大约需要0.15秒,将数据绘制到映像大约需要0.12秒
import java.nio.ByteBuffer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
int width = 12000 ;
int height = 8184 ;
byte[] data = new byte[width*height];
int[] colors = new int[256];
for (int i = 0 ; i < colors.length ; i++) {
colors[i] = (255<<24) | (i << 16) | (i << 8) | i ;
}
PixelFormat<ByteBuffer> format = PixelFormat.createByteIndexedInstance(colors);
long start = System.nanoTime();
for (int y = 0 ; y < height ; y++) {
for (int x = 0 ; x < width; x++) {
long dist2 = (1L * x - width/2) * (x- width/2) + (y - height/2) * (y-height/2);
double dist = Math.sqrt(dist2);
double val = (1 + Math.cos(Math.PI * dist / 1000)) / 2;
data[x + y * width] = (byte)(val * 255);
}
}
long imageDataCreated = System.nanoTime();
WritableImage img = new WritableImage(width, height);
long imageCreated = System.nanoTime();
img.getPixelWriter().setPixels(0, 0, width, height, format, data, 0, width);
long imageDrawn = System.nanoTime() ;
ImageView imageView = new ImageView();
imageView.setPreserveRatio(true);
imageView.setImage(img);
long imageViewCreated = System.nanoTime();
BorderPane root = new BorderPane(imageView);
imageView.fitWidthProperty().bind(root.widthProperty());
imageView.fitHeightProperty().bind(root.heightProperty());
Scene scene = new Scene(root, 800, 800);
stage.setScene(scene);
stage.show();
long stageShowCalled = System.nanoTime();
double nanosPerMilli = 1_000_000.0 ;
System.out.printf(
"Data creation time: %.3f%n"
+ "Image Creation Time: %.3f%n"
+ "Image Drawing Time: %.3f%n"
+ "ImageView Creation Time: %.3f%n"
+ "Stage Show Time: %.3f%n",
(imageDataCreated-start)/nanosPerMilli,
(imageCreated-imageDataCreated)/nanosPerMilli,
(imageDrawn-imageCreated)/nanosPerMilli,
(imageViewCreated-imageDrawn)/nanosPerMilli,
(stageShowCalled-imageViewCreated)/nanosPerMilli);
}
public static void main(String[] args) {
launch();
}
}
另一种方法是使用像素缓冲区。PixelBuffer似乎不支持索引颜色,因此这里没有选项,只能将字节数组数据转换为表示ARGB值的数组数据。在这里,我使用字节缓冲符,其中rgb值作为字节重复,并且alpha始终设置为0xff:
显然,正如所写的那样,这种方法消耗了更多的内存。可能有一种方法可以创建ByteBuffer的自定义实现,该实现只需查看底层字节数组并生成正确的va
没有冗余数据存储的lues。根据具体的用例,例如,如果可以重用转换后的数据,这可能会更有效
看一看可以传递给可写图像的。还可以看到PixelBuffer和PixelWrite似乎很有趣。但是,当我只能选择PixelFormat ByTebGraphResistence时,我如何转换单通道灰度图像,即长度=宽度*高度的字节数组,它有三个通道作为Iunderstand@Jakob:在灰度图像中,所有三个通道都有相同的值,因此单通道颜色x变成了颜色x,x,x。@KonradHöffner是的,但这是由像素写入器功能自动完成的吗?我不想修改图像的每个像素并添加额外的通道。此外,我还必须关心alpha通道,这是我的图像所没有的。当我从Bytebuffer(单通道8位图像)构造PixelBuffer时,我得到一个错误:java.lang.IllegalArgumentException:为Bytebuffer分配的内存不足请查看,您可以将其传递到可写图像。另请参阅PixelBuffer和PixelWrite,这似乎很有趣。但是,当我只能选择PixelFormat ByTebGraphResistence时,我如何转换单通道灰度图像,即长度=宽度*高度的字节数组,它有三个通道作为Iunderstand@Jakob:在灰度图像中,所有三个通道都有相同的值,因此单通道颜色x变成了颜色x,x,x。@KonradHöffner是的,但这是由像素写入器功能自动完成的吗?我不想修改图像的每个像素并添加额外的通道。此外,我还必须关心alpha通道,这是我的图像所没有的。当我从Bytebuffer(单通道8位图像)构造PixelBuffer时,我得到一个错误:java.lang.IllegalArgumentException:为Bytebuffer分配的内存不足谢谢你的回答。你能指出或建议我一些关于如何为我的颜色数组创建调色板的有用链接吗?单个整数中的压缩颜色格式对我来说是全新的。据我所知,我需要循环所有灰度值0到255,并用相应的灰度值填充当前颜色arry位置的值的三个分量。如何访问所需的组件,即整数中的字节?对于PixelBuffer,PixelBuffer字节_被索引unsupported@Jakob如果PixelBuffer不支持索引的字节\ U,则可以创建一个可写图像,并使用getPixelWriter.setPixels对其进行写入。。。谢谢你的回答。你能指出或建议我一些关于如何为我的颜色数组创建调色板的有用链接吗?单个整数中的压缩颜色格式对我来说是全新的。据我所知,我需要循环所有灰度值0到255,并用相应的灰度值填充当前颜色arry位置的值的三个分量。如何访问所需的组件,即整数中的字节?对于PixelBuffer,PixelBuffer字节_被索引unsupported@Jakob如果PixelBuffer不支持索引的字节\ U,则可以创建一个可写图像,并使用getPixelWriter.setPixels对其进行写入。。。相反,谢谢你!昨天晚上,我想出了如何实现您的第一种方法。但你的回答比我快,而且你的回答也比我的回答详细得多。谢谢你!。我会接受你的答案,也许会回来,当我尝试彩色图像时:D但是有点令人沮丧,使用JavaFX图像仍然比直接使用BuffereImage耗费更多的时间。@Jakob我想知道这样一种简单的暴力方法是否是正确的方法。处理超大图像的人通常使用其他技术来处理海量数据。通常情况下,您会构建一个图像金字塔并使用图像平铺。应该让你思考的是,你的屏幕是否可以同时显示8184*12000像素。谢谢!昨天晚上,我想出了如何实现您的第一种方法。但你的回答比我快,而且你的回答也比我的回答详细得多。谢谢你!。我会接受你的答案,也许会回来,当我尝试彩色图像时:D但是有点令人沮丧,使用JavaFX图像仍然比直接使用BuffereImage耗费更多的时间。@Jakob我想知道这样一种简单的暴力方法是否是正确的方法。处理超大图像的人通常使用其他技术来处理海量数据。通常情况下,您会构建一个图像金字塔并使用图像平铺。应该让你思考的是,你的屏幕是否可以同时显示8184*12000像素。