连接图像字节数组

时间:2013-12-18 09:26:34

标签: java bytearray bufferedimage

我已将byte arrays存储在数据库中,之前已从BufferedImage转换为:

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

使用这些图像我想创建纹理图集(即精灵图纸)。最天真的解决方案是将每个byte[]转换回BufferedImage(约500k次),将它们写入纹理图集,完成后转换纹理图集“BufferedImagebyte[]回来。

我想最优雅的方法就是连接所有byte arrays,但我怀疑它会起作用(有标题等),还是它?

2 个答案:

答案 0 :(得分:2)

“天真”的方式也是唯一的方式,也是一种好方法。我不知道你为什么不想这样做。

  

我想最优雅的方法是连接所有字节数组,但我怀疑它会起作用(有标题等) - 或者它会不会? -

它不会更优雅,只有当图像最​​初以一些非常原始的原始或无标题位图格式保存时才会起作用。

答案 1 :(得分:2)

仅当您只有未压缩的图像数据(如BMP,TIFF,TGA或PBM文件格式)时才可以这样做。

假设您有一大堆BMP文件(在3D编程中很常见),您必须更改标题中的一些字节。

  • bytes 3到6 :图像文件大小(以字节为单位)(因为文件大小会增加)
  • bytes 19到26 :图片宽度和高度(因为这会改变新图片的高度和/或宽度)

您需要的更多信息:

  • bytes 11到14 :反映数据内容的起始位置

此外,您必须正确设置内容的字节顺序。如果你想将你的图像连接在另一个之下,那么你只需要将第二个图像的字节连接到第一个(除了标题的字节;内容通常以字节 54 开始,请查看位置的字节 11到14

出于可视化目的:

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

如果要将它们连接到右侧,则必须读取每个图像的每个第一行并将它们连接起来。然后第二行等等......:

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

为了您的目的,我建议将它们放在彼此之下。

然后你必须要知道,图像的图像字节顺序是相反的(从底部的第一个像素开始,然后是从底部开始的第二个像素,依此类推......)。 还有一个可能的填充字节列,请参阅http://en.wikipedia.org/wiki/BMP_file_format#Pixel_storage

可能还有一些字节,您必须在生成的BMP格式文件头中进行设置。要进一步读取字节头结构,请参阅:http://en.wikipedia.org/wiki/BMP_file_format#File_structure

第一张图片的字节可能是这样的(只有红色像素):

-- 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                                          ......

这可能是两者的串联字节顺序(假设第二个图像只包含蓝色像素):

-- 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                                          ......

(明星很重要* ... *) 如您所见,在第一张图片的标题中,您可以找到05 00 00 00的大小,这意味着5个像素的高度。在第二个标题中,它设置为0A 00 00 00,它是十六进制的,表示10个像素。宽度由它们前面的4个字节表示(在这种情况下不会被修改,因为宽度将是相同的)。 如果将这两个字节顺序与BMP文件头和内容的描述进行比较,您可以想象,如何正确设置字节。

由于我对自己非常感兴趣,如何做到这一点,我已经编写了一个示例程序来完成任务:

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;
    }
}

这只是第一个版本,适用于简单的BMP文件。出于您的目的,您可能必须直接使用concatBMPImageData方法而不是concatBMPFiles。让我知道,如果这对你有用!