/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Copyright 1999,2000,2001 David Hodson <hodsond@acm.org>
 */

/** \file
 * \ingroup imbcineon
 *
 * Cineon image file format library routines.
 */

#include "logImageCore.h"
#include "cineonlib.h"
#include "dpxlib.h"
#include "logmemfile.h"

#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "BLI_fileops.h"
#include "BLI_utildefines.h"

#include "IMB_imbuf.h"

#include "MEM_guardedalloc.h"

/*
 * Declaration of static functions
 */

static int logImageSetData8(LogImageFile *logImage, LogImageElement logElement, float *data);
static int logImageSetData10(LogImageFile *logImage, LogImageElement logElement, float *data);
static int logImageSetData12(LogImageFile *logImage, LogImageElement logElement, float *data);
static int logImageSetData16(LogImageFile *logImage, LogImageElement logElement, float *data);
static int logImageElementGetData(LogImageFile *logImage, LogImageElement logElement, float *data);
static int logImageElementGetData1(LogImageFile *logImage,
                                   LogImageElement logElement,
                                   float *data);
static int logImageElementGetData8(LogImageFile *logImage,
                                   LogImageElement logElement,
                                   float *data);
static int logImageElementGetData10(LogImageFile *logImage,
                                    LogImageElement logElement,
                                    float *data);
static int logImageElementGetData10Packed(LogImageFile *logImage,
                                          LogImageElement logElement,
                                          float *data);
static int logImageElementGetData12(LogImageFile *logImage,
                                    LogImageElement logElement,
                                    float *data);
static int logImageElementGetData12Packed(LogImageFile *logImage,
                                          LogImageElement logElement,
                                          float *data);
static int logImageElementGetData16(LogImageFile *logImage,
                                    LogImageElement logElement,
                                    float *data);
static int convertLogElementToRGBA(float *src,
                                   float *dst,
                                   LogImageFile *logImage,
                                   LogImageElement logElement,
                                   int dstIsLinearRGB);
static int convertRGBAToLogElement(float *src,
                                   float *dst,
                                   LogImageFile *logImage,
                                   LogImageElement logElement,
                                   int srcIsLinearRGB);

/*
 * For debug purpose
 */

static int verbose = 0;

void logImageSetVerbose(int verbosity)
{
  verbose = verbosity;
  cineonSetVerbose(verbosity);
  dpxSetVerbose(verbosity);
}

/*
 * IO stuff
 */

int logImageIsDpx(const void *buffer)
{
  unsigned int magicNum = *(unsigned int *)buffer;
  return (magicNum == DPX_FILE_MAGIC || magicNum == swap_uint(DPX_FILE_MAGIC, 1));
}

int logImageIsCineon(const void *buffer)
{
  unsigned int magicNum = *(unsigned int *)buffer;
  return (magicNum == CINEON_FILE_MAGIC || magicNum == swap_uint(CINEON_FILE_MAGIC, 1));
}

LogImageFile *logImageOpenFromFile(const char *filename, int cineon)
{
  unsigned int magicNum;
  FILE *f = BLI_fopen(filename, "rb");

  (void)cineon;

  if (f == NULL) {
    return NULL;
  }

  if (fread(&magicNum, sizeof(unsigned int), 1, f) != 1) {
    fclose(f);
    return NULL;
  }

  fclose(f);

  if (logImageIsDpx(&magicNum)) {
    return dpxOpen((const unsigned char *)filename, 0, 0);
  }
  if (logImageIsCineon(&magicNum)) {
    return cineonOpen((const unsigned char *)filename, 0, 0);
  }

  return NULL;
}

LogImageFile *logImageOpenFromMemory(const unsigned char *buffer, unsigned int size)
{
  if (logImageIsDpx(buffer)) {
    return dpxOpen(buffer, 1, size);
  }
  if (logImageIsCineon(buffer)) {
    return cineonOpen(buffer, 1, size);
  }

  return NULL;
}

LogImageFile *logImageCreate(const char *filename,
                             int cineon,
                             int width,
                             int height,
                             int bitsPerSample,
                             int isLogarithmic,
                             int hasAlpha,
                             int referenceWhite,
                             int referenceBlack,
                             float gamma,
                             const char *creator)
{
  /* referenceWhite, referenceBlack and gamma values are only supported for DPX file */
  if (cineon) {
    return cineonCreate(filename, width, height, bitsPerSample, creator);
  }

  return dpxCreate(filename,
                   width,
                   height,
                   bitsPerSample,
                   isLogarithmic,
                   hasAlpha,
                   referenceWhite,
                   referenceBlack,
                   gamma,
                   creator);

  return NULL;
}

void logImageClose(LogImageFile *logImage)
{
  if (logImage != NULL) {
    if (logImage->file) {
      fclose(logImage->file);
      logImage->file = NULL;
    }
    MEM_freeN(logImage);
  }
}

void logImageGetSize(LogImageFile *logImage, int *width, int *height, int *depth)
{
  *width = logImage->width;
  *height = logImage->height;
  *depth = logImage->depth;
}

/*
 * Helper
 */

size_t getRowLength(size_t width, LogImageElement logElement)
{
  /* return the row length in bytes according to width and packing method */
  switch (logElement.bitsPerSample) {
    case 1:
      return ((width * logElement.depth - 1) / 32 + 1) * 4;

    case 8:
      return ((width * logElement.depth - 1) / 4 + 1) * 4;

    case 10:
      if (logElement.packing == 0) {
        return ((width * logElement.depth * 10 - 1) / 32 + 1) * 4;
      }
      else if (logElement.packing == 1 || logElement.packing == 2) {
        return ((width * logElement.depth - 1) / 3 + 1) * 4;
      }
      break;
    case 12:
      if (logElement.packing == 0) {
        return ((width * logElement.depth * 12 - 1) / 32 + 1) * 4;
      }
      else if (logElement.packing == 1 || logElement.packing == 2) {
        return width * logElement.depth * 2;
      }
      break;
    case 16:
      return width * logElement.depth * 2;
  }
  return 0;
}

/*
 * Data writing
 */

int logImageSetDataRGBA(LogImageFile *logImage, float *data, int dataIsLinearRGB)
{
  float *elementData;
  int returnValue;

  elementData = (float *)imb_alloc_pixels(
      logImage->width, logImage->height, logImage->depth, sizeof(float), __func__);
  if (elementData == NULL) {
    return 1;
  }

  if (convertRGBAToLogElement(
          data, elementData, logImage, logImage->element[0], dataIsLinearRGB) != 0) {
    MEM_freeN(elementData);
    return 1;
  }

  switch (logImage->element[0].bitsPerSample) {
    case 8:
      returnValue = logImageSetData8(logImage, logImage->element[0], elementData);
      break;

    case 10:
      returnValue = logImageSetData10(logImage, logImage->element[0], elementData);
      break;

    case 12:
      returnValue = logImageSetData12(logImage, logImage->element[0], elementData);
      break;

    case 16:
      returnValue = logImageSetData16(logImage, logImage->element[0], elementData);
      break;

    default:
      returnValue = 1;
      break;
  }

  MEM_freeN(elementData);
  return returnValue;
}

