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}