Wie kann man mehrere Bilddateien in einem *.tif speichern?

Das Bildformat *.tif ist in seiner enormen Komplexität u.a. auch in der Lage mehrere Bilder in einer Datei abzuspeichern. Mit Hilfe der JAI-Bibliothek ist dies auch in Java möglich.
Das Beispiel demonstriert, wie mehrere *.tif-Dateien in einer einzigen zusammengeführt werden können.

Das Abspeichern mehrerer Bilder in einer einzigen *.tif-Datei muss über einen Zwischenschritt erfolgen, bei dem die Einzeldateien in BufferedImage-Objeke konvertiert werden. Im angeführten Beispiel wird ein Array mit den Dateinamen-Strings dreier *.tif-Dateien und dem Speicherpfad an die Methode loadImages() übergeben. Sie ist für das Laden und die Dekodierung der Bilddaten zuständig.
In einem zweiten Schritt werden die erzeugten BufferedImage-Objekte kodiert und in die Zieldatei eingelesen.

Laden und Dekodieren der Bilder

In der Methode loadImages() wird zunächst ein Array zur späteren Speicherung von BufferedImage-Objekten erzeugt. In einer Schleife werden dann die einzelnen Quell-Dateien in einen FileInputStream (oder auch FileSeekableStream) eingelesen. Zur Weiterverarbeitung müssen die Daten dann dekodiert werden. Die JAI-Klasse ImageCodec stellt zur Erzeugung eines hierzu geeigneten Dekoders eine Reihe überladener Methoden createImageDecoder() bereit, die entsprechende ImageDecoder-Objekte zum Dekodieren des Streams erzeugen können, im vorliegenden Fall, indem ihr ein String des Zieltyps, die gestreamte Quelldatei und ggfs. Dekodierungs-Parameter in Form eines ImageDecodeParam-Objektes übergeben werden.
Der Dekoder erzeugt nun mit seiner Methode decodeAsRenderedImage() ein RenderedImage, das an den Konstruktor eines NullOpImage weitergereicht wird. Zusätzlich werden noch drei weitere Parameter an den Konstruktor übergeben: das optionale Color-Model in Form eines ImageLayout-Objektes, eine ebenfalls optionale Map mit Konfigurationsvariablen und eine Konstante, die einen Hinweis auf den Ursprung der Quelldatei gibt und zur Optimierung der Weiterverarbeitung dient. Das NullOpImage wird in ein Objekt der Elternklasse PlanarImage gecastet und schließlich von dort in ein BufferedImage gewandelt, das im o.a. Array gespeichert werden kann.

import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;

import javax.media.jai.NullOpImage;
import javax.media.jai.OpImage;
import javax.media.jai.PlanarImage;

import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.ImageDecoder;
import com.sun.media.jai.codec.ImageEncoder;
import com.sun.media.jai.codec.TIFFEncodeParam;

public class MultipleTiff {

    public static void main(String[] args) {
        String[] files = { "test1.tif", "test2.tif", "test3.tif" };
        MultipleTiff mt = new MultipleTiff();
        mt.saveMultipleTiff(mt.loadImages("img/", files), "out/", "out.tif");
    }

    private BufferedImage[] loadImages(String inputDir, String[] files) {
        BufferedImage images[] = new BufferedImage[files.length];
        FileInputStream stream = null;
        PlanarImage pi;
        for (int i = 0; i < files.length; i++) {
            try {
                stream = new FileInputStream(inputDir + files[i]);
                ImageDecoder decoder = ImageCodec.createImageDecoder("tiff",
                        stream, null);
                pi = new NullOpImage(
                        decoder.decodeAsRenderedImage(), null, null,
                        OpImage.OP_IO_BOUND);
                images[i] = pi.getAsBufferedImage();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    stream.close();
                }catch(IOException ioe) {
                }
            }
        }
        return images;
    }

    private void saveMultipleTiff(BufferedImage[] images, String outputDir,
            String imageName) {
        TIFFEncodeParam params = new TIFFEncodeParam();
        OutputStream out = null;
        try {
            out = new FileOutputStream(outputDir + imageName);
            ImageEncoder encoder = ImageCodec.createImageEncoder("tiff", out,
                    params);
            ArrayList<BufferedImage> list = new ArrayList<BufferedImage>();
            for (int i = 1; i < images.length; i++) {
                list.add(images[i]);
            }
            params.setExtraImages(list.iterator());
            encoder.encode(images[0]);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try{
                out.close();
            } catch(IOException ioe){}
        }
    }
}

Kodieren und Ausgabe der Bilder

Der zweite Schritt der Verarbeitung findet in der Methode saveMultipleTiff()statt. Hier werden zunächst ein OutputStream deklariert und ein Objekt vom Typ TIFFEncodeParam erzeugt. Letzteres speichert die Kodierungsparameter, die im Beispiel den Standardwerten: 'kompressionslose Speicherung' und 'Ablegen der Bilddaten in Stripes' entsprechen. Nach Erzeugen des Streams mit dem Zielspeicherort wird ein ImageEncoder mit Übergabe des Zieltyps, "tiff", des Streams und der Kodierungsparameter gebildet.

Zum Speichern mehrerer Bilddateien muss der Methode TIFFEncodeParam#setExtraImages() ein Iterator übergeben werden, der die Liste durchläuft und die zusätzlich zu speichernden Bilder erfasst. Das erste Bild darf hierbei nicht mit erfasst werden, um in der Zieldatei nicht doppelt zu erscheinen.
Um den Iterator zu erhalten, wird der Inhalt des Arrays mit den BufferedImages hier in eine ArrayList überführt und dort die Bilder bis auf das erste zwischengespeichert. Das erste BufferedImage wird dagegen als Parameter der Methode encode() des Kodierers übergeben. Sie kodiert das erste Bild und schreibt das Ergebnis in den OutputStream, der mit dem ImageEncoder verbunden ist. Die Reihenfolge an dieser Stelle ist wichtig. Kehrt man sie um, so wird lediglich das erste Bild in der Zieldatei gespeichert.

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.