static int logImageSetData8(LogImageFile *logImage, LogImageElement logElement, float *data)
{
  size_t rowLength = getRowLength(logImage->width, logElement);
  unsigned char *row;

  row = (unsigned char *)MEM_mallocN(rowLength, __func__);
  if (row == NULL) {
    if (verbose) {
      printf("DPX/Cineon: Cannot allocate row.\n");
    }
    return 1;
  }
  memset(row, 0, rowLength);

  for (size_t y = 0; y < logImage->height; y++) {
    for (size_t x = 0; x < logImage->width * logImage->depth; x++) {
      row[x] = (unsigned char)float_uint(data[y * logImage->width * logImage->depth + x], 255);
    }

    if (logimage_fwrite(row, rowLength, 1, logImage) == 0) {
      if (verbose) {
        printf("DPX/Cineon: Error while writing file.\n");
      }
      MEM_freeN(row);
      return 1;
    }
  }
  MEM_freeN(row);
  return 0;
}

static int logImageSetData10(LogImageFile *logImage, LogImageElement logElement, float *data)
{
  size_t rowLength = getRowLength(logImage->width, logElement);
  unsigned int pixel, index;
  unsigned int *row;

  row = (unsigned int *)MEM_mallocN(rowLength, __func__);
  if (row == NULL) {
    if (verbose) {
      printf("DPX/Cineon: Cannot allocate row.\n");
    }
    return 1;
  }

  for (size_t y = 0; y < logImage->height; y++) {
    int offset = 22;
    index = 0;
    pixel = 0;

    for (size_t x = 0; x < logImage->width * logImage->depth; x++) {
      pixel |= (unsigned int)float_uint(data[y * logImage->width * logImage->depth + x], 1023)
               << offset;
      offset -= 10;
      if (offset < 0) {
        row[index] = swap_uint(pixel, logImage->isMSB);
        index++;
        pixel = 0;
        offset = 22;
      }
    }
    if (pixel != 0) {
      row[index] = swap_uint(pixel, logImage->isMSB);
    }

    if (logimage_fwrite(row, rowLength, 1, logImage) == 0) {
      if (verbose) {
        printf("DPX/Cineon: Error while writing file.\n");
      }
      MEM_freeN(row);
      return 1;
    }
  }
  MEM_freeN(row);
  return 0;
}

static int logImageSetData12(LogImageFile *logImage, LogImageElement logElement, float *data)
{
  size_t rowLength = getRowLength(logImage->width, logElement);
  unsigned short *row;

  row = (unsigned short *)MEM_mallocN(rowLength, __func__);
  if (row == NULL) {
    if (verbose) {
      printf("DPX/Cineon: Cannot allocate row.\n");
    }
    return 1;
  }

  for (size_t y = 0; y < logImage->height; y++) {
    for (size_t x = 0; x < logImage->width * logImage->depth; x++) {
      row[x] = swap_ushort(
          ((unsigned short)float_uint(data[y * logImage->width * logImage->depth + x], 4095)) << 4,
          logImage->isMSB);
    }

    if (logimage_fwrite(row, rowLength, 1, logImage) == 0) {
      if (verbose) {
        printf("DPX/Cineon: Error while writing file.\n");
      }
      MEM_freeN(row);
      return 1;
    }
  }
  MEM_freeN(row);
  return 0;
}

static int logImageSetData16(LogImageFile *logImage, LogImageElement logElement, float *data)
{
  size_t rowLength = getRowLength(logImage->width, logElement);
  unsigned short *row;

  row = (unsigned short *)MEM_mallocN(rowLength, __func__);
  if (row == NULL) {
    if (verbose) {
      printf("DPX/Cineon: Cannot allocate row.\n");
    }
    return 1;
  }

  for (size_t y = 0; y < logImage->height; y++) {
    for (size_t x = 0; x < logImage->width * logImage->depth; x++) {
      row[x] = swap_ushort(
          (unsigned short)float_uint(data[y * logImage->width * logImage->depth + x], 65535),
          logImage->isMSB);
    }

    if (logimage_fwrite(row, rowLength, 1, logImage) == 0) {
      if (verbose) {
        printf("DPX/Cineon: Error while writing file.\n");
      }
      MEM_freeN(row);
      return 1;
    }
  }
  MEM_freeN(row);
  return 0;
}

/*
 * Data reading
 */

