更新:
向下滚动到最后一个代码 sn-p 以更新此答案。
这不是一个满意的答案,而是一个问题的答案:
不,这(几乎可以肯定)不可能。
当将InputStream 传递给ImageIO 时,它会在内部被包装成ImageInputStream。然后将此流传递给ImageReader。确切的实现将取决于图像数据的类型。 (这通常由“魔术头”确定,即输入数据的前几个字节)。
现在,这些ImageReader 实现的行为无法合理地更改或控制。 (对于其中一些人来说,实际阅读甚至发生在native 方法中)。
以下是显示不同行为的示例:
_
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class MultipleImagesFromSingleStream
{
public static void main(String[] args) throws IOException
{
readJpgAndPng();
readPngAndJpg();
}
private static void readJpgAndPng() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(createDummyImage("Image 0", 50), "jpg", baos);
ImageIO.write(createDummyImage("Image 1", 60), "png", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
BufferedImage image0 = ImageIO.read(inputStream);
System.out.println("Read " + image0);
BufferedImage image1 = ImageIO.read(inputStream);
System.out.println("Read " + image1);
}
private static void readPngAndJpg() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(createDummyImage("Image 0", 50), "png", baos);
ImageIO.write(createDummyImage("Image 1", 60), "jpg", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
BufferedImage image0 = ImageIO.read(inputStream);
System.out.println("Read " + image0);
BufferedImage image1 = ImageIO.read(inputStream);
System.out.println("Read " + image1);
}
private static InputStream createSlowInputStream(byte data[])
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return new InputStream()
{
private long counter = 0;
@Override
public int read() throws IOException
{
counter++;
if (counter % 100 == 0)
{
System.out.println(
"Read " + counter + " of " + data.length + " bytes");
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
return bais.read();
}
};
}
private static BufferedImage createDummyImage(String text, int h)
{
int w = 100;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.WHITE);
g.drawString(text, 20, 20);
g.dispose();
return image;
}
}
输出如下:
Read 100 of 1519 bytes
Read 200 of 1519 bytes
Read 300 of 1519 bytes
Read 400 of 1519 bytes
Read 500 of 1519 bytes
Read 600 of 1519 bytes
Read 700 of 1519 bytes
Read 800 of 1519 bytes
Read 900 of 1519 bytes
Read 1000 of 1519 bytes
Read 1100 of 1519 bytes
Read 1200 of 1519 bytes
Read 1300 of 1519 bytes
Read 1400 of 1519 bytes
Read 1500 of 1519 bytes
Read BufferedImage@3eb07fd3: type = 0 DirectColorModel: rmask=ff000000 gmask=ff0000 bmask=ff00 amask=ff IntegerInterleavedRaster: width = 100 height = 50 #Bands = 4 xOff = 0 yOff = 0 dataOffset[0] 0
Read null
Read 100 of 1499 bytes
Read 200 of 1499 bytes
Read BufferedImage@42110406: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@531d72ca transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 100 height = 50 #numDataElements 4 dataOff[0] = 3
Read null
请注意,虽然在第二种情况下它不会读取完整的流,但这仍然不必然意味着输入流位于“JPG数据的开头”。这只意味着它没有读取完整的流!
我也试图深入研究这一点。 如果可以确定图像始终只是 PNG 图像,可以尝试手动创建 PNGImageReader 实例并挂钩到其读取过程,以检查它何时真正完成了第一张图像。但同样,输入流在内部被包装到其他几个(缓冲和放气)输入流中,并且没有办法明智地检测某个字节集是否已经“用于”图像。
所以我认为这里唯一明智的解决方案是在读取图像后关闭流,并为下一张图像打开一个新流。
已在 cmets 中讨论的解决方法是向流中添加长度信息。这意味着图像数据的生产者首先将int写入流中,描述图像数据的长度。然后它将byte[length]数据与实际图像数据一起写入。
然后接收器可以使用此信息来加载单个图像。
这里以实现为例:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class MultipleImagesFromSingleStreamWorkaround
{
public static void main(String[] args) throws IOException
{
workaround();
}
private static void workaround() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
write(createDummyImage("Image 0", 50), "jpg", baos);
write(createDummyImage("Image 1", 60), "png", baos);
write(createDummyImage("Image 2", 70), "gif", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
BufferedImage image0 = read(inputStream);
System.out.println("Read " + image0);
BufferedImage image1 = read(inputStream);
System.out.println("Read " + image1);
BufferedImage image2 = read(inputStream);
System.out.println("Read " + image2);
showImages(image0, image1, image2);
}
private static void write(BufferedImage bufferedImage,
String formatName, OutputStream outputStream) throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, formatName, baos);
byte data[] = baos.toByteArray();
DataOutputStream dos = new DataOutputStream(outputStream);
dos.writeInt(data.length);
dos.write(data);
dos.flush();
}
private static BufferedImage read(
InputStream inputStream) throws IOException
{
DataInputStream dis = new DataInputStream(inputStream);
int length = dis.readInt();
byte data[] = new byte[length];
dis.read(data);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return ImageIO.read(bais);
}
private static InputStream createSlowInputStream(byte data[])
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return new InputStream()
{
private long counter = 0;
@Override
public int read() throws IOException
{
counter++;
if (counter % 100 == 0)
{
System.out.println(
"Read " + counter + " of " + data.length + " bytes");
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
return bais.read();
}
};
}
private static BufferedImage createDummyImage(String text, int h)
{
int w = 100;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.WHITE);
g.drawString(text, 20, 20);
g.dispose();
return image;
}
private static void showImages(BufferedImage ... images)
{
SwingUtilities.invokeLater(() ->
{
JFrame f = new JFrame();
f.getContentPane().setLayout(new GridLayout(1,0));
for (BufferedImage image : images)
{
f.getContentPane().add(new JLabel(new ImageIcon(image)));
}
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
更新
这是基于the answer from haraldK(点赞他的答案,而不是这个!)
展示 haraldK 提出的方法的示例实现。它设法读取一系列图像,尽管有一些限制:
- 在交付第一张图像之前,它似乎必须读取比严格必要的“更多”字节。
- 它无法加载不同类型的图像(即它无法读取混合的 PNG 和 JPG 图像序列)
- 具体来说,它似乎只适用于我的 JPG 图像。对于 PNG 或 GIF,只读取了第一张图片(至少对我来说......)
但是,将其发布在这里以供其他人轻松测试:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class MultipleImagesFromSingleStreamWorking
{
public static void main(String[] args) throws IOException
{
readExample();
}
private static void readExample() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(createDummyImage("Image 0", 50), "jpg", baos);
//ImageIO.write(createDummyImage("Image 1", 60), "png", baos);
ImageIO.write(createDummyImage("Image 2", 70), "jpg", baos);
ImageIO.write(createDummyImage("Image 3", 80), "jpg", baos);
ImageIO.write(createDummyImage("Image 4", 90), "jpg", baos);
ImageIO.write(createDummyImage("Image 5", 100), "jpg", baos);
ImageIO.write(createDummyImage("Image 6", 110), "jpg", baos);
ImageIO.write(createDummyImage("Image 7", 120), "jpg", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
List<BufferedImage> images = readImages(inputStream);
showImages(images);
}
private static List<BufferedImage> readImages(InputStream inputStream)
throws IOException
{
// From https://stackoverflow.com/a/53501316/3182664
List<BufferedImage> images = new ArrayList<BufferedImage>();
try (ImageInputStream in = ImageIO.createImageInputStream(inputStream))
{
Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
if (!readers.hasNext())
{
throw new AssertionError("No reader for file " + inputStream);
}
ImageReader reader = readers.next();
reader.setInput(in);
// It's possible to use reader.getNumImages(true) and a for-loop
// here.
// However, for many formats, it is more efficient to just read
// until there's no more images in the stream.
try
{
int i = 0;
while (true)
{
BufferedImage image = reader.read(i++);
System.out.println("Read " + image);
images.add(image);
}
}
catch (IndexOutOfBoundsException expected)
{
// We're done
}
reader.dispose();
}
return images;
}
private static InputStream createSlowInputStream(byte data[])
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return new InputStream()
{
private long counter = 0;
@Override
public int read() throws IOException
{
counter++;
if (counter % 100 == 0)
{
System.out.println(
"Read " + counter + " of " + data.length + " bytes");
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
return bais.read();
}
};
}
private static BufferedImage createDummyImage(String text, int h)
{
int w = 100;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.WHITE);
g.drawString(text, 20, 20);
g.dispose();
return image;
}
private static void showImages(List<BufferedImage> images)
{
SwingUtilities.invokeLater(() ->
{
JFrame f = new JFrame();
f.getContentPane().setLayout(new GridLayout(1,0));
for (BufferedImage image : images)
{
f.getContentPane().add(new JLabel(new ImageIcon(image)));
}
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}