/* -*- C++ -*-
 * Copyright 2019-2025 LibRaw LLC (info@libraw.org)
 *

 LibRaw is free software; you can redistribute it and/or modify
 it under the terms of the one of two licenses as you choose:

1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
   (See file LICENSE.LGPL provided in LibRaw distribution archive for details).

2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
   (See file LICENSE.CDDL provided in LibRaw distribution archive for details).

 */

#include "../../internal/libraw_cxx_defs.h"
#include "../../internal/libraw_cameraids.h"

#ifndef LIBRAW_NO_IOSTREAMS_DATASTREAM
int LibRaw::open_file(const char *fname, INT64 max_buf_size)
{
	int big = 0;
	if (max_buf_size == LIBRAW_OPEN_BIGFILE)
		big = 1;
	else if (max_buf_size == LIBRAW_OPEN_FILE)
		big = 0;
	else
	{
#ifndef LIBRAW_WIN32_CALLS
		struct stat st;
		if (stat(fname, &st))
			return LIBRAW_IO_ERROR;
		big = (st.st_size > max_buf_size) ? 1 : 0;
#else
		struct _stati64 st;
		if (_stati64(fname, &st))
			return LIBRAW_IO_ERROR;
		big = (st.st_size > max_buf_size) ? 1 : 0;
#endif
	}

  LibRaw_abstract_datastream *stream;
  try
  {
    if (big)
      stream = new LibRaw_bigfile_datastream(fname);
    else
      stream = new LibRaw_file_datastream(fname);
  }

  catch (const std::bad_alloc& )
  {
    recycle();
    return LIBRAW_UNSUFFICIENT_MEMORY;
  }
  if (!stream->valid())
  {
    delete stream;
    return LIBRAW_IO_ERROR;
  }
  ID.input_internal = 0; // preserve from deletion on error
  int ret = open_datastream(stream);
  if (ret == LIBRAW_SUCCESS)
  {
    ID.input_internal = 1; // flag to delete datastream on recycle
  }
  else
  {
    delete stream;
    ID.input_internal = 0;
  }
  return ret;
}

#if defined(WIN32) || defined(_WIN32)
#ifndef LIBRAW_WIN32_UNICODEPATHS
int LibRaw::open_file(const wchar_t *, INT64)
{
  return LIBRAW_NOT_IMPLEMENTED;
}
#else
int LibRaw::open_file(const wchar_t *fname, INT64 max_buf_size)
{
	int big = 0;
	if (max_buf_size == LIBRAW_OPEN_BIGFILE)
		big = 1;
	else if (max_buf_size == LIBRAW_OPEN_FILE)
		big = 0;
	else
	{
		struct _stati64 st;
		if (_wstati64(fname, &st))
			return LIBRAW_IO_ERROR;
		big = (st.st_size > max_buf_size) ? 1 : 0;
	}

  LibRaw_abstract_datastream *stream;
  try
  {
    if (big)
      stream = new LibRaw_bigfile_datastream(fname);
    else
      stream = new LibRaw_file_datastream(fname);
  }

  catch (const std::bad_alloc&)
  {
    recycle();
    return LIBRAW_UNSUFFICIENT_MEMORY;
  }
  if (!stream->valid())
  {
    delete stream;
    return LIBRAW_IO_ERROR;
  }
  ID.input_internal = 0; // preserve from deletion on error
  int ret = open_datastream(stream);
  if (ret == LIBRAW_SUCCESS)
  {
    ID.input_internal = 1; // flag to delete datastream on recycle
  }
  else
  {
    delete stream;
    ID.input_internal = 0;
  }
  return ret;
}
#endif
#endif

#else /* LIBRAW_NO_IOSTREAMS_DATASTREAM*/

int LibRaw::libraw_openfile_tail(LibRaw_abstract_datastream *stream)
{
    if (!stream->valid())
    {
        delete stream;
        return LIBRAW_IO_ERROR;
    }
    ID.input_internal = 0; // preserve from deletion on error
    int ret = open_datastream(stream);
    if (ret == LIBRAW_SUCCESS)
    {
        ID.input_internal = 1; // flag to delete datastream on recycle
    }
    else
    {
        delete stream;
        ID.input_internal = 0;
    }
    return ret;
}

int LibRaw::open_file(const char *fname)
{
    LibRaw_abstract_datastream *stream;
    try
    {
#ifdef LIBRAW_WIN32_CALLS
        stream = new LibRaw_bigfile_buffered_datastream(fname);
#else
        stream = new LibRaw_bigfile_datastream(fname);
#endif
    }
    catch (const std::bad_alloc&)
    {
        recycle();
        return LIBRAW_UNSUFFICIENT_MEMORY;
    }
    if ((stream->size() > (INT64)LIBRAW_MAX_NONDNG_RAW_FILE_SIZE) 
		&& (stream->size() > (INT64)LIBRAW_MAX_DNG_RAW_FILE_SIZE)
		&& (stream->size() > (INT64)LIBRAW_MAX_CR3_RAW_FILE_SIZE)
		)
    {
      delete stream;
      return LIBRAW_TOO_BIG;
    }
    return libraw_openfile_tail(stream);
}

#if defined(WIN32) || defined(_WIN32)
#ifndef LIBRAW_WIN32_UNICODEPATHS
int LibRaw::open_file(const wchar_t *)
{
    return LIBRAW_NOT_IMPLEMENTED;
}
#else
int LibRaw::open_file(const wchar_t *fname)
{
    LibRaw_abstract_datastream *stream;
    try
    {
#ifdef LIBRAW_WIN32_CALLS
        stream = new LibRaw_bigfile_buffered_datastream(fname);
#else
        stream = new LibRaw_bigfile_datastream(fname);
#endif
    }
    catch (const std::bad_alloc&)
    {
        recycle();
        return LIBRAW_UNSUFFICIENT_MEMORY;
    }
    if ((stream->size() > (INT64)LIBRAW_MAX_DNG_RAW_FILE_SIZE) 
		&& (stream->size() > (INT64)LIBRAW_MAX_NONDNG_RAW_FILE_SIZE) &&
        (stream->size() > (INT64)LIBRAW_MAX_CR3_RAW_FILE_SIZE)
		)
    {
      delete stream;
      return LIBRAW_TOO_BIG;
    }

    return libraw_openfile_tail(stream);
}
#endif
#endif

#endif

int LibRaw::open_buffer(const void *buffer, size_t size)
{
  // this stream will close on recycle()
  if (!buffer || buffer == (const void *)-1)
    return LIBRAW_IO_ERROR;

  if ((size > (INT64)LIBRAW_MAX_DNG_RAW_FILE_SIZE) 
	  && (size > (INT64)LIBRAW_MAX_NONDNG_RAW_FILE_SIZE)
	  	  && (size > (INT64)LIBRAW_MAX_CR3_RAW_FILE_SIZE)
	  )
      return LIBRAW_TOO_BIG;

  LibRaw_buffer_datastream *stream;
  try
  {
    stream = new LibRaw_buffer_datastream(buffer, size);
  }
  catch (const std::bad_alloc& )
  {
    recycle();
    return LIBRAW_UNSUFFICIENT_MEMORY;
  }
  if (!stream->valid())
  {
    delete stream;
    return LIBRAW_IO_ERROR;
  }
  ID.input_internal = 0; // preserve from deletion on error
  int ret = open_datastream(stream);
  if (ret == LIBRAW_SUCCESS)
  {
    ID.input_internal = 1; // flag to delete datastream on recycle
  }
  else
  {
    delete stream;
    ID.input_internal = 0;
  }
  return ret;
}

