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 int ver = SymbolID.getVersion(symbolID); 246 int affiliation = SymbolID.getAffiliation(symbolID); 247 String defaultFillColor = null; 248 returnSVG = svg; 249 if(strokeColor != null) 250 { 251 if(strokeColor.getAlpha() != 255) 252 { 253 strokeAlpha = strokeColor.getAlpha() / 255.0f; 254 strokeOpacity = " stroke-opacity=\"" + String.valueOf(strokeAlpha) + "\""; 255 fillOpacity = " fill-opacity=\"" + String.valueOf(strokeAlpha) + "\""; 256 } 257 258 hexStrokeColor = colorToHexString(strokeColor,false); 259 returnSVG = svg.replaceAll("stroke=\"#000000\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity); 260 returnSVG = returnSVG.replaceAll("fill=\"#000000\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity); 261 262 if(ss == SymbolID.SymbolSet_LandInstallation || 263 ss == SymbolID.SymbolSet_Space || 264 ss == SymbolID.SymbolSet_CyberSpace || 265 ss == SymbolID.SymbolSet_Activities) 266 {//add group fill so the extra shapes in these frames have the new frame color 267 String svgStart = "<g id=\"" + SVGLookup.getFrameID(symbolID) + "\">"; 268 String svgStartReplace = svgStart.substring(0,svgStart.length()-1) + " fill=\"" + hexStrokeColor + "\"" + fillOpacity + ">"; 269 returnSVG = returnSVG.replace(svgStart,svgStartReplace); 270 } 271 272 if((SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_LandInstallation && SymbolID.getFrameShape(symbolID)=='0') || 273 SymbolID.getFrameShape(symbolID)==SymbolID.FrameShape_LandInstallation) 274 { 275 int i1 = returnSVG.indexOf("<rect") + 5; 276 if(ver >= SymbolID.Version_2525E && affiliation == SymbolID.StandardIdentity_Affiliation_AssumedFriend) 277 i1 = returnSVG.indexOf("<rect",i1) + 5; 278 //make sure installation indicator matches line color 279 returnSVG = returnSVG.substring(0,i1) + " fill=\"" + hexStrokeColor + "\"" + returnSVG.substring(i1); 280 } 281 282 } 283 else if((SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_LandInstallation && SymbolID.getFrameShape(symbolID)=='0') || 284 SymbolID.getFrameShape(symbolID)==SymbolID.FrameShape_LandInstallation) 285 { 286 int i1 = returnSVG.indexOf("<rect") + 5; 287 if(ver >= SymbolID.Version_2525E && affiliation == SymbolID.StandardIdentity_Affiliation_AssumedFriend) 288 i1 = returnSVG.indexOf("<rect",i1) + 5; 289 //No line color change so make sure installation indicator stays black 290 returnSVG = returnSVG.substring(0,i1) + " fill=\"#000000\"" + returnSVG.substring(i1); 291 } 292 293 if(fillColor != null) 294 { 295 if(fillColor.getAlpha() != 255) 296 { 297 fillAlpha = fillColor.getAlpha() / 255.0f; 298 fillOpacity = " fill-opacity=\"" + String.valueOf(fillAlpha) + "\""; 299 } 300 301 hexFillColor = colorToHexString(fillColor,false); 302 switch(affiliation) 303 { 304 case SymbolID.StandardIdentity_Affiliation_Friend: 305 case SymbolID.StandardIdentity_Affiliation_AssumedFriend: 306 defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill 307 break; 308 case SymbolID.StandardIdentity_Affiliation_Hostile_Faker: 309 defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill 310 break; 311 case SymbolID.StandardIdentity_Affiliation_Suspect_Joker: 312 if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E) 313 defaultFillColor = "fill=\"#FFE599\"";//suspect frame fill 314 else 315 defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill 316 break; 317 case SymbolID.StandardIdentity_Affiliation_Unknown: 318 case SymbolID.StandardIdentity_Affiliation_Pending: 319 defaultFillColor = "fill=\"#FFFF80\"";//unknown frame fill 320 break; 321 case SymbolID.StandardIdentity_Affiliation_Neutral: 322 defaultFillColor = "fill=\"#AAFFAA\"";//neutral frame fill 323 break; 324 default: 325 defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill 326 break; 327 } 328 329 int fillIndex = returnSVG.lastIndexOf(defaultFillColor); 330 if(fillIndex != -1) 331 returnSVG = returnSVG.substring(0,fillIndex) + "fill=\"" + hexFillColor + "\"" + fillOpacity + returnSVG.substring(fillIndex + defaultFillColor.length()); 332 333 //returnSVG = returnSVG.replaceFirst(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity); 334 } 335 336 if(returnSVG != null) 337 return returnSVG; 338 else 339 return svg; 340 } 341 342 /** 343 * For Renderer Use Only 344 * Changes colors for single point control measures 345 * @param symbolID 346 * @param svg 347 * @param strokeColor hex value like "#FF0000"; 348 * @param fillColor hex value like "#FF0000"; 349 * @param isOutline true if this represents a thicker outline to render first beneath the normal symbol (the function must be called twice) 350 * @return SVG String 351 */ 352 public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor, boolean isOutline) 353 { 354 String returnSVG = svg; 355 String hexStrokeColor = null; 356 String hexFillColor = null; 357 float strokeAlpha = 1; 358 float fillAlpha = 1; 359 String strokeOpacity = ""; 360 String fillOpacity = ""; 361 String strokeCapSquare = " stroke-linecap=\"square\""; 362 String strokeCapButt = " stroke-linecap=\"butt\""; 363 String strokeCapRound = " stroke-linecap=\"round\""; 364 365 int affiliation = SymbolID.getAffiliation(symbolID); 366 String defaultFillColor = null; 367 if(strokeColor != null) 368 { 369 if(strokeColor.getAlpha() != 255) 370 { 371 strokeAlpha = strokeColor.getAlpha() / 255.0f; 372 strokeOpacity = " stroke-opacity=\"" + strokeAlpha + "\""; 373 fillOpacity = " fill-opacity=\"" + strokeAlpha + "\""; 374 } 375 376 hexStrokeColor = colorToHexString(strokeColor,false); 377 String defaultStrokeColor = "#000000"; 378 if(symbolID.length()==5) 379 { 380 int mod = Integer.valueOf(symbolID.substring(2,4)); 381 if(mod >= 13) 382 defaultStrokeColor = "#00A651"; 383 384 } 385 //key terrain 386 if(symbolID.length() >= 20 && 387 SymbolUtilities.getBasicSymbolID(symbolID).equals("25132100") && 388 SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E) 389 { 390 defaultStrokeColor = "#800080"; 391 } 392 returnSVG = returnSVG.replaceAll("stroke=\"" + defaultStrokeColor + "\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity); 393 returnSVG = returnSVG.replaceAll("fill=\"" + defaultStrokeColor + "\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity); 394 } 395 else 396 { 397 strokeColor = Color.BLACK; 398 } 399 400 if (isOutline) { 401 // Capture and scale stroke-widths to create outlines. Note that some stroke-widths are not integral numbers. 402 Pattern pattern = Pattern.compile("(stroke-width=\")(\\d+\\.?\\d*)\""); 403 Matcher m = pattern.matcher(svg); 404 TreeSet<String> strokeWidthStrings = new TreeSet<>(); 405 while (m.find()) { 406 strokeWidthStrings.add(m.group(0)); 407 } 408 // replace stroke width values in SVG from greatest to least to avoid unintended replacements 409 // TODO This might not actually sort strings from greatest to least stroke-width values because they're alphabetical 410 for (String target : strokeWidthStrings.descendingSet()) { 411 Pattern numPattern = Pattern.compile("\\d+\\.?\\d*"); 412 Matcher numMatcher = numPattern.matcher(target); 413 numMatcher.find(); 414 float f = Float.parseFloat(numMatcher.group(0)); 415 String replacement = "stroke-width=\"" + (f * OUTLINE_SCALING_FACTOR) + "\""; 416 returnSVG = returnSVG.replace(target, replacement); 417 } 418 419 // add stroke-width and stroke (color) to all groups 420 pattern = Pattern.compile("(<g)"); 421 m = pattern.matcher(svg); 422 TreeSet<String> groupStrings = new TreeSet<>(); 423 while (m.find()) { 424 groupStrings.add(m.group(0)); 425 } 426 for (String target : groupStrings) { 427 String replacement = target + strokeCapSquare + " stroke-width=\"" + (2.5f * OUTLINE_SCALING_FACTOR) + "\" stroke=\"#" + strokeColor.toHexString().substring(2) + "\" "; 428 returnSVG = returnSVG.replace(target, replacement); 429 } 430 431 } 432 else 433 { 434 /* 435 Pattern pattern = Pattern.compile("(font-size=\"\\d+\\.?\\d*)\""); 436 Matcher m = pattern.matcher(svg); 437 TreeSet<String> fontStrings = new TreeSet<>(); 438 while (m.find()) { 439 fontStrings.add(m.group(0)); 440 } 441 for (String target : fontStrings) { 442 String replacement = target + " fill=\"#" + strokeColor.toHexString().substring(2) + "\" "; 443 returnSVG = returnSVG.replace(target, replacement); 444 }//*/ 445 446 String replacement = " fill=\"" + colorToHexString(strokeColor,false) + "\" "; 447 returnSVG = returnSVG.replace("fill=\"#000000\"",replacement);//only replace black fills, leave white fills alone. 448 449 //In case there are lines that don't have stroke defined, apply stroke color to the top level group. 450 String topGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\">";//<g id="25212902"> 451 String newGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\" stroke=\"" + hexStrokeColor + "\"" + strokeOpacity + " " + replacement + ">"; 452 returnSVG = returnSVG.replace(topGroupTag,newGroupTag); 453 } 454 455 if(fillColor != null) 456 { 457 if(fillColor.getAlpha() != 255) 458 { 459 fillAlpha = fillColor.getAlpha() / 255.0f; 460 fillOpacity = " fill-opacity=\"" + fillAlpha + "\""; 461 } 462 463 hexFillColor = colorToHexString(fillColor,false); 464 defaultFillColor = "fill=\"#000000\""; 465 466 returnSVG = returnSVG.replaceAll(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity); 467 } 468 469 return returnSVG; 470 } 471 472 public static float findWidestStrokeWidth(String svg) { 473 Pattern pattern = Pattern.compile("(stroke-width=\")(\\d+\\.?\\d*)\""); 474 Matcher m = pattern.matcher(svg); 475 TreeSet<Float> strokeWidths = new TreeSet<>(); 476 while (m.find()) { 477 // Log.d("found stroke width", m.group(0)); 478 strokeWidths.add(Float.valueOf(m.group(2))); 479 } 480 481 float largest = 4.0f; 482 if (!strokeWidths.isEmpty()) { 483 largest = strokeWidths.descendingSet().first(); 484 } 485 return largest * OUTLINE_SCALING_FACTOR; 486 } 487 488 public static SVGInfo scaleIcon(String symbolID, SVGInfo icon) 489 { 490 SVGInfo retVal= icon; 491 //safe square inside octagon: <rect x="220" y="310" width="170" height="170"/> 492 double maxSize = 170; 493 RectF bbox = null; 494 if(icon != null) 495 bbox = icon.getBbox(); 496 double length = 0; 497 if(bbox != null) 498 length = Math.max(bbox.width(),bbox.height()); 499 if(length < 100 && length > 0 && 500 SymbolID.getCommonModifier1(symbolID)==0 && 501 SymbolID.getCommonModifier2(symbolID)==0 && 502 SymbolID.getModifier1(symbolID)==0 && 503 SymbolID.getModifier2(symbolID)==0)//if largest side smaller than 100 and there are no section mods, make it bigger 504 { 505 double ratio = maxSize / length; 506 double transx = ((bbox.left + (bbox.width()/2)) * ratio) - (bbox.left + (bbox.width()/2)); 507 double transy = ((bbox.top + (bbox.height()/2)) * ratio) - (bbox.top + (bbox.height()/2)); 508 String transform = " transform=\"translate(-" + transx + ",-" + transy + ") scale(" + ratio + " " + ratio + ")\">"; 509 String svg = icon.getSVG(); 510 svg = svg.replaceFirst(">",transform); 511 RectF newBbox = RectUtilities.makeRectF((float)(bbox.left - transx),(float)(bbox.top - transy),(float)(bbox.width() * ratio), (float) (bbox.height() * ratio)); 512 retVal = new SVGInfo(icon.getID(),newBbox,svg); 513 } 514 return retVal; 515 } 516 517 public static int getDistanceBetweenPoints(Point pt1, Point pt2) 518 { 519 int distance = (int)(Math.sqrt(Math.pow((pt2.x - pt1.x) ,2) + Math.pow((pt2.y - pt1.y) ,2))); 520 return distance; 521 } 522 523 // Overloaded method to return non-outline symbols as normal. 524 public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor) { 525 return setSVGSPCMColors(symbolID, svg, strokeColor, fillColor, false); 526 } 527}