【问题标题】:Is it possible to read multiple images from an InputStream using Java ImageIO?是否可以使用 Java ImageIO 从 InputStream 读取多个图像?
【发布时间】:2018-11-26 09:24:28
【问题描述】:

我正在尝试创建一个 Kotlin 线程,它可以简单地从单个 InputStream 读取多个图像。

为了测试,我有一个输入流,它在一个单独的线程中接收两个小图像文件的内容。这似乎工作正常,就好像我将此输入流的内容写入磁盘一样,生成的文件与两个源图像文件的串联相同。

使用 ImageIO 从输入流中读取图像时出现问题:

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import javax.imageio.ImageIO;

class ImgReader {

    InputStream input;

    ImgReader(InputStream input) {
        this.input = input;
    }

    public void run() {
        ImageIO.setUseCache(false);
        System.out.println("read start");
        int counter = 1;
        try {
            BufferedImage im = ImageIO.read(input);
            System.out.println("read: " + counter + " " + (im != null));

            if (im != null)
                ImageIO.write(im, "jpg", new File("pics/out/" + (counter++) +".jpeg"));

        } catch (Exception e){
            System.out.println("error while reading stream");
            e.printStackTrace(System.out);
        }

        System.out.println("read done");
    }
}

这适用于第一张图像,它被正确接收并保存到文件中。但是,第二张图片没有被读取:ImageIO.read(input) 返回 null。

是否可以从InputStream 读取多个图像?我做错了什么?

--- 编辑 ---

我尝试了一种变体,其中仅从流中解码了一个图像(这是正确完成的)。在此之后,我尝试将其余的流内容保存到二进制文件中,而不是尝试将其解码为图像。第二个二进制文件是空的,这意味着第一个 ImageIO.read 似乎消耗了整个流。

【问题讨论】:

  • 虽然ImageIO.read 不会关闭InputStream,但它不一定位于最后一张图片的开头。
  • 能否请您用 Java 重写您的示例。问题不在 Kotlin 中,而在 Java 中使用此代码可以让更多人理解它并为您提供帮助。
  • 您必须以某种方式获取 ImageIO 读取的字节并在复制的 InputStream 中跳过它们
  • 如果我理解正确,您将两个图像写入同一个文件(一个接一个,连接在一起),然后尝试将其读回,就好像这些文件已保存为单个文件一样?是否有支持读取这种连接图像的 Kotlin Lib?如果您有时间创建一个 poc github 项目,让我们有更好的了解,也许最好的办法是。
  • 严格来说,这里的问题不是ImageIO 造成的:通过一些调整,可以看到底层的ImageReader 在这里负责。例如,JPEGImageReader 确实 似乎读取了整个流,而PNGImageReader 似乎只(大致)读取了决定(第一个)图像所需的数据。由于无法阻止ImageReader 这样做,也无法检测输入中的字节是否已被“用于”图像,恐怕这是不可能的。不过,有趣的问题,+1

标签: java kotlin javax.imageio


【解决方案1】:

是的,可以从(单个)InputStream 读取多个图像。

我认为最明显的解决方案是使用已广泛支持多张图像的文件格式,例如 TIFF。 javax.imageio API 对读取和写入多图像文件有很好的支持,即使 ImageIO 类没有任何方便的方法,例如用于读取/写入单个图像的 ImageIO.read(...)/ImageIO.write(...) 方法。这意味着您需要编写更多代码(下面的代码示例)。

但是,如果输入是由您无法控制的第三方创建的,则可能无法选择使用不同的格式。从 cmets 中可以看出,您的输入实际上是串联的 Exif JPEG 流。好消息是 Java 的 JPEGImageReader/Writer 确实允许在同一流中使用多个 JPEG,尽管这不是一种非常常见的格式。

要从同一个流中读取多个 JPEG,您可以使用以下示例(请注意,代码是完全通用的,也适用于读取其他多图像文件,例如 TIFF):

