text: /*
    Copyright 2008 Larry Gritz and the other authors and contributors.
    All Rights Reserved.
  
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the software's owners nor the names of its
      contributors may be used to endorse or promote products derived from
      this software without specific prior written permission.
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
    (This is the Modified BSD License)
  */
  
  
  #include <iostream>
  
  #include <OpenEXR/half.h>
  
  #include "imageviewer.h"
  #include <OpenImageIO/fmath.h>
  #include <OpenImageIO/strutil.h>
  
  
  IvImage::IvImage(const std::string& filename)
      : ImageBuf(filename)
      , m_thumbnail(NULL)
      , m_thumbnail_valid(false)
      , m_gamma(1)
      , m_exposure(0)
      , m_file_dataformat(TypeDesc::UNKNOWN)
      , m_image_valid(false)
      , m_auto_subimage(false)
  {
  }
  
  
  
  IvImage::~IvImage() { delete[] m_thumbnail; }
  
  
  
  bool
  IvImage::init_spec_iv(const std::string& filename, int subimage, int miplevel)
  {
      // invalidate info strings
      m_shortinfo.clear();
      m_longinfo.clear();
  
      // If we're changing mip levels or subimages, the pixels will no
      // longer be valid.
      if (subimage != this->subimage() || miplevel != this->miplevel())
          m_image_valid = false;
      bool ok = ImageBuf::init_spec(filename, subimage, miplevel);
      if (ok && m_file_dataformat.basetype == TypeDesc::UNKNOWN) {
          m_file_dataformat = spec().format;
      }
      string_view colorspace = spec().get_string_attribute("oiio:ColorSpace");
      if (Strutil::istarts_with(colorspace, "GammaCorrected")) {
          float g = Strutil::from_string<float>(colorspace.c_str() + 14);
          if (g > 1.0 && g <= 3.0 /*sanity check*/) {
              gamma(gamma() / g);
          }
      }
      return ok;
  }
  
  
  
  bool
  IvImage::read_iv(int subimage, int miplevel, bool force, TypeDesc format,
                   ProgressCallback progress_callback,
                   void* progress_callback_data, bool secondary_data)
  {
      // Don't read if we already have it in memory, unless force is true.
      // FIXME: should we also check the time on the file to see if it's
      // been updated since we last loaded?
      if (m_image_valid && !force && subimage == this->subimage()
          && miplevel != this->miplevel())
          return true;
  
      m_image_valid = init_spec_iv(name(), subimage, miplevel);
      if (m_image_valid)
          m_image_valid = ImageBuf::read(subimage, miplevel, force, format,
                                         progress_callback,
                                         progress_callback_data);
  
      if (m_image_valid && secondary_data && spec().format == TypeDesc::UINT8) {
          m_corrected_image.reset("", ImageSpec(spec().width, spec().height,
                                                std::min(spec().nchannels, 4),
                                                spec().format));
      } else {
          m_corrected_image.clear();
      }
      return m_image_valid;
  }
  
  
  
  std::string
  IvImage::shortinfo() const
  {
      if (m_shortinfo.empty()) {
          m_shortinfo = Strutil::sprintf("%d x %d", spec().width, spec().height);
          if (spec().depth > 1)
              m_shortinfo += Strutil::sprintf(" x %d", spec().depth);
          m_shortinfo += Strutil::sprintf(" x %d channel %s (%.2f MB)",
                                          spec().nchannels, m_file_dataformat,
                                          (float)spec().image_bytes()
                                              / (1024.0 * 1024.0));
      }
      return m_shortinfo;
  }
  
  
  
  // Format name/value pairs as HTML table entries.
  std::string
  html_table_row(const char* name, const std::string& value)
  {
      std::string line = Strutil::sprintf("<tr><td><i>%s</i> : &nbsp;&nbsp;</td>",
                                          name);
      line += Strutil::sprintf("<td>%s</td></tr>\n", value.c_str());
      return line;
  }
  
  
  std::string
  html_table_row(const char* name, int value)
  {
      return html_table_row(name, Strutil::sprintf("%d", value));
  }
  
  
  std::string
  html_table_row(const char* name, float value)
  {
      return html_table_row(name, Strutil::sprintf("%g", value));
  }
  
  
  
  std::string
  IvImage::longinfo() const
  {
      if (m_longinfo.empty()) {
          const ImageSpec& m_spec(nativespec());
          m_longinfo += "<table>";
          //        m_longinfo += html_table_row (Strutil::sprintf("<b>%s</b>", m_name.c_str()).c_str(),
          //                                std::string());
          if (m_spec.depth <= 1)
              m_longinfo += html_table_row("Dimensions",
                                           Strutil::sprintf("%d x %d pixels",
                                                            m_spec.width,
                                                            m_spec.height));
          else
              m_longinfo += html_table_row("Dimensions",
                                           Strutil::sprintf("%d x %d x %d pixels",
                                                            m_spec.width,
                                                            m_spec.height,
                                                            m_spec.depth));
          m_longinfo += html_table_row("Channels", m_spec.nchannels);
          std::string chanlist;
          for (int i = 0; i < m_spec.nchannels; ++i) {
              chanlist += m_spec.channelnames[i].c_str();
              if (i != m_spec.nchannels - 1)
                  chanlist += ", ";
          }
          m_longinfo += html_table_row("Channel list", chanlist);
          m_longinfo += html_table_row("File format", file_format_name());
          m_longinfo += html_table_row("Data format", m_file_dataformat.c_str());
          m_longinfo += html_table_row(
              "Data size", Strutil::sprintf("%.2f MB", (float)m_spec.image_bytes()
                                                           / (1024.0 * 1024.0)));
          m_longinfo += html_table_row("Image origin",
                                       Strutil::sprintf("%d, %d, %d", m_spec.x,
                                                        m_spec.y, m_spec.z));
          m_longinfo += html_table_row("Full/display size",
                                       Strutil::sprintf("%d x %d x %d",
                                                        m_spec.full_width,
                                                        m_spec.full_height,
                                                        m_spec.full_depth));
          m_longinfo
              += html_table_row("Full/display origin",
                                Strutil::sprintf("%d, %d, %d", m_spec.full_x,
                                                 m_spec.full_y, m_spec.full_z));
          if (m_spec.tile_width)
              m_longinfo += html_table_row("Scanline/tile",
                                           Strutil::sprintf("tiled %d x %d x %d",
                                                            m_spec.tile_width,
                                                            m_spec.tile_height,
                                                            m_spec.tile_depth));
          else
              m_longinfo += html_table_row("Scanline/tile", "scanline");
          if (m_spec.alpha_channel >= 0)
              m_longinfo += html_table_row("Alpha channel", m_spec.alpha_channel);
          if (m_spec.z_channel >= 0)
              m_longinfo += html_table_row("Depth (z) channel", m_spec.z_channel);
  
          for (auto&& p : m_spec.extra_attribs) {
              std::string s = m_spec.metadata_val(p, true);
              m_longinfo += html_table_row(p.name().c_str(), s);
          }
  
          m_longinfo += "</table>";
      }
      return m_longinfo;
  }
  
  
  
  // Used by pixel_transform to convert from UINT8 to float.
  static EightBitConverter<float> converter;
  
  
  /// Helper routine: compute (gain*value)^invgamma
  ///
  
  namespace {
  
  inline float
  calc_exposure(float value, float gain, float invgamma)
  {
      if (invgamma != 1 && value >= 0)
          return powf(gain * value, invgamma);
      // Simple case - skip the expensive pow; also fall back to this
      // case for negative values, for which gamma makes no sense.
      return gain * value;
  }
  
  }  // namespace
  
  
  void
  IvImage::pixel_transform(bool srgb_to_linear, int color_mode,
                           int select_channel)
  {
      /// This table obeys the following function:
      ///
      ///   unsigned char srgb2linear(unsigned char x)
      ///   {
      ///       float x_f = x/255.0;
      ///       float x_l = 0.0;
      ///       if (x_f <= 0.04045)
      ///           x_l = x_f/12.92;
      ///       else
      ///           x_l = powf((x_f+0.055)/1.055,2.4);
      ///       return (unsigned char)(x_l * 255 + 0.5)
      ///   }
      ///
      ///  It's used to transform from sRGB color space to linear color space.
      // clang-format off
      static const unsigned char srgb_to_linear_lut[256] = {
          0, 0, 0, 0, 0, 0, 0, 1,
          1, 1, 1, 1, 1, 1, 1, 1,
          1, 1, 2, 2, 2, 2, 2, 2,
          2, 2, 3, 3, 3, 3, 3, 3,
          4, 4, 4, 4, 4, 5, 5, 5,
          5, 6, 6, 6, 6, 7, 7, 7,
          8, 8, 8, 8, 9, 9, 9, 10,
          10, 10, 11, 11, 12, 12, 12, 13,
          13, 13, 14, 14, 15, 15, 16, 16,
          17, 17, 17, 18, 18, 19, 19, 20,
          20, 21, 22, 22, 23, 23, 24, 24,
          25, 25, 26, 27, 27, 28, 29, 29,
          30, 30, 31, 32, 32, 33, 34, 35,
          35, 36, 37, 37, 38, 39, 40, 41,
          41, 42, 43, 44, 45, 45, 46, 47,
          48, 49, 50, 51, 51, 52, 53, 54,
          55, 56, 57, 58, 59, 60, 61, 62,
          63, 64, 65, 66, 67, 68, 69, 70,
          71, 72, 73, 74, 76, 77, 78, 79,
          80, 81, 82, 84, 85, 86, 87, 88,
          90, 91, 92, 93, 95, 96, 97, 99,
          100, 101, 103, 104, 105, 107, 108, 109,
          111, 112, 114, 115, 116, 118, 119, 121,
          122, 124, 125, 127, 128, 130, 131, 133,
          134, 136, 138, 139, 141, 142, 144, 146,
          147, 149, 151, 152, 154, 156, 157, 159,
          161, 163, 164, 166, 168, 170, 171, 173,
          175, 177, 179, 181, 183, 184, 186, 188,
          190, 192, 194, 196, 198, 200, 202, 204,
          206, 208, 210, 212, 214, 216, 218, 220,
          222, 224, 226, 229, 231, 233, 235, 237,
          239, 242, 244, 246, 248, 250, 253, 255
      };
      // clang-format on
      unsigned char correction_table[256];
      int total_channels = spec().nchannels;
      int color_channels = spec().nchannels;
      int max_channels   = m_corrected_image.nchannels();
  
      // FIXME: Now with the iterator and data proxy in place, it should be
      // trivial to apply the transformations to any kind of data, not just
      // UINT8.
      if (spec().format != TypeDesc::UINT8 || !m_corrected_image.localpixels()) {
          return;
      }
  
      if (color_channels > 3) {
          color_channels = 3;
      } else if (color_channels == 2) {
          color_channels = 1;
      }
  
      // This image is Luminance or Luminance + Alpha, and we are asked to show
      // luminance.
      if (color_channels == 1 && color_mode == 3) {
          color_mode = 0;  // Just copy as usual.
      }
  
      // Happy path:
      if (!srgb_to_linear && color_mode <= 1 && m_gamma == 1.0
          && m_exposure == 0.0) {
          ImageBuf::ConstIterator<unsigned char, unsigned char> src(*this);
          ImageBuf::Iterator<unsigned char, unsigned char> dst(m_corrected_image);
          for (; src.valid(); ++src) {
              dst.pos(src.x(), src.y());
              for (int i = 0; i < max_channels; i++)
                  dst[i] = src[i];
          }
          return;
      }
  
      // fill the correction_table
      if (gamma() == 1.0 && exposure() == 0.0) {
          for (int pixelvalue = 0; pixelvalue < 256; ++pixelvalue) {
              correction_table[pixelvalue] = pixelvalue;
          }
      } else {
          float inv_gamma = 1.0 / gamma();
          float gain      = powf(2.0f, exposure());
          for (int pixelvalue = 0; pixelvalue < 256; ++pixelvalue) {
              float pv_f = converter(pixelvalue);
              pv_f = clamp(calc_exposure(pv_f, gain, inv_gamma), 0.0f, 1.0f);
              correction_table[pixelvalue] = (unsigned char)(pv_f * 255 + 0.5);
          }
      }
  
      ImageBuf::ConstIterator<unsigned char, unsigned char> src(*this);
      ImageBuf::Iterator<unsigned char, unsigned char> dst(m_corrected_image);
      for (; src.valid(); ++src) {
          dst.pos(src.x(), src.y());
          if (color_mode == 0 || color_mode == 1) {
              // RGBA, RGB modes.
              int ch = 0;
              for (ch = 0; ch < color_channels; ch++) {
                  if (srgb_to_linear)
                      dst[ch] = correction_table[srgb_to_linear_lut[src[ch]]];
                  else
                      dst[ch] = correction_table[src[ch]];
              }
              for (; ch < max_channels; ch++) {
                  dst[ch] = src[ch];
              }
          } else if (color_mode == 3) {
              // Convert RGB to luminance, (Rec. 709 luma coefficients).
              float luminance;
              if (srgb_to_linear) {
                  luminance = converter(srgb_to_linear_lut[src[0]]) * 0.2126f
                              + converter(srgb_to_linear_lut[src[1]]) * 0.7152f
                              + converter(srgb_to_linear_lut[src[2]]) * 0.0722f;
              } else {
                  luminance = converter(src[0]) * 0.2126f
                              + converter(src[1]) * 0.7152f
                              + converter(src[2]) * 0.0722f;
              }
              unsigned char val
                  = (unsigned char)(clamp(luminance, 0.0f, 1.0f) * 255.0 + 0.5);
              val    = correction_table[val];
              dst[0] = val;
              dst[1] = val;
              dst[2] = val;
  
              // Handle the rest of the channels
              for (int ch = 3; ch < total_channels; ++ch) {
                  dst[ch] = src[ch];
              }
          } else {  // Single channel, heatmap.
              unsigned char v = 0;
              if (select_channel < color_channels) {
                  if (srgb_to_linear)
                      v = correction_table[srgb_to_linear_lut[src[select_channel]]];
                  else
                      v = correction_table[src[select_channel]];
              } else if (select_channel < total_channels) {
                  v = src[select_channel];
              }
              int ch = 0;
              for (; ch < color_channels; ++ch) {
                  dst[ch] = v;
              }
              for (; ch < max_channels; ++ch) {
                  dst[ch] = src[ch];
              }
          }
      }
  }
  
  
  
  void
  IvImage::invalidate()
  {
      ustring filename(name());
      reset(filename.string());
      m_thumbnail_valid = false;
      m_image_valid     = false;
      if (imagecache())
          imagecache()->invalidate(filename);
  }
  