001package armyc2.c5isr.renderer.utilities; 002 003import android.graphics.Canvas; 004import android.graphics.Paint; 005import android.graphics.Paint.Style; 006import android.graphics.Point; 007import android.graphics.RectF; 008import android.util.SparseArray; 009 010import java.util.TreeSet; 011import java.util.logging.Level; 012import java.util.regex.Matcher; 013import java.util.regex.Pattern; 014 015public class RendererUtilities { 016 017 private static final float OUTLINE_SCALING_FACTOR = 2.5f; 018 private static SparseArray<Color> pastIdealOutlineColors = new SparseArray<Color>(); 019 /** 020 * 021 * @param color {String} color like "#FFFFFF" 022 * @return {String} 023 */ 024 public static Color getIdealOutlineColor(Color color){ 025 Color idealColor = Color.white; 026 027 if(color != null && pastIdealOutlineColors.indexOfKey(color.toInt())>=0) 028 { 029 return pastIdealOutlineColors.get(color.toInt()); 030 }//*/ 031 032 if(color != null) 033 { 034 035 int threshold = RendererSettings.getInstance().getTextBackgroundAutoColorThreshold(); 036 037 int r = color.getRed(); 038 int g = color.getGreen(); 039 int b = color.getBlue(); 040 041 float delta = ((r * 0.299f) + (g * 0.587f) + (b * 0.114f)); 042 043 if((255 - delta < threshold)) 044 { 045 idealColor = Color.black; 046 } 047 else 048 { 049 idealColor = Color.white; 050 } 051 } 052 053 if(color != null) 054 pastIdealOutlineColors.put(color.toInt(),idealColor); 055 056 return idealColor; 057 } 058 059 public static void renderSymbolCharacter(Canvas ctx, String symbol, int x, int y, Paint paint, Color color, int outlineWidth) 060 { 061 int tbm = RendererSettings.getInstance().getTextBackgroundMethod(); 062 063 Color outlineColor = RendererUtilities.getIdealOutlineColor(color); 064 065 //if(tbm == RendererSettings.TextBackgroundMethod_OUTLINE_QUICK) 066 //{ 067 //draw symbol outline 068 paint.setStyle(Style.FILL); 069 070 paint.setColor(outlineColor.toInt()); 071 if(outlineWidth > 0) 072 { 073 for(int i = 1; i <= outlineWidth; i++) 074 { 075 if(i % 2 == 1) 076 { 077 ctx.drawText(symbol, x - i, y, paint); 078 ctx.drawText(symbol, x + i, y, paint); 079 ctx.drawText(symbol, x, y + i, paint); 080 ctx.drawText(symbol, x, y - i, paint); 081 } 082 else 083 { 084 ctx.drawText(symbol, x - i, y - i, paint); 085 ctx.drawText(symbol, x + i, y - i, paint); 086 ctx.drawText(symbol, x - i, y + i, paint); 087 ctx.drawText(symbol, x + i, y + i, paint); 088 } 089 090 } 091 092 } 093 //draw symbol 094 paint.setColor(color.toInt()); 095 096 ctx.drawText(symbol, x, y, paint); 097 098 /*} 099 else 100 { 101 //draw text outline 102 paint.setStyle(Style.STROKE); 103 paint.setStrokeWidth(RendererSettings.getInstance().getTextOutlineWidth()); 104 paint.setColor(outlineColor.toInt()); 105 if(outlineWidth > 0) 106 { 107 108 ctx.drawText(symbol, x, y, paint); 109 110 } 111 //draw text 112 paint.setColor(color.toInt()); 113 paint.setStyle(Style.FILL); 114 115 ctx.drawText(symbol, x, y, paint); 116 }//*/ 117 } 118 119 /** 120 * Create a copy of the {@Color} object with the passed alpha value. 121 * @param color {@Color} object used for RGB values 122 * @param alpha {@float} value between 0 and 1 123 * @return 124 */ 125 public static Color setColorAlpha(Color color, float alpha) { 126 if (color != null) 127 { 128 if(alpha >= 0 && alpha <= 1) 129 return new Color(color.getRed(),color.getGreen(),color.getBlue(),(int)(alpha*255f)); 130 else 131 return color; 132 } 133 else 134 return null; 135 } 136 public static String colorToHexString(Color color, Boolean withAlpha) 137 { 138 if(color != null) 139 { 140 String hex = color.toHexString(); 141 hex = hex.toUpperCase(); 142 if(withAlpha) 143 return "#" + hex; 144 else 145 return "#" + hex.substring(2); 146 } 147 return null; 148 } 149 150 /** 151 * 152 * @param hexValue - String representing hex value (formatted "0xRRGGBB" 153 * i.e. "0xFFFFFF") OR formatted "0xAARRGGBB" i.e. "0x00FFFFFF" for a color 154 * with an alpha value I will also put up with "RRGGBB" and "AARRGGBB" 155 * without the starting "0x" 156 * @return 157 */ 158 public static Color getColorFromHexString(String hexValue) 159 { 160 try 161 { 162 if(hexValue==null || hexValue.isEmpty()) 163 return null; 164 String hexOriginal = hexValue; 165 166 String hexAlphabet = "0123456789ABCDEF"; 167 168 if (hexValue.charAt(0) == '#') 169 { 170 hexValue = hexValue.substring(1); 171 } 172 if (hexValue.substring(0, 2).equals("0x") || hexValue.substring(0, 2).equals("0X")) 173 { 174 hexValue = hexValue.substring(2); 175 } 176 177 hexValue = hexValue.toUpperCase(); 178 179 int count = hexValue.length(); 180 int[] value = null; 181 int k = 0; 182 int int1 = 0; 183 int int2 = 0; 184 185 if (count == 8 || count == 6) 186 { 187 value = new int[(count / 2)]; 188 for (int i = 0; i < count; i += 2) 189 { 190 int1 = hexAlphabet.indexOf(hexValue.charAt(i)); 191 int2 = hexAlphabet.indexOf(hexValue.charAt(i + 1)); 192 193 if(int1 == -1 || int2 == -1) 194 { 195 ErrorLogger.LogMessage("SymbolUtilities", "getColorFromHexString", "Bad hex value: " + hexOriginal, Level.WARNING); 196 return null; 197 } 198 199 value[k] = (int1 * 16) + int2; 200 k++; 201 } 202 203 if (count == 8) 204 { 205 return new Color(value[1], value[2], value[3], value[0]); 206 } 207 else if (count == 6) 208 { 209 return new Color(value[0], value[1], value[2]); 210 } 211 } 212 else 213 { 214 ErrorLogger.LogMessage("SymbolUtilities", "getColorFromHexString", "Bad hex value: " + hexOriginal, Level.WARNING); 215 } 216 return null; 217 } 218 catch (Exception exc) 219 { 220 ErrorLogger.LogException("SymbolUtilities", "getColorFromHexString", exc); 221 return null; 222 } 223 } 224 225 /** 226 * For Renderer Use Only 227 * Assumes a fresh SVG String from the SVGLookup with its default values 228 * @param symbolID 229 * @param svg 230 * @param strokeColor hex value like "#FF0000"; 231 * @param fillColor hex value like "#FF0000"; 232 * @return SVG String 233 */ 234 public static String setSVGFrameColors(String symbolID, String svg, Color strokeColor, Color fillColor) 235 { 236 String returnSVG = null; 237 String hexStrokeColor = null; 238 String hexFillColor = null; 239 float strokeAlpha = 1; 240 float fillAlpha = 1; 241 String strokeOpacity = ""; 242 String fillOpacity = ""; 243 244 int ss = SymbolID.getSymbolSet(symbolID); 245 246 int affiliation = SymbolID.getAffiliation(symbolID); 247 String defaultFillColor = null; 248 if(strokeColor != null) 249 { 250 if(strokeColor.getAlpha() != 255) 251 { 252 strokeAlpha = strokeColor.getAlpha() / 255.0f; 253 strokeOpacity = " stroke-opacity=\"" + String.valueOf(strokeAlpha) + "\""; 254 fillOpacity = " fill-opacity=\"" + String.valueOf(strokeAlpha) + "\""; 255 } 256 257 hexStrokeColor = colorToHexString(strokeColor,false); 258 returnSVG = svg.replaceAll("stroke=\"#000000\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity); 259 returnSVG = returnSVG.replaceAll("fill=\"#000000\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity); 260 261 if(ss == SymbolID.SymbolSet_LandInstallation || 262 ss == SymbolID.SymbolSet_Space || 263 ss == SymbolID.SymbolSet_CyberSpace || 264 ss == SymbolID.SymbolSet_Activities) 265 {//add group fill so the extra shapes in these frames have the new frame color 266 String svgStart = "<g id=\"" + SVGLookup.getFrameID(symbolID) + "\">"; 267 String svgStartReplace = svgStart.substring(0,svgStart.length()-1) + " fill=\"" + hexStrokeColor + "\"" + fillOpacity + ">"; 268 returnSVG = returnSVG.replace(svgStart,svgStartReplace); 269 } 270 271 } 272 if(fillColor != null) 273 { 274 if(fillColor.getAlpha() != 255) 275 { 276 fillAlpha = fillColor.getAlpha() / 255.0f; 277 fillOpacity = " fill-opacity=\"" + String.valueOf(fillAlpha) + "\""; 278 } 279 280 hexFillColor = colorToHexString(fillColor,false); 281 switch(affiliation) 282 { 283 case SymbolID.StandardIdentity_Affiliation_Friend: 284 case SymbolID.StandardIdentity_Affiliation_AssumedFriend: 285 defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill 286 break; 287 case SymbolID.StandardIdentity_Affiliation_Hostile_Faker: 288 defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill 289 break; 290 case SymbolID.StandardIdentity_Affiliation_Suspect_Joker: 291 if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E) 292 defaultFillColor = "fill=\"#FFE599\"";//suspect frame fill 293 else 294 defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill 295 break; 296 case SymbolID.StandardIdentity_Affiliation_Unknown: 297 case SymbolID.StandardIdentity_Affiliation_Pending: 298 defaultFillColor = "fill=\"#FFFF80\"";//unknown frame fill 299 break; 300 case SymbolID.StandardIdentity_Affiliation_Neutral: 301 defaultFillColor = "fill=\"#AAFFAA\"";//neutral frame fill 302 break; 303 default: 304 defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill 305 break; 306 } 307 308 if(returnSVG == null) 309 returnSVG = svg.replaceFirst(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity); 310 else 311 returnSVG = returnSVG.replaceFirst(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity); 312 } 313 314 if(returnSVG != null) 315 return returnSVG; 316 else 317 return svg; 318 } 319 320 /** 321 * For Renderer Use Only 322 * Changes colors for single point control measures 323 * @param symbolID 324 * @param svg 325 * @param strokeColor hex value like "#FF0000"; 326 * @param fillColor hex value like "#FF0000"; 327 * @param isOutline true if this represents a thicker outline to render first beneath the normal symbol (the function must be called twice) 328 * @return SVG String 329 */ 330 public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor, boolean isOutline) 331 { 332 String returnSVG = svg; 333 String hexStrokeColor = null; 334 String hexFillColor = null; 335 float strokeAlpha = 1; 336 float fillAlpha = 1; 337 String strokeOpacity = ""; 338 String fillOpacity = ""; 339 String strokeCapSquare = " stroke-linecap=\"square\""; 340 String strokeCapButt = " stroke-linecap=\"butt\""; 341 String strokeCapRound = " stroke-linecap=\"round\""; 342 343 int affiliation = SymbolID.getAffiliation(symbolID); 344 String defaultFillColor = null; 345 if(strokeColor != null) 346 { 347 if(strokeColor.getAlpha() != 255) 348 { 349 strokeAlpha = strokeColor.getAlpha() / 255.0f; 350 strokeOpacity = " stroke-opacity=\"" + strokeAlpha + "\""; 351 fillOpacity = " fill-opacity=\"" + strokeAlpha + "\""; 352 } 353 354 hexStrokeColor = colorToHexString(strokeColor,false); 355 String defaultStrokeColor = "#000000"; 356 if(symbolID.length()==5) 357 { 358 int mod = Integer.valueOf(symbolID.substring(2,4)); 359 if(mod >= 13) 360 defaultStrokeColor = "#00A651"; 361 362 } 363 //key terrain 364 if(symbolID.length() >= 20 && 365 SymbolUtilities.getBasicSymbolID(symbolID).equals("25132100") && 366 SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E) 367 { 368 defaultStrokeColor = "#800080"; 369 } 370 returnSVG = returnSVG.replaceAll("stroke=\"" + defaultStrokeColor + "\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity); 371 returnSVG = returnSVG.replaceAll("fill=\"" + defaultStrokeColor + "\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity); 372 } 373 else 374 { 375 strokeColor = Color.BLACK; 376 } 377 378 if (isOutline) { 379 // Capture and scale stroke-widths to create outlines. Note that some stroke-widths are not integral numbers. 380 Pattern pattern = Pattern.compile("(stroke-width=\")(\\d+\\.?\\d*)\""); 381 Matcher m = pattern.matcher(svg); 382 TreeSet<String> strokeWidthStrings = new TreeSet<>(); 383 while (m.find()) { 384 strokeWidthStrings.add(m.group(0)); 385 } 386 // replace stroke width values in SVG from greatest to least to avoid unintended replacements 387 // TODO This might not actually sort strings from greatest to least stroke-width values because they're alphabetical 388 for (String target : strokeWidthStrings.descendingSet()) { 389 Pattern numPattern = Pattern.compile("\\d+\\.?\\d*"); 390 Matcher numMatcher = numPattern.matcher(target); 391 numMatcher.find(); 392 float f = Float.parseFloat(numMatcher.group(0)); 393 String replacement = "stroke-width=\"" + (f * OUTLINE_SCALING_FACTOR) + "\""; 394 returnSVG = returnSVG.replace(target, replacement); 395 } 396 397 // add stroke-width and stroke (color) to all groups 398 pattern = Pattern.compile("(<g)"); 399 m = pattern.matcher(svg); 400 TreeSet<String> groupStrings = new TreeSet<>(); 401 while (m.find()) { 402 groupStrings.add(m.group(0)); 403 } 404 for (String target : groupStrings) { 405 String replacement = target + strokeCapSquare + " stroke-width=\"" + (2.5f * OUTLINE_SCALING_FACTOR) + "\" stroke=\"#" + strokeColor.toHexString().substring(2) + "\" "; 406 returnSVG = returnSVG.replace(target, replacement); 407 } 408 409 } 410 else 411 { 412 /* 413 Pattern pattern = Pattern.compile("(font-size=\"\\d+\\.?\\d*)\""); 414 Matcher m = pattern.matcher(svg); 415 TreeSet<String> fontStrings = new TreeSet<>(); 416 while (m.find()) { 417 fontStrings.add(m.group(0)); 418 } 419 for (String target : fontStrings) { 420 String replacement = target + " fill=\"#" + strokeColor.toHexString().substring(2) + "\" "; 421 returnSVG = returnSVG.replace(target, replacement); 422 }//*/ 423 424 String replacement = " fill=\"" + colorToHexString(strokeColor,false) + "\" "; 425 returnSVG = returnSVG.replace("fill=\"#000000\"",replacement);//only replace black fills, leave white fills alone. 426 427 //In case there are lines that don't have stroke defined, apply stroke color to the top level group. 428 String topGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\">";//<g id="25212902"> 429 String newGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\" stroke=\"" + hexStrokeColor + "\"" + strokeOpacity + " " + replacement + ">"; 430 returnSVG = returnSVG.replace(topGroupTag,newGroupTag); 431 } 432 433 if(fillColor != null) 434 { 435 if(fillColor.getAlpha() != 255) 436 { 437 fillAlpha = fillColor.getAlpha() / 255.0f; 438 fillOpacity = " fill-opacity=\"" + fillAlpha + "\""; 439 } 440 441 hexFillColor = colorToHexString(fillColor,false); 442 defaultFillColor = "fill=\"#000000\""; 443 444 returnSVG = returnSVG.replaceAll(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity); 445 } 446 447 return returnSVG; 448 } 449 450 public static float findWidestStrokeWidth(String svg) { 451 Pattern pattern = Pattern.compile("(stroke-width=\")(\\d+\\.?\\d*)\""); 452 Matcher m = pattern.matcher(svg); 453 TreeSet<Float> strokeWidths = new TreeSet<>(); 454 while (m.find()) { 455 // Log.d("found stroke width", m.group(0)); 456 strokeWidths.add(Float.valueOf(m.group(2))); 457 } 458 459 float largest = 4.0f; 460 if (!strokeWidths.isEmpty()) { 461 largest = strokeWidths.descendingSet().first(); 462 } 463 return largest * OUTLINE_SCALING_FACTOR; 464 } 465 466 public static SVGInfo scaleIcon(String symbolID, SVGInfo icon) 467 { 468 SVGInfo retVal= icon; 469 //safe square inside octagon: <rect x="220" y="310" width="170" height="170"/> 470 double maxSize = 170; 471 RectF bbox = icon.getBbox(); 472 double length = Math.max(bbox.width(),bbox.height()); 473 if(length < 100 && length > 0 && 474 SymbolID.getCommonModifier1(symbolID)==0 && 475 SymbolID.getCommonModifier2(symbolID)==0 && 476 SymbolID.getModifier1(symbolID)==0 && 477 SymbolID.getModifier2(symbolID)==0)//if largest side smaller than 100 and there are no section mods, make it bigger 478 { 479 double ratio = maxSize / length; 480 double transx = ((bbox.left + (bbox.width()/2)) * ratio) - (bbox.left + (bbox.width()/2)); 481 double transy = ((bbox.top + (bbox.height()/2)) * ratio) - (bbox.top + (bbox.height()/2)); 482 String transform = " transform=\"translate(-" + transx + ",-" + transy + ") scale(" + ratio + " " + ratio + ")\">"; 483 String svg = icon.getSVG(); 484 svg = svg.replaceFirst(">",transform); 485 RectF newBbox = RectUtilities.makeRectF((float)(bbox.left - transx),(float)(bbox.top - transy),(float)(bbox.width() * ratio), (float) (bbox.height() * ratio)); 486 retVal = new SVGInfo(icon.getID(),newBbox,svg); 487 } 488 return retVal; 489 } 490 491 public static int getDistanceBetweenPoints(Point pt1, Point pt2) 492 { 493 int distance = (int)(Math.sqrt(Math.pow((pt2.x - pt1.x) ,2) + Math.pow((pt2.y - pt1.y) ,2))); 494 return distance; 495 } 496 497 // Overloaded method to return non-outline symbols as normal. 498 public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor) { 499 return setSVGSPCMColors(symbolID, svg, strokeColor, fillColor, false); 500 } 501}