001/** 002 * 003 * Copyright © 2018 Paul Schaub 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.colors; 018 019import org.jivesoftware.smack.util.Objects; 020import org.jivesoftware.smack.util.SHA1; 021 022public class ConsistentColor { 023 024 private static final ConsistentColorSettings DEFAULT_SETTINGS = new ConsistentColorSettings(); 025 026 // See XEP-0392 §13.1 Constants for YCbCr (BT.601) 027 private static final double KR = 0.299; 028 private static final double KG = 0.587; 029 private static final double KB = 0.114; 030 031 // See XEP-0392 §5.4 CbCr to RGB 032 private static final double Y = 0.732; 033 034 public enum Deficiency { 035 /** 036 * Do not apply measurements for color vision deficiency correction. 037 */ 038 none, 039 040 /** 041 * Activate color correction for users suffering from red-green-blindness. 042 */ 043 redGreenBlindness, 044 045 /** 046 * Activate color correction for users suffering from blue-blindness. 047 */ 048 blueBlindness 049 } 050 051 /** 052 * Generate an angle in the CbCr plane from the input string. 053 * @see <a href="https://xmpp.org/extensions/xep-0392.html#algorithm-angle">§5.1: Angle generation</a> 054 * 055 * @param input input string 056 * @return output angle 057 */ 058 private static double createAngle(CharSequence input) { 059 byte[] h = SHA1.bytes(input.toString()); 060 double v = u(h[0]) + (256 * u(h[1])); 061 double d = v / 65536; 062 return d * 2 * Math.PI; 063 } 064 065 /** 066 * Apply correction for color vision deficiencies to an angle in the CbCr plane. 067 * @see <a href="https://xmpp.org/extensions/xep-0392.html#algorithm-cvd">§5.2: Corrections for Color Vision Deficiencies</a> 068 * 069 * @param angle angle in CbCr plane 070 * @param deficiency type of vision deficiency 071 * @return corrected angle in CbCr plane 072 */ 073 private static double applyColorDeficiencyCorrection(double angle, Deficiency deficiency) { 074 switch (deficiency) { 075 case none: 076 break; 077 case redGreenBlindness: 078 angle %= Math.PI; 079 break; 080 case blueBlindness: 081 angle -= Math.PI / 2; 082 angle %= Math.PI; 083 angle += Math.PI / 2; 084 break; 085 } 086 return angle; 087 } 088 089 /** 090 * Convert an angle in the CbCr plane to values cb,cr in the YCbCr color space. 091 * @see <a href="https://xmpp.org/extensions/xep-0392.html#algorithm-cbcr">§5.3: CbCr generation</a> 092 * 093 * @param angle angel in CbCr plane. 094 * @return value pair cb,cr 095 */ 096 private static double[] angleToCbCr(double angle) { 097 double cb = Math.cos(angle); 098 double cr = Math.sin(angle); 099 100 double acb = Math.abs(cb); 101 double acr = Math.abs(cr); 102 double factor; 103 if (acr > acb) { 104 factor = 0.5 / acr; 105 } else { 106 factor = 0.5 / acb; 107 } 108 109 cb *= factor; 110 cr *= factor; 111 112 return new double[] {cb, cr}; 113 } 114 115 /** 116 * Convert a value pair cb,cr in the YCbCr color space to RGB. 117 * @see <a href="https://xmpp.org/extensions/xep-0392.html#algorithm-rgb">§5.4: CbCr to RGB</a> 118 * 119 * @param cbcr value pair from the YCbCr color space 120 * @return RGB value triple (R,G,B in [0,1]) 121 */ 122 private static float[] CbCrToRGB(double[] cbcr, double y) { 123 double cb = cbcr[0]; 124 double cr = cbcr[1]; 125 126 double r = 2 * (1 - KR) * cr + y; 127 double b = 2 * (1 - KB) * cb + y; 128 double g = (y - KR * r - KB * b) / KG; 129 130 // Clip values to [0,1] 131 r = clip(r); 132 g = clip(g); 133 b = clip(b); 134 135 return new float[] {(float) r, (float) g, (float) b}; 136 } 137 138 /** 139 * Clip values to stay in range(0,1). 140 * 141 * @param value input 142 * @return input clipped to stay in boundaries from 0 to 1. 143 */ 144 private static double clip(double value) { 145 double out = value; 146 147 if (value < 0) { 148 out = 0; 149 } 150 151 if (value > 1) { 152 out = 1; 153 } 154 155 return out; 156 } 157 158 /** 159 * Treat a signed java byte as unsigned to get its numerical value. 160 * 161 * @param b signed java byte 162 * @return integer value of its unsigned representation 163 */ 164 private static int u(byte b) { 165 // Get unsigned value of signed byte as an integer. 166 return b & 0xFF; 167 } 168 169 /** 170 * Return the consistent RGB color value of the input. 171 * This method uses the default {@link ConsistentColorSettings}. 172 * 173 * @param input input string (for example username) 174 * @return consistent color of that username as RGB values in range [0,1]. 175 * @see #RGBFrom(CharSequence, ConsistentColorSettings) 176 */ 177 public static float[] RGBFrom(CharSequence input) { 178 return RGBFrom(input, DEFAULT_SETTINGS); 179 } 180 181 /** 182 * Return the consistent RGB color value for the input. 183 * This method respects the color vision deficiency mode set by the user. 184 * 185 * @param input input string (for example username) 186 * @param settings the settings for consistent color creation. 187 * @return consistent color of that username as RGB values in range [0,1]. 188 */ 189 public static float[] RGBFrom(CharSequence input, ConsistentColorSettings settings) { 190 double angle = createAngle(input); 191 double correctedAngle = applyColorDeficiencyCorrection(angle, settings.getDeficiency()); 192 double[] CbCr = angleToCbCr(correctedAngle); 193 float[] rgb = CbCrToRGB(CbCr, Y); 194 return rgb; 195 } 196 197 public static class ConsistentColorSettings { 198 199 private final Deficiency deficiency; 200 201 public ConsistentColorSettings() { 202 this(Deficiency.none); 203 } 204 205 public ConsistentColorSettings(Deficiency deficiency) { 206 this.deficiency = Objects.requireNonNull(deficiency, "Deficiency must be given"); 207 } 208 209 /** 210 * Return the deficiency setting. 211 * 212 * @return deficiency setting. 213 */ 214 public Deficiency getDeficiency() { 215 return deficiency; 216 } 217 } 218}