int logImageGetDataRGBA(LogImageFile *logImage, float *data, int dataIsLinearRGB)
{
  /* Fills data with 32 bits float RGBA values */
  int i, j, returnValue, sortedElementData[8], hasAlpha;
  float *elementData[8];
  float *elementData_ptr[8];
  float *mergedData;
  unsigned int sampleIndex;
  LogImageElement mergedElement;

  /* Determine the depth of the picture and if there's a separate alpha element.
   * If the element is supported, load it into an unsigned ints array. */
  memset(&elementData, 0, 8 * sizeof(float *));
  hasAlpha = 0;

  for (i = 0; i < logImage->numElements; i++) {
    /* descriptor_Depth and descriptor_Composite are not supported */
    if (logImage->element[i].descriptor != descriptor_Depth &&
        logImage->element[i].descriptor != descriptor_Composite) {
      /* Allocate memory */
      elementData[i] = imb_alloc_pixels(
          logImage->width, logImage->height, logImage->element[i].depth, sizeof(float), __func__);
      if (elementData[i] == NULL) {
        if (verbose) {
          printf("DPX/Cineon: Cannot allocate memory for elementData[%d]\n.", i);
        }
        for (j = 0; j < i; j++) {
          if (elementData[j] != NULL) {
            MEM_freeN(elementData[j]);
          }
        }
        return 1;
      }
      elementData_ptr[i] = elementData[i];

      /* Load data */
      if (logImageElementGetData(logImage, logImage->element[i], elementData[i]) != 0) {
        if (verbose) {
          printf("DPX/Cineon: Cannot read elementData[%d]\n.", i);
        }
        for (j = 0; j < i; j++) {
          if (elementData[j] != NULL) {
            MEM_freeN(elementData[j]);
          }
        }
        return 1;
      }
    }

    if (logImage->element[i].descriptor == descriptor_Alpha) {
      hasAlpha = 1;
    }
  }

  /* only one element, easy case, no need to do anything  */
  if (logImage->numElements == 1) {
    returnValue = convertLogElementToRGBA(
        elementData[0], data, logImage, logImage->element[0], dataIsLinearRGB);
    MEM_freeN(elementData[0]);
  }
  else {
    /* The goal here is to merge every elements into only one
     * to recreate a classic 16 bits RGB, RGBA or YCbCr element.
     * Unsupported elements are skipped (depth, composite) */

    memcpy(&mergedElement, &logImage->element[0], sizeof(LogImageElement));
    mergedElement.descriptor = -1;
    mergedElement.depth = logImage->depth;
    memset(&sortedElementData, -1, sizeof(int[8]));

    /* Try to know how to assemble the elements */
    for (i = 0; i < logImage->numElements; i++) {
      switch (logImage->element[i].descriptor) {
        case descriptor_Red:
        case descriptor_RGB:
          if (hasAlpha == 0) {
            mergedElement.descriptor = descriptor_RGB;
          }
          else {
            mergedElement.descriptor = descriptor_RGBA;
          }

          sortedElementData[0] = i;
          break;

        case descriptor_Green:
          if (hasAlpha == 0) {
            mergedElement.descriptor = descriptor_RGB;
          }
          else {
            mergedElement.descriptor = descriptor_RGBA;
          }

          sortedElementData[1] = i;
          break;

        case descriptor_Blue:
          if (hasAlpha == 0) {
            mergedElement.descriptor = descriptor_RGB;
          }
          else {
            mergedElement.descriptor = descriptor_RGBA;
          }

          sortedElementData[2] = i;
          break;

        case descriptor_Alpha:
          /* Alpha component is always the last one */
          sortedElementData[mergedElement.depth - 1] = i;
          break;

        case descriptor_Luminance:
          if (mergedElement.descriptor == -1) {
            if (hasAlpha == 0) {
              mergedElement.descriptor = descriptor_Luminance;
            }
            else {
              mergedElement.descriptor = descriptor_YA;
            }
          }
          else if (mergedElement.descriptor == descriptor_Chrominance) {
            if (mergedElement.depth == 2) {
              mergedElement.descriptor = descriptor_CbYCrY;
            }
            else if (mergedElement.depth == 3) {
              if (hasAlpha == 0) {
                mergedElement.descriptor = descriptor_CbYCr;
              }
              else {
                mergedElement.descriptor = descriptor_CbYACrYA;
              }
            }
            else if (mergedElement.depth == 4) {
              mergedElement.descriptor = descriptor_CbYCrA;
            }
          }

          /* Y component always in 1 except if it's alone or with alpha */
          if (mergedElement.depth == 1 || (mergedElement.depth == 2 && hasAlpha == 1)) {
            sortedElementData[0] = i;
          }
          else {
            sortedElementData[1] = i;
          }
          break;

        case descriptor_Chrominance:
          if (mergedElement.descriptor == -1) {
            mergedElement.descriptor = descriptor_Chrominance;
          }
          else if (mergedElement.descriptor == descriptor_Luminance) {
            if (mergedElement.depth == 2) {
              mergedElement.descriptor = descriptor_CbYCrY;
            }
            else if (mergedElement.depth == 3) {
              if (hasAlpha == 0) {
                mergedElement.descriptor = descriptor_CbYCr;
              }
              else {
                mergedElement.descriptor = descriptor_CbYACrYA;
              }
            }
            else if (mergedElement.depth == 4) {
              mergedElement.descriptor = descriptor_CbYCrA;
            }
          }

          /* Cb and Cr always in 0 or 2 */
          if (sortedElementData[0] == -1) {
            sortedElementData[0] = i;
          }
          else {
            sortedElementData[2] = i;
          }
          break;

        case descriptor_CbYCr:
          if (hasAlpha == 0) {
            mergedElement.descriptor = descriptor_CbYCr;
          }
          else {
            mergedElement.descriptor = descriptor_CbYCrA;
          }

          sortedElementData[0] = i;
          break;

        case descriptor_RGBA:
        case descriptor_ABGR:
        case descriptor_CbYACrYA:
        case descriptor_CbYCrY:
        case descriptor_CbYCrA:
          /* I don't think these ones can be seen in a planar image */
          mergedElement.descriptor = logImage->element[i].descriptor;
          sortedElementData[0] = i;
          break;

        case descriptor_Depth:
        case descriptor_Composite:
          /* Not supported */
          break;
      }
    }

    mergedData = (float *)imb_alloc_pixels(
        logImage->width, logImage->height, mergedElement.depth, sizeof(float), __func__);
    if (mergedData == NULL) {
      if (verbose) {
        printf("DPX/Cineon: Cannot allocate mergedData.\n");
      }
      for (i = 0; i < logImage->numElements; i++) {
        if (elementData[i] != NULL) {
          MEM_freeN(elementData[i]);
        }
      }
      return 1;
    }

    sampleIndex = 0;
    while (sampleIndex < logImage->width * logImage->height * mergedElement.depth) {
      for (i = 0; i < logImage->numElements; i++) {
        for (j = 0; j < logImage->element[sortedElementData[i]].depth; j++) {
          mergedData[sampleIndex++] = *(elementData_ptr[sortedElementData[i]]++);
        }
      }
    }

    /* Done with elements data, clean-up */
    for (i = 0; i < logImage->numElements; i++) {
      if (elementData[i] != NULL) {
        MEM_freeN(elementData[i]);
      }
    }

    returnValue = convertLogElementToRGBA(
        mergedData, data, logImage, mergedElement, dataIsLinearRGB);
    MEM_freeN(mergedData);
  }
  return returnValue;
}

static int logImageElementGetData(LogImageFile *logImage, LogImageElement logElement, float *data)
{
  switch (logElement.bitsPerSample) {
    case 1:
      return logImageElementGetData1(logImage, logElement, data);

    case 8:
      return logImageElementGetData8(logImage, logElement, data);

    case 10:
      if (logElement.packing == 0) {
        return logImageElementGetData10Packed(logImage, logElement, data);
      }
      else if (logElement.packing == 1 || logElement.packing == 2) {
        return logImageElementGetData10(logImage, logElement, data);
      }
      break;

    case 12:
      if (logElement.packing == 0) {
        return logImageElementGetData12Packed(logImage, logElement, data);
      }
      else if (logElement.packing == 1 || logElement.packing == 2) {
        return logImageElementGetData12(logImage, logElement, data);
      }
      break;

    case 16:
      return logImageElementGetData16(logImage, logElement, data);
  }
  /* format not supported */
  return 1;
}

