Notes on SGI image format

There is an old image file format called (among other things) “SGI image”. In this post, I’ll take a look at it, and make some suggestions about how to decode it. I have no particular reason to care about this format; it’s just one that I was studying recently.

SGI image is a low-tech still-image raster graphics format, with one image per file. The image can be compressed with a simple run-length encoding scheme, or it can be stored uncompressed. In a number of ways, it is functionally similar to TGA (Targa) format.

It is an old format — I’m not sure exactly how old, but some form of it probably dates to the early 1980s.

A specification document exists, titled “The SGI Image File Format”, but it seems to date to 1994 or so. I’m not exactly sure what information developers used before that.

Please note: This post is not meant to disrespect the people responsible for the SGI image format or its documentation. I’m afraid it’s going to seem like I’m doing that. SGI image is actually one of the better formats in its class, especially considering its age; and I assume it worked fine for its intended uses.

The format was commonly used on SGI (Silicon Graphics) workstations, a kind of computer that I know next to nothing about. If I understand correctly, it was used by an image library that was included with the operating system.

You will not encounter many examples of SGI image files, unless you go out of your way to look for them. But a great many such files do exist. If DiscMaster is still around at the time you read this, you can use it to find them. As of this writing, the format name to search for is “sgi”.

Format name

A negative aspect of this format is its overabundance of names, none of which is particularly good.

“SGI” isn’t a very good name, because it’s the name of the company.

Some editions of the relevant operating system were named IRIX, and some names for the format include the word “IRIX”.

Some models of SGI workstations were named IRIS, and some names for the format include the word “IRIS”.

It is also known as “RGB” format, but that’s not a very distinctive name, and it also might only refer to a subset of SGI image format.

Identification of files

Files in SGI format normally have a filename extension, but unfortunately they do not use just one distinctive extension. Extensions “.rgb”, “.rgba”, “.bw”, “.sgi”, and others, may be found.

Fortunately, it is possible to identify the format with decent reliability by looking at the first few bytes of the file. SGI image files always start with two bytes having values 0x01, 0xda.

I suggest checking other constraints, such as:

  • The byte at offset 2 (the compression method) must be 0x00 or 0x01.
  • The byte at offset 3 (bytes per sample) must be 0x01 or 0x02.
  • The byte at offset 4 (high byte of dimension count) must be 0x00.
  • The byte at offset 5 (dimension count) must be 0x01, 0x02, 0x03, or 0x04. 0x04 isn’t actually legal, but you should tolerate it.

About the specification

I’ve found two versions of the specification: 0.97 and 1.00. The documents for the two versions do not have the same file format, and I couldn’t figure out a good automated way to compare them for changes.

Reading through them, the only difference I noticed was in some of the example C code. Version 0.97 sometimes assumes pointers are 4 bytes in size, with instructions like “optr+=4“. That’s not a safe assumption in general, though maybe it’s okay for SGI workstations. Anyway, it’s fixed in v1.00.

There are several typos in the specification, none of which seem to have been fixed between v0.97 and v1.00. One of them is a nuisance: In one instance, “PIXMIN” and “PIXMAX” are misspelled as “PINMIN” and “PINMAX”. These fields turn out to be the source of the most frustrating part of decoding the format, and it feels like adding insult to injury that you can’t reliably search the specification for their names.

Dimensionality

The format’s most unusual feature has to be its concept of “dimensions”. There is a dimension count field, whose valid values are 1, 2, and 3.

A 1-dimensional image would be a single-row 1-sample-per-pixel (grayscale) image.

A 2-dimensional image is a normal 2-dimensional 1-sample-per-pixel (grayscale) image.

A 3-dimensional image is not a voxel-based image; it’s a normal 2-dimensional image with more than one sample per pixel, usually a color R-G-B image.

It’s an interesting idea, but it just ends up creating unnecessary confusion. What it boils down to is that the x-size field is the image width in pixels, the y-size field is the height in pixels, and the z-size field is the number of samples per pixel — just like a normal image format. Except that there is this extra dimension-count field, and if it’s less than 3 you have to wonder whether all the “-size” fields contain valid values, or if the last {3 − dimension-count} of them should be ignored and assumed to have value 1.

My claim that it is confusing is confirmed by the fact that I’ve found a handful of files where it is set incorrectly: to 1 instead of 2, or to 4 instead of 3. My recommendation for decoders is to, at least as a first draft, just ignore the dimension-count field. (If you find a “size” field with value 0, I guess you could assume it means 1.)

I haven’t found any actual “1-dimensional” SGI image files, with the dimension-count field set to 1. Note that a zero-dimensional image would also make sense — it would be a 1×1 pixel grayscale image. But it’s not approved by the specification.

Image types

For the most part, SGI only supports images with 8 bits per sample, or 16 bits per sample. 16-bit images are fairly rare.

While this restriction can lead to larger files, it makes decoding SGI files easier. And it’s just nice that they’re not modeled after how some particular ancient video card happened to arrange its memory.

There is also an RGB 3-3-2 image type, but it’s considered obsolete, and I haven’t found any such files.