File file = ...; // May also use InputStream here
List<BufferedImage> images = new ArrayList<>();

try (ImageInputStream in = ImageIO.createImageInputStream(file)) {
    Iterator<ImageReader> readers = ImageIO.getImageReaders(in);

    if (!readers.hasNext()) {
        throw new AssertionError("No reader for file " + file);
    }

    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) {
            images.add(reader.read(i++));
        }
    }
    catch (IndexOutOfBoundsException expected) {
        // We're done
    }

    reader.dispose();
}   

这条线以下的任何内容都只是额外的额外信息。

以下是使用 ImageIO API编写多图像文件的方法(代码示例使用 TIFF,但它非常通用,理论上应该也适用于除压缩类型之外的其他格式范围)。

File file = ...; // May also use OutputStream/InputStream here
List<BufferedImage> images = new ArrayList<>(); // Just add images...

Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("TIFF");

if (!writers.hasNext()) {
    throw new AssertionError("Missing plugin");
}

ImageWriter writer = writers.next();

if (!writer.canWriteSequence()) {
    throw new AssertionError("Plugin doesn't support multi page file");       
}

ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("JPEG"); // The allowed compression types may vary from plugin to plugin
// The most common values for TIFF, are NONE, LZW, Deflate or Zip, or JPEG

try (ImageOutputStream out = ImageIO.createImageOutputStream(file)) {
    writer.setOutput(out);

    writer.prepareWriteSequence(null); // No stream metadata needed for TIFF

    for (BufferedImage image : images) {
        writer.writeToSequence(new IIOImage(image, null, null), param);
    }

    writer.endWriteSequence();
}

writer.dispose();

请注意,在 Java 9 之前,您还需要第三方 TIFF 插件,例如 JAI 或我自己的 TwelveMonkeys ImageIO,才能使用 ImageIO 读取/写入 TIFF。


如果您真的不喜欢编写这种冗长的代码,另一种选择是将图像包装在您自己的最小容器格式中,其中包括(至少)每个图像的长度。然后你可以用ImageIO.write(...)写,用ImageIO.read(...)读,但是你需要围绕它实现一些简单的流逻辑。当然,反对它的主要论点是它将完全是专有的。

但是,如果您在类似客户端/服务器的设置中异步读取/写入(我怀疑,根据您的问题),这可能是完全合理的,并且可能是一个可以接受的权衡。

类似:

File file = new File(args[0]);
List<BufferedImage> images = new ArrayList<>();

try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024 * 1024); // Use larger buffer for large images

    for (BufferedImage image : images) {
        buffer.reset();

        ImageIO.write(image, "JPEG", buffer); // Or PNG or any other format you like, really

        out.writeInt(buffer.size());
        buffer.writeTo(out);
        out.flush();
    }

    out.writeInt(-1); // EOF marker (alternatively, catch EOFException while reading)
}

// And, reading back:
try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
    int size;

    while ((size = in.readInt()) != -1) {
        byte[] buffer = new byte[size];
        in.readFully(buffer); // May be more efficient to create a FilterInputStream that counts bytes read, with local EOF after size

        images.add(ImageIO.read(new ByteArrayInputStream(buffer)));
    }
}

PS:如果您只想将收到的图像写入磁盘,则不应为此使用ImageIO。相反,使用普通 I/O(假设前面示例中的格式):

try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
    int counter = 0;

    int size;        
    while ((size = in.readInt()) != -1) {
        byte[] buffer = new byte[size];
        in.readFully(buffer);

        try (FileOutputStream out = new FileOutputStream(new File("pics/out/" + (counter++) +".jpeg"))) {
            out.write(buffer);
            out.flush();
        }
    }
}

