2004.01.16 17:32 "[Tiff] libtiff and CALS files...", by Bill Hess

2004.01.20 13:44 "[Tiff] libtiff and CALS files...", by Ross Finlayson


CALS is a DOD, Department of Defense, standard for acquisition and life cycle support, it is said. Anyways part of the CALS standards are CALS raster and document file formats.

CALS 1, or CALS raster or C1, .img, or perhaps .mil images, are as described, a fixed length header of maybe 2048 bytes, I forget, then a block of Group 4 facsimile encoded data.

CALS2 has two varieties, one form is a raster image, and the other uses an SGML profile of some form, perhaps similar to DSSSL, some form of ODAP. Anyways the raster image is similar to the CALS Type 1 image, but different. I think type 2 raster files might also be called TRIF files.

The CALS3 format is a raster format and is very similar to TIFF. Also called NIFF, for Navy Image File Format, or NIRS/NIFF, it is almost identical in structure to a TIFF. Where TIFF has the header ir signature of of "42", NIFF uses "N1". All NIFF images, at least for the specifications I have read, are black and white images. If the image is less than or equal to letter size, it is an untiled images, else it is a tiled image.

CALS4 is also known as EDMICs, and JEDMICS, EDMICS/JEDMICS, ImageCenter, tg4, or sometimes C4, or IMG files. These are files with a fixed header and tiles of Group 4 compressed fax data to comprise basically engineering drawings.

I wrote a short synopsis how to read NIRS/NIFF images with libtiff, although I'm still looking for NIRS/NIFF images to test it. If you have any NIRS/NIFF samples that you can share I'm interested in looking at them. The way I read NIFF files using libtiff is to add support for reading the NIFF private fields and the NIFF header instead of the TIFF header.

Programs to convert CALS type 1 or 4 into TIFF could be similar to the fax2tiff program.

Hopefully if you actually have a NIFF file this might be useful. If so, please e-mail to me sample NIFF files of small size, standard tiled and untiled, thank you.


Reading NIRS/NIFF files with libtiff and these patches.

NIFF, the "Navy Image File Format", also known as "CALS Raster Type 3", according to "Navy Specification, Advanced Technical Information Support (ATIS), Raster Technical Manuals, Preparation of" is an image file format for the storage of bilevel/black and white data in tiled and untiled forms that is very similar to little-endian TIFF.

The difference between a NIFF and a valid TIFF file is that the NIFF file has as its header "N1", 0x4e31 instead of 42, 0x0042. As the file is defined to use little-endian byte order the actual value of zero-based bytes 2 and 3 are hexadecimal 31 and 4e instead of 42 and 00.

NIFF defines several private TIFF tag definitions and as well overloads for its use a few of the TIFF tags. Here are some definitions for the NIFF tags and specified values:

#define NIFFTAG_SUBFILETYPE             254     /* Same tag as TIFFTAG_SUBFILETYPE, different interpretation */
#define NIFF_SUBFILE_TILED          0x02
#define NIFFTAG_PELPATHLENGTH               256     /* Same as TIFFTAG_IMAGEWIDTH */
#define NIFFTAG_BITSPERSAMPLE          258     /* Same as TIFFTAG_BITSPERSAMPLE */
#define NIFFTAG_PHOTOMETRIC          262     /* Same as TIFFTAG_PHOTOMETRIC */
#define NIFFTAG_DATAOFFSET             273     /* Either strip offset or tile offsets */
#define NIFFTAG_SAMPLESPERPIXEL                277     /* Same as TIFFTAG_SAMPLESPERPIXEL */
#define NIFFTAG_DATABYTECOUNTS             279     /* Either strip byte counts or tile byte counts */
#define NIFFTAG_ROWSPERLINEPATH          323     /* Same as TIFFTAG_TILELENGTH */
#define NIFFTAG_ROTATION                33465   /* Private tag, 1 x uint16 */
#define NIFF_ROTATION_0                    0
#define NIFF_ROTATION_90               1
#define NIFF_ROTATION_180              2
#define NIFF_ROTATION_270              3
#define NIFFTAG_NAVYCOMPRESSION                33466   /* Private tag, 1 x uint16 or tile count x uint16 */
#define NIFFTAG_TILEINDEX              33467   /* Private tag, tile count * uint16 */

If the image is less than the size of one 8 1/2" by 11" page, then it may be stored as a single-strip image, otherwise it is stored as a tiled image, with tiles of dimensions 512 x 512.

The tile index field stores for each tile of the image, in order of the tiles stored in the file as indicated by the tile offsets and tile byte counts, the tile's location in the sequence of tiles of the image. For example, for a 1024 x 2048 image, with two tiles in the x dimension and four tiles in the y dimension, the tile with tile index 0 is the upper left tile, and the tile with tile index 7 is the lower right tile, and they may otherwise any of the tiles stored physically in the image.