Paletted images

The SGI format supports palette-color images (a.k.a. color-mapped, or indexed color), but the specification classifies them as obsolete, and doesn’t fully explain the format. That’s unfortunate, since such files do still exist.

As is a common annoyance with image formats of this era, the palette and the image are stored in two separate files. The format of the image file is obvious and is documented, but the format of the palette file is neither.

Even the very few SGI palette files I’ve found seem to use at least two different formats. While I could probably figure out how to interpret these specific palette files, it doesn’t bode well for supporting paletted SGI images in general.

Compression – Low level

If compression is used, each row of samples is compressed independently using a simple RLE algorithm:

0: End of row
1-127: Emit the next pixel (1 or 2 bytes) N times
128: End of row
129-255: Emit the next N-128 pixels literally

Simple as it is, checking my old RLE survey, I see only one format whose scheme is essentially the same as it: Segmented Hypergraphics, a fairly obscure format.

Since the compression scheme has special end-of-row opcodes, it is possible for a row to end sooner than it should, leaving some pixels undefined. I haven’t noticed any images that do this, other than some broken ones discussed in the “bad alpha channel” section below. If you’re writing a decoder, just make sure you handle this possibility in a sensible manner.

Compression – High level

Each compressed sample-row is stored somewhere in the file. The image definition is then just an array of pointers to these compressed sample-rows. If an image contains some identical rows, the specification explicitly approves pointing to the same sample-row more than once, as a way to reduce the file size. Also, the row data can appear in any order.

I like that this is possible. It can slightly increase the size of some files, though, due to the extra space required by the pointers.

Some of the files I’ve looked at do take advantage of this, pointing to the same sample-row more than once.

Others don’t appear to reuse sample-rows, but they do put them in a different order in the file than they appear in the index.

It’s interesting to consider whether the rows could overlap, but this possibility is mostly spoiled by the requirement of a special marker at the end of each row. These end-of-row opcodes are doubly superfluous, because we already know both the compressed and the decompressed size of each row. But the specification requires them.

The PIXMIN/PIXMAX problem

Every SGI image file has a pair of fields named “PIXMIN” and “PIXMAX”. The specification says that these fields reflect the minimum and maximum sample values used in the image. Then it adds words that effectively contradict that, implying that these are the values that correspond to the display‘s minimum and maximum brightness.

In practice (assuming an 8 bits/sample image), some files have the image’s actual min/max sample values in these fields, but expect the viewer to ignore them. Some put 0, 255 in the fields no matter what. Some put 0, 0 in the fields. So far, so good. But unfortunately, there are a few files that seem to expect the viewer to treat these fields as critically important, and scale the image’s brightness and contrast accordingly.

I don’t have any great advice for what a decoder should do. Ignoring the PIXMIN/PINMAX fields usually works, but to support all SGI files, one may have to use heuristics to try to identify that last type of file.

It could be that images with 16 bits/sample are more likely to need their brightness and contrast normalized than images with 8 bits/sample. But I don’t have enough 16-bits/sample images to be sure.

The specification says nothing about how the PIXMIN/PIXMAX fields relate to alpha (transparency) samples. In practice, it seems pretty safe to ignore the fields if an image has transparency.

Reserved space

According to the specification, the 404 bytes from offset 108 to offset 511 are unused. It’s listed as “DUMMY” space.

Also, there is an 80-byte field for an image name, which is usually mostly unused.

I’m not overly impressed with these design decisions. The good thing is that it makes it possible to extend the format, albeit in a somewhat chaotic way. The bad thing is that it makes every file larger than it needs to be. This wasted space is insignificant for large images, but this format was sometimes used for very small images, whose file sizes could have been much smaller.

A lot of files do appear to use some of this space for something. I haven’t made an effort to figure out what it is used for. Whatever it is, it’s not obvious.

Since compressed rows can potentially be stored anywhere in the file, you might think of putting some of the rows in this extra space in the first 512 bytes of the file. And yeah, I bet that would work, for most SGI viewers. But it’s probably a bad idea — it’s not technically allowed, and some viewers might not like it, and some software evidently uses those bytes for other things.

Odds and ends

Grayscale + alpha

While the specification doesn’t specifically define such a thing, I’ve found some images with a z-size of 2 that seem to be grayscale images with an alpha channel. Supporting them is no problem, if you know they exist.

Invisible images

The specification says nothing about how alpha samples are to be interpreted, but based on actual SGI image files, it’s clear that 0 is usually supposed to mean transparent.

But I’ve found a few files in which all of the alpha samples are 0, making the image completely invisible. Most of them do not look like they are intended to be invisible, so a decoder might consider detecting such images and making them visible. There do exist deliberately-invisible placeholder images, though, so that isn’t always the right thing to do.

Images with a bad alpha channel

There are a lot of broken RLE-compressed SGI images, perhaps all from the same source, that have a bad alpha channel. The scanline index has an offset value of 0 for all the sample-rows in the alpha channel.

I suggest interpreting an offset of 0 as a special case meaning “no data”. Treat it as opaque for alpha samples, black otherwise.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s