Обновление:
Прокрутите вниз до последнего фрагмента кода для обновления этого ответа.
Это не удовлетворительный ответ, а ответна вопрос:
Нет, это (почти наверняка) невозможно.
При передаче InputStream
в ImageIO
он будет внутреннебыть завернутым в ImageInputStream
.Этот поток затем передается в ImageReader
.Точная реализация будет зависеть от типа данных изображения.(Обычно это определяется из «магического заголовка», т. Е. Первых нескольких байтов входных данных).
Теперь поведение этих реализаций ImageReader
не может быть разумно изменено или проконтролировано.(Для некоторых из них фактическое чтение происходит даже в native
методах).
Ниже приведен пример, демонстрирующий различные варианты поведения:
Сначала создается поток ввода, содержащий одно изображение JPG и одно изображение PNG.Выходные данные показывают, что входной поток считывается полностью до возвращения изображения JPG.
Затем он генерирует входной поток, содержащий одно изображение PNG и одно изображение JPG.Видно, что он читает только несколько байтов, пока не сможет декодировать результат первого изображения PNG.
_
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
и подключиться к его процессу чтения, чтобы проверить, когда он фактически закончил первое изображение.Но опять же, входной поток внутренне упакован в несколько других (буферизованных и дефлируемых) входных потоков, и нет никакого способа разумно определить, был ли определенный набор байтов уже «использован» для изображения.
Поэтому я думаю, что единственное разумное решение здесь - это закрыть поток после того, как изображение было прочитано, и открыть новый поток для следующего изображения.
Обходной путь, который обсуждался в комментарияхэто добавить информацию о длине в поток.Это означает, что производитель данных изображения сначала записывает 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);
});
}
}
Обновление
Это основано на ответе от 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);
});
}
}