【讨论】:

  • 感谢您的回答。因此,从单个流中读取多个图像适用于 TIFF 文件。我想具有多个图像的 TIFF 文件具有标题,基本上与您在上一个示例中所做的相同(将文件长度包含到流中)。在我看来,我的问题略有不同(可能解释得不够好),因为我有一个输入流,它接收单独的图像文件(不是单个 TIFF),并且我无法操作(包括图像长度)。
  • @EloyVillasclaras 好的......所以,这让事情变得更难了。你知道这些图像的格式吗?他们总是JPEG吗?还是总是PNG?还是随机的?
  • 我正在尝试从 raspistill 延时摄影中读取标准输出流。图片为EXIF/JPG。所以问题似乎是JPG阅读器消耗了整个流来解码第一张图像:S.
  • 抱歉,您的哪一点代码现在应该适用于 JPG 图片?你是说最后一个吗?
  • 我可以确认它也适用于来自 raspistill timelapse 的输入流。在您的代码中,您使用ImageIO.createImageInputStream(file),但它也适用于ImageIO.createImageInputStream(inputStream)。之所以可行,是因为 ImageIO 使用您的代码创建了一个 MemoryCacheImageInputStreamFileCacheImageInputStream,它允许读者返回寻找下一张图像。
【解决方案2】:

这是输入流的一个众所周知的“特性”。

输入流只能读取一次(好吧,有 mark() 和 reset(),但不是每个实现都支持它(检查 Javadoc 中的 markSupported()),恕我直言,使用起来不太方便),你应该保留您的图像并将路径作为参数传递,或者您应该将其读取到字节数组并为您尝试读取它的每个调用创建一个 ByteArrayInputStream:

// read your original stream once (e.g. with commons IO, just the sake of shortness)
byte[] imageByteArray = IOUtils.toByteArray(input);
...
// and create new input stream every time
InputStream newInput = new ByteArrayInputStream(imageByteArray);
...
// and call your reader in this way:
new ImgReader(newInput);

【讨论】:

  • 然后你继续阅读同一张图片,问题是是否可以从一个流中读取多张图片。
  • 这是他问题的第二部分 :)
  • 我无法将整个流保存到一个数组中,因为这会删除在第二个图像到达之前读取第一个图像的选项(第二个可能会延迟),依此类推。
【解决方案3】:

更新:

向下滚动到最后一个代码 sn-p 以更新此答案。

这不是一个满意的答案,而是一个问题的答案:

不,这(几乎可以肯定)不可能。

当将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 实例并挂钩到其读取过程,以检查它何时真正完成了第一张图像。但同样,输入流在内部被包装到其他几个(缓冲和放气)输入流中,并且没有办法明智地检测某个字节集是否已经“用于”图像。

所以我认为这里唯一明智的解决方案是在读取图像后关闭流,并为下一张图像打开一个新流。


已在 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);
        });
    }
}

【讨论】:

  • 我发现 JPG 的结果相同。另一种看待这个问题的方式是,将图像附加到没有任何元数据的流的通信协议实际上没有意义。要真正实现这一点,源必须添加一些元数据。简单地流式传输包含图像长度的整数(或长整数)在实际图像数据之前将允许通过流发送多个图像。
  • 我认为这是正确的(即使有点令人失望)答案,所以我会将其标记为已接受。如果稍后发布一个可行的解决方案,我会更改接受的答案。
  • @EloyVillasclaras 当然可以!以length(int) + data[length] 的形式发送数据将允许您在接收端读取长度,然后只需将length 字节读取到数组中,然后将其传递给ImageIO 以读取图像。这将允许保持流打开。这也应该很简单。也许我可以分配一些时间来使用这种解决方案扩展测试。
  • @Marco13 我认为这很好地解释了为什么问题中的代码不起作用。但我不同意这个结论,所以我添加了我自己的答案。 :-)
  • @haraldK 当然,如果可以使用像 TIFF 这样的“多图像容器”,这将是一种选择。我们讨论过的以及您也提到的length+data[length] 的使用对我来说似乎更可行。但也许这个问题的限制还没有(还)被排序。让我们看看您的评论讨论会产生什么结果。
猜你喜欢
  • 2023-04-06
  • 2010-10-22
  • 2015-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
相关资源
最近更新 更多