/*------------------------------------------------------------------------------
 src/lib/djImage/imageIoDpx.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include "djImageIoDpx.h"
#include <time.h>

namespace dj {
namespace ImageIoDpx {

static inline void _zero(uint8_t * in) { *in = 0xff; }
static inline void _zero(uint16_t * in) { *in = 0xffff; }
static inline void _zero(uint32_t * in) { *in = 0xffffffff; }
static inline void _zero(float * in) { *((uint32_t *)in) = 0xffffffff; }
static inline void _zero(char * in, int size) { memZero(in, size + 1); }

static inline bool _isValid(uint8_t in) { return in != 0xff; }
static inline bool _isValid(uint16_t in) { return in != 0xffff; }
static inline bool _isValid(uint32_t in) { return in != 0xffffffff; }

// XXX Cut the chatter on files with improper uninitialized values.
//
static const float _floatMax = 1000000.0;
static const char _charMin = 32;
static const char _charMax = 126;

static inline bool _isValid(float in)
{
  //return *((uint32_t *)&in) != 0xffffffff;
  
  return *((uint32_t *)&in) != 0xffffffff && in < _floatMax;
}

static inline bool _isValid(const char * in, int size)
{
  //return size ? in[0] : false;
  
  const char * p = in;
  const char * const end = p + size;
  for (; *p && p < end; ++p)
    if (*p < _charMin || *p > _charMax)
      return false;
  return size ? in[0] : false;
}

Header::Header()
{
  zero();
}

bool Header::set(
  FileIo * f, const PixelDataInfo & info, const ImageTagList & tagList,
  COLOR color, BIT_DEPTH bitDepth, CHANNELS channels, bool pack
) {
  Str str;
  StrList strList;
  int intValue;
  IntList intList;
  double floatValue;
  Vec2i vec2i;
  Vec2f vec2f;
  StrList data;
  char tmp [256];
  
  zero();
  
  // Generic information.
  //
  memCopy("V2.0", version, 4);
  dittoKey = 1;
  userHeaderSize = 0;
  imageOffset = 0;
  toAscii(fileName, fileName, 100, false);
  
  // XXX timezone string?
  //
  const time_t time = timeCurrent();
  const struct tm * timeBits = localtime(&time);
  const Str timeStr = Str("%1:%2:%3:%4:%5:%6").
    arg(1900 + timeBits->tm_year).
    arg(1 + timeBits->tm_mon, 2, '0').
    arg(timeBits->tm_mday, 2, '0').
    arg(timeBits->tm_hour, 2, '0').
    arg(timeBits->tm_min, 2, '0').
    arg(timeBits->tm_sec, 2, '0');
  DJ_DBG_PRN("time = " << timeStr);
  toAscii(timeStr, dateTime, 24, false);

  data = tagList[imageTagStr()[IMAGE_TAG_CREATOR]];
  if (inflateStr(data, &str)) toAscii(str, creator, 100, false);
  data = tagList[tagStr()[TAG_PROJECT]];
  if (inflateStr(data, &str)) toAscii(str, project, 200, false);
  data = tagList[imageTagStr()[IMAGE_TAG_COPYRIGHT]];
  if (inflateStr(data, &str)) toAscii(str, copyright, 200, false);
  
  if (! f->setU32(imageOffset)) return false;
  if (! f->set(&version, 8)) return false;
  if (! f->setU32(fileSize)) return false;
  if (! f->setU32(dittoKey)) return false;
  if (! f->setU32(genericHeaderSize)) return false;
  if (! f->setU32(industryHeaderSize)) return false;
  if (! f->setU32(userHeaderSize)) return false;
  if (! f->set(&fileName, 100)) return false;
  if (! f->set(&dateTime, 24)) return false;
  if (! f->set(&creator, 100)) return false;
  if (! f->set(&project, 200)) return false;
  if (! f->set(&copyright, 200)) return false;
  if (! f->setU32(encryptionKey)) return false;
  _zero(tmp, 104);
  if (! f->set(tmp, 104)) return false;
  
  // Image information.
  //
  image.orient = DPX_ORIENT_LEFT_RIGHT_BOTTOM_TOP;
  
  switch (info.format()) {
    case PIXEL_L:
      image.elemSize = 1;
      imageElem[0].descriptor = DPX_DESCRIPTOR_L;
      break;
    case PIXEL_LA:
      image.elemSize = 2;
      imageElem[0].descriptor = DPX_DESCRIPTOR_L;
      imageElem[1].descriptor = DPX_DESCRIPTOR_A;
      break;
    case PIXEL_RGB:
      switch (channels) {
        case INTERLEAVE:
          image.elemSize = 1;
          imageElem[0].descriptor = DPX_DESCRIPTOR_RGB;
          break;
        case PLANAR:
          image.elemSize = 3;
          imageElem[0].descriptor = DPX_DESCRIPTOR_R;
          imageElem[1].descriptor = DPX_DESCRIPTOR_G;
          imageElem[2].descriptor = DPX_DESCRIPTOR_B;
          break;
      }
      break;
    case PIXEL_RGBA:
      switch (channels) {
        case INTERLEAVE:
          image.elemSize = 1;
          imageElem[0].descriptor = DPX_DESCRIPTOR_RGBA;
          break;
        case PLANAR:
          image.elemSize = 4;
          imageElem[0].descriptor = DPX_DESCRIPTOR_R;
          imageElem[1].descriptor = DPX_DESCRIPTOR_G;
          imageElem[2].descriptor = DPX_DESCRIPTOR_B;
          imageElem[3].descriptor = DPX_DESCRIPTOR_A;
          break;
      }
      break;
  }
  
  image.size[0] = info.width();
  image.size[1] = info.height();
  
  if (! f->setU16(image.orient)) return false;
  if (! f->setU16(image.elemSize)) return false;
  if (! f->setU32(image.size, 2)) return false;

  int bits = 0;
  switch (bitDepth) {
    case BIT_DEPTH_AUTO: bits = pixelSize(info.type()) * 8; break;
    case U8: bits = 8; break;
    case U10: bits = 10; break;
    case U12: bits = 12; break;
    case U16: bits = 16; break;
  }
  for (int i = 0; i < image.elemSize; ++i) {
  
    imageElem[i].dataSign = 0;
    
    imageElem[i].lowData = 0;
    switch (bits) {
      case 8: imageElem[i].highData = 255; break;
      case 10: imageElem[i].highData = 1023; break;
      case 12: imageElem[i].highData = 4095; break;
      case 16: imageElem[i].highData = 65535; break;
    }
    
    if (COLOR_LINEAR == color || DPX_DESCRIPTOR_A == imageElem[i].descriptor) {
      imageElem[i].transfer = DPX_TRANSFER_LINEAR;
      imageElem[i].colorimetric = DPX_COLORIMETRIC_USER;
    } else {
      imageElem[i].transfer = DPX_TRANSFER_PRINT;
      imageElem[i].colorimetric = DPX_COLORIMETRIC_PRINT;
    }
    
    imageElem[i].bitDepth = bits;
    imageElem[i].packing = pack ? 0 : 1;

    // XXX Only uncompressed data for now.
    //
    imageElem[i].encoding = 0;
    
    imageElem[i].linePadding = 0;
    imageElem[i].imagePadding = 0;
  }
  
  for (int i = 0; i < 8; ++i) {
    if (! f->setU32(imageElem[i].dataSign)) return false;
    if (! f->setU32(imageElem[i].lowData)) return false;
    if (! f->setF32(imageElem[i].lowQuantity)) return false;
    if (! f->setU32(imageElem[i].highData)) return false;
    if (! f->setF32(imageElem[i].highQuantity)) return false;
    if (! f->setU8(imageElem[i].descriptor)) return false;
    if (! f->setU8(imageElem[i].transfer)) return false;
    if (! f->setU8(imageElem[i].colorimetric)) return false;
    if (! f->setU8(imageElem[i].bitDepth)) return false;
    if (! f->setU16(imageElem[i].packing)) return false;
    if (! f->setU16(imageElem[i].encoding)) return false;
    if (! f->setU32(imageElem[i].dataOffset)) return false;
    if (! f->setU32(imageElem[i].linePadding)) return false;
    if (! f->setU32(imageElem[i].imagePadding)) return false;
    if (! f->set(&imageElem[i].description, 32)) return false;
  }
  _zero(tmp, 52);
  if (! f->set(tmp, 52)) return false;
  
  // Source information.
  //
  data = tagList[tagStr()[TAG_SOURCE_OFFSET]];
  if (inflateStr(data, &vec2i)) {
    source.offset[0] = vec2i[0];
    source.offset[1] = vec2i[1];
  }
  data = tagList[tagStr()[TAG_SOURCE_CENTER]];
  if (inflateStr(data, &vec2f)) {
    source.center[0] = vec2f[0];
    source.center[1] = vec2f[1];
  }
  data = tagList[tagStr()[TAG_SOURCE_SIZE]];
  if (inflateStr(data, &vec2i)) {
    source.size[0] = vec2i[0];
    source.size[1] = vec2i[1];
  }
  data = tagList[tagStr()[TAG_SOURCE_FILE_NAME]];
  if (inflateStr(data, &str))
    toAscii(str, source.fileName, 100, false);
  data = tagList[imageTagStr()[IMAGE_TAG_TIME_STAMP]];
  if (inflateStr(data, &str))
    toAscii(str, source.dateTime, 24, false);
  data = tagList[tagStr()[TAG_SOURCE_INPUT_DEVICE]];
  if (inflateStr(data, &str))
    toAscii(str, source.inputDevice, 32, false);
  data = tagList[tagStr()[TAG_SOURCE_INPUT_SERIAL]];
  if (inflateStr(data, &str))
    toAscii(str, source.inputSerial, 32, false);
  data = tagList[tagStr()[TAG_SOURCE_BORDER]];
  if (inflateStr(data, &intList))
    for (int i = 0; i < intList.size() && i < 4; ++i)
      source.border[i] = intList[i];
  data = tagList[tagStr()[TAG_SOURCE_PIXEL_ASPECT]];
  if (inflateStr(data, &intList))
    for (int i = 0; i < intList.size() && i < 2; ++i)
      source.pixelAspect[i] = intList[i];
  
  if (! f->setU32(source.offset, 2)) return false;
  if (! f->setF32(source.center, 2)) return false;
  if (! f->setU32(source.size, 2)) return false;
  if (! f->set(source.fileName, 100)) return false;
  if (! f->set(source.dateTime, 24)) return false;
  if (! f->set(source.inputDevice, 32)) return false;
  if (! f->set(source.inputSerial, 32)) return false;
  if (! f->setU16(source.border, 4)) return false;
  if (! f->setU32(source.pixelAspect, 2)) return false;
  _zero(tmp, 28);
  if (! f->set(tmp, 28)) return false;
  
  // Setup film information.
  //
  data = tagList[imageTagStr()[IMAGE_TAG_KEY_CODE]];
  if (inflateStr(data, &str)) {
    int id = 0, type = 0, prefix = 0, count = 0, offset = 0;
    strToKeyCode(str, &id, &type, &prefix, &count, &offset);
    toAscii(toStr(id), film.id, 2, false);
    toAscii(toStr(type), film.type, 2, false);
    toAscii(toStr(prefix), film.prefix, 6, false);
    toAscii(toStr(count), film.count, 4, false);
    toAscii(toStr(offset), film.offset, 2, false);
  }
  if (inflateStr(data, &str)) toAscii(str, film.count, 4, false);
  data = tagList[tagStr()[TAG_FILM_FORMAT]];
  if (inflateStr(data, &str)) toAscii(str, film.format, 32, false);
  data = tagList[tagStr()[TAG_FILM_FRAME]];
  if (inflateStr(data, &intValue)) film.frame = intValue;
  data = tagList[tagStr()[TAG_FILM_SEQUENCE]];
  if (inflateStr(data, &intValue)) film.sequence = intValue;
  data = tagList[tagStr()[TAG_FILM_HOLD]];
  if (inflateStr(data, &intValue)) film.hold = intValue;
  data = tagList[tagStr()[TAG_FILM_FRAME_RATE]];
  if (inflateStr(data, &floatValue)) film.frameRate = floatValue;
  data = tagList[tagStr()[TAG_FILM_SHUTTER]];
  if (inflateStr(data, &floatValue)) film.shutter = floatValue;
  data = tagList[tagStr()[TAG_FILM_FRAME_ID]];
  if (inflateStr(data, &str)) toAscii(str, film.frameId, 32, false);
  data = tagList[tagStr()[TAG_FILM_SLATE]];
  if (inflateStr(data, &str)) toAscii(str, film.slate, 100, false);
  
  if (! f->set(film.id, 2)) return false;
  if (! f->set(film.type, 2)) return false;
  if (! f->set(film.offset, 2)) return false;
  if (! f->set(film.prefix, 6)) return false;
  if (! f->set(film.count, 4)) return false;
  if (! f->set(film.format, 32)) return false;
  if (! f->setU32(film.frame)) return false;
  if (! f->setU32(film.sequence)) return false;
  if (! f->setU32(film.hold)) return false;
  if (! f->setF32(film.frameRate)) return false;
  if (! f->setF32(film.shutter)) return false;
  if (! f->set(film.frameId, 32)) return false;
  if (! f->set(film.slate, 100)) return false;
  _zero(tmp, 56);
  if (! f->set(tmp, 56)) return false;
  
  // Setup TV information.
  //
  data = tagList[imageTagStr()[IMAGE_TAG_TIME_CODE]];
  if (inflateStr(data, &str)) tv.timeCode = strToTimeCode(str);
  data = tagList[tagStr()[TAG_TV_INTERLACE]];
  if (inflateStr(data, &intValue)) tv.interlace = intValue;
  data = tagList[tagStr()[TAG_TV_FIELD]];
  if (inflateStr(data, &intValue)) tv.field = intValue;
  data = tagList[tagStr()[TAG_TV_VIDEO_SIGNAL]];
  if (inflateStr(data, &intValue)) tv.videoSignal = intValue;
  data = tagList[tagStr()[TAG_TV_SAMPLE_RATE]];
  if (inflateStr(data, &vec2f)) {
    tv.sampleRate[0] = vec2f.x();
    tv.sampleRate[1] = vec2f.y();
  }
  data = tagList[tagStr()[TAG_TV_FRAME_RATE]];
  if (inflateStr(data, &floatValue)) tv.frameRate = floatValue;
  data = tagList[tagStr()[TAG_TV_TIME_OFFSET]];
  if (inflateStr(data, &floatValue)) tv.timeOffset = floatValue;
  data = tagList[tagStr()[TAG_TV_GAMMA]];
  if (inflateStr(data, &floatValue)) tv.gamma = floatValue;
  data = tagList[tagStr()[TAG_TV_BLACK_LEVEL]];
  if (inflateStr(data, &floatValue)) tv.blackLevel = floatValue;
  data = tagList[tagStr()[TAG_TV_BLACK_GAIN]];
  if (inflateStr(data, &floatValue)) tv.blackGain = floatValue;
  data = tagList[tagStr()[TAG_TV_BREAK_POINT]];
  if (inflateStr(data, &floatValue)) tv.breakpoint = floatValue;
  data = tagList[tagStr()[TAG_TV_WHITE_LEVEL]];
  if (inflateStr(data, &floatValue)) tv.whiteLevel = floatValue;
  data = tagList[tagStr()[TAG_TV_INTEGRATION_TIMES]];
  if (inflateStr(data, &floatValue)) tv.integrationTimes = floatValue;
  
  if (! f->setU32(tv.timeCode)) return false;
  if (! f->setU32(tv.userBits)) return false;
  if (! f->setU8(tv.interlace)) return false;
  if (! f->setU8(tv.field)) return false;
  if (! f->setU8(tv.videoSignal)) return false;
  if (! f->set(tmp, 1)) return false;
  if (! f->setF32(tv.sampleRate, 2)) return false;
  if (! f->setF32(tv.frameRate)) return false;
  if (! f->setF32(tv.timeOffset)) return false;
  if (! f->setF32(tv.gamma)) return false;
  if (! f->setF32(tv.blackLevel)) return false;
  if (! f->setF32(tv.blackGain)) return false;
  if (! f->setF32(tv.breakpoint)) return false;
  if (! f->setF32(tv.whiteLevel)) return false;
  if (! f->setF32(tv.integrationTimes)) return false;
  _zero(tmp, 76);
  if (! f->set(tmp, 76)) return false;

  //dbg(this);
  return true;
}

bool Header::get(FileIo * f, ImageTagList * tagList)
{
  zero();
  
  // Generic information.
  //
  char tmp [256];
  if (! f->getU32(&imageOffset)) return false;
  if (! f->get(&version, 8)) return false;
  if (! f->getU32(&fileSize)) return false;
  if (! f->getU32(&dittoKey)) return false;
  if (! f->getU32(&genericHeaderSize)) return false;
  if (! f->getU32(&industryHeaderSize)) return false;
  if (! f->getU32(&userHeaderSize)) return false;
  if (! f->get(&fileName, 100)) return false;
  if (! f->get(&dateTime, 24)) return false;
  if (! f->get(&creator, 100)) return false;
  if (! f->get(&project, 200)) return false;
  if (! f->get(&copyright, 200)) return false;
  if (! f->getU32(&encryptionKey)) return false;
  if (! f->get(tmp, 104)) return false;
  
  if (_isValid(creator, 100))
    tagList->set(
      imageTagStr()[IMAGE_TAG_CREATOR], deflateStr(Str(creator, 100))
    );
  if (_isValid(project, 200))
    tagList->set(tagStr()[TAG_PROJECT], deflateStr(Str(project, 200)));
  if (_isValid(copyright, 200))
    tagList->set(
      imageTagStr()[IMAGE_TAG_COPYRIGHT], deflateStr(Str(copyright, 200))
    );

  // Image information.
  //
  if (! f->getU16(&image.orient)) return false;
  if (! f->getU16(&image.elemSize)) return false;
  if (! f->getU32(image.size, 2)) return false;
  
  for (int i = 0; i < 8; ++i) {
  
    if (! f->getU32(&imageElem[i].dataSign)) return false;
    if (! f->getU32(&imageElem[i].lowData)) return false;
    if (! f->getF32(&imageElem[i].lowQuantity)) return false;
    if (! f->getU32(&imageElem[i].highData)) return false;
    if (! f->getF32(&imageElem[i].highQuantity)) return false;
    if (! f->getU8(&imageElem[i].descriptor)) return false;
    if (! f->getU8(&imageElem[i].transfer)) return false;
    if (! f->getU8(&imageElem[i].colorimetric)) return false;
    if (! f->getU8(&imageElem[i].bitDepth)) return false;
    if (! f->getU16(&imageElem[i].packing)) return false;
    if (! f->getU16(&imageElem[i].encoding)) return false;
    if (! f->getU32(&imageElem[i].dataOffset)) return false;
    if (! f->getU32(&imageElem[i].linePadding)) return false;
    if (! f->getU32(&imageElem[i].imagePadding)) return false;
    if (! f->get(&imageElem[i].description, 32)) return false;
    
    if (! _isValid(imageElem[i].linePadding))
      imageElem[i].linePadding = 0;
    if (! _isValid(imageElem[i].imagePadding))
      imageElem[i].imagePadding = 0;
  }
  if (! f->get(tmp, 52)) return false;
  
  // Source information.
  //
  if (! f->getU32(source.offset, 2)) return false;
  if (! f->getF32(source.center, 2)) return false;
  if (! f->getU32(source.size, 2)) return false;
  if (! f->get(source.fileName, 100)) return false;
  if (! f->get(source.dateTime, 24)) return false;
  if (! f->get(source.inputDevice, 32)) return false;
  if (! f->get(source.inputSerial, 32)) return false;
  if (! f->getU16(source.border, 4)) return false;
  if (! f->getU32(source.pixelAspect, 2)) return false;
  if (! f->get(tmp, 28)) return false;
  
  if (
    _isValid(source.offset[0]) &&
    _isValid(source.offset[1])
  )
    tagList->set(
      tagStr()[TAG_SOURCE_OFFSET],
      deflateStr(Vec2i(source.offset[0], source.offset[1]))
    );
  if (
    _isValid(source.center[0]) &&
    _isValid(source.center[1])
  )
    tagList->set(
      tagStr()[TAG_SOURCE_CENTER],
      deflateStr(Vec2f(source.center[0], source.center[1]))
    );
  if (
    _isValid(source.size[0]) &&
    _isValid(source.size[1])
  )
    tagList->set(
      tagStr()[TAG_SOURCE_SIZE],
      deflateStr(Vec2i(source.size[0], source.size[1]))
    );
  if (_isValid(source.fileName, 100))
    tagList->set(
      tagStr()[TAG_SOURCE_FILE_NAME],
      deflateStr(Str(source.fileName, 100))
    );
  if (_isValid(source.dateTime, 24))
    tagList->set(
      imageTagStr()[IMAGE_TAG_TIME_STAMP],
      deflateStr(Str(source.dateTime, 24))
    );
  if (_isValid(source.inputDevice, 32))
    tagList->set(
      tagStr()[TAG_SOURCE_INPUT_DEVICE],
      deflateStr(Str(source.inputDevice, 32))
    );
  if (_isValid(source.inputSerial, 32))
    tagList->set(
      tagStr()[TAG_SOURCE_INPUT_SERIAL],
      deflateStr(Str(source.inputSerial, 32))
    );
  if (
    _isValid(source.border[0]) &&
    _isValid(source.border[1]) &&
    _isValid(source.border[2]) &&
    _isValid(source.border[3])
  )
    tagList->set(
      tagStr()[TAG_SOURCE_BORDER],
      deflateStr(
        IntList() +
        int(source.border[0]) +
        int(source.border[1]) +
        int(source.border[2]) +
        int(source.border[3])
      )
    );
  if (
    _isValid(source.pixelAspect[0]) &&
    _isValid(source.pixelAspect[1])
  )
    tagList->set(
      tagStr()[TAG_SOURCE_PIXEL_ASPECT],
      deflateStr(
        IntList() +
        int(source.pixelAspect[0]) +
        int(source.pixelAspect[1])
      )
    );
  
  // Film information.
  //
  if (! f->get(film.id, 2)) return false;
  if (! f->get(film.type, 2)) return false;
  if (! f->get(film.offset, 2)) return false;
  if (! f->get(film.prefix, 6)) return false;
  if (! f->get(film.count, 4)) return false;
  if (! f->get(film.format, 32)) return false;
  if (! f->getU32(&film.frame)) return false;
  if (! f->getU32(&film.sequence)) return false;
  if (! f->getU32(&film.hold)) return false;
  if (! f->getF32(&film.frameRate)) return false;
  if (! f->getF32(&film.shutter)) return false;
  if (! f->get(film.frameId, 32)) return false;
  if (! f->get(film.slate, 100)) return false;
  if (! f->get(tmp, 56)) return false;
  
  if (
    _isValid(film.id, 2) &&
    _isValid(film.type, 2) &&
    _isValid(film.offset, 2) &&
    _isValid(film.prefix, 6) &&
    _isValid(film.count, 4)
  ) {
    tagList->set(
      imageTagStr()[IMAGE_TAG_KEY_CODE],
      deflateStr(
        keyCodeToStr(
          toInt(Str(film.id, 2)),
          toInt(Str(film.type, 2)),
          toInt(Str(film.prefix, 6)),
          toInt(Str(film.count, 4)),
          toInt(Str(film.offset, 2))
        )
      )
    );
  }
  if (_isValid(film.format, 32))
    tagList->set(tagStr()[TAG_FILM_FORMAT], deflateStr(Str(film.format, 32)));
  if (_isValid(film.frame))
    tagList->set(tagStr()[TAG_FILM_FRAME], deflateStr(int(film.frame)));
  if (_isValid(film.sequence))
    tagList->set(tagStr()[TAG_FILM_SEQUENCE], deflateStr(int(film.sequence)));
  if (_isValid(film.hold))
    tagList->set(tagStr()[TAG_FILM_HOLD], deflateStr(int(film.hold)));
  if (_isValid(film.frameRate))
    tagList->set(tagStr()[TAG_FILM_FRAME_RATE], deflateStr(film.frameRate));
  if (_isValid(film.shutter))
    tagList->set(tagStr()[TAG_FILM_SHUTTER], deflateStr(film.shutter));
  if (_isValid(film.frameId, 32))
    tagList->set(
      tagStr()[TAG_FILM_FRAME_ID], deflateStr(Str(film.frameId, 32))
    );
  if (_isValid(film.slate, 100))
    tagList->set(tagStr()[TAG_FILM_SLATE], deflateStr(Str(film.slate, 100)));
  
  // TV information.
  //
  if (! f->getU32(&tv.timeCode)) return false;
  if (! f->getU32(&tv.userBits)) return false;
  if (! f->getU8(&tv.interlace)) return false;
  if (! f->getU8(&tv.field)) return false;
  if (! f->getU8(&tv.videoSignal)) return false;
  if (! f->get(tmp, 1)) return false;
  if (! f->getF32(tv.sampleRate, 2)) return false;
  if (! f->getF32(&tv.frameRate)) return false;
  if (! f->getF32(&tv.timeOffset)) return false;
  if (! f->getF32(&tv.gamma)) return false;
  if (! f->getF32(&tv.blackLevel)) return false;
  if (! f->getF32(&tv.blackGain)) return false;
  if (! f->getF32(&tv.breakpoint)) return false;
  if (! f->getF32(&tv.whiteLevel)) return false;
  if (! f->getF32(&tv.integrationTimes)) return false;
  if (! f->get(tmp, 76)) return false;
  
  if (_isValid(tv.timeCode))
    tagList->set(
      imageTagStr()[IMAGE_TAG_TIME_CODE],
      deflateStr(timeCodeToStr(tv.timeCode))
    );
  if (_isValid(tv.interlace))
    tagList->set(tagStr()[TAG_TV_INTERLACE], deflateStr(tv.interlace));
  if (_isValid(tv.field))
    tagList->set(tagStr()[TAG_TV_FIELD], deflateStr(tv.field));
  if (_isValid(tv.videoSignal))
    tagList->set(tagStr()[TAG_TV_VIDEO_SIGNAL], deflateStr(tv.videoSignal));
  if (
    _isValid(tv.sampleRate[0]) &&
    _isValid(tv.sampleRate[1])
  )
    tagList->set(
      tagStr()[TAG_TV_SAMPLE_RATE],
      deflateStr(Vec2f(tv.sampleRate[0], tv.sampleRate[1]))
    );
  if (_isValid(tv.frameRate))
    tagList->set(tagStr()[TAG_TV_FRAME_RATE], deflateStr(tv.frameRate));
  if (_isValid(tv.timeOffset))
    tagList->set(tagStr()[TAG_TV_TIME_OFFSET], deflateStr(tv.timeOffset));
  if (_isValid(tv.gamma))
    tagList->set(tagStr()[TAG_TV_GAMMA], deflateStr(tv.gamma));
  if (_isValid(tv.blackLevel))
    tagList->set(tagStr()[TAG_TV_BLACK_LEVEL], deflateStr(tv.blackLevel));
  if (_isValid(tv.blackGain))
    tagList->set(tagStr()[TAG_TV_BLACK_GAIN], deflateStr(tv.blackGain));
  if (_isValid(tv.breakpoint))
    tagList->set(tagStr()[TAG_TV_BREAK_POINT], deflateStr(tv.breakpoint));
  if (_isValid(tv.whiteLevel))
    tagList->set(tagStr()[TAG_TV_WHITE_LEVEL], deflateStr(tv.whiteLevel));
  if (_isValid(tv.integrationTimes))
    tagList->set(
      tagStr()[TAG_TV_INTEGRATION_TIMES], deflateStr(tv.integrationTimes)
    );
  
  return true;
}

void Header::zero()
{
  // Generic information.
  //
  _zero(&imageOffset);
  _zero(version, 8);
  _zero(&fileSize);
  _zero(&dittoKey);
  _zero(&genericHeaderSize);
  _zero(&industryHeaderSize);
  _zero(&userHeaderSize);
  _zero(fileName, 100);
  _zero(dateTime, 24);
  _zero(creator, 100);
  _zero(project, 200);
  _zero(copyright, 200);
  _zero(&encryptionKey);
  
  // Image information.
  //
  _zero(&image.orient);
  _zero(&image.elemSize);
  _zero(&image.size[0]);
  _zero(&image.size[1]);

  // Image elements.
  //
  for (int i = 0; i < 8; ++i) {
    _zero(&imageElem[i].dataSign);
    _zero(&imageElem[i].lowData);
    _zero(&imageElem[i].lowQuantity);
    _zero(&imageElem[i].highData);
    _zero(&imageElem[i].highQuantity);
    _zero(&imageElem[i].descriptor);
    _zero(&imageElem[i].transfer);
    _zero(&imageElem[i].colorimetric);
    _zero(&imageElem[i].bitDepth);
    _zero(&imageElem[i].packing);
    _zero(&imageElem[i].encoding);
    _zero(&imageElem[i].dataOffset);
    _zero(&imageElem[i].linePadding);
    _zero(&imageElem[i].imagePadding);
    _zero(imageElem[i].description, 32);
  }

  // Source information.
  //
  _zero(&source.offset[0]);
  _zero(&source.offset[1]);
  _zero(&source.center[0]);
  _zero(&source.center[1]);
  _zero(&source.size[0]);
  _zero(&source.size[1]);
  _zero(source.fileName, 100);
  _zero(source.dateTime, 24);
  _zero(source.inputDevice, 32);
  _zero(source.inputSerial, 32);
  _zero(&source.border[0]);
  _zero(&source.border[1]);
  _zero(&source.border[2]);
  _zero(&source.border[3]);
  _zero(&source.pixelAspect[0]);
  _zero(&source.pixelAspect[1]);

  // Film information.
  //
  _zero(film.id, 2);
  _zero(film.type, 2);
  _zero(film.offset, 2);
  _zero(film.prefix, 6);
  _zero(film.count, 4);
  _zero(film.format, 32);
  _zero(&film.frame);
  _zero(&film.sequence);
  _zero(&film.hold);
  _zero(&film.frameRate);
  _zero(&film.shutter);
  _zero(film.frameId, 32);
  _zero(film.slate, 100);

  // TV information.
  //
  _zero(&tv.timeCode);
  _zero(&tv.userBits);
  _zero(&tv.interlace);
  _zero(&tv.field);
  _zero(&tv.videoSignal);
  _zero(&tv.sampleRate[0]);
  _zero(&tv.sampleRate[1]);
  _zero(&tv.frameRate);
  _zero(&tv.timeOffset);
  _zero(&tv.gamma);
  _zero(&tv.blackLevel);
  _zero(&tv.blackGain);
  _zero(&tv.breakpoint);
  _zero(&tv.whiteLevel);
  _zero(&tv.integrationTimes);
}

const StrList tagStr()
{
  return StrList() +
  
    Str("Project") +
    
    Str("Source Offset") + Str("Source Center") + Str("Source Size") +
    Str("Source File Name") + Str("Source Input Device") +
    Str("Source Input Serial") + Str("Source Border") +
    Str("Source Pixel Aspect") +
    
    Str("Film Format") + Str("Film Frame") + Str("Film Sequence") +
    Str("Film Hold") + Str("Film Frame Rate") + Str("Film Shutter") +
    Str("Film Frame Id") + Str("Film Slate") +
    
    Str("TV Interlace") + Str("TV Field") + Str("TV Video Signal") +
    Str("TV Sample Rate") + Str("TV Frame Rate") + Str("TV Time Offset") +
    Str("TV Gamma") + Str("TV Black Level") + Str("TV Black Gain") +
    Str("TV Break Point") + Str("TV White Level") +
    Str("TV Integration Times");
}

#ifdef DJ_DBG
static const Str _toStr(uint8_t in)
  { return _isValid(in) ? Str("%1").arg(int(in)) : "-"; }
static const Str _toStr(uint16_t in)
  { return _isValid(in) ? Str("%1").arg(int(in)) : "-"; }
static const Str _toStr(uint32_t in)
  { return _isValid(in) ? Str("%1").arg(int(in)) : "-"; }
  //{ return dbgBits(in); }
static const Str _toStr(float in)
  { return _isValid(in) ? Str("%1").arg(in) : "-"; }
  //{ return dbgBits(in); }
static const Str _toStr(const char * in, int size)
  { return _isValid(in, size) ? Str(in, size) : "-"; }
#endif

void dbg(const Header & in)
{
  DJ_DBG_FNC("dj::ImageIoDpx::Header");
  
  // Generic information.
  //
  DJ_DBG_PRN("image offset = " << _toStr(in.imageOffset));
  DJ_DBG_PRN("version = " << _toStr(in.version, 8));
  DJ_DBG_PRN("file size = " << _toStr(in.fileSize));
  DJ_DBG_PRN("ditto key = " << _toStr(in.dittoKey));
  DJ_DBG_PRN("generic header size = " << _toStr(in.genericHeaderSize));
  DJ_DBG_PRN("industry header size = " << _toStr(in.industryHeaderSize));
  DJ_DBG_PRN("user header size = " << _toStr(in.userHeaderSize));
  DJ_DBG_PRN("file name = " << _toStr(in.fileName, 100));
  DJ_DBG_PRN("date time = " << _toStr(in.dateTime, 24));
  DJ_DBG_PRN("creator = " << _toStr(in.creator, 100));
  DJ_DBG_PRN("project = " << _toStr(in.project, 200));
  DJ_DBG_PRN("copyright = " << _toStr(in.copyright, 200));
  DJ_DBG_PRN("encryption key = " << _toStr(in.encryptionKey));

  // Image.
  //
  DJ_DBG_PRN("image orient = " << _toStr(in.image.orient));
  DJ_DBG_PRN("image element size = " << _toStr(in.image.elemSize));
  DJ_DBG_PRN(
    "image size = " << _toStr(in.image.size[0]) << " " <<
      _toStr(in.image.size[1])
  );

  // Image elements.
  //
  for (int i = 0; i < in.image.elemSize; ++i) {
    DJ_DBG_PRN("image element " << i);
    DJ_DBG_PRN("  data sign = " << _toStr(in.imageElem[i].dataSign));
    DJ_DBG_PRN("  low data = " << _toStr(in.imageElem[i].lowData));
    DJ_DBG_PRN("  low quantity = " << _toStr(in.imageElem[i].lowQuantity));
    DJ_DBG_PRN("  high data = " << _toStr(in.imageElem[i].highData));
    DJ_DBG_PRN("  high quantity = " << _toStr(in.imageElem[i].highQuantity));
    DJ_DBG_PRN("  descriptor = " << _toStr(in.imageElem[i].descriptor));
    DJ_DBG_PRN("  transfer = " << _toStr(in.imageElem[i].transfer));
    DJ_DBG_PRN("  colorimetric = " << _toStr(in.imageElem[i].colorimetric));
    DJ_DBG_PRN("  bit depth = " << _toStr(in.imageElem[i].bitDepth));
    DJ_DBG_PRN("  packing = " << _toStr(in.imageElem[i].packing));
    DJ_DBG_PRN("  encoding = " << _toStr(in.imageElem[i].encoding));
    DJ_DBG_PRN("  data offset = " << _toStr(in.imageElem[i].dataOffset));
    DJ_DBG_PRN("  line padding = " << _toStr(in.imageElem[i].linePadding));
    DJ_DBG_PRN("  image padding = " << _toStr(in.imageElem[i].imagePadding));
    DJ_DBG_PRN("  description = " << _toStr(in.imageElem[i].description, 32));
  }
  
  // Source information.
  //
  DJ_DBG_PRN(
    "source offset = " << _toStr(in.source.offset[0]) << " " <<
      _toStr(in.source.offset[1])
  );
  DJ_DBG_PRN(
    "source center = " << _toStr(in.source.center[0]) << " " <<
      _toStr(in.source.center[1])
  );
  DJ_DBG_PRN("source size = " << _toStr(in.source.size[0]) << " " <<
    _toStr(in.source.size[1]));
  DJ_DBG_PRN("source file name = " << _toStr(in.source.fileName, 100));
  DJ_DBG_PRN("source date time = " << _toStr(in.source.dateTime, 24));
  DJ_DBG_PRN("source input device = " << _toStr(in.source.inputDevice, 32));
  DJ_DBG_PRN("source inputs serial = " << _toStr(in.source.inputSerial, 32));
  DJ_DBG_PRN(
    "source border = " <<
    _toStr(in.source.border[0]) << " " <<
    _toStr(in.source.border[1]) << " " << 
    _toStr(in.source.border[2]) << " " << 
    _toStr(in.source.border[3])
  );
  DJ_DBG_PRN(
    "source pixel aspect = " <<
    _toStr(in.source.pixelAspect[0]) << " " <<
    _toStr(in.source.pixelAspect[1])
  );

  // Film information.
  //
  DJ_DBG_PRN("film id = " << _toStr(in.film.id, 2));
  DJ_DBG_PRN("film type = " << _toStr(in.film.type, 2));
  DJ_DBG_PRN("film offset = " << _toStr(in.film.offset, 2));
  DJ_DBG_PRN("film prefix = " << _toStr(in.film.prefix, 6));
  DJ_DBG_PRN("film count = " << _toStr(in.film.count, 4));
  DJ_DBG_PRN("film format = " << _toStr(in.film.format, 32));
  DJ_DBG_PRN("film frame = " << _toStr(in.film.frame));
  DJ_DBG_PRN("film sequence = " << _toStr(in.film.sequence));
  DJ_DBG_PRN("film hold = " << _toStr(in.film.hold));
  DJ_DBG_PRN("film frameRate = " << _toStr(in.film.frameRate));
  DJ_DBG_PRN("film shutter = " << _toStr(in.film.shutter));
  DJ_DBG_PRN("film frame id = " << _toStr(in.film.frameId, 32));
  DJ_DBG_PRN("film slate = " << _toStr(in.film.slate, 100));

  // TV information.
  //
  DJ_DBG_PRN("tv time code = " << _toStr(in.tv.timeCode));
  DJ_DBG_PRN("tv user bits = " << _toStr(in.tv.userBits));
  DJ_DBG_PRN("tv interlace = " << _toStr(in.tv.interlace));
  DJ_DBG_PRN("tv field = " << _toStr(in.tv.field));
  DJ_DBG_PRN("tv video signal = " << _toStr(in.tv.videoSignal));
  DJ_DBG_PRN(
    "tv sample rate = " <<
    _toStr(in.tv.sampleRate[0]) << " " << _toStr(in.tv.sampleRate[1])
  );
  DJ_DBG_PRN("tv tv frame rate = " << _toStr(in.tv.frameRate));
  DJ_DBG_PRN("tv tv time offset = " << _toStr(in.tv.timeOffset));
  DJ_DBG_PRN("tv gamma = " << _toStr(in.tv.gamma));
  DJ_DBG_PRN("tv black level = " << _toStr(in.tv.blackLevel));
  DJ_DBG_PRN("tv black gain = " << _toStr(in.tv.blackGain));
  DJ_DBG_PRN("tv breakpoint = " << _toStr(in.tv.breakpoint));
  DJ_DBG_PRN("tv white level = " << _toStr(in.tv.whiteLevel));
  DJ_DBG_PRN("tv integration times = " << _toStr(in.tv.integrationTimes));
}

}
}