static int logImageElementGetData1(LogImageFile *logImage, LogImageElement logElement, float *data)
{
  unsigned int pixel;

  /* seek at the right place */
  if (logimage_fseek(logImage, logElement.dataOffset, SEEK_SET) != 0) {
    if (verbose) {
      printf("DPX/Cineon: Couldn't seek at %d\n", logElement.dataOffset);
    }
    return 1;
  }

  /* read 1 bit data padded to 32 bits */
  for (size_t y = 0; y < logImage->height; y++) {
    for (size_t x = 0; x < logImage->width * logElement.depth; x += 32) {
      if (logimage_read_uint(&pixel, logImage) != 0) {
        if (verbose) {
          printf("DPX/Cineon: EOF reached\n");
        }
        return 1;
      }
      pixel = swap_uint(pixel, logImage->isMSB);
      for (int offset = 0; offset < 32 && x + offset < logImage->width; offset++) {
        data[y * logImage->width * logElement.depth + x + offset] = (float)((pixel >> offset) &
                                                                            0x01);
      }
    }
  }
  return 0;
}

static int logImageElementGetData8(LogImageFile *logImage, LogImageElement logElement, float *data)
{
  size_t rowLength = getRowLength(logImage->width, logElement);
  unsigned char pixel;

  /* extract required pixels */
  for (size_t y = 0; y < logImage->height; y++) {
    /* 8 bits are 32-bits padded so we need to seek at each row */
    if (logimage_fseek(logImage, logElement.dataOffset + y * rowLength, SEEK_SET) != 0) {
      if (verbose) {
        printf("DPX/Cineon: Couldn't seek at %d\n", (int)(logElement.dataOffset + y * rowLength));
      }
      return 1;
    }

    for (size_t x = 0; x < logImage->width * logElement.depth; x++) {
      if (logimage_read_uchar(&pixel, logImage) != 0) {
        if (verbose) {
          printf("DPX/Cineon: EOF reached\n");
        }
        return 1;
      }
      data[y * logImage->width * logElement.depth + x] = (float)pixel / 255.0f;
    }
  }
  return 0;
}

static int logImageElementGetData10(LogImageFile *logImage,
                                    LogImageElement logElement,
                                    float *data)
{
  unsigned int pixel;

  /* seek to data */
  if (logimage_fseek(logImage, logElement.dataOffset, SEEK_SET) != 0) {
    if (verbose) {
      printf("DPX/Cineon: Couldn't seek at %d\n", logElement.dataOffset);
    }
    return 1;
  }

  if (logImage->depth == 1 && logImage->srcFormat == format_DPX) {
    for (size_t y = 0; y < logImage->height; y++) {
      int offset = 32;
      for (size_t x = 0; x < logImage->width * logElement.depth; x++) {
        /* we need to read the next long */
        if (offset >= 30) {
          if (logElement.packing == 1) {
            offset = 2;
          }
          else if (logElement.packing == 2) {
            offset = 0;
          }

          if (logimage_read_uint(&pixel, logImage) != 0) {
            if (verbose) {
              printf("DPX/Cineon: EOF reached\n");
            }
            return 1;
          }
          pixel = swap_uint(pixel, logImage->isMSB);
        }
        data[y * logImage->width * logElement.depth + x] = (float)((pixel >> offset) & 0x3ff) /
                                                           1023.0f;
        offset += 10;
      }
    }
  }
  else {
    for (size_t y = 0; y < logImage->height; y++) {
      int offset = -1;
      for (size_t x = 0; x < logImage->width * logElement.depth; x++) {
        /* we need to read the next long */
        if (offset < 0) {
          if (logElement.packing == 1) {
            offset = 22;
          }
          else if (logElement.packing == 2) {
            offset = 20;
          }

          if (logimage_read_uint(&pixel, logImage) != 0) {
            if (verbose) {
              printf("DPX/Cineon: EOF reached\n");
            }
            return 1;
          }
          pixel = swap_uint(pixel, logImage->isMSB);
        }
        data[y * logImage->width * logElement.depth + x] = (float)((pixel >> offset) & 0x3ff) /
                                                           1023.0f;
        offset -= 10;
      }
    }
  }

  return 0;
}

static int logImageElementGetData10Packed(LogImageFile *logImage,
                                          LogImageElement logElement,
                                          float *data)
{
  size_t rowLength = getRowLength(logImage->width, logElement);
  unsigned int pixel, oldPixel;

  /* converting bytes to pixels */
  for (size_t y = 0; y < logImage->height; y++) {
    /* seek to data */
    if (logimage_fseek(logImage, y * rowLength + logElement.dataOffset, SEEK_SET) != 0) {
      if (verbose) {
        printf("DPX/Cineon: Couldn't seek at %u\n",
               (unsigned int)(y * rowLength + logElement.dataOffset));
      }
      return 1;
    }

    oldPixel = 0;
    int offset = 0;
    int offset2 = 0;

    for (size_t x = 0; x < logImage->width * logElement.depth; x++) {
      if (offset2 != 0) {
        offset = 10 - offset2;
        offset2 = 0;
        oldPixel = 0;
      }
      else if (offset == 32) {
        offset = 0;
      }
      else if (offset + 10 > 32) {
        /* next pixel is on two different longs */
        oldPixel = (pixel >> offset);
        offset2 = 32 - offset;
        offset = 0;
      }

      if (offset == 0) {
        /* we need to read the next long */
        if (logimage_read_uint(&pixel, logImage) != 0) {
          if (verbose) {
            printf("DPX/Cineon: EOF reached\n");
          }
          return 1;
        }
        pixel = swap_uint(pixel, logImage->isMSB);
      }
      data[y * logImage->width * logElement.depth + x] =
          (float)((((pixel << offset2) >> offset) & 0x3ff) | oldPixel) / 1023.0f;
      offset += 10;
    }
  }
  return 0;
}

static int logImageElementGetData12(LogImageFile *logImage,
                                    LogImageElement logElement,
                                    float *data)
{
  unsigned int sampleIndex;
  unsigned int numSamples = logImage->width * logImage->height * logElement.depth;
  unsigned short pixel;

  /* seek to data */
  if (logimage_fseek(logImage, logElement.dataOffset, SEEK_SET) != 0) {
    if (verbose) {
      printf("DPX/Cineon: Couldn't seek at %d\n", logElement.dataOffset);
    }
    return 1;
  }

  /* convert bytes to pixels */
  sampleIndex = 0;

  for (sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) {
    if (logimage_read_ushort(&pixel, logImage) != 0) {
      if (verbose) {
        printf("DPX/Cineon: EOF reached\n");
      }
      return 1;
    }
    pixel = swap_ushort(pixel, logImage->isMSB);

    if (logElement.packing == 1) { /* padded to the right */
      data[sampleIndex] = (float)(pixel >> 4) / 4095.0f;
    }
    else if (logElement.packing == 2) { /* padded to the left */
      data[sampleIndex] = (float)pixel / 4095.0f;
    }
  }
  return 0;
}

