This would only be possible if you have only uncompressed image data (like BMP, TIFF, TGA or PBM file formats).
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.
- bytes 3 to 6: image file size in bytes (because the file size will increase)
- bytes 19 to 26: image width and height (because this will change the height and/or width in your new image)
Some more information you need:
- bytes 11 to 14: reflects the start position of the data content
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).
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.
There are probably some more bytes, you have to set in the resulting BMP format file header. For further reading of the byte header structure see: 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. In the second header it is set to 0A 00 00 00
, which is hexadecimal and means 10 pixels. 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).
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.
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. For your purpose, you probably have to use the method concatBMPImageData
directly instead of concatBMPFiles
. Let me know, if this works for you, too!