int LibRaw::open_bayer(const unsigned char *buffer, unsigned datalen,
                       ushort _raw_width, ushort _raw_height,
                       ushort _left_margin, ushort _top_margin,
                       ushort _right_margin, ushort _bottom_margin,
                       unsigned char procflags, unsigned char bayer_pattern,
                       unsigned unused_bits, unsigned otherflags,
                       unsigned black_level)
{
  // this stream will close on recycle()
  if (!buffer || buffer == (const void *)-1)
    return LIBRAW_IO_ERROR;

  LibRaw_buffer_datastream *stream;
  try
  {
    stream = new LibRaw_buffer_datastream(buffer, datalen);
  }
  catch (const std::bad_alloc& )
  {
    recycle();
    return LIBRAW_UNSUFFICIENT_MEMORY;
  }
  if (!stream->valid())
  {
    delete stream;
    return LIBRAW_IO_ERROR;
  }
  ID.input = stream;
  SET_PROC_FLAG(LIBRAW_PROGRESS_OPEN);
  // From identify
  initdata();
  strcpy(imgdata.idata.make, "BayerDump");
  snprintf(imgdata.idata.model, sizeof(imgdata.idata.model) - 1,
           "%u x %u pixels", _raw_width, _raw_height);
  S.flip = procflags >> 2;
  libraw_internal_data.internal_output_params.zero_is_bad = procflags & 2;
  libraw_internal_data.unpacker_data.data_offset = 0;
  S.raw_width = _raw_width;
  S.raw_height = _raw_height;
  S.left_margin = _left_margin;
  S.top_margin = _top_margin;
  S.width = S.raw_width - S.left_margin - _right_margin;
  S.height = S.raw_height - S.top_margin - _bottom_margin;

  imgdata.idata.filters = 0x1010101 * bayer_pattern;
  imgdata.idata.colors =
      4 - !((imgdata.idata.filters & imgdata.idata.filters >> 1) & 0x5555);
  libraw_internal_data.unpacker_data.load_flags = otherflags;
  switch (libraw_internal_data.unpacker_data.tiff_bps =
              (datalen)*8 / (S.raw_width * S.raw_height))
  {
  case 8:
    load_raw = &LibRaw::eight_bit_load_raw;
    break;
  case 10:
    if ((datalen) / S.raw_height * 3u >= S.raw_width * 4u)
    {
      load_raw = &LibRaw::android_loose_load_raw;
      break;
    }
    else if (libraw_internal_data.unpacker_data.load_flags & 1)
    {
      load_raw = &LibRaw::android_tight_load_raw;
      break;
    }
  case 12:
    libraw_internal_data.unpacker_data.load_flags |= 128;
    load_raw = &LibRaw::packed_load_raw;
    break;
  case 16:
    libraw_internal_data.unpacker_data.order =
        0x4949 | 0x404 * (libraw_internal_data.unpacker_data.load_flags & 1);
    libraw_internal_data.unpacker_data.tiff_bps -=
        libraw_internal_data.unpacker_data.load_flags >> 4;
    libraw_internal_data.unpacker_data.tiff_bps -=
        libraw_internal_data.unpacker_data.load_flags =
            libraw_internal_data.unpacker_data.load_flags >> 1 & 7;
    load_raw = &LibRaw::unpacked_load_raw;
  }
  C.maximum =
      (1 << libraw_internal_data.unpacker_data.tiff_bps) - (1 << unused_bits);
  C.black = black_level;
  S.iwidth = S.width;
  S.iheight = S.height;
  imgdata.idata.colors = 3;
  imgdata.idata.filters |= ((imgdata.idata.filters >> 2 & 0x22222222) |
                            (imgdata.idata.filters << 2 & 0x88888888)) &
                           imgdata.idata.filters << 1;

  imgdata.idata.raw_count = 1;
  for (int i = 0; i < 4; i++)
    imgdata.color.pre_mul[i] = 1.0;

  strcpy(imgdata.idata.cdesc, "RGBG");

  ID.input_internal = 1;
  SET_PROC_FLAG(LIBRAW_PROGRESS_IDENTIFY);
  return LIBRAW_SUCCESS;
}

struct foveon_data_t
{
  const char *make;
  const char *model;
  const int raw_width, raw_height;
  const int white;
  const int left_margin, top_margin;
  const int width, height;
} foveon_data[] = {
    {"Sigma", "SD9", 2304, 1531, 12000, 20, 8, 2266, 1510},
    {"Sigma", "SD9", 1152, 763, 12000, 10, 2, 1132, 755},
    {"Sigma", "SD10", 2304, 1531, 12000, 20, 8, 2266, 1510},
    {"Sigma", "SD10", 1152, 763, 12000, 10, 2, 1132, 755},
    {"Sigma", "SD14", 2688, 1792, 14000, 18, 12, 2651, 1767},
    {"Sigma", "SD14", 2688, 896, 14000, 18, 6, 2651, 883}, // 2/3
    {"Sigma", "SD14", 1344, 896, 14000, 9, 6, 1326, 883},  // 1/2
    {"Sigma", "SD15", 2688, 1792, 2900, 18, 12, 2651, 1767},
    {"Sigma", "SD15", 2688, 896, 2900, 18, 6, 2651, 883}, // 2/3 ?
    {"Sigma", "SD15", 1344, 896, 2900, 9, 6, 1326, 883},  // 1/2 ?
    {"Sigma", "DP1", 2688, 1792, 2100, 18, 12, 2651, 1767},
    {"Sigma", "DP1", 2688, 896, 2100, 18, 6, 2651, 883}, // 2/3 ?
    {"Sigma", "DP1", 1344, 896, 2100, 9, 6, 1326, 883},  // 1/2 ?
    {"Sigma", "DP1S", 2688, 1792, 2200, 18, 12, 2651, 1767},
    {"Sigma", "DP1S", 2688, 896, 2200, 18, 6, 2651, 883}, // 2/3
    {"Sigma", "DP1S", 1344, 896, 2200, 9, 6, 1326, 883},  // 1/2
    {"Sigma", "DP1X", 2688, 1792, 3560, 18, 12, 2651, 1767},
    {"Sigma", "DP1X", 2688, 896, 3560, 18, 6, 2651, 883}, // 2/3
    {"Sigma", "DP1X", 1344, 896, 3560, 9, 6, 1326, 883},  // 1/2
    {"Sigma", "DP2", 2688, 1792, 2326, 13, 16, 2651, 1767},
    {"Sigma", "DP2", 2688, 896, 2326, 13, 8, 2651, 883}, // 2/3 ??
    {"Sigma", "DP2", 1344, 896, 2326, 7, 8, 1325, 883},  // 1/2 ??
    {"Sigma", "DP2S", 2688, 1792, 2300, 18, 12, 2651, 1767},
    {"Sigma", "DP2S", 2688, 896, 2300, 18, 6, 2651, 883}, // 2/3
    {"Sigma", "DP2S", 1344, 896, 2300, 9, 6, 1326, 883},  // 1/2
    {"Sigma", "DP2X", 2688, 1792, 2300, 18, 12, 2651, 1767},
    {"Sigma", "DP2X", 2688, 896, 2300, 18, 6, 2651, 883},           // 2/3
    {"Sigma", "DP2X", 1344, 896, 2300, 9, 6, 1325, 883},            // 1/2
    {"Sigma", "SD1", 4928, 3264, 3900, 12, 52, 4807, 3205},         // Full size
    {"Sigma", "SD1", 4928, 1632, 3900, 12, 26, 4807, 1603},         // 2/3 size
    {"Sigma", "SD1", 2464, 1632, 3900, 6, 26, 2403, 1603},          // 1/2 size
    {"Sigma", "SD1 Merrill", 4928, 3264, 3900, 12, 52, 4807, 3205}, // Full size
    {"Sigma", "SD1 Merrill", 4928, 1632, 3900, 12, 26, 4807, 1603}, // 2/3 size
    {"Sigma", "SD1 Merrill", 2464, 1632, 3900, 6, 26, 2403, 1603},  // 1/2 size
    {"Sigma", "DP1 Merrill", 4928, 3264, 3900, 12, 0, 4807, 3205},
    {"Sigma", "DP1 Merrill", 2464, 1632, 3900, 12, 0, 2403, 1603}, // 1/2 size
    {"Sigma", "DP1 Merrill", 4928, 1632, 3900, 12, 0, 4807, 1603}, // 2/3 size
    {"Sigma", "DP2 Merrill", 4928, 3264, 3900, 12, 0, 4807, 3205},
    {"Sigma", "DP2 Merrill", 2464, 1632, 3900, 12, 0, 2403, 1603}, // 1/2 size
    {"Sigma", "DP2 Merrill", 4928, 1632, 3900, 12, 0, 4807, 1603}, // 2/3 size
    {"Sigma", "DP3 Merrill", 4928, 3264, 3900, 12, 0, 4807, 3205},
    {"Sigma", "DP3 Merrill", 2464, 1632, 3900, 12, 0, 2403, 1603}, // 1/2 size
    {"Sigma", "DP3 Merrill", 4928, 1632, 3900, 12, 0, 4807, 1603}, // 2/3 size
    {"Polaroid", "x530", 1440, 1088, 2700, 10, 13, 1419, 1059},
    // dp2 Q
    {"Sigma", "dp3 Quattro", 5888, 3776, 16383, 204, 76, 5446,
     3624}, // full size, new fw ??
    {"Sigma", "dp3 Quattro", 5888, 3672, 16383, 204, 24, 5446,
     3624}, // full size
    {"Sigma", "dp3 Quattro", 2944, 1836, 16383, 102, 12, 2723,
     1812}, // half size
    {"Sigma", "dp3 Quattro", 2944, 1888, 16383, 102, 38, 2723,
     1812}, // half size, new fw??

    {"Sigma", "dp2 Quattro", 5888, 3776, 16383, 204, 76, 5446,
     3624}, // full size, new fw
    {"Sigma", "dp2 Quattro", 5888, 3672, 16383, 204, 24, 5446,
     3624}, // full size
    {"Sigma", "dp2 Quattro", 2944, 1836, 16383, 102, 12, 2723,
     1812}, // half size
    {"Sigma", "dp2 Quattro", 2944, 1888, 16383, 102, 38, 2723,
     1812}, // half size, new fw

    {"Sigma", "dp1 Quattro", 5888, 3776, 16383, 204, 76, 5446,
     3624}, // full size, new fw??
    {"Sigma", "dp1 Quattro", 5888, 3672, 16383, 204, 24, 5446,
     3624}, // full size
    {"Sigma", "dp1 Quattro", 2944, 1836, 16383, 102, 12, 2723,
     1812}, // half size
    {"Sigma", "dp1 Quattro", 2944, 1888, 16383, 102, 38, 2723,
     1812}, // half size, new fw

    {"Sigma", "dp0 Quattro", 5888, 3776, 16383, 204, 76, 5446,
     3624}, // full size, new fw??
    {"Sigma", "dp0 Quattro", 5888, 3672, 16383, 204, 24, 5446,
     3624}, // full size
    {"Sigma", "dp0 Quattro", 2944, 1836, 16383, 102, 12, 2723,
     1812}, // half size
    {"Sigma", "dp0 Quattro", 2944, 1888, 16383, 102, 38, 2723,
     1812}, // half size, new fw
    // Sigma sd Quattro
    {"Sigma", "sd Quattro", 5888, 3776, 16383, 204, 76, 5446,
     3624}, // full size
    {"Sigma", "sd Quattro", 2944, 1888, 16383, 102, 38, 2723,
     1812}, // half size
    // Sd Quattro H
    {"Sigma", "sd Quattro H", 6656, 4480, 4000, 224, 160, 6208,
     4160}, // full size
    {"Sigma", "sd Quattro H", 3328, 2240, 4000, 112, 80, 3104,
     2080},                                                        // half size
    {"Sigma", "sd Quattro H", 5504, 3680, 4000, 0, 4, 5496, 3668}, // full size
    {"Sigma", "sd Quattro H", 2752, 1840, 4000, 0, 2, 2748, 1834}, // half size
};
const int foveon_count = sizeof(foveon_data) / sizeof(foveon_data[0]);