static int logImageElementGetData12Packed(LogImageFile *logImage,
                                          LogImageElement logElement,
                                          float *data)
{
  size_t rowLength = getRowLength(logImage->width, logElement);
  unsigned int pixel, oldPixel;

  /* converting bytes to pixels */
  for (size_t y = 0; y < logImage->height; y++) {
    /* seek to data */
    if (logimage_fseek(logImage, y * rowLength + logElement.dataOffset, SEEK_SET) != 0) {
      if (verbose) {
        printf("DPX/Cineon: Couldn't seek at %u\n",
               (unsigned int)(y * rowLength + logElement.dataOffset));
      }
      return 1;
    }

    oldPixel = 0;
    int offset = 0;
    int offset2 = 0;

    for (size_t x = 0; x < logImage->width * logElement.depth; x++) {
      if (offset2 != 0) {
        offset = 12 - offset2;
        offset2 = 0;
        oldPixel = 0;
      }
      else if (offset == 32) {
        offset = 0;
      }
      else if (offset + 12 > 32) {
        /* next pixel is on two different longs */
        oldPixel = (pixel >> offset);
        offset2 = 32 - offset;
        offset = 0;
      }

      if (offset == 0) {
        /* we need to read the next long */
        if (logimage_read_uint(&pixel, logImage) != 0) {
          if (verbose) {
            printf("DPX/Cineon: EOF reached\n");
          }
          return 1;
        }
        pixel = swap_uint(pixel, logImage->isMSB);
      }
      data[y * logImage->width * logElement.depth + x] =
          (float)((((pixel << offset2) >> offset) & 0xfff) | oldPixel) / 4095.0f;
      offset += 12;
    }
  }
  return 0;
}

static int logImageElementGetData16(LogImageFile *logImage,
                                    LogImageElement logElement,
                                    float *data)
{
  unsigned int numSamples = logImage->width * logImage->height * logElement.depth;
  unsigned int sampleIndex;
  unsigned short pixel;

  /* seek to data */
  if (logimage_fseek(logImage, logElement.dataOffset, SEEK_SET) != 0) {
    if (verbose) {
      printf("DPX/Cineon: Couldn't seek at %d\n", logElement.dataOffset);
    }
    return 1;
  }

  for (sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) {
    if (logimage_read_ushort(&pixel, logImage) != 0) {
      if (verbose) {
        printf("DPX/Cineon: EOF reached\n");
      }
      return 1;
    }
    pixel = swap_ushort(pixel, logImage->isMSB);
    data[sampleIndex] = (float)pixel / 65535.0f;
  }

  return 0;
}

/*
 * Color conversion
 */

static int getYUVtoRGBMatrix(float *matrix, LogImageElement logElement)
{
  float scaleY, scaleCbCr;
  float refHighData = (float)logElement.refHighData / logElement.maxValue;
  float refLowData = (float)logElement.refLowData / logElement.maxValue;

  scaleY = 1.0f / (refHighData - refLowData);
  scaleCbCr = scaleY * ((940.0f - 64.0f) / (960.0f - 64.0f));

  switch (logElement.transfer) {
    case 2: /* linear */
      matrix[0] = 1.0f * scaleY;
      matrix[1] = 1.0f * scaleCbCr;
      matrix[2] = 1.0f * scaleCbCr;
      matrix[3] = 1.0f * scaleY;
      matrix[4] = 1.0f * scaleCbCr;
      matrix[5] = 1.0f * scaleCbCr;
      matrix[6] = 1.0f * scaleY;
      matrix[7] = 1.0f * scaleCbCr;
      matrix[8] = 1.0f * scaleCbCr;
      return 0;

    case 5: /* SMPTE 240M */
      matrix[0] = 1.0000f * scaleY;
      matrix[1] = 0.0000f * scaleCbCr;
      matrix[2] = 1.5756f * scaleCbCr;
      matrix[3] = 1.0000f * scaleY;
      matrix[4] = -0.2253f * scaleCbCr;
      matrix[5] = -0.5000f * scaleCbCr;
      matrix[6] = 1.0000f * scaleY;
      matrix[7] = 1.8270f * scaleCbCr;
      matrix[8] = 0.0000f * scaleCbCr;
      return 0;

    case 6: /* CCIR 709-1 */
      matrix[0] = 1.000000f * scaleY;
      matrix[1] = 0.000000f * scaleCbCr;
      matrix[2] = 1.574800f * scaleCbCr;
      matrix[3] = 1.000000f * scaleY;
      matrix[4] = -0.187324f * scaleCbCr;
      matrix[5] = -0.468124f * scaleCbCr;
      matrix[6] = 1.000000f * scaleY;
      matrix[7] = 1.855600f * scaleCbCr;
      matrix[8] = 0.000000f * scaleCbCr;
      return 0;

    case 7: /* CCIR 601 */
    case 8: /* I'm not sure 7 and 8 should share the same matrix */
      matrix[0] = 1.000000f * scaleY;
      matrix[1] = 0.000000f * scaleCbCr;
      matrix[2] = 1.402000f * scaleCbCr;
      matrix[3] = 1.000000f * scaleY;
      matrix[4] = -0.344136f * scaleCbCr;
      matrix[5] = -0.714136f * scaleCbCr;
      matrix[6] = 1.000000f * scaleY;
      matrix[7] = 1.772000f * scaleCbCr;
      matrix[8] = 0.000000f * scaleCbCr;
      return 0;

    default:
      return 1;
  }
}

static float *getLinToLogLut(LogImageFile *logImage, LogImageElement logElement)
{
  float *lut;
  float gain, negativeFilmGamma, offset, step;
  unsigned int lutsize = (unsigned int)(logElement.maxValue + 1);
  unsigned int i;

  lut = MEM_mallocN(sizeof(float) * lutsize, "getLinToLogLut");

  negativeFilmGamma = 0.6;
  step = logElement.refHighQuantity / logElement.maxValue;
  gain = logElement.maxValue /
         (1.0f - powf(10,
                      (logImage->referenceBlack - logImage->referenceWhite) * step /
                          negativeFilmGamma * logImage->gamma / 1.7f));
  offset = gain - logElement.maxValue;

  for (i = 0; i < lutsize; i++) {
    lut[i] = (logImage->referenceWhite +
              log10f(powf((i + offset) / gain, 1.7f / logImage->gamma)) /
                  (step / negativeFilmGamma)) /
             logElement.maxValue;
  }

  return lut;
}

