简体   繁体   English

连接图像字节数组

[英]Concatenating image byte arrays

I have byte arrays stored in the database, previously converted from BufferedImage with: 我有数据库中存储的byte arrays ,以前从BufferedImage转换为:

ImageIO.write(bufferedImage, extension, bao);
return bao.toByteArray();

With those images I want to create a texture atlas (ie a sprite sheet). 有了这些图像,我想创建一个纹理图集(即精灵图纸)。 The most naive solution would be to convert every single byte[] back to a BufferedImage (about 500k times), write them to the texture atlas, and when done, convert the texture atlas' BufferedImage to byte[] back. 最天真的解决方案是将每个byte[]转换回BufferedImage (大约500k次),将它们写入纹理图集,完成后,将纹理图集' BufferedImage '转换回byte[]

I guess the most elegant way would be to just concatenate all byte arrays , yet I doubt that would work (with headers etc.), or would it? 我想最优雅的方法是连接所有byte arrays ,但我怀疑它会起作用(有标题等),还是会?

This would only be possible if you have only uncompressed image data (like BMP, TIFF, TGA or PBM file formats). 只有在您只有未压缩的图像数据(如BMP,TIFF,TGA或PBM文件格式)时才可以这样做。

Assuming you have a whole bunch of BMP files (which is in 3D programming very common), you have to change some bytes in the header. 假设你有一大堆BMP文件(在3D编程中很常见),你必须改变标题中的一些字节。

  • bytes 3 to 6 : image file size in bytes (because the file size will increase) 字节3到6 :图像文件大小(以字节为单位)(因为文件大小会增加)
  • bytes 19 to 26 : image width and height (because this will change the height and/or width in your new image) 字节19到26 :图像宽度和高度(因为这将改变新图像中的高度和/或宽度)

Some more information you need: 您需要的更多信息:

  • bytes 11 to 14 : reflects the start position of the data content 字节11到14 :反映数据内容的开始位置

Furthermore, you have to set your byte order of the content correctly. 此外,您必须正确设置内容的字节顺序。 If you want to concatenate your images one below another, than you only have to concatenate the bytes of the second image to the first (except the bytes of the header; content starts usually with byte 54 , look at the bytes 11 to 14 for position). 如果你想将你的图像连接在另一个之下,那么你只需要将第二个图像的字节连接到第一个(除了标题的字节;内容通常用字节54开始,查看位置的字节11到14) )。

For visualization purpose: 出于可视化目的:

#########
#########
######### Image 1
#########
#########
=========
=========
========= Image 2
=========
=========

If you want to concatenate them to the right, you have to read each first line of each image and concatenate them. 如果要将它们连接到右侧,则必须读取每个图像的每个第一行并将它们连接起来。 Then the second line and so on...: 然后第二行等等......:

 Image 1  Image 2
#########=========
#########=========
#########=========
#########=========
#########=========

For your purpose i recommend putting them below each other. 为了您的目的,我建议将它们放在彼此之下。

Then you have to be aware, that the image byte orders of the images are reversed (beginning with the first pixel from the bottom, then the second pixel from the bottom, and so on...). 然后你必须要知道,图像的图像字节顺序是相反的(从底部的第一个像素开始,然后是从底部开始的第二个像素,依此类推......)。
Also there is a possible padding byte column, see http://en.wikipedia.org/wiki/BMP_file_format#Pixel_storage . 还有一个可能的填充字节列,请参阅http://en.wikipedia.org/wiki/BMP_file_format#Pixel_storage

There are probably some more bytes, you have to set in the resulting BMP format file header. 可能还有一些字节,您必须在生成的BMP格式文件头中进行设置。 For further reading of the byte header structure see: http://en.wikipedia.org/wiki/BMP_file_format#File_structure 有关字节头结构的进一步读取,请参阅: http//en.wikipedia.org/wiki/BMP_file_format#File_structure

The bytes of the first image could be something like this (only red color pixels): 第一个图像的字节可能是这样的(只有红色像素):

