bool writeDng()

in source/util/RawUtil.cpp [135:437]


bool writeDng(
    const filesystem::path& rawImageFilename,
    const filesystem::path& outputFilename,
    CameraIsp& cameraIsp) {
  LOG(INFO) << folly::sformat("Writing: {}", outputFilename.string()) << std::endl;
  // - sanity check
  CHECK_EQ(rawImageFilename.extension(), ".raw");
  int outputBitsPerPixel = 8 * sizeof(T);

  LOG_IF(WARNING, outputBitsPerPixel != cameraIsp.getSensorBitsPerPixel())
      << outputBitsPerPixel << "-bit output precision != " << cameraIsp.getSensorBitsPerPixel()
      << "-bit input precision" << std::endl;

  // - load ISP config and read raw image
  switch (cameraIsp.getSensorBitsPerPixel()) {
    case 8: {
      std::vector<uint8_t> rawImage = readRawImage<uint8_t>(rawImageFilename, cameraIsp);
      cameraIsp.loadImageFromSensor(rawImage);
      break;
    }
    case 16: {
      std::vector<uint16_t> rawImage = readRawImage<uint16_t>(rawImageFilename, cameraIsp);
      cameraIsp.loadImageFromSensor(rawImage);
      break;
    }
    default:
      CHECK(false) << "Unsupported output precision" << std::endl;
  };

  // The step below involves an internal conversion to and from float32, which, for uint16
  // input+output type, can result in off-by-one differences between pixels in the original image
  // and the written DNG
  cv::Mat_<T> preprocessedRawImage = cameraIsp.getRawImage<T>();

  FILE* fDng = fopen(outputFilename.string().c_str(), "w");
  if (fDng == NULL) {
    LOG(ERROR) << folly::sformat("Failed to open file: {}", outputFilename.string()) << std::endl;
    return false;
  }

  uint32_t width = preprocessedRawImage.cols;
  uint32_t height = preprocessedRawImage.rows;

  // TIFF data layout calculations (64k strip for 16bit data)
  uint16_t rowsPerStrip = (32 * 1024) / width;
  uint16_t stripsPerImg = (height / rowsPerStrip);
  if (height % rowsPerStrip != 0) {
    ++stripsPerImg;
  }

  const std::string cameraSoftware("RawToRgb");

  // Write the TIFF file header
  const char byteOrder[3] = "II";
  fwrite(byteOrder, sizeof(char), 2, fDng);

  const uint16_t version = 42;
  fwrite(&version, sizeof(uint16_t), 1, fDng);

  const uint32_t Idf0Offset = 0x00000008;
  fwrite(&Idf0Offset, sizeof(unsigned), 1, fDng);

  const uint16_t ifdCount = 42;
  fwrite(&ifdCount, sizeof(uint16_t), 1, fDng);

  // Map ISP cfa pattern code to DNG's
  uint32_t cfaFilter;

  switch (cameraIsp.getFilters()) {
    case 0x94949494:
      cfaFilter = 0x02010100;
      break;
    case 0x16161616:
      cfaFilter = 0x00010102;
      break;
    case 0x49494949:
      cfaFilter = 0x01000201;
      break;
    case 0x61616161:
      cfaFilter = 0x01020001;
      break;
    default:
      LOG(ERROR) << "Unknown bayer-pattern found while writing DNG file" << std::endl;
      fclose(fDng);
      return false;
  }

  // Write the tags
  const uint32_t kIfdEntrySize = sizeof(uint16_t) * 2 + sizeof(uint32_t) * 2;
  uint32_t dOffset = 10 + ifdCount * kIfdEntrySize + 4;

  writeIfd(kTiffTagNewSubFileType, kTiffTypeLONG, 1, 0, 0, dOffset, fDng);
  writeIfd(kTiffTagImageWidth, kTiffTypeLONG, 1, width, 0, dOffset, fDng);
  writeIfd(kTiffTagImageLength, kTiffTypeLONG, 1, height, 0, dOffset, fDng);
  writeIfd(kTiffTagBitsPerSample, kTiffTypeSHORT, 1, outputBitsPerPixel, 0, dOffset, fDng);
  writeIfd(kTiffTagCompression, kTiffTypeSHORT, 1, 1, 0, dOffset, fDng);
  writeIfd(kTiffTagPhotometricInterpretation, kTiffTypeSHORT, 1, 32803, 0, dOffset, fDng);
  writeIfd(
      kTiffTagStripOffsets, kTiffTypeLONG, stripsPerImg, dOffset, stripsPerImg * 4, dOffset, fDng);
  writeIfd(kTiffTagOrientation, kTiffTypeSHORT, 1, 1, 0, dOffset, fDng);
  writeIfd(kTiffTagSamplesPerPixel, kTiffTypeSHORT, 1, 1, 0, dOffset, fDng);
  writeIfd(kTiffTagRowsPerStrip, kTiffTypeSHORT, 1, rowsPerStrip, 0, dOffset, fDng);
  writeIfd(
      kTiffTagStripByteCounts,
      kTiffTypeSHORT,
      stripsPerImg,
      dOffset,
      stripsPerImg * 2,
      dOffset,
      fDng);
  writeIfd(kTiffTagPlanarConfiguration, kTiffTypeSHORT, 1, 1, 0, dOffset, fDng);
  writeIfd(kTiffTagResolutionUnit, kTiffTypeSHORT, 1, 2, 0, dOffset, fDng);
  writeIfd(
      kTiffTagSoftware,
      kTiffTypeASCII,
      cameraSoftware.size() + 1,
      dOffset,
      cameraSoftware.size() + 1,
      dOffset,
      fDng);
  writeIfd(kTiffTagDateTime, kTiffTypeASCII, 20, dOffset, 20, dOffset, fDng);
  writeIfd(kTiffEpTagCFARepeatPatternDim, kTiffTypeSHORT, 2, 0x00020002, 0, dOffset, fDng);
  writeIfd(kTiffEpTagCFAPattern, kTiffTypeBYTE, 4, cfaFilter, 0, dOffset, fDng);
  writeIfd(kDngTagDNGVersion, kTiffTypeBYTE, 4, 0x00000301, 0, dOffset, fDng);
  writeIfd(kDngTagDNGBackwardVersion, kTiffTypeBYTE, 4, 0x00000101, 0, dOffset, fDng);
  writeIfd(kDngTagCFAPlaneColor, kTiffTypeBYTE, 3, 0x00020100, 0, dOffset, fDng);
  writeIfd(kDngTagCFALayout, kTiffTypeSHORT, 1, 1, 0, dOffset, fDng);
  writeIfd(kDngTagBlackLevelRepeatDim, kTiffTypeSHORT, 2, 0x00020002, 0, dOffset, fDng);
  writeIfd(kDngTagBlackLevel, kTiffTypeSHORT, 4, dOffset, 8, dOffset, fDng);
  writeIfd(kDngTagWhiteLevel, kTiffTypeLONG, 1, (1 << outputBitsPerPixel) - 1, 0, dOffset, fDng);
  writeIfd(kDngTagDefaultScale, kTiffTypeRATIONAL, 2, dOffset, 16, dOffset, fDng);
  writeIfd(kDngTagDefaultCropOrigin, kTiffTypeSHORT, 2, 0, 0, dOffset, fDng);
  writeIfd(kDngTagDefaultCropSize, kTiffTypeSHORT, 2, (height << 16) | width, 0, dOffset, fDng);
  writeIfd(kDngTagColorMatrix1, kTiffTypeSRATIONAL, 9, dOffset, 9 * 8, dOffset, fDng);
  writeIfd(kDngTagAnalogBalance, kTiffTypeRATIONAL, 3, dOffset, 3 * 8, dOffset, fDng);
  writeIfd(kDngTagAsShotNeutral, kTiffTypeRATIONAL, 3, dOffset, 3 * 8, dOffset, fDng);
  writeIfd(kDngTagBaselineExposure, kTiffTypeSRATIONAL, 1, dOffset, 8, dOffset, fDng);
  writeIfd(kDngTagBaselineSharpness, kTiffTypeRATIONAL, 1, dOffset, 8, dOffset, fDng);
  writeIfd(kDngTagBayerGreenSplit, kTiffTypeLONG, 1, 0, 0, dOffset, fDng);
  writeIfd(kDngTagLinearResponseLimit, kTiffTypeRATIONAL, 1, dOffset, 8, dOffset, fDng);
  writeIfd(kDngTagLensInfo, kTiffTypeRATIONAL, 4, dOffset, 32, dOffset, fDng);
  writeIfd(kDngTagAntiAliasStrength, kTiffTypeRATIONAL, 1, dOffset, 8, dOffset, fDng);
  writeIfd(kDngTagCalibrationIlluminant1, kTiffTypeSHORT, 1, 23, 0, dOffset, fDng);
  writeIfd(kDngTagBestQualityScale, kTiffTypeRATIONAL, 1, dOffset, 8, dOffset, fDng);

  // Now write data fields
  uint32_t firstIFD = 0x00000000;

  fwrite(&firstIFD, sizeof(uint32_t), 1, fDng);

  std::vector<uint32_t> stripOff;
  stripOff.push_back(dOffset); // where image data will be written...
  for (int16_t s = 1; s < stripsPerImg; ++s) {
    stripOff.push_back(stripOff[s - 1] + (rowsPerStrip * width) * 2);
  }

  fwrite(&stripOff[0], sizeof(unsigned), stripsPerImg, fDng);

  std::vector<uint16_t> stripCnt;
  int nRowsLeft = height;
  uint32_t t = 0;
  while (nRowsLeft > 0) {
    if ((unsigned)nRowsLeft > rowsPerStrip) {
      stripCnt.push_back(rowsPerStrip * width * 2);
    } else {
      stripCnt.push_back(nRowsLeft * width * 2);
    }
    ++t;
    nRowsLeft -= rowsPerStrip;
  }

  fwrite(&stripCnt[0], sizeof(uint16_t), stripsPerImg, fDng);
  fwrite(cameraSoftware.c_str(), sizeof(char), cameraSoftware.size() + 1, fDng);

  char szDateTime[72];
  time_t time = 0; // Need to get this from the camera meta data.
  struct tm* tlocal = localtime(&time);
  snprintf(
      szDateTime,
      72,
      "%04d-%02d-%02d %02d:%02d:%02d",
      tlocal->tm_year + 1900,
      tlocal->tm_mon,
      tlocal->tm_mday,
      tlocal->tm_hour,
      tlocal->tm_min,
      tlocal->tm_sec);
  szDateTime[71] = '\0';

  fwrite(szDateTime, sizeof(char), 20, fDng);

  // Conversion to XYZ - Bradford adapted using D50 reference white point
  cv::Mat_<float> sRgbToXyzD50(3, 3);
  sRgbToXyzD50(0, 0) = 0.4360747;
  sRgbToXyzD50(0, 1) = 0.3850649;
  sRgbToXyzD50(0, 2) = 0.1430804;

  sRgbToXyzD50(1, 0) = 0.2225045;
  sRgbToXyzD50(1, 1) = 0.7168786;
  sRgbToXyzD50(1, 2) = 0.0606169;

  sRgbToXyzD50(2, 0) = 0.0139322;
  sRgbToXyzD50(2, 1) = 0.0971045;
  sRgbToXyzD50(2, 2) = 0.7141733;

  cv::Mat_<float> ccm = cameraIsp.getCCM();
  cv::Mat_<float> camToXyz = ccm * sRgbToXyzD50;
  cv::Mat_<float> xyzToCam;
  invert(camToXyz, xyzToCam);

  uint16_t uBlackLevel[4]; // {G, R, B, G}
  cv::Point3f bl = cameraIsp.getBlackLevel() * ((1 << outputBitsPerPixel) - 1);
  uBlackLevel[0] = bl.y;
  uBlackLevel[3] = bl.y;
  uBlackLevel[1] = bl.x;
  uBlackLevel[2] = bl.z;
  fwrite(uBlackLevel, sizeof(uint16_t), 4, fDng);

  uint32_t defaultScale[4];
  defaultScale[0] = 1;
  defaultScale[2] = 1;
  defaultScale[1] = 1;
  defaultScale[3] = 1;

  fwrite(defaultScale, sizeof(unsigned), 4, fDng);

  int colorMatrix[18];
  for (int i = 0; i < 9; ++i) {
    const int x = i % 3;
    const int y = i / 3;
    colorMatrix[2 * i] = xyzToCam(y, x) * (1 << 28);
    colorMatrix[2 * i + 1] = (1 << 28);
  }
  fwrite(colorMatrix, sizeof(int), 18, fDng);

  uint32_t analogBalance[6];
  analogBalance[0] = 256;
  analogBalance[1] = 256;
  analogBalance[2] = 256;
  analogBalance[3] = 256;
  analogBalance[4] = 256;
  analogBalance[5] = 256;

  fwrite(analogBalance, sizeof(unsigned), 6, fDng);

  // kDngTagAsShotNeutral
  cv::Point3f whitePoint = cameraIsp.getWhiteBalanceGain();
  uint32_t asShotNeutral[6];
  float minChannel = std::min(std::min(whitePoint.x, whitePoint.y), whitePoint.z);
  asShotNeutral[0] = (minChannel / whitePoint.x) * float(1 << 28);
  asShotNeutral[1] = 1 << 28;
  asShotNeutral[2] = (minChannel / whitePoint.y) * float(1 << 28);
  asShotNeutral[3] = 1 << 28;
  asShotNeutral[4] = (minChannel / whitePoint.z) * float(1 << 28);
  asShotNeutral[5] = 1 << 28;

  fwrite(asShotNeutral, sizeof(uint32_t), 6, fDng);

  int32_t baseExposure[2];

  baseExposure[0] = -log2f(1.0f / minChannel) * (1 << 28);
  baseExposure[1] = (1 << 28);

  fwrite(baseExposure, sizeof(unsigned), 2, fDng);

  uint32_t baseSharp[2];

  baseSharp[0] = 1;
  baseSharp[1] = 1;

  fwrite(baseSharp, sizeof(unsigned), 2, fDng);

  uint32_t linearLimit[2];
  linearLimit[0] = 1;
  linearLimit[1] = 1; // 1.0 = sensor is linear

  fwrite(linearLimit, sizeof(unsigned), 2, fDng);

  uint32_t lensInfo[8] = {0, 0, 0, 0, 0, 0, 0, 0};
  fwrite(lensInfo, sizeof(unsigned), 8, fDng);

  uint32_t antiAlias[2];
  antiAlias[0] = 0;
  antiAlias[1] = 1; // Turn off antiAliasStrength

  fwrite(antiAlias, sizeof(unsigned), 2, fDng);

  uint32_t bestScale[2];
  bestScale[0] = 1;
  bestScale[1] = 1; // use 1:1 scaling

  fwrite(bestScale, sizeof(unsigned), 2, fDng);

  // Raw image data
  const size_t status = fwrite(preprocessedRawImage.data, sizeof(T), width * height, fDng);

  if (status != width * height) {
    LOG(ERROR) << "DNG write error: image data" << std::endl;
  }

  fclose(fDng);
  return true;
}