static float *getLogToLinLut(LogImageFile *logImage, LogImageElement logElement)
{
  float *lut;
  float breakPoint, gain, kneeGain, kneeOffset, negativeFilmGamma, offset, step, softClip;
  /* float filmGamma; unused */
  unsigned int lutsize = (unsigned int)(logElement.maxValue + 1);
  unsigned int i;

  lut = MEM_mallocN(sizeof(float) * lutsize, "getLogToLinLut");

  /* Building the Log -> Lin LUT */
  step = logElement.refHighQuantity / logElement.maxValue;
  negativeFilmGamma = 0.6;

  /* these are default values */
  /* filmGamma = 2.2f;  unused */
  softClip = 0;

  breakPoint = logImage->referenceWhite - softClip;
  gain = logElement.maxValue /
         (1.0f - powf(10,
                      (logImage->referenceBlack - logImage->referenceWhite) * step /
                          negativeFilmGamma * logImage->gamma / 1.7f));
  offset = gain - logElement.maxValue;
  kneeOffset = powf(10,
                    (breakPoint - logImage->referenceWhite) * step / negativeFilmGamma *
                        logImage->gamma / 1.7f) *
                   gain -
               offset;
  kneeGain = (logElement.maxValue - kneeOffset) / powf(5 * softClip, softClip / 100);

  for (i = 0; i < lutsize; i++) {
    if (i < logImage->referenceBlack) {
      lut[i] = 0.0f;
    }
    else if (i > breakPoint) {
      lut[i] = (powf(i - breakPoint, softClip / 100) * kneeGain + kneeOffset) /
               logElement.maxValue;
    }
    else {
      lut[i] = (powf(10,
                     ((float)i - logImage->referenceWhite) * step / negativeFilmGamma *
                         logImage->gamma / 1.7f) *
                    gain -
                offset) /
               logElement.maxValue;
    }
  }

  return lut;
}

static float *getLinToSrgbLut(LogImageElement logElement)
{
  float col, *lut;
  unsigned int lutsize = (unsigned int)(logElement.maxValue + 1);
  unsigned int i;

  lut = MEM_mallocN(sizeof(float) * lutsize, "getLogToLinLut");

  for (i = 0; i < lutsize; i++) {
    col = (float)i / logElement.maxValue;
    if (col < 0.0031308f) {
      lut[i] = (col < 0.0f) ? 0.0f : col * 12.92f;
    }
    else {
      lut[i] = 1.055f * powf(col, 1.0f / 2.4f) - 0.055f;
    }
  }

  return lut;
}

static float *getSrgbToLinLut(LogImageElement logElement)
{
  float col, *lut;
  unsigned int lutsize = (unsigned int)(logElement.maxValue + 1);
  unsigned int i;

  lut = MEM_mallocN(sizeof(float) * lutsize, "getLogToLinLut");

  for (i = 0; i < lutsize; i++) {
    col = (float)i / logElement.maxValue;
    if (col < 0.04045f) {
      lut[i] = (col < 0.0f) ? 0.0f : col * (1.0f / 12.92f);
    }
    else {
      lut[i] = powf((col + 0.055f) * (1.0f / 1.055f), 2.4f);
    }
  }

  return lut;
}

static int convertRGBA_RGB(float *src,
                           float *dst,
                           LogImageFile *logImage,
                           LogImageElement logElement,
                           int elementIsSource)
{
  unsigned int i;
  float *src_ptr = src;
  float *dst_ptr = dst;

  switch (logElement.transfer) {
    case transfer_Unspecified:
    case transfer_UserDefined:
    case transfer_Linear:
    case transfer_Logarithmic: {
      for (i = 0; i < logImage->width * logImage->height; i++) {
        *(dst_ptr++) = *(src_ptr++);
        *(dst_ptr++) = *(src_ptr++);
        *(dst_ptr++) = *(src_ptr++);
        src_ptr++;
      }

      return 0;
    }

    case transfer_PrintingDensity: {
      float *lut;

      if (elementIsSource == 1) {
        lut = getLogToLinLut(logImage, logElement);
      }
      else {
        lut = getLinToLogLut(logImage, logElement);
      }

      for (i = 0; i < logImage->width * logImage->height; i++) {
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        src_ptr++;
      }

      MEM_freeN(lut);

      return 0;
    }

    default:
      if (verbose) {
        printf("DPX/Cineon: Unknown transfer %d.\n", logElement.transfer);
      }
      return 1;
  }
}

static int convertRGB_RGBA(float *src,
                           float *dst,
                           LogImageFile *logImage,
                           LogImageElement logElement,
                           int elementIsSource)
{
  unsigned int i;
  float *src_ptr = src;
  float *dst_ptr = dst;

  switch (logElement.transfer) {
    case transfer_Unspecified:
    case transfer_UserDefined:
    case transfer_Linear:
    case transfer_Logarithmic: {
      for (i = 0; i < logImage->width * logImage->height; i++) {
        *(dst_ptr++) = *(src_ptr++);
        *(dst_ptr++) = *(src_ptr++);
        *(dst_ptr++) = *(src_ptr++);
        *(dst_ptr++) = 1.0f;
      }

      return 0;
    }

    case transfer_PrintingDensity: {
      float *lut;

      if (elementIsSource == 1) {
        lut = getLogToLinLut(logImage, logElement);
      }
      else {
        lut = getLinToLogLut(logImage, logElement);
      }

      for (i = 0; i < logImage->width * logImage->height; i++) {
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = 1.0f;
      }

      MEM_freeN(lut);

      return 0;
    }

    default:
      if (verbose) {
        printf("DPX/Cineon: Unknown transfer %d.\n", logElement.transfer);
      }
      return 1;
  }
}

static int convertRGBA_RGBA(float *src,
                            float *dst,
                            LogImageFile *logImage,
                            LogImageElement logElement,
                            int elementIsSource)
{
  unsigned int i;
  float *src_ptr = src;
  float *dst_ptr = dst;

  switch (logElement.transfer) {
    case transfer_UserDefined:
    case transfer_Linear:
    case transfer_Logarithmic: {
      memcpy(dst, src, 4 * (size_t)logImage->width * (size_t)logImage->height * sizeof(float));
      return 0;
    }

    case transfer_PrintingDensity: {
      float *lut;

      if (elementIsSource == 1) {
        lut = getLogToLinLut(logImage, logElement);
      }
      else {
        lut = getLinToLogLut(logImage, logElement);
      }

      for (i = 0; i < logImage->width * logImage->height; i++) {
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
        *(dst_ptr++) = *(src_ptr++);
      }

      MEM_freeN(lut);

      return 0;
    }

    default:
      return 1;
  }
}

