5

I have a raw fax file (G3/T.4 format) and need to convert it into a multi-page TIFF programmatically via Java. JAI has not been successful for me so far even if I think it should work. Tools from sFaxTools have been successful for converting my raw fax files into TIFF (Batch Fax2Tif or Faxsee), but I need to do this programmatically via Java. I think there should be a possibility using java advanced imaging, please check the code snipplet below:

 private void writeTiff(byte[] buffer, OutputStream outStream) {
    try {
         //reading image from given buffer
         RenderedImage rendImage = null;
         TIFFDecodeParam decodeParams = new TIFFDecodeParam();
         ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
         ImageDecoder decoder = ImageCodec.createImageDecoder("tiff", stream, decodeParams);
         TIFFEncodeParam encodeParams = new TIFFEncodeParam();
         int numPages = decoder.getNumPages();
         for (int i = 0; i < numPages; i++) {
            rendImage = decoder.decodeAsRenderedImage(i);
            ImageEncoder encoder = ImageCodec.createImageEncoder("TIFF", outStream, encodeParams);
            encoder.encode(rendImage);
         }
    } catch (Exception e) {
        e.printStackTrace();
    } catch (Error err) {
        err.printStackTrace();
    }
}

The problem is, that the reading section especially ImageDecoder decoder = ImageCodec.createImageDecoder("tiff", stream, decodeParams); should be replaced by some ImageDecoder implementation which internally uses a FaxDecoder for decoding a g3 raw fax file. There is a protected class TIFFFaxDecoder within the the jai package, is it possible and how to use this for my purpose? Any idea? Thanks