-- Header ----------------------------------------------------------------------
42  4D *86  00  00  00* 00  00  00  00 *36  00  00  00* 28  00  BM........6...(.
00  00  05  00  00  00 *05  00  00  00* 01  00  18  00  00  00  ................
00  00  50  00  00  00  00  00  00  00  00  00  00  00  00  00  ..P.............
00  00  00  00  00  00                                          ......
-- Body / Content --------------------------------------------------------------
                         v---v---v---< red pixel RGB FF,00,00 in reverse >
      < Padding >---v   00  00  FF  00  00  FF  00  00  FF  00        ..........
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00                                          ......

And this could be a concatenated byte order of both (assuming the second image only consists of blue pixels): 这可能是两者的串联字节顺序(假设第二个图像仅由蓝色像素组成):

-- Header ----------------------------------------------------------------------
42  4D  *D6 00  00  00* 00  00  00  00 *36  00  00  00* 28  00  BM........6...(.
00  00  05  00  00  00 *0A  00  00  00* 01  00  18  00  00  00  ................
00  00  A0  00  00  00  00  00  00  00  00  00  00  00  00  00  ................
00  00  00  00  00  00                                          ......
-- Body / Content --------------------------------------------------------------
                         v---v---v---< blue pixel RGB 00,00,FF in reverse >
      < Padding >---v   FF  00  00  FF  00  00  FF  00  00  FF        ..........
00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  00  FF  ................
00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  00  FF  ................
00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  00  FF  ................
00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  00  FF  ................
00  00  FF  00  00  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00  00  00  FF  00  00  FF  00  00  FF  00  ................
00  FF  00  00  FF  00                                          ......

(marked important with stars *...*) As you can see, in the header of the first image you can find the size of 05 00 00 00 which means 5 pixels of height. (标记为星星很重要* ... *)如您所见,在第一张图像的标题中,您可以找到05 00 00 00的大小,这意味着5个像素的高度。 In the second header it is set to 0A 00 00 00 , which is hexadecimal and means 10 pixels. 在第二个标题中,它设置为0A 00 00 00 ,它是十六进制的,表示10个像素。 The width is represented by the 4 bytes in front of them (which are in this case not modified, since the width will be the same). 宽度由它们前面的4个字节表示(在这种情况下不会被修改,因为宽度将是相同的)。 If you compare these two byte orders with the description of the BMP file header and content, you could imagine, how you have to set your bytes correctly. 如果将这两个字节顺序与BMP文件头和内容的描述进行比较,您可以想象,如何正确设置字节。

Because of I was very interested by myself, how this can be done, I've written an example programm to do the task: 因为我对自己非常感兴趣,如何做到这一点,我编写了一个示例程序来完成任务:

import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.imageio.ImageIO;

public class ConcatImages {
    private static final int POS_FILE_SIZE = 2;
    private static final int POS_START_CONTENT = 10;
    private static final int POS_WIDTH = 18;
    private static final int POS_HEIGHT = 22;

    public static void main(final String[] args) throws IOException, IllegalAccessException {
        final String[] files = {
            "image1.bmp", "image2.bmp", "image3.bmp"
        };

        concatBMPFiles(files, "result_image.bmp");
    }

    private static void concatBMPFiles(final String[] filenames, final String resultFilename) throws IOException, IllegalAccessException {
        final byte[][] fileContents = new byte[filenames.length][];

        int i = 0;

        for (final String file : filenames) {
            fileContents[i++] = readImageBytes(file);
        }

        final byte[] result = concatBMPImageData(fileContents);

        final OutputStream out = new BufferedOutputStream(new FileOutputStream(resultFilename));
        out.write(result);
        out.close();
    }

    private static byte[] concatBMPImageData(final byte[] ... imageDatas) throws IllegalAccessException {
        int newFileSize = 0;
        int newHeight = 0;
        int compWidth = -1;

        for (final byte[] imageData : imageDatas) {
            if (compWidth > -1) {
                // remove header length for all images, except the first
                newFileSize -=  getInt(imageData, POS_START_CONTENT);

                if (compWidth != getInt(imageDatas[0], POS_WIDTH)) {
                    throw new IllegalAccessException("All images must have the same width!");
                }
            } else {
                compWidth = getInt(imageDatas[0], POS_WIDTH);
            }

            newHeight += getInt(imageData, POS_HEIGHT);
            newFileSize += imageData.length;
        }

        newFileSize += getInt(imageDatas[0], POS_START_CONTENT);

        final byte[] result = new byte[newFileSize];
        int idx = 0;

        // read header from first image
        for (int i = 0; i < getInt(imageDatas[0], POS_START_CONTENT); i++) {
            result[idx++] = imageDatas[0][i];
        }

        // read content from all images
        for (int fIdx = imageDatas.length - 1; fIdx >= 0; fIdx--) {
            final int startContentDest = getInt(imageDatas[fIdx], POS_START_CONTENT);

            for (int i = startContentDest; i < imageDatas[fIdx].length; i++) {
                result[idx++] = imageDatas[fIdx][i];
            }
        }

        // set new file size to header
        setInt(result, POS_FILE_SIZE, newFileSize);

        // set new height to header
        setInt(result, POS_HEIGHT, newHeight);

        return result;
    }

    private static byte[] readImageBytes(final String filename) throws IOException {
        final BufferedImage image = ImageIO.read(new File(filename));
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        ImageIO.write(image, "bmp", baos);

        return baos.toByteArray();
    }

    private static int getInt(byte[] src, int start) {
        return ((0xFF & src[start + 3]) << 24) |
            ((0xFF & src[start + 2]) << 16) |
            ((0xFF & src[start + 1]) << 8) |
            (0xFF & src[start]);
    }

    private static void setInt(byte[] src, int start, int newValue) {
        byte[] value = intToByteArr(newValue);

        src[start] = value[3];
        src[start + 1] = value[2];
        src[start + 2] = value[1];
        src[start + 3] = value[0];
    }

    private static byte[] intToByteArr(int value) {
        byte[] result = new byte[4];

        for (int i = 0; i < 4; i++) {
            int shift = i << 3;
            result[3 - i] = (byte) ((value & (0xff << shift)) >>> shift);
        }

        return result;
    }
}

This is just a first version and it works for simple BMP files. 这只是第一个版本,适用于简单的BMP文件。 For your purpose, you probably have to use the method concatBMPImageData directly instead of concatBMPFiles . 出于您的目的,您可能必须直接使用concatBMPImageData方法而不是concatBMPFiles Let me know, if this works for you, too! 让我知道,如果这对你有用!

The "naive" way is also the only way, and a good way. “天真”的方式也是唯一的方式,也是一种好方法。 I don't know why you don't want to do that. 我不知道你为什么不想这样做。

I guess the most elegant way would be to just concatenate all byte arrays, yet I doubt that would work (with headers etc.) - or would it? 我想最优雅的方法是连接所有字节数组,但我怀疑它会起作用(有标题等) - 或者它会吗?

It would not really be more elegant, and it would only work if the images were originally saved in some extremely primitive raw or headerless bitmap format. 它实际上并不会更优雅,只有当图像最​​初以一些非常原始的原始或无头位图格式保存时才会起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM