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}