manfy
  • 61
  • 4
  • 2
    If you've found a tool outside of Java that works for you, you can execute the tool via Java using `Runtime.getRuntime().exec(command)`. Alternately, Java has TIFF support in the JAI Image I/O Tools, which is so distinct from JAI that you might not have found it: http://stackoverflow.com/questions/7502181/where-can-i-download-jai-and-jai-imageio – JoshDM Nov 10 '15 at 16:30
  • 1
    Alternately, check out this SO question on getting started with TIFFs and JAI: http://stackoverflow.com/questions/30320434/write-a-tiff-with-jai – JoshDM Nov 10 '15 at 16:34
  • 2
    Is there a spec for what a "raw G3 FAX file" is somewhere? If it is the same as the image data part of a G3 (T.4) compressed TIFF, it should be possible (and quite easy) to just wrap the data in a TIFF container. – Harald K Nov 11 '15 at 09:00
  • 1
    Just to prove that, I downloaded the only [sample file](http://www.filesuffix.com/en/extension/fax) I could find, wrote a TIFF container (using [TwelveMonkeys ImageIO](http://haraldk.github.io/TwelveMonkeys/)) with the required fields from the [TIFF Class F spec](http://cool.conservation-us.org/bytopic/imaging/std/tiff-f.html) and then appended the raw G3 data. The file now opens and displays as a valid TIFF. See [the following Gist](https://gist.github.com/haraldk/7cd962c9ebfcec160b71). I don't give any guarantees that it will work for anything but that sample file though. ;-) – Harald K Nov 11 '15 at 10:20
  • The edited question is still asking for a recommendation for software. – AdrianHHH Nov 11 '15 at 12:05
  • 2
    sorry, but I am asking for a solution based on an already used framework, I again modified my question. I would not understand, if you still complain about it. Software development in these days always means trying to play around with existing (open source) frameworks, if I am not allowed to mention them in my post or to ask if it supports my requirements, this forum becomes obsolete in my opinion. regards – manfy Nov 11 '15 at 12:30
  • Thanks AdrianHHH, I tried your code and it is at least (partially) working with my samples. Some part of the fax is missing and I don't know how to handle multipage tiffs so far, but it was already some really good hint. I am still happy for other inputs... – manfy Nov 11 '15 at 12:56

2 Answers2

0

I don't think JAI supports reading G3/T.4 raw fax data directly. However, here's sample code you can modify and extend to suit your needs, implementing the idea outlined in the comments (originally posted as a Gist).

It does not decode the G3/T.4 data in any way, it simply wraps the raw fax data in a minimal TIFF container. This allows the data to be read as a normal TIFF later. It uses (my own) TwelveMonkeys ImageIO library to do so.

If you don't know the width/height of the fax files, you might be able to implement an algorithm to find them, by using the CCITTFaxDecoderStream, trying the different widths (columns) defined in the standard, and see how many whole lines you can read. If you got the numbers right, you should fully consume the stream.

import com.twelvemonkeys.imageio.metadata.AbstractEntry;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
import com.twelvemonkeys.imageio.metadata.exif.Rational;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.io.*;
import java.util.ArrayList;

public class G3Test {
    public static void main(String[] args) throws IOException {
        File input = new File(args[0]);
        File output = new File(args.length > 1 ? args[1] : input.getName().replace(".g3", ".tif"));

        // ImageWidth = 1728, 2048, 2482. SHORT or LONG. These are the fixed page widths in pixels defined in CCITT Group 3.
        int columns = 1728; // The default
        int rows = 100;     // Trial and error for sample file found at http://www.filesuffix.com/en/extension/fax

        ArrayList<Entry> entries = new ArrayList<>();

        // http://cool.conservation-us.org/bytopic/imaging/std/tiff-f.html
        // Required Class F tags
        entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, 3)); // CCITT T.4
        entries.add(new TIFFEntry(TIFF.TAG_FILL_ORDER, TIFF.TYPE_SHORT, 1));  // Left to right
        entries.add(new TIFFEntry(TIFF.TAG_GROUP3OPTIONS, TIFF.TYPE_LONG, 0)); // No options set
        entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, columns));
        entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, TIFF.TYPE_LONG, rows));
        entries.add(new TIFFEntry(TIFF.TAG_SUBFILE_TYPE, TIFF.TYPE_LONG, 2)); // Page
        entries.add(new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, 2)); // Inches
        entries.add(new TIFFEntry(TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(204))); // 204
        entries.add(new TIFFEntry(TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(98))); // 98, 196
        // Required Bilevel (Class B) tags
        entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, TIFF.TYPE_SHORT, 1)); // 1 bit/sample
        entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, 0)); // White is zero
        entries.add(new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys FAX2TIFF 0.1 BETA ;-)"));
        entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, TIFF.TYPE_LONG, rows));
        entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, TIFF.TYPE_SHORT, 1)); // 1 sample/pixel
        entries.add(new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, input.length()));
        entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, -1)); // placeholder for now

        // We now have all our entries, compute size of the entries, and make that the offset (we'll write the data right after).
        EXIFWriter writer = new EXIFWriter();
        long offset = 12 + writer.computeIFDSize(entries); // + 12 for TIFF magic (4), IFD0 pointer (4) and EOF (4)
        entries.remove(entries.size() - 1);
        entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, offset));


        try (InputStream in = new FileInputStream(input)) {
            try (ImageOutputStream out = ImageIO.createImageOutputStream(output)) {
                // Write the TIFF IFD for the image data
                writer.write(entries, out);

                // Copy the already G3 compressed bytes verbatim to the output
                byte[] buffer = new byte[1024];
                int read;
                while ((read = in.read(buffer)) >= 0) {
                    out.write(buffer, 0, read);
                }
            }
        }
    }

    // API stupidity, should be fixed in later verisons (ie. contain a predefined TIFFEntry class)
    static final class TIFFEntry extends AbstractEntry {
        private final short type;

        TIFFEntry(int identifier, short type, Object value) {
            super(identifier, value);
            this.type = type;
        }

        @Override
        public String getTypeName() {
            return TIFF.TYPE_NAMES[type];
        }
    }
}
Harald K
  • 26,314
  • 7
  • 65
  • 111
0

For not guessing the image height, its possible to find out the number of rows.

If you know how the image look like, you can read the encoded image data bitwise (take note of the bitorder) and count the 'EOL flags'. There are two different flags, dependent of the row begins with a white pixel or a black. The full description is here Tiff Format Specification under section : Modified Huffman Compression.