Обновлять:
Прокрутите вниз до последнего фрагмента кода, чтобы узнать об обновлении этого ответа.
Это не удовлетворительный ответ, а ответ на вопрос:
Нет, это (почти наверняка) невозможно.
При передаче 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);
});
}
}
person
Marco13
schedule
26.11.2018
ImageIO.read
не закрываетInputStream
, он не обязательно располагается в начале следующего изображения в конце. - person Maurice Perry   schedule 26.11.2018ImageIO
: с некоторыми изменениями можно увидеть, что здесь виноват лежащий в основеImageReader
. Например,JPEGImageReader
действительно читает весь поток, в то время какPNGImageReader
читает (примерно) только те данные, которые необходимы для определения (первого) изображения. Поскольку нет способа предотвратить это отImageReader
и нет способа определить, использовались ли уже байты из ввода для изображения, я боюсь, что это невозможно. Интересный вопрос, однако, +1 - person Marco13   schedule 26.11.2018