int LibRaw::open_datastream(LibRaw_abstract_datastream *stream)
{

  if (!stream)
    return ENOENT;
  if (!stream->valid())
    return LIBRAW_IO_ERROR;
  if ((stream->size() > (INT64)LIBRAW_MAX_DNG_RAW_FILE_SIZE) 
	  && (stream->size() > (INT64)LIBRAW_MAX_NONDNG_RAW_FILE_SIZE) &&
      (stream->size() > (INT64)LIBRAW_MAX_CR3_RAW_FILE_SIZE)
	  )
      return LIBRAW_TOO_BIG;

  recycle();
  if (callbacks.pre_identify_cb)
  {
    int r = (callbacks.pre_identify_cb)(this);
    if (r == 1)
      goto final;
  }

  try
  {
	  ID.input = stream;
	  SET_PROC_FLAG(LIBRAW_PROGRESS_OPEN);

	  identify();

	  // Fuji layout files: either DNG or unpacked_load_raw should be used
	  if (libraw_internal_data.internal_output_params.fuji_width || libraw_internal_data.unpacker_data.fuji_layout)
	  {
		  if (!imgdata.idata.dng_version && load_raw != &LibRaw::unpacked_load_raw
			  && load_raw != &LibRaw::unpacked_load_raw_FujiDBP
			  && load_raw != &LibRaw::unpacked_load_raw_fuji_f700s20
			  )
			  return LIBRAW_FILE_UNSUPPORTED;
	  }
	  // Remove unsupported Nikon thumbnails
	  if (makeIs(LIBRAW_CAMERAMAKER_Nikon) &&
		  (!strncasecmp(imgdata.idata.model, "Z 8", 3) || !strcasecmp(imgdata.idata.model, "Z f")
			  || !strncasecmp(imgdata.idata.model, "Z6_3", 4))
		  &&
		  imgdata.thumbs_list.thumbcount > 1)
	  {
		  int tgtidx = 0;
		  for (int idx = 0; idx < imgdata.thumbs_list.thumbcount && idx < LIBRAW_THUMBNAIL_MAXCOUNT; idx++)
		  {
			  int bps = imgdata.thumbs_list.thumblist[idx].tmisc & 0x1f;
			  if (bps > 8) // remove high-bit thumbs
			  {
				  // nothing: just skip this item
			  }
			  else
			  {
				  if (tgtidx < idx)
				  {
					  memmove(&imgdata.thumbs_list.thumblist[tgtidx], &imgdata.thumbs_list.thumblist[idx],
						  sizeof(imgdata.thumbs_list.thumblist[idx]));
					  tgtidx++;
				  }
				  else // same index, just assign next tgtidx
					  tgtidx = idx + 1;
			  }
		  }
		  if (tgtidx > 0 && tgtidx < imgdata.thumbs_list.thumbcount)
		  {
			  int selidx = 0;
			  INT64 maximgbits = INT64(imgdata.thumbs_list.thumblist[0].twidth) *
				  INT64(imgdata.thumbs_list.thumblist[0].theight) * INT64(imgdata.thumbs_list.thumblist[0].tmisc & 0x1f);
			  for (int i = 1; i < tgtidx; i++)
			  {
				  INT64 imgbits = INT64(imgdata.thumbs_list.thumblist[i].twidth) *
					  INT64(imgdata.thumbs_list.thumblist[i].theight) *
					  INT64(imgdata.thumbs_list.thumblist[i].tmisc & 0x1f);
				  if (imgbits > maximgbits)
				  {
					  selidx = i;
					  maximgbits = imgbits;
				  }
			  }
			  libraw_internal_data.internal_data.toffset = imgdata.thumbs_list.thumblist[selidx].toffset;
			  imgdata.thumbnail.tlength = imgdata.thumbs_list.thumblist[selidx].tlength;
			  libraw_internal_data.unpacker_data.thumb_format = imgdata.thumbs_list.thumblist[selidx].tformat;
			  imgdata.thumbnail.twidth = imgdata.thumbs_list.thumblist[selidx].twidth;
			  imgdata.thumbnail.theight = imgdata.thumbs_list.thumblist[selidx].theight;
			  libraw_internal_data.unpacker_data.thumb_misc = imgdata.thumbs_list.thumblist[selidx].tmisc;
			  // find another largest thumb and copy it to single thumbnail data
		  }
		  imgdata.thumbs_list.thumbcount = tgtidx > 0 ? tgtidx : 1;
	  }

	  // promote the old single thumbnail to the thumbs_list if not present already
	  if (imgdata.thumbs_list.thumbcount < LIBRAW_THUMBNAIL_MAXCOUNT)
	  {
		  bool already = false;
		  if (imgdata.thumbnail.tlength || libraw_internal_data.internal_data.toffset)
			  for (int i = 0; i < imgdata.thumbs_list.thumbcount; i++)
				  if (imgdata.thumbs_list.thumblist[i].toffset == libraw_internal_data.internal_data.toffset
					  && imgdata.thumbs_list.thumblist[i].tlength == imgdata.thumbnail.tlength)
				  {
					  already = true;
					  break;
				  }
		  if (!already)
		  {
			  int idx = imgdata.thumbs_list.thumbcount;
			  imgdata.thumbs_list.thumblist[idx].toffset = libraw_internal_data.internal_data.toffset;
			  imgdata.thumbs_list.thumblist[idx].tlength = imgdata.thumbnail.tlength;
			  imgdata.thumbs_list.thumblist[idx].tflip = 0xffff;
			  imgdata.thumbs_list.thumblist[idx].tformat = libraw_internal_data.unpacker_data.thumb_format;
			  imgdata.thumbs_list.thumblist[idx].tmisc = libraw_internal_data.unpacker_data.thumb_misc;
			  // promote if set
			  imgdata.thumbs_list.thumblist[idx].twidth = imgdata.thumbnail.twidth;
			  imgdata.thumbs_list.thumblist[idx].theight = imgdata.thumbnail.theight;
			  imgdata.thumbs_list.thumbcount++;
		  }
	  }


	  imgdata.lens.Lens[sizeof(imgdata.lens.Lens) - 1] = 0; // make sure lens is 0-terminated

	  if (callbacks.post_identify_cb)
		  (callbacks.post_identify_cb)(this);

#define isRIC imgdata.sizes.raw_inset_crops[0]

	  if (!imgdata.idata.dng_version && makeIs(LIBRAW_CAMERAMAKER_Fujifilm)
		  && (!strcmp(imgdata.idata.normalized_model, "S3Pro")
			  || !strcmp(imgdata.idata.normalized_model, "S5Pro")
			  || !strcmp(imgdata.idata.normalized_model, "S2Pro")))
	  {
		  isRIC.cleft = isRIC.ctop = 0xffff;
		  isRIC.cwidth = isRIC.cheight = 0;
	  }
	  // Wipe out canon  incorrect in-camera crop
	  if (!imgdata.idata.dng_version && makeIs(LIBRAW_CAMERAMAKER_Canon)
		  && isRIC.cleft == 0 && isRIC.ctop == 0 // non symmetric!
		  && isRIC.cwidth < (imgdata.sizes.raw_width * 4 / 5))  // less than 80% of sensor width
	  {
		  isRIC.cleft = isRIC.ctop = 0xffff;
		  isRIC.cwidth = isRIC.cheight = 0;
	  }

	  // Wipe out non-standard WB
	  if (!imgdata.idata.dng_version &&
		  (makeIs(LIBRAW_CAMERAMAKER_Sony) && !strcmp(imgdata.idata.normalized_model, "DSC-F828"))
		  && !(imgdata.rawparams.options & LIBRAW_RAWOPTIONS_PROVIDE_NONSTANDARD_WB))
	  {
		  for (int i = 0; i < 4; i++) imgdata.color.cam_mul[i] = (i == 1);
		  memset(imgdata.color.WB_Coeffs, 0, sizeof(imgdata.color.WB_Coeffs));
		  memset(imgdata.color.WBCT_Coeffs, 0, sizeof(imgdata.color.WBCT_Coeffs));
	  }

	  if (load_raw == &LibRaw::nikon_load_raw)
		  nikon_read_curve();

	  if (load_raw == &LibRaw::lossless_jpeg_load_raw &&
		  MN.canon.RecordMode && makeIs(LIBRAW_CAMERAMAKER_Kodak) &&
		  /* Not normalized models here, it is intentional */
		  (!strncasecmp(imgdata.idata.model, "EOS D2000", 9) || // if we want something different for B&W cameras,
			  !strncasecmp(imgdata.idata.model, "EOS D6000", 9)))  // it's better to compare with CamIDs
	  {
		  imgdata.color.black = 0;
		  imgdata.color.maximum = 4501;
		  memset(imgdata.color.cblack, 0, sizeof(imgdata.color.cblack));
		  memset(imgdata.sizes.mask, 0, sizeof(imgdata.sizes.mask));
		  imgdata.sizes.mask[0][3] = 1; // to skip mask re-calc
		  libraw_internal_data.unpacker_data.load_flags |= 512;
	  }

	  if (load_raw == &LibRaw::panasonic_load_raw)
	  {
		  if (libraw_internal_data.unpacker_data.pana_encoding == 6 ||
			  libraw_internal_data.unpacker_data.pana_encoding == 7 ||
			  libraw_internal_data.unpacker_data.pana_encoding == 8)
		  {
			  for (int i = 0; i < 3; i++)
				  imgdata.color.cblack[i] =
				  libraw_internal_data.internal_data.pana_black[i];
			  imgdata.color.cblack[3] = imgdata.color.cblack[1];
			  imgdata.color.cblack[4] = imgdata.color.cblack[5] = 0;
			  imgdata.color.black = 0;
			  imgdata.color.maximum =
				  MAX(imgdata.color.linear_max[0],
					  MAX(imgdata.color.linear_max[1], imgdata.color.linear_max[2]));
		  }

		  if (libraw_internal_data.unpacker_data.pana_encoding == 6)
		  {
			  int rowbytes11 = imgdata.sizes.raw_width / 11 * 16;
			  int rowbytes14 = imgdata.sizes.raw_width / 14 * 16;
			  INT64 ds = INT64(libraw_internal_data.unpacker_data.data_size);
			  if (!ds)
				  ds = libraw_internal_data.internal_data.input->size() - libraw_internal_data.unpacker_data.data_offset;
			  if ((imgdata.sizes.raw_width % 11) == 0 &&
				  (INT64(imgdata.sizes.raw_height) * rowbytes11 == ds))
				  load_raw = &LibRaw::panasonicC6_load_raw;
			  else if ((imgdata.sizes.raw_width % 14) == 0 &&
				  (INT64(imgdata.sizes.raw_height) * rowbytes14 == ds))
				  load_raw = &LibRaw::panasonicC6_load_raw;
			  else
				  imgdata.idata.raw_count = 0; // incorrect size
		  }
		  else if (libraw_internal_data.unpacker_data.pana_encoding == 7)
		  {
			  int pixperblock =
				  libraw_internal_data.unpacker_data.pana_bpp == 14 ? 9 : 10;
			  int rowbytes = imgdata.sizes.raw_width / pixperblock * 16;
			  if ((imgdata.sizes.raw_width % pixperblock) == 0 &&
				  (INT64(imgdata.sizes.raw_height) * rowbytes ==
					  INT64(libraw_internal_data.unpacker_data.data_size)))
				  load_raw = &LibRaw::panasonicC7_load_raw;
			  else
				  imgdata.idata.raw_count = 0; // incorrect size
		  }
		  else if (libraw_internal_data.unpacker_data.pana_encoding == 8)
		  {
			  if (libraw_internal_data.unpacker_data.pana8.stripe_count > 0)
				  load_raw = &LibRaw::panasonicC8_load_raw;
			  else
				  imgdata.idata.raw_count = 0; // incorrect stripes count
		  }
	  }

#define NIKON_14BIT_SIZE(rw, rh)                                               \
  (((unsigned)(ceilf((float)(rw * 7 / 4) / 16.f)) * 16) * rh)

#define NIKON_14BIT_SIZE3COLOR(rw, rh) ( ceilf(float(rw * 21 / 4)/16.f) * 16 * rh)

	  // Ugly hack, replace with proper data/line size for different
	  // cameras/format when available
	  if (makeIs(LIBRAW_CAMERAMAKER_Nikon)
		  && (!strncasecmp(imgdata.idata.model, "Z", 1) || !strcasecmp(imgdata.idata.model, "D6"))
		  )
	  {
		  if (NIKON_14BIT_SIZE(imgdata.sizes.raw_width, imgdata.sizes.raw_height) ==
			  libraw_internal_data.unpacker_data.data_size)
		  {
			  load_raw = &LibRaw::nikon_14bit_load_raw;
		  }
          if (NIKON_14BIT_SIZE3COLOR(imgdata.sizes.raw_width, imgdata.sizes.raw_height) ==
              libraw_internal_data.unpacker_data.data_size)
          {
            load_raw = &LibRaw::nikon_14bit_load_raw;
			imgdata.idata.filters = 0; // 3-color nefx image
          }
	  }
#undef NIKON_14BIT_SIZE
#undef NIKON_14BIT_SIZE3COLOR

	  // Linear max from 14-bit camera, but on 12-bit data?
	  if (makeIs(LIBRAW_CAMERAMAKER_Sony) &&
		  imgdata.color.maximum > 0 &&
		  imgdata.color.linear_max[0] > imgdata.color.maximum &&
		  imgdata.color.linear_max[0] <= imgdata.color.maximum * 4)
		  for (int c = 0; c < 4; c++)
			  imgdata.color.linear_max[c] /= 4;

	  if (makeIs(LIBRAW_CAMERAMAKER_Canon))
	  {
		  if (!imgdata.idata.dng_version && (MN.canon.DefaultCropAbsolute.l != -1))  // tag 0x00e0 SensorInfo was parsed
		  {
			  if (imgdata.sizes.raw_aspect != LIBRAW_IMAGE_ASPECT_UNKNOWN)
			  { // tag 0x009a AspectInfo was parsed
				  isRIC.cleft += MN.canon.DefaultCropAbsolute.l;
				  isRIC.ctop  += MN.canon.DefaultCropAbsolute.t;
			  }
			  else
			  {
				  isRIC.cleft   = MN.canon.DefaultCropAbsolute.l;
				  isRIC.ctop    = MN.canon.DefaultCropAbsolute.t;
				  isRIC.cwidth  = MN.canon.DefaultCropAbsolute.r - MN.canon.DefaultCropAbsolute.l + 1;
				  isRIC.cheight = MN.canon.DefaultCropAbsolute.b - MN.canon.DefaultCropAbsolute.t + 1;
			  }
		  }
		  else
		  {
			  if (imgdata.sizes.raw_aspect != LIBRAW_IMAGE_ASPECT_UNKNOWN)
			  {
			  }
			  else
			  { // Canon PowerShot S2 IS
			  }
		  }
#undef isRIC
          if (imgdata.color.raw_bps < 14 && !imgdata.idata.dng_version && load_raw != &LibRaw::canon_sraw_load_raw)
          {
              int xmax = (1 << imgdata.color.raw_bps) - 1;
              if (MN.canon.SpecularWhiteLevel > xmax) // Adjust 14-bit metadata to real bps
              {
                int div = 1 << (14 - imgdata.color.raw_bps);
                for (int c = 0; c < 4; c++) imgdata.color.linear_max[c] /= div;
                for (int c = 0; c < 4; c++)  MN.canon.ChannelBlackLevel[c] /= div;
                MN.canon.AverageBlackLevel /= div;
                MN.canon.SpecularWhiteLevel /= div;
                MN.canon.NormalWhiteLevel /= div;
              }
          }
	  }

	  if (makeIs(LIBRAW_CAMERAMAKER_Canon) &&
		  (load_raw == &LibRaw::canon_sraw_load_raw) &&
		  imgdata.sizes.raw_width > 0)
	  {
		  float ratio =
			  float(imgdata.sizes.raw_height) / float(imgdata.sizes.raw_width);
		  if ((ratio < 0.57 || ratio > 0.75) &&
			  MN.canon.SensorHeight > 1 &&
			  MN.canon.SensorWidth > 1)
		  {
			  imgdata.sizes.raw_width = MN.canon.SensorWidth;
			  imgdata.sizes.left_margin = MN.canon.DefaultCropAbsolute.l;
			  imgdata.sizes.iwidth = imgdata.sizes.width =
				  MN.canon.DefaultCropAbsolute.r - MN.canon.DefaultCropAbsolute.l + 1;
			  imgdata.sizes.raw_height = MN.canon.SensorHeight;
			  imgdata.sizes.top_margin = MN.canon.DefaultCropAbsolute.t;
			  imgdata.sizes.iheight = imgdata.sizes.height =
				  MN.canon.DefaultCropAbsolute.b - MN.canon.DefaultCropAbsolute.t + 1;
			  libraw_internal_data.unpacker_data.load_flags |=
				  256; // reset width/height in canon_sraw_load_raw()
			  imgdata.sizes.raw_pitch = 8 * imgdata.sizes.raw_width;
		  }
		  else if (imgdata.sizes.raw_width == 4032 &&
			  imgdata.sizes.raw_height == 3402 &&
			  !strcasecmp(imgdata.idata.model, "EOS 80D")) // 80D hardcoded
		  {
			  imgdata.sizes.raw_width = 4536;
			  imgdata.sizes.left_margin = 28;
			  imgdata.sizes.iwidth = imgdata.sizes.width =
				  imgdata.sizes.raw_width - imgdata.sizes.left_margin;
			  imgdata.sizes.raw_height = 3024;
			  imgdata.sizes.top_margin = 8;
			  imgdata.sizes.iheight = imgdata.sizes.height =
				  imgdata.sizes.raw_height - imgdata.sizes.top_margin;
			  libraw_internal_data.unpacker_data.load_flags |= 256;
			  imgdata.sizes.raw_pitch = 8 * imgdata.sizes.raw_width;
		  }
	  }

#ifdef USE_DNGSDK
	  if (imgdata.idata.dng_version
		  &&libraw_internal_data.unpacker_data.tiff_compress == 34892
		  && libraw_internal_data.unpacker_data.tiff_bps == 8
		  && libraw_internal_data.unpacker_data.tiff_samples == 3
		  && load_raw == &LibRaw::lossy_dng_load_raw)
	  {
		  // Data should be linearized by DNG SDK
		  C.black = 0;
		  memset(C.cblack, 0, sizeof(C.cblack));
	  }
#endif

	  // XTrans Compressed?
	  if (!imgdata.idata.dng_version &&
		  makeIs(LIBRAW_CAMERAMAKER_Fujifilm) &&
		  (load_raw == &LibRaw::unpacked_load_raw))
	  {
		  if (imgdata.sizes.raw_width * (imgdata.sizes.raw_height * 2ul) !=
			  libraw_internal_data.unpacker_data.data_size)
		  {
			  if ((imgdata.sizes.raw_width * (imgdata.sizes.raw_height * 7ul)) / 4 ==
				  libraw_internal_data.unpacker_data.data_size)
				  load_raw = &LibRaw::fuji_14bit_load_raw;
			  else
				  parse_fuji_compressed_header();
		  }
		  else if (!strcmp(imgdata.idata.normalized_model, "X-H2S") 
			  && libraw_internal_data.internal_data.input->size() 
			  < (libraw_internal_data.unpacker_data.data_size + libraw_internal_data.unpacker_data.data_offset))
		  {
            parse_fuji_compressed_header(); // try to use compressed header: X-H2S may record wrong data size
		  }
	  }
      // set raw_inset_crops[1] via raw_aspect
      if (imgdata.sizes.raw_aspect >= LIBRAW_IMAGE_ASPECT_MINIMAL_REAL_ASPECT_VALUE
          && imgdata.sizes.raw_aspect <= LIBRAW_IMAGE_ASPECT_MAXIMAL_REAL_ASPECT_VALUE
          /* crops[0] is valid*/
          && (imgdata.sizes.raw_inset_crops[0].cleft < 0xffff)
          && (imgdata.sizes.raw_inset_crops[0].cleft + imgdata.sizes.raw_inset_crops[0].cwidth <= imgdata.sizes.raw_width)
          && (imgdata.sizes.raw_inset_crops[0].ctop < 0xffff)
          && (imgdata.sizes.raw_inset_crops[0].ctop + imgdata.sizes.raw_inset_crops[0].cheight <= imgdata.sizes.raw_height)
          && imgdata.sizes.raw_inset_crops[0].cwidth > 0 && imgdata.sizes.raw_inset_crops[0].cheight >0
          /* crops[1] is not set*/
          && (imgdata.sizes.raw_inset_crops[1].cleft == 0xffff)
          && (imgdata.sizes.raw_inset_crops[1].ctop == 0xffff)
          )
      {
          float c0_ratio = float(imgdata.sizes.raw_inset_crops[0].cwidth) / float(imgdata.sizes.raw_inset_crops[0].cheight);
          float c1_ratio = float(imgdata.sizes.raw_aspect) / 1000.f;
          if (c0_ratio / c1_ratio < 0.98 || c0_ratio / c1_ratio > 1.02) // set crops[1]
          {
              if (c1_ratio > c0_ratio) // requested image is wider, cut from top/bottom
              {
                  int newheight =  int(imgdata.sizes.raw_inset_crops[0].cwidth / c1_ratio);
                  int dtop = (imgdata.sizes.raw_inset_crops[0].cheight - newheight) / 2;
                  imgdata.sizes.raw_inset_crops[1].ctop = imgdata.sizes.raw_inset_crops[0].ctop + dtop;
                  imgdata.sizes.raw_inset_crops[1].cheight = newheight;
                  imgdata.sizes.raw_inset_crops[1].cleft = imgdata.sizes.raw_inset_crops[0].cleft;
                  imgdata.sizes.raw_inset_crops[1].cwidth = imgdata.sizes.raw_inset_crops[0].cwidth;
              }
              else
              {
                  int newwidth = int(imgdata.sizes.raw_inset_crops[0].cheight * c1_ratio);
                  int dleft = (imgdata.sizes.raw_inset_crops[0].cwidth - newwidth) / 2;
                  imgdata.sizes.raw_inset_crops[1].cleft = imgdata.sizes.raw_inset_crops[0].cleft + dleft;
                  imgdata.sizes.raw_inset_crops[1].cwidth = newwidth;
                  imgdata.sizes.raw_inset_crops[1].ctop = imgdata.sizes.raw_inset_crops[0].ctop;
                  imgdata.sizes.raw_inset_crops[1].cheight = imgdata.sizes.raw_inset_crops[0].cheight;
              }
          }
      }

      int adjust_margins = 0;
      if (makeIs(LIBRAW_CAMERAMAKER_Fujifilm) && (imgdata.idata.filters == 9))
      {
          // Adjust top/left margins for X-Trans
          int newtm = imgdata.sizes.top_margin % 6
              ? (imgdata.sizes.top_margin / 6 + 1) * 6
              : imgdata.sizes.top_margin;
          int newlm = imgdata.sizes.left_margin % 6
              ? (imgdata.sizes.left_margin / 6 + 1) * 6
              : imgdata.sizes.left_margin;
          if (newtm != imgdata.sizes.top_margin ||
              newlm != imgdata.sizes.left_margin)
          {
              imgdata.sizes.height -= (newtm - imgdata.sizes.top_margin);
              imgdata.sizes.top_margin = newtm;
              imgdata.sizes.width -= (newlm - imgdata.sizes.left_margin);
              imgdata.sizes.left_margin = newlm;
              for (int c1 = 0; c1 < 6; c1++)
                  for (int c2 = 0; c2 < 6; c2++)
                      imgdata.idata.xtrans[c1][c2] = imgdata.idata.xtrans_abs[c1][c2];
          }
          adjust_margins = 6;
      }
      else if (!libraw_internal_data.internal_output_params.fuji_width
          && imgdata.idata.filters >= 1000)
	  {
          if ((imgdata.sizes.top_margin % 2) || (imgdata.sizes.left_margin % 2))
          {
              int crop[2] = { 0,0 };
              unsigned filt;
              int c;
              if (imgdata.sizes.top_margin % 2)
              {
                  imgdata.sizes.top_margin += 1;
                  imgdata.sizes.height -= 1;
                  crop[1] = 1;
              }
              if (imgdata.sizes.left_margin % 2)
              {
                  imgdata.sizes.left_margin += 1;
                  imgdata.sizes.width -= 1;
                  crop[0] = 1;
              }
              for (filt = c = 0; c < 16; c++)
                  filt |= FC((c >> 1) + (crop[1]), (c & 1) + (crop[0])) << c * 2;
              imgdata.idata.filters = filt;
          }
          adjust_margins = 2;
	  }

      if(adjust_margins) // adjust crop_inset margins
          for (int i = 0; i < 2; i++)
          {
              if (imgdata.sizes.raw_inset_crops[i].cleft && imgdata.sizes.raw_inset_crops[i].cleft < 0xffff
                  && imgdata.sizes.raw_inset_crops[i].cwidth && imgdata.sizes.raw_inset_crops[i].cwidth < 0xffff
                  && (imgdata.sizes.raw_inset_crops[i].cleft%adjust_margins)
                  && (imgdata.sizes.raw_inset_crops[i].cwidth > adjust_margins))
              {
                  int newleft = ((imgdata.sizes.raw_inset_crops[i].cleft / adjust_margins) + 1) * adjust_margins;
                  int diff = newleft - imgdata.sizes.raw_inset_crops[i].cleft;
                  if (diff > 0)
                  {
                      imgdata.sizes.raw_inset_crops[i].cleft += diff;
                      imgdata.sizes.raw_inset_crops[i].cwidth -= diff;
                  }
              }
              if (imgdata.sizes.raw_inset_crops[i].ctop && imgdata.sizes.raw_inset_crops[i].ctop < 0xffff
                  && imgdata.sizes.raw_inset_crops[i].cheight && imgdata.sizes.raw_inset_crops[i].cheight < 0xffff
                  && (imgdata.sizes.raw_inset_crops[i].ctop%adjust_margins)
                  && (imgdata.sizes.raw_inset_crops[i].cheight > adjust_margins))
              {
                  int newtop = ((imgdata.sizes.raw_inset_crops[i].ctop / adjust_margins) + 1) * adjust_margins;
                  int diff = newtop - imgdata.sizes.raw_inset_crops[i].ctop;
                  if (diff > 0)
                  {
                      imgdata.sizes.raw_inset_crops[i].ctop += diff;
                      imgdata.sizes.raw_inset_crops[i].cheight -= diff;
                  }
              }
          }


#ifdef USE_DNGSDK
	  if (
		  imgdata.rawparams.use_dngsdk &&
		  !(imgdata.rawparams.options & (LIBRAW_RAWOPTIONS_DNG_STAGE2 | LIBRAW_RAWOPTIONS_DNG_STAGE3 | LIBRAW_RAWOPTIONS_DNG_DISABLEWBADJUST)))
#endif
	  {
		  // Fix DNG white balance if needed: observed only for Kalpanika X3F tools produced DNGs
		  if (imgdata.idata.dng_version && (imgdata.idata.filters == 0) &&
			  imgdata.idata.colors > 1 && imgdata.idata.colors < 5)
		  {
			  float delta[4] = { 0.f, 0.f, 0.f, 0.f };
			  int black[4];
			  for (int c = 0; c < 4; c++)
				  black[c] = imgdata.color.dng_levels.dng_black +
				  imgdata.color.dng_levels.dng_cblack[c];
			  for (int c = 0; c < imgdata.idata.colors; c++)
				  delta[c] = float(imgdata.color.dng_levels.dng_whitelevel[c] - black[c]);
			  float mindelta = delta[0], maxdelta = delta[0];
			  for (int c = 1; c < imgdata.idata.colors; c++)
			  {
				  if (mindelta > delta[c])
					  mindelta = delta[c];
				  if (maxdelta < delta[c])
					  maxdelta = delta[c];
			  }
			  if (mindelta > 1 && maxdelta < (mindelta * 20)) // safety
			  {
				  for (int c = 0; c < imgdata.idata.colors; c++)
				  {
					  imgdata.color.cam_mul[c] /= (delta[c] / maxdelta);
					  imgdata.color.pre_mul[c] /= (delta[c] / maxdelta);
				  }
				  imgdata.color.maximum = unsigned(imgdata.color.cblack[0] + maxdelta);
			  }
		  }
	  }

    if (imgdata.idata.dng_version &&
		makeIs(LIBRAW_CAMERAMAKER_Panasonic)
          && !strcasecmp(imgdata.idata.normalized_model, "DMC-LX100"))
      imgdata.sizes.width = 4288;

    if (imgdata.idata.dng_version && makeIs(LIBRAW_CAMERAMAKER_Leica))
	{
		if (!strcasecmp(imgdata.idata.normalized_model, "SL2"))
			imgdata.sizes.height -= 16;
		else if (!strcasecmp(imgdata.idata.normalized_model, "SL3"))
		{
			if (imgdata.sizes.raw_width == 9536)
				imgdata.sizes.width -= 12;
            else if (imgdata.sizes.raw_width == 7424)
              imgdata.sizes.width -= 14;
            else if (imgdata.sizes.raw_width == 5312)
              imgdata.sizes.width -= 18;
		}
	}

	if (makeIs(LIBRAW_CAMERAMAKER_Sony) &&
        imgdata.idata.dng_version)
    {
      if (S.raw_width == 3984)
        S.width = 3925;
      else if (S.raw_width == 4288)
        S.width = S.raw_width - 32;
      else if (S.raw_width == 4928 && S.height < 3280)
        S.width = S.raw_width - 8;
      else if (S.raw_width == 5504)
        S.width = S.raw_width - (S.height > 3664 ? 8 : 32);
    }

	if (makeIs(LIBRAW_CAMERAMAKER_Sony) &&
        !imgdata.idata.dng_version)
    {
        if(load_raw ==&LibRaw::sony_arq_load_raw)
        {
            if(S.raw_width > 12000) // A7RM4 16x, both APS-C and APS
                S.width = S.raw_width - 64;
            else // A7RM3/M4 4x merge
                S.width = S.raw_width - 32;
        }

      if (((!strncasecmp(imgdata.idata.model, "ILCE-7RM", 8) ||
            !strcasecmp(imgdata.idata.model, "ILCA-99M2")) &&
           (S.raw_width == 5216 || S.raw_width == 6304)) // A7RM2/M3/A99M2 in APS mode; A7RM4 in APS-C
          ||
          (!strcasecmp(imgdata.idata.model, "ILCE-7R") && S.raw_width >= 4580 &&
           S.raw_width < 5020) // A7R in crop mode, no samples, so size est.
          || (!strcasecmp(imgdata.idata.model, "ILCE-7") &&
              S.raw_width == 3968) // A7 in crop mode
          ||
          ((!strncasecmp(imgdata.idata.model, "ILCE-7M", 7) ||
            !strcasecmp(imgdata.idata.model, "ILCE-9") ||
#if 0
            !strcasecmp(imgdata.idata.model,
                        "SLT-A99V")) // Does SLT-A99 also have APS-C mode??
#endif
           (mnCamID == SonyID_SLT_A99)) // 2 reasons: some cameras are SLT-A99, no 'V'; some are Hasselblad HV
           && S.raw_width > 3750 &&
           S.raw_width < 4120) // A7M2, A7M3, AA9, most likely APS-C raw_width
                               // is 3968 (same w/ A7), but no samples, so guess
          || (!strncasecmp(imgdata.idata.model, "ILCE-7S", 7) &&
              S.raw_width == 2816) // A7S2=> exact, hope it works for A7S-I too
      )
        S.width = S.raw_width - 32;
    }


    // FIXME: it is possible that DNG contains 4x main frames + some previews; in this case imgdata.idata.raw_count will be greater than 4
	if (makeIs(LIBRAW_CAMERAMAKER_Pentax) &&
        /*!strcasecmp(imgdata.idata.model,"K-3 II")  &&*/
            imgdata.idata.raw_count == 4 &&
        (imgdata.rawparams.options & LIBRAW_RAWOPTIONS_PENTAX_PS_ALLFRAMES))
    {
      imgdata.idata.raw_count = 1;
      imgdata.idata.filters = 0;
      imgdata.idata.colors = 4;
      imgdata.sizes.top_margin+=2;
      imgdata.sizes.left_margin+=2;
      imgdata.sizes.width-=4;
      imgdata.sizes.height-=4;
      IO.mix_green = 1;
      pentax_component_load_raw = load_raw;
      load_raw = &LibRaw::pentax_4shot_load_raw;
    }

	if (!imgdata.idata.dng_version && makeIs(LIBRAW_CAMERAMAKER_Leaf) &&
        !strcmp(imgdata.idata.model, "Credo 50"))
    {
      imgdata.color.pre_mul[0] = 1.f / 0.3984f;
      imgdata.color.pre_mul[2] = 1.f / 0.7666f;
      imgdata.color.pre_mul[1] = imgdata.color.pre_mul[3] = 1.0;
    }

	if (!imgdata.idata.dng_version && makeIs(LIBRAW_CAMERAMAKER_Fujifilm) &&
        (!strncmp(imgdata.idata.model, "S20Pro", 6) ||
         !strncmp(imgdata.idata.model, "F700", 4)))
    {
      imgdata.sizes.raw_width /= 2;
      load_raw = &LibRaw::unpacked_load_raw_fuji_f700s20;
    }

    if (load_raw == &LibRaw::packed_load_raw &&
		makeIs(LIBRAW_CAMERAMAKER_Nikon) &&
        !libraw_internal_data.unpacker_data.load_flags &&
        (!strncasecmp(imgdata.idata.model, "D810", 4) ||
         !strcasecmp(imgdata.idata.model, "D4S")) &&
        libraw_internal_data.unpacker_data.data_size * 2u ==
            imgdata.sizes.raw_height * imgdata.sizes.raw_width * 3u)
    {
      libraw_internal_data.unpacker_data.load_flags = 80;
    }
    // Adjust BL for Sony A900/A850
    if (load_raw == &LibRaw::packed_load_raw &&
		makeIs(LIBRAW_CAMERAMAKER_Sony)) // 12 bit sony, but metadata may be for 14-bit range
    {
      if (C.maximum > 4095)
        C.maximum = 4095;
      if (C.black > 256 || C.cblack[0] > 256)
      {
        C.black /= 4;
        for (int c = 0; c < 4; c++)
          C.cblack[c] /= 4;
        for (unsigned c = 0; c < C.cblack[4] * C.cblack[5]; c++)
          C.cblack[6 + c] /= 4;
      }
    }

	if (load_raw == &LibRaw::nikon_yuv_load_raw) // Is it Nikon sRAW?
    {
      load_raw = &LibRaw::nikon_load_sraw;
      C.black = 0;
      memset(C.cblack, 0, sizeof(C.cblack));
      imgdata.idata.filters = 0;
      libraw_internal_data.unpacker_data.tiff_samples = 3;
      imgdata.idata.colors = 3;
      double beta_1 = -5.79342238397656E-02;
      double beta_2 = 3.28163551282665;
      double beta_3 = -8.43136004842678;
      double beta_4 = 1.03533181861023E+01;
      for (int i = 0; i <= 3072; i++)
      {
        double x = (double)i / 3072.;
        double y = (1. - exp(-beta_1 * x - beta_2 * x * x - beta_3 * x * x * x -
                             beta_4 * x * x * x * x));
        if (y < 0.)
          y = 0.;
        imgdata.color.curve[i] = ushort(y * 16383.f);
      }
      for (int i = 0; i < 3; i++)
        for (int j = 0; j < 4; j++)
          imgdata.color.rgb_cam[i][j] = float(i == j);
    }
    // Adjust BL for Nikon 12bit
    if ((load_raw == &LibRaw::nikon_load_raw ||
         load_raw == &LibRaw::packed_load_raw ||
         load_raw == &LibRaw::nikon_load_padded_packed_raw) &&
		 makeIs(LIBRAW_CAMERAMAKER_Nikon) &&
        strncmp(imgdata.idata.model, "COOLPIX", 7) &&
        libraw_internal_data.unpacker_data.tiff_bps == 12)
    {
      C.maximum = 4095;
      C.black /= 4;
      for (int c = 0; c < 4; c++)
        C.cblack[c] /= 4;
      for (unsigned c = 0; c < C.cblack[4] * C.cblack[5]; c++)
        C.cblack[6 + c] /= 4;
    }

    // Adjust wb_already_applied
    if (load_raw == &LibRaw::nikon_load_sraw)
      imgdata.color.as_shot_wb_applied =
          LIBRAW_ASWB_APPLIED | LIBRAW_ASWB_NIKON_SRAW;
    else if (makeIs(LIBRAW_CAMERAMAKER_Canon) &&
             MN.canon.multishot[0] >= 8 &&
             MN.canon.multishot[1] > 0)
      imgdata.color.as_shot_wb_applied =
          LIBRAW_ASWB_APPLIED | LIBRAW_ASWB_CANON;
    else if (makeIs(LIBRAW_CAMERAMAKER_Nikon) &&
             MN.nikon.ExposureMode == 1)
      imgdata.color.as_shot_wb_applied =
          LIBRAW_ASWB_APPLIED | LIBRAW_ASWB_NIKON;
	else if (makeIs(LIBRAW_CAMERAMAKER_Pentax) &&
             ((MN.pentax.MultiExposure & 0x01) == 1))
      imgdata.color.as_shot_wb_applied =
          LIBRAW_ASWB_APPLIED | LIBRAW_ASWB_PENTAX;
	else if (makeIs(LIBRAW_CAMERAMAKER_Sony) && load_raw == &LibRaw::sony_ycbcr_load_raw)
		imgdata.color.as_shot_wb_applied = LIBRAW_ASWB_APPLIED | LIBRAW_ASWB_SONY;
    else
      imgdata.color.as_shot_wb_applied = 0;

    // Adjust Highlight Linearity limit
    if (C.linear_max[0] < 0)
    {
      if (imgdata.idata.dng_version)
      {
        for (int c = 0; c < 4; c++)
          C.linear_max[c] = -1 * C.linear_max[c] + imgdata.color.cblack[c + 6];
      }
      else
      {
        for (int c = 0; c < 4; c++)
          C.linear_max[c] = -1 * C.linear_max[c] + imgdata.color.cblack[c];
      }
    }

	if (makeIs(LIBRAW_CAMERAMAKER_Nikon) &&
		(!C.linear_max[0]) && (C.maximum > 1024) && (load_raw != &LibRaw::nikon_load_sraw))
    {
      C.linear_max[0] = C.linear_max[1] = C.linear_max[2] = C.linear_max[3] =
          (long)((float)(C.maximum) / 1.07f);
    }

    // Correct WB for Samsung GX20
	if (
#if 0
        /* GX20 should be corrected, but K20 is not */
        makeIs(LIBRAW_CAMERAMAKER_Pentax) &&
		!strcasecmp(imgdata.idata.normalized_model, "K20D")
#endif
#if 0
		!strcasecmp(imgdata.idata.make, "Samsung") &&
        !strcasecmp(imgdata.idata.model, "GX20")
#endif
    makeIs(LIBRAW_CAMERAMAKER_Pentax) &&
    (mnCamID == PentaxID_GX20) // Samsung rebranding
		)
    {
      for (int cnt = LIBRAW_WBI_Unknown; cnt <= LIBRAW_WBI_StudioTungsten; cnt++) {
        if (C.WB_Coeffs[cnt][1]) {
          C.WB_Coeffs[cnt][0] = (int)((float)(C.WB_Coeffs[cnt][0]) * 1.0503f);
          C.WB_Coeffs[cnt][2] = (int)((float)(C.WB_Coeffs[cnt][2]) * 2.2867f);
        }
      }
      for (int cnt = 0; cnt < 64; cnt++) {
        if (C.WBCT_Coeffs[cnt][0] > 0.0f) {
          C.WBCT_Coeffs[cnt][1] *= 1.0503f;
          C.WBCT_Coeffs[cnt][3] *= 2.2867f;
        }
      }
      for(int cnt = 0; cnt < 4; cnt++)
        imgdata.color.pre_mul[cnt] =
          float(C.WB_Coeffs[LIBRAW_WBI_Daylight][cnt]);
    }

    // Adjust BL for Panasonic
    if (load_raw == &LibRaw::panasonic_load_raw &&
		makeIs(LIBRAW_CAMERAMAKER_Panasonic) &&
        ID.pana_black[0] && ID.pana_black[1] && ID.pana_black[2])
    {
      if (libraw_internal_data.unpacker_data.pana_encoding == 5)
        libraw_internal_data.internal_output_params.zero_is_bad = 0;
      C.black = 0;
      int add = libraw_internal_data.unpacker_data.pana_encoding == 4 ? 15 : 0;
      C.cblack[0] = ID.pana_black[0] + add;
      C.cblack[1] = C.cblack[3] = ID.pana_black[1] + add;
      C.cblack[2] = ID.pana_black[2] + add;
      unsigned i = C.cblack[3];
      for (int c = 0; c < 3; c++)
        if (i > C.cblack[c])
          i = C.cblack[c];
      for (int c = 0; c < 4; c++)
        C.cblack[c] -= i;
      C.black = i;
    }

    // Adjust sizes for X3F processing
#ifdef USE_X3FTOOLS
	if (load_raw == &LibRaw::x3f_load_raw)
    {
      for (int i = 0; i < foveon_count; i++)
        if (!strcasecmp(imgdata.idata.make, foveon_data[i].make) &&
            !strcasecmp(imgdata.idata.model, foveon_data[i].model) &&
            imgdata.sizes.raw_width == foveon_data[i].raw_width &&
            imgdata.sizes.raw_height == foveon_data[i].raw_height)
        {
          imgdata.sizes.top_margin = foveon_data[i].top_margin;
          imgdata.sizes.left_margin = foveon_data[i].left_margin;
          imgdata.sizes.width = imgdata.sizes.iwidth = foveon_data[i].width;
          imgdata.sizes.height = imgdata.sizes.iheight = foveon_data[i].height;
          C.maximum = foveon_data[i].white;
          break;
        }
    }
#endif
#if 0
    size_t bytes = ID.input->size()-libraw_internal_data.unpacker_data.data_offset;
    float bpp = float(bytes)/float(S.raw_width)/float(S.raw_height);
    float bpp2 = float(bytes)/float(S.width)/float(S.height);
    if(!strcasecmp(imgdata.idata.make,"Hasselblad") && bpp == 6.0f)
      {
        load_raw = &LibRaw::hasselblad_full_load_raw;
        S.width = S.raw_width;
        S.height = S.raw_height;
        P1.filters = 0;
        P1.colors=3;
        P1.raw_count=1;
        C.maximum=0xffff;
      }
#endif
    if (C.profile_length)
    {
      if (C.profile)
        free(C.profile);
	  INT64 profile_sz = MIN(INT64(C.profile_length), ID.input->size() - ID.profile_offset);
	  if (profile_sz > 0LL && profile_sz < LIBRAW_MAX_PROFILE_SIZE_MB * 1024LL * 1024LL)
	  {
        C.profile = calloc(size_t(profile_sz),1);
        C.profile_length = unsigned(profile_sz);
        ID.input->seek(ID.profile_offset, SEEK_SET);
        ID.input->read(C.profile, size_t(profile_sz), 1);
	  }
	  else
		  C.profile = NULL;
    }

    SET_PROC_FLAG(LIBRAW_PROGRESS_IDENTIFY);
  }
  catch (const std::bad_alloc&)
  {
      EXCEPTION_HANDLER(LIBRAW_EXCEPTION_ALLOC);
  }
  catch (const LibRaw_exceptions& err)
  {
    EXCEPTION_HANDLER(err);
  }
  catch (const std::exception& )
  {
    EXCEPTION_HANDLER(LIBRAW_EXCEPTION_IO_CORRUPT);
  }