static int convertABGR_RGBA(float *src,
                            float *dst,
                            LogImageFile *logImage,
                            LogImageElement logElement,
                            int elementIsSource)
{
  unsigned int i;
  float *src_ptr = src;
  float *dst_ptr = dst;

  switch (logElement.transfer) {
    case transfer_UserDefined:
    case transfer_Linear:
    case transfer_Logarithmic: {
      for (i = 0; i < logImage->width * logImage->height; i++) {
        src_ptr += 4;
        *(dst_ptr++) = *(src_ptr--);
        *(dst_ptr++) = *(src_ptr--);
        *(dst_ptr++) = *(src_ptr--);
        *(dst_ptr++) = *(src_ptr--);
        src_ptr += 4;
      }
      return 0;
    }

    case transfer_PrintingDensity: {
      float *lut;

      if (elementIsSource == 1) {
        lut = getLogToLinLut(logImage, logElement);
      }
      else {
        lut = getLinToLogLut(logImage, logElement);
      }

      for (i = 0; i < logImage->width * logImage->height; i++) {
        src_ptr += 4;
        *(dst_ptr++) = lut[float_uint(*(src_ptr--), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr--), logElement.maxValue)];
        *(dst_ptr++) = lut[float_uint(*(src_ptr--), logElement.maxValue)];
        *(dst_ptr++) = *(src_ptr--);
        src_ptr += 4;
      }

      MEM_freeN(lut);

      return 0;
    }

    default:
      return 1;
  }
}

static int convertCbYCr_RGBA(float *src,
                             float *dst,
                             LogImageFile *logImage,
                             LogImageElement logElement)
{
  unsigned int i;
  float conversionMatrix[9], refLowData, y, cb, cr;
  float *src_ptr = src;
  float *dst_ptr = dst;

  if (getYUVtoRGBMatrix((float *)&conversionMatrix, logElement) != 0) {
    return 1;
  }

  refLowData = (float)logElement.refLowData / logElement.maxValue;

  for (i = 0; i < logImage->width * logImage->height; i++) {
    cb = *(src_ptr++) - 0.5f;
    y = *(src_ptr++) - refLowData;
    cr = *(src_ptr++) - 0.5f;

    *(dst_ptr++) = clamp_float(
        y * conversionMatrix[0] + cb * conversionMatrix[1] + cr * conversionMatrix[2], 0.0f, 1.0f);
    *(dst_ptr++) = clamp_float(
        y * conversionMatrix[3] + cb * conversionMatrix[4] + cr * conversionMatrix[5], 0.0f, 1.0f);
    *(dst_ptr++) = clamp_float(
        y * conversionMatrix[6] + cb * conversionMatrix[7] + cr * conversionMatrix[8], 0.0f, 1.0f);
    *(dst_ptr++) = 1.0f;
  }
  return 0;
}

static int convertCbYCrA_RGBA(float *src,
                              float *dst,
                              LogImageFile *logImage,
                              LogImageElement logElement)
{
  unsigned int i;
  float conversionMatrix[9], refLowData, y, cb, cr, a;
  float *src_ptr = src;
  float *dst_ptr = dst;

  if (getYUVtoRGBMatrix((float *)&conversionMatrix, logElement) != 0) {
    return 1;
  }

  refLowData = (float)logElement.refLowData / logElement.maxValue;

  for (i = 0; i < logImage->width * logImage->height; i++) {
    cb = *(src_ptr++) - 0.5f;
    y = *(src_ptr++) - refLowData;
    cr = *(src_ptr++) - 0.5f;
    a = *(src_ptr++);

    *(dst_ptr++) = clamp_float(
        y * conversionMatrix[0] + cb * conversionMatrix[1] + cr * conversionMatrix[2], 0.0f, 1.0f);
    *(dst_ptr++) = clamp_float(
        y * conversionMatrix[3] + cb * conversionMatrix[4] + cr * conversionMatrix[5], 0.0f, 1.0f);
    *(dst_ptr++) = clamp_float(
        y * conversionMatrix[6] + cb * conversionMatrix[7] + cr * conversionMatrix[8], 0.0f, 1.0f);
    *(dst_ptr++) = a;
  }
  return 0;
}

