• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 package com.github.mikephil.charting.utils;
3 
4 import android.annotation.SuppressLint;
5 import android.content.Context;
6 import android.content.res.Resources;
7 import android.graphics.Canvas;
8 import android.graphics.Paint;
9 import android.graphics.Rect;
10 import android.graphics.drawable.BitmapDrawable;
11 import android.graphics.drawable.Drawable;
12 import android.os.Build;
13 import android.text.Layout;
14 import android.text.StaticLayout;
15 import android.text.TextPaint;
16 import android.util.DisplayMetrics;
17 import android.util.Log;
18 import android.util.SizeF;
19 import android.view.MotionEvent;
20 import android.view.VelocityTracker;
21 import android.view.View;
22 import android.view.ViewConfiguration;
23 
24 import com.github.mikephil.charting.formatter.DefaultValueFormatter;
25 import com.github.mikephil.charting.formatter.IValueFormatter;
26 
27 import java.util.List;
28 
29 /**
30  * Utilities class that has some helper methods. Needs to be initialized by
31  * calling Utils.init(...) before usage. Inside the Chart.init() method, this is
32  * done, if the Utils are used before that, Utils.init(...) needs to be called
33  * manually.
34  *
35  * @author Philipp Jahoda
36  */
37 public abstract class Utils {
38 
39     private static DisplayMetrics mMetrics;
40     private static int mMinimumFlingVelocity = 50;
41     private static int mMaximumFlingVelocity = 8000;
42     public final static double DEG2RAD = (Math.PI / 180.0);
43     public final static float FDEG2RAD = ((float) Math.PI / 180.f);
44 
45     @SuppressWarnings("unused")
46     public final static double DOUBLE_EPSILON = Double.longBitsToDouble(1);
47 
48     @SuppressWarnings("unused")
49     public final static float FLOAT_EPSILON = Float.intBitsToFloat(1);
50 
51     /**
52      * initialize method, called inside the Chart.init() method.
53      *
54      * @param context
55      */
56     @SuppressWarnings("deprecation")
init(Context context)57     public static void init(Context context) {
58 
59         if (context == null) {
60             // noinspection deprecation
61             mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
62             // noinspection deprecation
63             mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
64 
65             Log.e("MPChartLib-Utils"
66                     , "Utils.init(...) PROVIDED CONTEXT OBJECT IS NULL");
67 
68         } else {
69             ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
70             mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
71             mMaximumFlingVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
72 
73             Resources res = context.getResources();
74             mMetrics = res.getDisplayMetrics();
75         }
76     }
77 
78     /**
79      * initialize method, called inside the Chart.init() method. backwards
80      * compatibility - to not break existing code
81      *
82      * @param res
83      */
84     @Deprecated
init(Resources res)85     public static void init(Resources res) {
86 
87         mMetrics = res.getDisplayMetrics();
88 
89         // noinspection deprecation
90         mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
91         // noinspection deprecation
92         mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
93     }
94 
95     /**
96      * This method converts dp unit to equivalent pixels, depending on device
97      * density. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE.
98      *
99      * @param dp A value in dp (density independent pixels) unit. Which we need
100      *           to convert into pixels
101      * @return A float value to represent px equivalent to dp depending on
102      * device density
103      */
convertDpToPixel(float dp)104     public static float convertDpToPixel(float dp) {
105 
106         if (mMetrics == null) {
107 
108             Log.e("MPChartLib-Utils",
109                     "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before" +
110                             " calling Utils.convertDpToPixel(...). Otherwise conversion does not " +
111                             "take place.");
112             return dp;
113         }
114 
115         return dp * mMetrics.density;
116     }
117 
118     /**
119      * This method converts device specific pixels to density independent
120      * pixels. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE.
121      *
122      * @param px A value in px (pixels) unit. Which we need to convert into db
123      * @return A float value to represent dp equivalent to px value
124      */
convertPixelsToDp(float px)125     public static float convertPixelsToDp(float px) {
126 
127         if (mMetrics == null) {
128 
129             Log.e("MPChartLib-Utils",
130                     "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before" +
131                             " calling Utils.convertPixelsToDp(...). Otherwise conversion does not" +
132                             " take place.");
133             return px;
134         }
135 
136         return px / mMetrics.density;
137     }
138 
139     /**
140      * calculates the approximate width of a text, depending on a demo text
141      * avoid repeated calls (e.g. inside drawing methods)
142      *
143      * @param paint
144      * @param demoText
145      * @return
146      */
calcTextWidth(Paint paint, String demoText)147     public static int calcTextWidth(Paint paint, String demoText) {
148         return (int) paint.measureText(demoText);
149     }
150 
151     private static Rect mCalcTextHeightRect = new Rect();
152     /**
153      * calculates the approximate height of a text, depending on a demo text
154      * avoid repeated calls (e.g. inside drawing methods)
155      *
156      * @param paint
157      * @param demoText
158      * @return
159      */
calcTextHeight(Paint paint, String demoText)160     public static int calcTextHeight(Paint paint, String demoText) {
161 
162         Rect r = mCalcTextHeightRect;
163         r.set(0,0,0,0);
164         paint.getTextBounds(demoText, 0, demoText.length(), r);
165         return r.height();
166     }
167 
168     private static Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
169 
getLineHeight(Paint paint)170     public static float getLineHeight(Paint paint) {
171         return getLineHeight(paint, mFontMetrics);
172     }
173 
getLineHeight(Paint paint, Paint.FontMetrics fontMetrics)174     public static float getLineHeight(Paint paint, Paint.FontMetrics fontMetrics){
175         paint.getFontMetrics(fontMetrics);
176         return fontMetrics.descent - fontMetrics.ascent;
177     }
178 
getLineSpacing(Paint paint)179     public static float getLineSpacing(Paint paint) {
180         return getLineSpacing(paint, mFontMetrics);
181     }
182 
getLineSpacing(Paint paint, Paint.FontMetrics fontMetrics)183     public static float getLineSpacing(Paint paint, Paint.FontMetrics fontMetrics){
184         paint.getFontMetrics(fontMetrics);
185         return fontMetrics.ascent - fontMetrics.top + fontMetrics.bottom;
186     }
187 
188     /**
189      * Returns a recyclable FSize instance.
190      * calculates the approximate size of a text, depending on a demo text
191      * avoid repeated calls (e.g. inside drawing methods)
192      *
193      * @param paint
194      * @param demoText
195      * @return A Recyclable FSize instance
196      */
calcTextSize(Paint paint, String demoText)197     public static FSize calcTextSize(Paint paint, String demoText) {
198 
199         FSize result = FSize.getInstance(0,0);
200         calcTextSize(paint, demoText, result);
201         return result;
202     }
203 
204     private static Rect mCalcTextSizeRect = new Rect();
205     /**
206      * calculates the approximate size of a text, depending on a demo text
207      * avoid repeated calls (e.g. inside drawing methods)
208      *
209      * @param paint
210      * @param demoText
211      * @param outputFSize An output variable, modified by the function.
212      */
calcTextSize(Paint paint, String demoText, FSize outputFSize)213     public static void calcTextSize(Paint paint, String demoText, FSize outputFSize) {
214 
215         Rect r = mCalcTextSizeRect;
216         r.set(0,0,0,0);
217         paint.getTextBounds(demoText, 0, demoText.length(), r);
218         outputFSize.width = r.width();
219         outputFSize.height = r.height();
220 
221     }
222 
223 
224     /**
225      * Math.pow(...) is very expensive, so avoid calling it and create it
226      * yourself.
227      */
228     private static final int POW_10[] = {
229             1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
230     };
231 
232     private static IValueFormatter mDefaultValueFormatter = generateDefaultValueFormatter();
233 
generateDefaultValueFormatter()234     private static IValueFormatter generateDefaultValueFormatter() {
235         final DefaultValueFormatter formatter = new DefaultValueFormatter(1);
236         return formatter;
237     }
238 
239     /// - returns: The default value formatter used for all chart components that needs a default
getDefaultValueFormatter()240     public static IValueFormatter getDefaultValueFormatter()
241     {
242         return mDefaultValueFormatter;
243     }
244 
245     /**
246      * Formats the given number to the given number of decimals, and returns the
247      * number as a string, maximum 35 characters. If thousands are separated, the separating
248      * character is a dot (".").
249      *
250      * @param number
251      * @param digitCount
252      * @param separateThousands set this to true to separate thousands values
253      * @return
254      */
formatNumber(float number, int digitCount, boolean separateThousands)255     public static String formatNumber(float number, int digitCount, boolean separateThousands) {
256         return formatNumber(number, digitCount, separateThousands, '.');
257     }
258 
259     /**
260      * Formats the given number to the given number of decimals, and returns the
261      * number as a string, maximum 35 characters.
262      *
263      * @param number
264      * @param digitCount
265      * @param separateThousands set this to true to separate thousands values
266      * @param separateChar      a caracter to be paced between the "thousands"
267      * @return
268      */
formatNumber(float number, int digitCount, boolean separateThousands, char separateChar)269     public static String formatNumber(float number, int digitCount, boolean separateThousands,
270                                       char separateChar) {
271 
272         char[] out = new char[35];
273 
274         boolean neg = false;
275         if (number == 0) {
276             return "0";
277         }
278 
279         boolean zero = false;
280         if (number < 1 && number > -1) {
281             zero = true;
282         }
283 
284         if (number < 0) {
285             neg = true;
286             number = -number;
287         }
288 
289         if (digitCount > POW_10.length) {
290             digitCount = POW_10.length - 1;
291         }
292 
293         number *= POW_10[digitCount];
294         long lval = Math.round(number);
295         int ind = out.length - 1;
296         int charCount = 0;
297         boolean decimalPointAdded = false;
298 
299         while (lval != 0 || charCount < (digitCount + 1)) {
300             int digit = (int) (lval % 10);
301             lval = lval / 10;
302             out[ind--] = (char) (digit + '0');
303             charCount++;
304 
305             // add decimal point
306             if (charCount == digitCount) {
307                 out[ind--] = ',';
308                 charCount++;
309                 decimalPointAdded = true;
310 
311                 // add thousand separators
312             } else if (separateThousands && lval != 0 && charCount > digitCount) {
313 
314                 if (decimalPointAdded) {
315 
316                     if ((charCount - digitCount) % 4 == 0) {
317                         out[ind--] = separateChar;
318                         charCount++;
319                     }
320 
321                 } else {
322 
323                     if ((charCount - digitCount) % 4 == 3) {
324                         out[ind--] = separateChar;
325                         charCount++;
326                     }
327                 }
328             }
329         }
330 
331         // if number around zero (between 1 and -1)
332         if (zero) {
333             out[ind--] = '0';
334             charCount += 1;
335         }
336 
337         // if the number is negative
338         if (neg) {
339             out[ind--] = '-';
340             charCount += 1;
341         }
342 
343         int start = out.length - charCount;
344 
345         // use this instead of "new String(...)" because of issue < Android 4.0
346         return String.valueOf(out, start, out.length - start);
347     }
348 
349     /**
350      * rounds the given number to the next significant number
351      *
352      * @param number
353      * @return
354      */
roundToNextSignificant(double number)355     public static float roundToNextSignificant(double number) {
356         if (Double.isInfinite(number) ||
357             Double.isNaN(number) ||
358             number == 0.0)
359             return 0;
360 
361         final float d = (float) Math.ceil((float) Math.log10(number < 0 ? -number : number));
362         final int pw = 1 - (int) d;
363         final float magnitude = (float) Math.pow(10, pw);
364         final long shifted = Math.round(number * magnitude);
365         return shifted / magnitude;
366     }
367 
368     /**
369      * Returns the appropriate number of decimals to be used for the provided
370      * number.
371      *
372      * @param number
373      * @return
374      */
getDecimals(float number)375     public static int getDecimals(float number) {
376 
377         float i = roundToNextSignificant(number);
378 
379         if (Float.isInfinite(i))
380             return 0;
381 
382         return (int) Math.ceil(-Math.log10(i)) + 2;
383     }
384 
385     /**
386      * Converts the provided Integer List to an int array.
387      *
388      * @param integers
389      * @return
390      */
convertIntegers(List<Integer> integers)391     public static int[] convertIntegers(List<Integer> integers) {
392 
393         int[] ret = new int[integers.size()];
394 
395         copyIntegers(integers, ret);
396 
397         return ret;
398     }
399 
copyIntegers(List<Integer> from, int[] to)400     public static void copyIntegers(List<Integer> from, int[] to){
401         int count = to.length < from.size() ? to.length : from.size();
402         for(int i = 0 ; i < count ; i++){
403             to[i] = from.get(i);
404         }
405     }
406 
407     /**
408      * Converts the provided String List to a String array.
409      *
410      * @param strings
411      * @return
412      */
413     public static String[] convertStrings(List<String> strings) {
414 
415         String[] ret = new String[strings.size()];
416 
417         for (int i = 0; i < ret.length; i++) {
418             ret[i] = strings.get(i);
419         }
420 
421         return ret;
422     }
423 
424     public static void copyStrings(List<String> from, String[] to){
425         int count = to.length < from.size() ? to.length : from.size();
426         for(int i = 0 ; i < count ; i++){
427             to[i] = from.get(i);
428         }
429     }
430 
431     /**
432      * Replacement for the Math.nextUp(...) method that is only available in
433      * HONEYCOMB and higher. Dat's some seeeeek sheeet.
434      *
435      * @param d
436      * @return
437      */
438     public static double nextUp(double d) {
439         if (d == Double.POSITIVE_INFINITY)
440             return d;
441         else {
442             d += 0.0d;
443             return Double.longBitsToDouble(Double.doubleToRawLongBits(d) +
444                     ((d >= 0.0d) ? +1L : -1L));
445         }
446     }
447 
448     /**
449      * Returns a recyclable MPPointF instance.
450      * Calculates the position around a center point, depending on the distance
451      * from the center, and the angle of the position around the center.
452      *
453      * @param center
454      * @param dist
455      * @param angle  in degrees, converted to radians internally
456      * @return
457      */
458     public static MPPointF getPosition(MPPointF center, float dist, float angle) {
459 
460         MPPointF p = MPPointF.getInstance(0,0);
461         getPosition(center, dist, angle, p);
462         return p;
463     }
464 
465     public static void getPosition(MPPointF center, float dist, float angle, MPPointF outputPoint){
466         outputPoint.x = (float) (center.x + dist * Math.cos(Math.toRadians(angle)));
467         outputPoint.y = (float) (center.y + dist * Math.sin(Math.toRadians(angle)));
468     }
469 
470     public static void velocityTrackerPointerUpCleanUpIfNecessary(MotionEvent ev,
471                                                                   VelocityTracker tracker) {
472 
473         // Check the dot product of current velocities.
474         // If the pointer that left was opposing another velocity vector, clear.
475         tracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
476         final int upIndex = ev.getActionIndex();
477         final int id1 = ev.getPointerId(upIndex);
478         final float x1 = tracker.getXVelocity(id1);
479         final float y1 = tracker.getYVelocity(id1);
480         for (int i = 0, count = ev.getPointerCount(); i < count; i++) {
481             if (i == upIndex)
482                 continue;
483 
484             final int id2 = ev.getPointerId(i);
485             final float x = x1 * tracker.getXVelocity(id2);
486             final float y = y1 * tracker.getYVelocity(id2);
487 
488             final float dot = x + y;
489             if (dot < 0) {
490                 tracker.clear();
491                 break;
492             }
493         }
494     }
495 
496     /**
497      * Original method view.postInvalidateOnAnimation() only supportd in API >=
498      * 16, This is a replica of the code from ViewCompat.
499      *
500      * @param view
501      */
502     @SuppressLint("NewApi")
503     public static void postInvalidateOnAnimation(View view) {
504         if (Build.VERSION.SDK_INT >= 16)
505             view.postInvalidateOnAnimation();
506         else
507             view.postInvalidateDelayed(10);
508     }
509 
510     public static int getMinimumFlingVelocity() {
511         return mMinimumFlingVelocity;
512     }
513 
514     public static int getMaximumFlingVelocity() {
515         return mMaximumFlingVelocity;
516     }
517 
518     /**
519      * returns an angle between 0.f < 360.f (not less than zero, less than 360)
520      */
521     public static float getNormalizedAngle(float angle) {
522         while (angle < 0.f)
523             angle += 360.f;
524 
525         return angle % 360.f;
526     }
527 
528     private static Rect mDrawableBoundsCache = new Rect();
529 
530     public static void drawImage(Canvas canvas,
531                                  Drawable drawable,
532                                  int x, int y,
533                                  int width, int height) {
534 
535         MPPointF drawOffset = MPPointF.getInstance();
536         drawOffset.x = x - (width / 2);
537         drawOffset.y = y - (height / 2);
538 
539         drawable.copyBounds(mDrawableBoundsCache);
540         drawable.setBounds(
541                 mDrawableBoundsCache.left,
542                 mDrawableBoundsCache.top,
543                 mDrawableBoundsCache.left + width,
544                 mDrawableBoundsCache.top + width);
545 
546         int saveId = canvas.save();
547         // translate to the correct position and draw
548         canvas.translate(drawOffset.x, drawOffset.y);
549         drawable.draw(canvas);
550         canvas.restoreToCount(saveId);
551     }
552 
553     private static Rect mDrawTextRectBuffer = new Rect();
554     private static Paint.FontMetrics mFontMetricsBuffer = new Paint.FontMetrics();
555 
556     public static void drawXAxisValue(Canvas c, String text, float x, float y,
557                                       Paint paint,
558                                       MPPointF anchor, float angleDegrees) {
559 
560         float drawOffsetX = 0.f;
561         float drawOffsetY = 0.f;
562 
563         final float lineHeight = paint.getFontMetrics(mFontMetricsBuffer);
564         paint.getTextBounds(text, 0, text.length(), mDrawTextRectBuffer);
565 
566         // Android sometimes has pre-padding
567         drawOffsetX -= mDrawTextRectBuffer.left;
568 
569         // Android does not snap the bounds to line boundaries,
570         //  and draws from bottom to top.
571         // And we want to normalize it.
572         drawOffsetY += -mFontMetricsBuffer.ascent;
573 
574         // To have a consistent point of reference, we always draw left-aligned
575         Paint.Align originalTextAlign = paint.getTextAlign();
576         paint.setTextAlign(Paint.Align.LEFT);
577 
578         if (angleDegrees != 0.f) {
579 
580             // Move the text drawing rect in a way that it always rotates around its center
581             drawOffsetX -= mDrawTextRectBuffer.width() * 0.5f;
582             drawOffsetY -= lineHeight * 0.5f;
583 
584             float translateX = x;
585             float translateY = y;
586 
587             // Move the "outer" rect relative to the anchor, assuming its centered
588             if (anchor.x != 0.5f || anchor.y != 0.5f) {
589                 final FSize rotatedSize = getSizeOfRotatedRectangleByDegrees(
590                         mDrawTextRectBuffer.width(),
591                         lineHeight,
592                         angleDegrees);
593 
594                 translateX -= rotatedSize.width * (anchor.x - 0.5f);
595                 translateY -= rotatedSize.height * (anchor.y - 0.5f);
596                 FSize.recycleInstance(rotatedSize);
597             }
598 
599             c.save();
600             c.translate(translateX, translateY);
601             c.rotate(angleDegrees);
602 
603             c.drawText(text, drawOffsetX, drawOffsetY, paint);
604 
605             c.restore();
606         } else {
607             if (anchor.x != 0.f || anchor.y != 0.f) {
608 
609                 drawOffsetX -= mDrawTextRectBuffer.width() * anchor.x;
610                 drawOffsetY -= lineHeight * anchor.y;
611             }
612 
613             drawOffsetX += x;
614             drawOffsetY += y;
615 
616             c.drawText(text, drawOffsetX, drawOffsetY, paint);
617         }
618 
619         paint.setTextAlign(originalTextAlign);
620     }
621 
622     public static void drawMultilineText(Canvas c, StaticLayout textLayout,
623                                          float x, float y,
624                                          TextPaint paint,
625                                          MPPointF anchor, float angleDegrees) {
626 
627         float drawOffsetX = 0.f;
628         float drawOffsetY = 0.f;
629         float drawWidth;
630         float drawHeight;
631 
632         final float lineHeight = paint.getFontMetrics(mFontMetricsBuffer);
633 
634         drawWidth = textLayout.getWidth();
635         drawHeight = textLayout.getLineCount() * lineHeight;
636 
637         // Android sometimes has pre-padding
638         drawOffsetX -= mDrawTextRectBuffer.left;
639 
640         // Android does not snap the bounds to line boundaries,
641         //  and draws from bottom to top.
642         // And we want to normalize it.
643         drawOffsetY += drawHeight;
644 
645         // To have a consistent point of reference, we always draw left-aligned
646         Paint.Align originalTextAlign = paint.getTextAlign();
647         paint.setTextAlign(Paint.Align.LEFT);
648 
649         if (angleDegrees != 0.f) {
650 
651             // Move the text drawing rect in a way that it always rotates around its center
652             drawOffsetX -= drawWidth * 0.5f;
653             drawOffsetY -= drawHeight * 0.5f;
654 
655             float translateX = x;
656             float translateY = y;
657 
658             // Move the "outer" rect relative to the anchor, assuming its centered
659             if (anchor.x != 0.5f || anchor.y != 0.5f) {
660                 final FSize rotatedSize = getSizeOfRotatedRectangleByDegrees(
661                         drawWidth,
662                         drawHeight,
663                         angleDegrees);
664 
665                 translateX -= rotatedSize.width * (anchor.x - 0.5f);
666                 translateY -= rotatedSize.height * (anchor.y - 0.5f);
667                 FSize.recycleInstance(rotatedSize);
668             }
669 
670             c.save();
671             c.translate(translateX, translateY);
672             c.rotate(angleDegrees);
673 
674             c.translate(drawOffsetX, drawOffsetY);
675             textLayout.draw(c);
676 
677             c.restore();
678         } else {
679             if (anchor.x != 0.f || anchor.y != 0.f) {
680 
681                 drawOffsetX -= drawWidth * anchor.x;
682                 drawOffsetY -= drawHeight * anchor.y;
683             }
684 
685             drawOffsetX += x;
686             drawOffsetY += y;
687 
688             c.save();
689 
690             c.translate(drawOffsetX, drawOffsetY);
691             textLayout.draw(c);
692 
693             c.restore();
694         }
695 
696         paint.setTextAlign(originalTextAlign);
697     }
698 
699     public static void drawMultilineText(Canvas c, String text,
700                                          float x, float y,
701                                          TextPaint paint,
702                                          FSize constrainedToSize,
703                                          MPPointF anchor, float angleDegrees) {
704 
705         StaticLayout textLayout = new StaticLayout(
706                 text, 0, text.length(),
707                 paint,
708                 (int) Math.max(Math.ceil(constrainedToSize.width), 1.f),
709                 Layout.Alignment.ALIGN_NORMAL, 1.f, 0.f, false);
710 
711 
712         drawMultilineText(c, textLayout, x, y, paint, anchor, angleDegrees);
713     }
714 
715     /**
716      * Returns a recyclable FSize instance.
717      * Represents size of a rotated rectangle by degrees.
718      *
719      * @param rectangleSize
720      * @param degrees
721      * @return A Recyclable FSize instance
722      */
723     public static FSize getSizeOfRotatedRectangleByDegrees(FSize rectangleSize, float degrees) {
724         final float radians = degrees * FDEG2RAD;
725         return getSizeOfRotatedRectangleByRadians(rectangleSize.width, rectangleSize.height,
726                 radians);
727     }
728 
729     /**
730      * Returns a recyclable FSize instance.
731      * Represents size of a rotated rectangle by radians.
732      *
733      * @param rectangleSize
734      * @param radians
735      * @return A Recyclable FSize instance
736      */
737     public static FSize getSizeOfRotatedRectangleByRadians(FSize rectangleSize, float radians) {
738         return getSizeOfRotatedRectangleByRadians(rectangleSize.width, rectangleSize.height,
739                 radians);
740     }
741 
742     /**
743      * Returns a recyclable FSize instance.
744      * Represents size of a rotated rectangle by degrees.
745      *
746      * @param rectangleWidth
747      * @param rectangleHeight
748      * @param degrees
749      * @return A Recyclable FSize instance
750      */
751     public static FSize getSizeOfRotatedRectangleByDegrees(float rectangleWidth, float
752             rectangleHeight, float degrees) {
753         final float radians = degrees * FDEG2RAD;
754         return getSizeOfRotatedRectangleByRadians(rectangleWidth, rectangleHeight, radians);
755     }
756 
757     /**
758      * Returns a recyclable FSize instance.
759      * Represents size of a rotated rectangle by radians.
760      *
761      * @param rectangleWidth
762      * @param rectangleHeight
763      * @param radians
764      * @return A Recyclable FSize instance
765      */
766     public static FSize getSizeOfRotatedRectangleByRadians(float rectangleWidth, float
767             rectangleHeight, float radians) {
768         return FSize.getInstance(
769                 Math.abs(rectangleWidth * (float) Math.cos(radians)) + Math.abs(rectangleHeight *
770                         (float) Math.sin(radians)),
771                 Math.abs(rectangleWidth * (float) Math.sin(radians)) + Math.abs(rectangleHeight *
772                         (float) Math.cos(radians))
773         );
774     }
775 
776     public static int getSDKInt() {
777         return android.os.Build.VERSION.SDK_INT;
778     }
779 }
780