final:;

  if (P1.raw_count < 1)
    return LIBRAW_FILE_UNSUPPORTED;

  write_fun = &LibRaw::write_ppm_tiff;

  if (load_raw == &LibRaw::kodak_ycbcr_load_raw)
  {
    S.height += S.height & 1;
    S.width += S.width & 1;
  }

  IO.shrink =
      P1.filters &&
      (O.half_size || ((O.threshold || O.aber[0] != 1 || O.aber[2] != 1)));
  if (IO.shrink && P1.filters >= 1000)
  {
    S.width &= 65534;
    S.height &= 65534;
  }

  S.iheight = (S.height + IO.shrink) >> IO.shrink;
  S.iwidth = (S.width + IO.shrink) >> IO.shrink;

  // Save color,sizes and internal data into raw_image fields
  memmove(&imgdata.rawdata.color, &imgdata.color, sizeof(imgdata.color));
  memmove(&imgdata.rawdata.sizes, &imgdata.sizes, sizeof(imgdata.sizes));
  memmove(&imgdata.rawdata.iparams, &imgdata.idata, sizeof(imgdata.idata));
  memmove(&imgdata.rawdata.ioparams,
          &libraw_internal_data.internal_output_params,
          sizeof(libraw_internal_data.internal_output_params));

  SET_PROC_FLAG(LIBRAW_PROGRESS_SIZE_ADJUST);

  return LIBRAW_SUCCESS;
}
