# HG changeset patch
# User rhendz <apatel79@vols.utk.edu>
# Date 1521157078 18000
#      Thu Mar 15 18:37:58 2018 -0500
# Node ID 54e7c88dd678012d5a985bbc93c1cc6fd3082822
# Parent  75df28049249a81df09281b8c6fb27c0242785a0
New functions for Octave-Forge Image package

* inst/lin2rgb.m: Applies gamma correction to rgb images
* inst/rgb2lin.m: Removes gamma correction from rgb images
* inst/private/linfunc.m: Private function for lin2rgb and rgb2lin

diff --git a/inst/lin2rgb.m b/inst/lin2rgb.m
new file mode 100644
--- /dev/null
+++ b/inst/lin2rgb.m
@@ -0,0 +1,92 @@
+## Copyright (C) 2018 Ankush Patel <apatel79@vols.utk.edu>
+##
+## 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 3 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, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{RGB} =} lin2rgb(@var{LIN})
+## @deftypefnx {Function File} {@dots{} =} lin2rgb(@dots{}, @var{standard})
+## Applies gamma correction to linear RGB values.
+##
+## The conversion changes the image @var{LIN} by applying gamma correction to
+## linear RGB values. @var{LIN} must be of class double, single, uint8, or uint16.
+##
+## Note: This function does not guarantee a valid RGB image because
+## all RGB values may not be gamma-corrected. In other words, applying
+## gamma correction to linear RGB values may result in color values
+## that are negative.
+##
+## There are two standards used to apply gamma correction to linear RGB values:
+## @table @asis
+## @item "sRGB" (default)
+## @item "adobe-rgb-1998"
+## @end table
+##
+## The following papers were used:
+##
+## [1] Ebner, Marc. "Gamma Correction." Color Constancy. Chichester, West Sussex: John Wiley & Sons, 2007.
+## [2] Adobe Systems Incorporated. "Inverting the color component transfer function." Adobe RGB (1998) Color Image Encoding. Section 4.3.5.2, May 2005, p.12.
+## @seealso{lin2rgb}
+## @end deftypefn
+
+function rgb = lin2rgb (lin, standard = "sRGB")
+  if (nargin < 1 || nargin > 2)
+    print_usage();
+  endif
+  rgb = linfunc ("lin2rgb", lin, standard);
+endfunction
+
+## Test white and black images
+## These should result in no transformation
+%!test
+%! assert (lin2rgb (ones (64, 64, 3)), ones (64, 64, 3), 2e-5) # test white
+%! assert (lin2rgb (zeros (64, 64, 3)), zeros (64, 64, 3), 2e-5) # test black
+%! assert (lin2rgb (ones (64, 64, 3), "adobe-rgb-1998"), ones (64, 64, 3), 2e-5) # test white
+%! assert (lin2rgb (zeros (64, 64, 3), "adobe-rgb-1998"), zeros (64, 64, 3), 2e-5) # test black
+
+## Test tolerance via colormaps
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("viridis"));
+%! assert (lin2rgb (rgb2lin (rgb_img)), rgb_img, 2e-5)
+%! assert (lin2rgb (rgb2lin (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("copper"));
+%! assert (lin2rgb (rgb2lin (rgb_img)), rgb_img, 2e-5)
+%! assert (lin2rgb (rgb2lin (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("colorcube"));
+%! assert (lin2rgb (rgb2lin (rgb_img)), rgb_img, 2e-5)
+%! assert (lin2rgb (rgb2lin (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("ocean"));
+%! assert (lin2rgb (rgb2lin (rgb_img)), rgb_img, 2e-5)
+%! assert (lin2rgb (rgb2lin (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+## Test uint8 and uint16
+%!test
+%! rgb_img_uint8 = im2uint8 (ones (64, 64, 3) * 255); # white uint8 image
+%! rgb_img_uint16 = im2uint16 (ones (64, 64, 3) * 255); # white uint16 image
+%! assert (lin2rgb (rgb_img_uint8), rgb_img_uint8)
+%! assert (lin2rgb (rgb_img_uint8, "adobe-rgb-1998"), rgb_img_uint8)
+%! assert (lin2rgb (rgb_img_uint16), rgb_img_uint16)
+%! assert (lin2rgb (rgb_img_uint16, "adobe-rgb-1998"), rgb_img_uint16)
+
+## Test input validation
+%!error lin2rgb ()
+%!error lin2rgb (1, 2)
+%!error lin2rgb (ones (64, 64))
+%!error lin2rgb (ones (64, 64, 2))
+%!error lin2rgb (ones (64, 64, 2), "adobe-rgb-1998")
diff --git a/inst/private/linfunc.m b/inst/private/linfunc.m
new file mode 100644
--- /dev/null
+++ b/inst/private/linfunc.m
@@ -0,0 +1,108 @@
+## Copyright (C) 2018 Ankush Patel <apatel79@vols.utk.edu>
+##
+## 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 3 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, see <http://www.gnu.org/licenses/>.
+
+## Private function for lin2rgb and rgb2lin functions.
+
+function out = linfunc (func, in, standard)
+  ## Make sure image size is valid
+  sz = size (in);
+  if (ndims (in) == 3)
+    if (sz(1) > 0 && sz(2) > 0 && sz(3) == 3)
+    else
+      error ("%s: input must be an RGB image (MxNx3)", func);
+    endif
+  else
+    error ("%s: input must be an RGB image (MxNx3)", func);
+  endif
+
+  ## Make sure standard is a string and is compliant
+  if (ischar (standard))
+    if (strcmpi (standard, "sRGB") ||
+        strcmpi (standard, "adobe-rgb-1998"))
+      ## do nothing - standard is compliant
+    else
+      error (["%s: is not a valid standard.\n Please select one of ", ...
+      "the following options: sRGB (default) or ", ...
+      "adobe-rgb-1998"], standard);
+    endif
+  else
+    error("%s: is not a string", func);
+  endif
+
+  ## Convert the image to an operable format [0, 1]
+  cls = class (in);
+  if (! isfloat (in))
+    in = im2double (in);
+  endif
+
+  ## Constants for sRGB
+  a = 1 / 1.055;
+  c = 1 / 12.92;
+  gam = 2.4; # gamma const. val.
+
+  ## Constant for adobe_rgb_1998_gam
+  adobe_rgb_1998_gam = 2.19921875;
+
+  ## Note: sRGB is not an invertible process
+  ## Used for lin2rgb
+  if (strcmpi (func, "lin2rgb"))
+    if (strcmpi (standard, "sRGB"))
+      rgb = reshape (in, [], 3);
+      ## Constants for lin2rgb
+      b = -0.055;
+      d = 0.0031308;
+      ## Applies gamma correction to (r, g, b) values
+      if (rgb < d)
+        rgb *= (1 / c);
+      else
+        rgb = (1 / a) * rgb .^ (1 / gam) + b;
+      endif
+      out = reshape (rgb, [sz(1), sz(2), sz(3)]);
+    else # standard is "adobe-rgb-1998"
+      out = in .^ (1 / adobe_rgb_1998_gam);
+    endif
+  ## Used for rgb2lin
+  elseif (strcmpi (func, "rgb2lin"))
+    if (strcmpi (standard, "sRGB"))
+      rgb = reshape (in, [], 3);
+      ## Constants for rgb2lin
+      b = 0.055/1.055;
+      d = 0.04045;
+      ## Corrects gamma correction to (r, g, b) values
+      if (rgb < d)
+        rgb *= c;
+      else
+        rgb = (a * rgb + b) .^ gam;
+      endif
+      out = reshape (rgb, [sz(1), sz(2), sz(3)]);
+    else # standard is "adobe-rgb-1998"
+      out = in .^ adobe_rgb_1998_gam;
+    endif
+  else
+    error(["internal error for lin conversion. Unknown", ...
+    "function %s"], func);
+  endif
+
+  switch (cls)
+    case { "single", "double" }
+      ## already isfloat
+    case "uint8"
+      out = im2uint8 (out);
+    case "uint16"
+      out = im2uint16 (out);
+    otherwise
+      error ("%s: unsupported image class %s", func, cls);
+  endswitch
+endfunction
diff --git a/inst/rgb2lin.m b/inst/rgb2lin.m
new file mode 100644
--- /dev/null
+++ b/inst/rgb2lin.m
@@ -0,0 +1,92 @@
+## Copyright (C) 2018 Ankush Patel <apatel79@vols.utk.edu>
+##
+## 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 3 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, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{LIN} =} rgb2lin(@var{RGB})
+## @deftypefnx {Function File} {@dots{} =} rgb2lin(@dots{}, @var{standard})
+## Converts gamma corrected RGB values to linear RGB values.
+##
+## The conversion changes the image @var{RGB} from gamma corrected RGB values to
+## linear RGB values. @var{RGB} must be of class double, single, uint8, or uint16.
+##
+## Note: This function does not guarantee a valid RGB image because
+## all RGB values may not be linearized. Therefore, it is imperative
+## to use this function for linearizing RGB values that have been
+## gamma-corrected.
+##
+## There are two standards used to linearize the gamma corrected RGB values:
+## @table @asis
+## @item "sRGB" (default)
+## @item "adobe-rgb-1998"
+## @end table
+##
+## The following papers were used:
+##
+## [1] Ebner, Marc. "Gamma Correction." Color Constancy. Chichester, West Sussex: John Wiley & Sons, 2007.
+## [2] Adobe Systems Incorporated. "Inverting the color component transfer function." Adobe RGB (1998) Color Image Encoding. Section 4.3.5.2, May 2005, p.12.
+## @seealso{lin2rgb}
+## @end deftypefn
+
+function lin = rgb2lin (rgb, standard = "sRGB")
+  if (nargin < 1 || nargin > 2)
+    print_usage();
+  endif
+  lin = linfunc ("rgb2lin", rgb, standard);
+endfunction
+
+## Test white and black images
+## These should result in no transformation
+%!test
+%! assert (rgb2lin (ones (64, 64, 3)), ones (64, 64, 3), 2e-5) # test white
+%! assert (rgb2lin (zeros (64, 64, 3)), zeros (64, 64, 3), 2e-5) # test black
+%! assert (rgb2lin (ones (64, 64, 3), "adobe-rgb-1998"), ones (64, 64, 3), 2e-5) # test white
+%! assert (rgb2lin (zeros (64, 64, 3), "adobe-rgb-1998"), zeros (64, 64, 3), 2e-5) # test black
+
+## Test tolerance via colormaps
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("viridis"));
+%! assert (rgb2lin (lin2rgb (rgb_img)), rgb_img, 2e-5)
+%! assert (rgb2lin (lin2rgb (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("copper"));
+%! assert (rgb2lin (lin2rgb (rgb_img)), rgb_img, 2e-5)
+%! assert (rgb2lin (lin2rgb (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("colorcube"));
+%! assert (rgb2lin (lin2rgb (rgb_img)), rgb_img, 2e-5)
+%! assert (rgb2lin (lin2rgb (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+%!test
+%! rgb_img = ind2rgb (1:8, colormap ("ocean"));
+%! assert (rgb2lin (lin2rgb (rgb_img)), rgb_img, 2e-5)
+%! assert (rgb2lin (lin2rgb (rgb_img, "adobe-rgb-1998"), "adobe-rgb-1998"), rgb_img, 2e-5)
+
+## Test uint8 and uint16
+%!test
+%! rgb_img_uint8 = im2uint8 (ones (64, 64, 3) * 255); # white uint8 image
+%! rgb_img_uint16 = im2uint16 (ones (64, 64, 3) * 255); # white uint16 image
+%! assert (rgb2lin (rgb_img_uint8), rgb_img_uint8)
+%! assert (rgb2lin (rgb_img_uint8, "adobe-rgb-1998"), rgb_img_uint8)
+%! assert (rgb2lin (rgb_img_uint16), rgb_img_uint16)
+%! assert (rgb2lin (rgb_img_uint16, "adobe-rgb-1998"), rgb_img_uint16)
+
+## Test input validation
+%!error rgb2lin ()
+%!error rgb2lin (1, 2)
+%!error rgb2lin (ones (64, 64))
+%!error rgb2lin (ones (64, 64, 2))
+%!error rgb2lin (ones (64, 64, 2), "adobe-rgb-1998")
