001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package imagingbook.common.color.gamma; 010 011/** 012 * <p> 013 * Defines the "modified gamma correction" used for converting linear to non-linear color component values. The mapping 014 * function consists of a linear and non-linear part. In the forward mapping ({@link #applyFwd(double)}), the linear 015 * part covers input values between a = 0,...,a0, while values a = a0,...,1 are mapped non-linearly. For the inverse 016 * mapping ({@link #applyInv(double)}), the linear part is b = 0,...,b0, the non-linear part is b = b0,...,1. 017 * Theoretically all mapping parameters can be derived from parameters {@code gamma} and {@code a0} (the linear to 018 * non-linear transition point) only. Note that {@code gamma} specifies the nominal γ parameter for the 019 * <em>forward</em> (i.e., linear to non-linear) mapping, e.g., γ = 1/2.4 for sRGB. See Sec. 3.7.6 of [1] for more 020 * details. 021 * </p> 022 * <p> 023 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 024 * (2022). 025 * </p> 026 * 027 * @author WB 028 * @version 2022/11/14 029 */ 030public class ModifiedGammaMapping implements GammaMapping { 031 032 /** Gamma mapping function instance for ITU-R BT.709 (see Table 3.1 of [1]). */ 033 public static ModifiedGammaMapping ITU709 = new ModifiedGammaMapping(1/2.222, 0.018); //, 4.5, 0.099); 034 035 /** 036 * Gamma mapping function instance for sRGB (see Table 3.1 of [1]). Note that we need to specify parameters s and d 037 * too to comply strictly with the sRGB standard. 038 */ 039 public static ModifiedGammaMapping sRGB = new ModifiedGammaMapping(1/2.4, 0.0031308, 12.92, 0.055); 040 041 private final double gamma; 042 private final double igamma; 043 private final double a0; 044 private final double b0; 045 private final double s; 046 private final double d; 047 048 /** 049 * Constructor, derives parameters s and d from gamma and a0 (see Eq. 3.35 in [1]). 050 * 051 * @param gamma in [0,1], the nominal gamma value for the forward mapping (e.g., gamma = 1/2.4 for sRGB) 052 * @param a0 in [0,1], the linear to non-linear transition point (e.g., a0 = 1/2.4 for sRGB) 053 */ 054 public ModifiedGammaMapping(double gamma, double a0) { 055 this(gamma, a0, s(gamma, a0), d(gamma, a0)); 056 } 057 058 /** 059 * Constructor, accepts explicit parameters s and d to allow for minor inaccuracies in published standards. 060 * 061 * @param gamma in [0,1], the nominal gamma value for the forward mapping (e.g., gamma = 1/2.4 for sRGB) 062 * @param a0 in [0,1], the linear to non-linear transition point (e.g., a0 = 1/2.4 for sRGB) 063 * @param s > 0, the slope s of the linear section (e.g., s = 12.92 for sRGB) 064 * @param d ≥ 0 the offset d of the non-linear section (e.g., s = 0.055 for sRGB) 065 */ 066 public ModifiedGammaMapping(double gamma, double a0, double s, double d) { 067 this.a0 = a0; 068 this.b0 = s * a0; 069 this.gamma = gamma; 070 this.igamma = 1.0 / gamma; 071 this.s = s; 072 this.d = d; 073 } 074 075 /** 076 * Function s(gamma, a0) derives the slope s of the linear section from parameters gamma and a0 (see Eq. 3.35 in 077 * [1]). 078 * 079 * @param gamma the nominal gamma value for the forward mapping 080 * @param a0 the linear to non-linear transition point 081 * @return the slope s of the linear section 082 */ 083 protected static double s(double gamma, double a0) { // protected only to show in JavaDoc 084 return gamma / (a0 * (gamma - 1) + Math.pow(a0, 1 - gamma)); 085 } 086 087 /** 088 * Function d(gamma, a0) derives the offset d of the non-linear section from parameters gamma and a0 (see Eq. 3.35 089 * in [1]). 090 * 091 * @param gamma the nominal gamma value for the forward mapping 092 * @param a0 the linear to non-linear transition point 093 * @return the offset d of the non-linear section 094 */ 095 protected static double d(double gamma, double a0) { // protected only to show in JavaDoc 096 return 1.0 / (Math.pow(a0, gamma) * (gamma - 1) + 1) - 1; 097 } 098 099 // ------------------------------------------------------------------------------------- 100 101 @Override 102 public double applyFwd(double a) { 103 return (a <= a0) ? 104 s * a : 105 (1 + d) * Math.pow(a, gamma) - d; 106 } 107 108 @Override 109 public double applyInv(double b) { 110 return (b <= b0) ? 111 (b / s) : 112 Math.pow((b + d) / (1 + d), igamma); 113 } 114 115 @Override 116 public float applyFwd(float a) { 117 return (float) applyFwd((double) a); 118 } 119 120 @Override 121 public float applyInv(float b) { 122 return (float) applyInv((double) b); 123 } 124 125 // ----------------------------------------------- 126 127 /** 128 * Forward Gamma mapping (from linear to non-linear component values). 129 * 130 * @param A an array of linear component values in [0,1] 131 * @return an array of gamma-corrected (non-linear) component values 132 */ 133 public float[] applyFwd(float A[]) { 134 float[] B = new float[A.length]; 135 for (int i = 0; i < A.length; i++) { 136 B[i] = applyFwd(A[i]); 137 } 138 return B; 139 } 140 141 /** 142 * Inverse Gamma mapping (from non-linear to linear component values). 143 * 144 * @param B an array of non-linear (Gamma-corrected) component value in [0,1] 145 * @return an array of linear component values 146 */ 147 public float[] applyInv(float B[]) { 148 float[] A = new float[B.length]; 149 for (int i = 0; i < B.length; i++) { 150 A[i] = applyInv(B[i]); 151 } 152 return A; 153 } 154 155}