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