static int convertCbYCrY_RGBA(float *src,
                              float *dst,
                              LogImageFile *logImage,
                              LogImageElement logElement)
{
  unsigned int i;
  float conversionMatrix[9], refLowData, y1, y2, cb, cr;
  float *src_ptr = src;
  float *dst_ptr = dst;

  if (getYUVtoRGBMatrix((float *)&conversionMatrix, logElement) != 0) {
    return 1;
  }

  refLowData = (float)logElement.refLowData / logElement.maxValue;

  for (i = 0; i < logImage->width * logImage->height / 2; i++) {
    cb = *(src_ptr++) - 0.5f;
    y1 = *(src_ptr++) - refLowData;
    cr = *(src_ptr++) - 0.5f;
    y2 = *(src_ptr++) - refLowData;

    *(dst_ptr++) = clamp_float(y1 * conversionMatrix[0] + cb * conversionMatrix[1] +
                                   cr * conversionMatrix[2],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y1 * conversionMatrix[3] + cb * conversionMatrix[4] +
                                   cr * conversionMatrix[5],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y1 * conversionMatrix[6] + cb * conversionMatrix[7] +
                                   cr * conversionMatrix[8],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = 1.0f;
    *(dst_ptr++) = clamp_float(y2 * conversionMatrix[0] + cb * conversionMatrix[1] +
                                   cr * conversionMatrix[2],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y2 * conversionMatrix[3] + cb * conversionMatrix[4] +
                                   cr * conversionMatrix[5],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y2 * conversionMatrix[6] + cb * conversionMatrix[7] +
                                   cr * conversionMatrix[8],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = 1.0f;
  }
  return 0;
}

static int convertCbYACrYA_RGBA(float *src,
                                float *dst,
                                LogImageFile *logImage,
                                LogImageElement logElement)
{
  unsigned int i;
  float conversionMatrix[9], refLowData, y1, y2, cb, cr, a1, a2;
  float *src_ptr = src;
  float *dst_ptr = dst;

  if (getYUVtoRGBMatrix((float *)&conversionMatrix, logElement) != 0) {
    return 1;
  }

  refLowData = (float)logElement.refLowData / logElement.maxValue;

  for (i = 0; i < logImage->width * logImage->height / 2; i++) {
    cb = *(src_ptr++) - 0.5f;
    y1 = *(src_ptr++) - refLowData;
    a1 = *(src_ptr++);
    cr = *(src_ptr++) - 0.5f;
    y2 = *(src_ptr++) - refLowData;
    a2 = *(src_ptr++);

    *(dst_ptr++) = clamp_float(y1 * conversionMatrix[0] + cb * conversionMatrix[1] +
                                   cr * conversionMatrix[2],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y1 * conversionMatrix[3] + cb * conversionMatrix[4] +
                                   cr * conversionMatrix[5],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y1 * conversionMatrix[6] + cb * conversionMatrix[7] +
                                   cr * conversionMatrix[8],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = a1;
    *(dst_ptr++) = clamp_float(y2 * conversionMatrix[0] + cb * conversionMatrix[1] +
                                   cr * conversionMatrix[2],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y2 * conversionMatrix[3] + cb * conversionMatrix[4] +
                                   cr * conversionMatrix[5],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = clamp_float(y2 * conversionMatrix[6] + cb * conversionMatrix[7] +
                                   cr * conversionMatrix[8],
                               0.0f,
                               1.0f);
    *(dst_ptr++) = a2;
  }
  return 0;
}

static int convertLuminance_RGBA(float *src,
                                 float *dst,
                                 LogImageFile *logImage,
                                 LogImageElement logElement)
{
  unsigned int i;
  float conversionMatrix[9], value, refLowData;
  float *src_ptr = src;
  float *dst_ptr = dst;

  if (getYUVtoRGBMatrix((float *)&conversionMatrix, logElement) != 0) {
    return 1;
  }

  refLowData = (float)logElement.refLowData / logElement.maxValue;

  for (i = 0; i < logImage->width * logImage->height; i++) {
    value = clamp_float((*(src_ptr++) - refLowData) * conversionMatrix[0], 0.0f, 1.0f);
    *(dst_ptr++) = value;
    *(dst_ptr++) = value;
    *(dst_ptr++) = value;
    *(dst_ptr++) = 1.0f;
  }
  return 0;
}

static int convertYA_RGBA(float *src,
                          float *dst,
                          LogImageFile *logImage,
                          LogImageElement logElement)
{
  unsigned int i;
  float conversionMatrix[9], value, refLowData;
  float *src_ptr = src;
  float *dst_ptr = dst;

  if (getYUVtoRGBMatrix((float *)&conversionMatrix, logElement) != 0) {
    return 1;
  }

  refLowData = (float)logElement.refLowData / logElement.maxValue;

  for (i = 0; i < logImage->width * logImage->height; i++) {
    value = clamp_float((*(src_ptr++) - refLowData) * conversionMatrix[0], 0.0f, 1.0f);
    *(dst_ptr++) = value;
    *(dst_ptr++) = value;
    *(dst_ptr++) = value;
    *(dst_ptr++) = *(src_ptr++);
  }
  return 0;
}

static int convertLogElementToRGBA(
    float *src, float *dst, LogImageFile *logImage, LogImageElement logElement, int dstIsLinearRGB)
{
  int rvalue;
  unsigned int i;
  float *src_ptr;
  float *dst_ptr;

  /* Convert data in src to linear RGBA in dst */
  switch (logElement.descriptor) {
    case descriptor_RGB:
      rvalue = convertRGB_RGBA(src, dst, logImage, logElement, 1);
      break;

    case descriptor_RGBA:
      rvalue = convertRGBA_RGBA(src, dst, logImage, logElement, 1);
      break;

    case descriptor_ABGR:
      rvalue = convertABGR_RGBA(src, dst, logImage, logElement, 1);
      break;

    case descriptor_Luminance:
      rvalue = convertLuminance_RGBA(src, dst, logImage, logElement);
      break;

    case descriptor_CbYCr:
      rvalue = convertCbYCr_RGBA(src, dst, logImage, logElement);
      break;

    case descriptor_CbYCrY:
      rvalue = convertCbYCrY_RGBA(src, dst, logImage, logElement);
      break;

    case descriptor_CbYACrYA:
      rvalue = convertCbYACrYA_RGBA(src, dst, logImage, logElement);
      break;

    case descriptor_CbYCrA:
      rvalue = convertCbYCrA_RGBA(src, dst, logImage, logElement);
      break;

    case descriptor_YA: /* this descriptor is for internal use only */
      rvalue = convertYA_RGBA(src, dst, logImage, logElement);
      break;

    default:
      return 1;
  }

  if (rvalue == 1) {
    return 1;
  }
  if (dstIsLinearRGB) {
    /* convert data from sRGB to Linear RGB via lut */
    float *lut = getSrgbToLinLut(logElement);
    src_ptr = dst; /* no error here */
    dst_ptr = dst;
    for (i = 0; i < logImage->width * logImage->height; i++) {
      *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
      *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
      *(dst_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
      dst_ptr++;
      src_ptr++;
    }
    MEM_freeN(lut);
  }
  return 0;
}

static int convertRGBAToLogElement(
    float *src, float *dst, LogImageFile *logImage, LogImageElement logElement, int srcIsLinearRGB)
{
  unsigned int i;
  int rvalue;
  float *srgbSrc;
  float *srgbSrc_ptr;
  float *src_ptr = src;
  float *lut;

  if (srcIsLinearRGB != 0) {
    /* we need to convert src to sRGB */
    srgbSrc = (float *)imb_alloc_pixels(
        logImage->width, logImage->height, 4, sizeof(float), __func__);
    if (srgbSrc == NULL) {
      return 1;
    }

    memcpy(srgbSrc, src, 4 * (size_t)logImage->width * (size_t)logImage->height * sizeof(float));
    srgbSrc_ptr = srgbSrc;

    /* convert data from Linear RGB to sRGB via lut */
    lut = getLinToSrgbLut(logElement);
    for (i = 0; i < logImage->width * logImage->height; i++) {
      *(srgbSrc_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
      *(srgbSrc_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
      *(srgbSrc_ptr++) = lut[float_uint(*(src_ptr++), logElement.maxValue)];
      srgbSrc_ptr++;
      src_ptr++;
    }
    MEM_freeN(lut);
  }
  else {
    srgbSrc = src;
  }

  /* Convert linear RGBA data in src to format described by logElement in dst */
  switch (logElement.descriptor) {
    case descriptor_RGB:
      rvalue = convertRGBA_RGB(srgbSrc, dst, logImage, logElement, 0);
      break;

    case descriptor_RGBA:
      rvalue = convertRGBA_RGBA(srgbSrc, dst, logImage, logElement, 0);
      break;

    /* these ones are not supported for the moment */
    case descriptor_ABGR:
    case descriptor_Luminance:
    case descriptor_CbYCr:
    case descriptor_CbYCrY:
    case descriptor_CbYACrYA:
    case descriptor_CbYCrA:
    case descriptor_YA: /* this descriptor is for internal use only */
    default:
      rvalue = 1;
      break;
  }

  if (srcIsLinearRGB != 0) {
    MEM_freeN(srgbSrc);
  }

  return rvalue;
}