NIFF uses only CCITT Fax Group 4 compression, (Modifed Modified READ, MMR), and otherwise stores uncompressed data. The data is to be compressed with Group 4 facsimile encoding unless the compressed data would be larger than the uncompressed data, in which case it is to be stored uncompressed.

The Navy Compression field contains either one entry or entry for each tile. If it contains only one entry then its value applies to each tile of the image, else for each tile its compression method is indicated. The specified allowable values are 1 for uncompressed data and 4 for Group 4 facsimile compressed data.

The NIFF rotation tag is used instead of the TIFF orientation tag, values represent degrees counterclockwise rotation, 0, 1, 2, and 3 correspond to TIFF orientations 0, 6, 3, and 8.

In reading the NIFF contents, it is either a tiled or untiled image. IF it is a tiled image, bit 1 of the Navy Subfiletype uint16 field is set. If it is an untiled image it has exactly one strip, which could be considered a tile, the only difference between an untiled image and a tiled image is that the single strip or tile of the untiled image may have dimensions that are not 512 x 512. The TIFFIsTiled() function will return a positive value as the TIFFTAG_TILEWIDTH and TIFFTAG_TILELENGTH tags are set. As well, bit 1 of the NIFF subfiletype will be set if the image is tiled. Otherwise the image is untiled and consists of a single strip image that is compressed with Group 4 fax encoding unless the NIFFTAG_NAVYCOMPRESSION field has the value NIFF_NAVYCOMPRESSION_UNCOMPRESSED.

It is necessary to read the contents of the tile index field, when the image is tiled, to determine which tile in the image file represents a particular tile in the image. One way to do that is to store an array of structures with the tile index and the ttile_t libtiff tile number type and sort it on the tile index, then when the tile at that index is to be read, to read the tile from the file of the ttile_t.

Define a structure to contain the reference to the tile from the tile index.

typedef struct {
        uint16 tile_index;
      ttile_t tile_number;
    uint16 tile_uncompressed;
} tile;

Before reading the tiles, sort an array of (or rather pointer to) the tile indexes/indices.

tile* tiles;
uint16* tileindex;
uint16* tilecompression;

ttile_t tile_count = TIFFNumberOfTiles(tiff);

tiles = _TIFFmalloc(tile_count * sizeof(tile));

_TIFFmemset(tiles, 0x00, tile_count * sizeof(tile));

TIFFGetField(tiff, NIFFTAG_TILEINDEX, &tileindex);

for(i=0; i<tile_count; i++){
    tiles[i].tile_index = tileindex[i];
     tiles[i].tile_number = i;

 for(i=0; i<tile_count;i++){
              for(i=0; i<tile_count;i++){

qsort(tiles, tile_count, sizeof(tile), qsort_cmp_tile_index);

The qsort_cmp_tile_index function compares the tile indexes.

int qsort_cmp_tile_index(const void* e1, const void* e2){
       return( ((tile*)e1)->tile_index - ((tile*)e2)->tileindex );

Then, you have the tile array. When you go to read the tiles, for ttile_t i from 0 to tile_count in order, instead of calling TIFFReadEncodedTile on i, call it on tiles[i].tile_number. If tiles[i].tile_uncompressed is equal to 1, call TIFFReadRawTile instead of TIFFReadEncodedTile.

These patches here don't really handle writing NIFF files. In writing NIFF files it is necessary to determine for each tile whether or not "compression" would increase the size of the data. In Group 4 compression, data that is likely to be increased in size by encoding is stippled with few long runs of black or white pixels. Normally the method to determine if data would be increased in size by compression is to compress it and compare the size to the uncompressed data, which shall be a 512 x 512 block for tiles of 512*512/8 bytes, or the complete image for the single strip image representing less than or equal to a letter size piece of paper. A way to do that would be to generate a regular compressed tiled TIFF. Then, go through the tiles and compare their size against the size of the uncompressed tile to form the arrays of short 16-bit integers representing for each tile whether it is to be stored compressed or uncompressed. Then, generate a NIFF file, with storing for each file either the raw data of the image to be stored for the tile, if and only if the compressed size is larger than the Group 4 fax compressed size, otherwise copying over the raw tile from the fully compressed image. The writing of the tiles to the output NIFF would be done after setting the NIFFTAG_SUBFILETYPE, NIFFTAG_NAVYCOMPRESSION, NIFFTAG_TILEINDEX, and other NIFF/TIFF fields besides NIFFTAG_DATAOFFSETS and NIFFTAG_DATABYTECOUNTS.