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}