Merge pull request #6817 from nextcloud/avatar

Avatar: use display name & adapted algorithm to server one
This commit is contained in:
Tobias Kaminsky 2020-09-03 10:21:57 +02:00 committed by GitHub
commit 33ece4a557
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 223 additions and 201 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -1 +1 @@
326 320

View file

@ -0,0 +1,79 @@
/*
*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2020 Tobias Kaminsky
* Copyright (C) 2020 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.utils
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test
class BitmapUtilsIT {
@Test
@Suppress("MagicNumber")
fun usernameToColor() {
assertEquals(BitmapUtils.Color(0, 0, 0), BitmapUtils.Color(0, 0, 0))
assertEquals(BitmapUtils.Color(221, 203, 85), BitmapUtils.usernameToColor("User"))
assertEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.usernameToColor("Admin"))
assertEquals(BitmapUtils.Color(0, 130, 201), BitmapUtils.usernameToColor(""))
assertEquals(BitmapUtils.Color(201, 136, 121), BitmapUtils.usernameToColor("68b329da9893e34099c7d8ad5cb9c940"))
// tests from server
assertEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.usernameToColor("Alishia Ann Lowry"))
assertEquals(BitmapUtils.Color(0, 130, 201), BitmapUtils.usernameToColor("Arham Johnson"))
assertEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.usernameToColor("Brayden Truong"))
assertEquals(BitmapUtils.Color(151, 80, 164), BitmapUtils.usernameToColor("Daphne Roy"))
assertEquals(BitmapUtils.Color(195, 114, 133), BitmapUtils.usernameToColor("Ellena Wright Frederic Conway"))
assertEquals(BitmapUtils.Color(214, 180, 97), BitmapUtils.usernameToColor("Gianluca Hills"))
assertEquals(BitmapUtils.Color(214, 180, 97), BitmapUtils.usernameToColor("Haseeb Stephens"))
assertEquals(BitmapUtils.Color(151, 80, 164), BitmapUtils.usernameToColor("Idris Mac"))
assertEquals(BitmapUtils.Color(0, 130, 201), BitmapUtils.usernameToColor("Kristi Fisher"))
assertEquals(BitmapUtils.Color(188, 92, 145), BitmapUtils.usernameToColor("Lillian Wall"))
assertEquals(BitmapUtils.Color(221, 203, 85), BitmapUtils.usernameToColor("Lorelai Taylor"))
assertEquals(BitmapUtils.Color(151, 80, 164), BitmapUtils.usernameToColor("Madina Knight"))
assertEquals(BitmapUtils.Color(121, 90, 171), BitmapUtils.usernameToColor("Rae Hope"))
assertEquals(BitmapUtils.Color(188, 92, 145), BitmapUtils.usernameToColor("Santiago Singleton"))
assertEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.usernameToColor("Sid Combs"))
assertEquals(BitmapUtils.Color(30, 120, 193), BitmapUtils.usernameToColor("Vivienne Jacobs"))
assertEquals(BitmapUtils.Color(110, 166, 143), BitmapUtils.usernameToColor("Zaki Cortes"))
assertEquals(BitmapUtils.Color(91, 100, 179), BitmapUtils.usernameToColor("a user"))
assertEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.usernameToColor("admin"))
assertEquals(BitmapUtils.Color(151, 80, 164), BitmapUtils.usernameToColor("admin@cloud.example.com"))
assertEquals(BitmapUtils.Color(221, 203, 85), BitmapUtils.usernameToColor("another user"))
assertEquals(BitmapUtils.Color(36, 142, 181), BitmapUtils.usernameToColor("asd"))
assertEquals(BitmapUtils.Color(0, 130, 201), BitmapUtils.usernameToColor("bar"))
assertEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.usernameToColor("foo"))
assertEquals(BitmapUtils.Color(182, 70, 157), BitmapUtils.usernameToColor("wasd"))
}
@Test
@Suppress("MagicNumber")
fun checkEqual() {
assertEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.Color(208, 158, 109))
assertNotEquals(BitmapUtils.Color(208, 158, 109), BitmapUtils.Color(208, 158, 100))
}
@Test
@Suppress("MagicNumber")
fun checkHashCode() {
assertEquals(BitmapUtils.Color(208, 158, 109).hashCode(), BitmapUtils.Color(208, 158, 109).hashCode())
assertNotEquals(BitmapUtils.Color(208, 158, 109).hashCode(), BitmapUtils.Color(208, 158, 100).hashCode())
}
}

View file

@ -20,12 +20,15 @@
package com.nextcloud.client.account; package com.nextcloud.client.account;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import com.nextcloud.java.util.Optional; import com.nextcloud.java.util.Optional;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.resources.status.OwnCloudVersion; import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import java.util.List; import java.util.List;
@ -154,13 +157,14 @@ public interface UserAccountManager extends CurrentAccountProvider {
/** /**
* Extract username from account. * Extract username from account.
* * <p>
* Full account name is in form of "username@nextcloud.domain". * Full account name is in form of "username@nextcloud.domain".
* *
* @param account Account instance * @param account Account instance
* @return User name (without domain) or null, if name cannot be extracted. * @return User name (without domain) or null, if name cannot be extracted.
*/ */
static String getUsername(Account account) { static @Nullable
String getUsername(Account account) {
if (account != null && account.name != null) { if (account != null && account.name != null) {
return account.name.substring(0, account.name.lastIndexOf('@')); return account.name.substring(0, account.name.lastIndexOf('@'));
} else { } else {
@ -168,9 +172,15 @@ public interface UserAccountManager extends CurrentAccountProvider {
} }
} }
static @Nullable
String getDisplayName(Account account) {
return AccountManager.get(MainApp.getAppContext()).getUserData(account,
AccountUtils.Constants.KEY_DISPLAY_NAME);
}
/** /**
* Launch account registration activity. * Launch account registration activity.
* * <p>
* This method returns immediately. Authenticator activity will be launched asynchronously. * This method returns immediately. Authenticator activity will be launched asynchronously.
* *
* @param activity Activity used to launch authenticator flow via {@link Activity#startActivity(Intent)} * @param activity Activity used to launch authenticator flow via {@link Activity#startActivity(Intent)}

View file

@ -1136,13 +1136,10 @@ public final class ThumbnailsCacheManager {
} }
public static class AsyncMediaThumbnailDrawable extends BitmapDrawable { public static class AsyncMediaThumbnailDrawable extends BitmapDrawable {
private final WeakReference<MediaThumbnailGenerationTask> bitmapWorkerTaskReference;
public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap, public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap) {
MediaThumbnailGenerationTask bitmapWorkerTask) {
super(res, bitmap); super(res, bitmap);
bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
} }
} }

View file

@ -69,19 +69,17 @@ public class TextDrawable extends Drawable {
* Create a TextDrawable with the given radius. * Create a TextDrawable with the given radius.
* *
* @param text the text to be rendered * @param text the text to be rendered
* @param r rgb red value * @param color color
* @param g rgb green value
* @param b rgb blue value
* @param radius circle radius * @param radius circle radius
*/ */
public TextDrawable(String text, int r, int g, int b, float radius) { public TextDrawable(String text, BitmapUtils.Color color, float radius) {
mRadius = radius; mRadius = radius;
mText = text; mText = text;
mBackground = new Paint(); mBackground = new Paint();
mBackground.setStyle(Paint.Style.FILL); mBackground.setStyle(Paint.Style.FILL);
mBackground.setAntiAlias(true); mBackground.setAntiAlias(true);
mBackground.setColor(Color.rgb(r, g, b)); mBackground.setColor(Color.rgb(color.r, color.g, color.b));
mTextPaint = new Paint(); mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE); mTextPaint.setColor(Color.WHITE);
@ -101,8 +99,9 @@ public class TextDrawable extends Drawable {
*/ */
@NonNull @NonNull
@NextcloudServer(max = 12) @NextcloudServer(max = 12)
public static TextDrawable createAvatar(Account account, float radiusInDp) throws NoSuchAlgorithmException { public static TextDrawable createAvatar(Account account, float radiusInDp) throws
String username = UserAccountManager.getUsername(account); NoSuchAlgorithmException {
String username = UserAccountManager.getDisplayName(account);
return createNamedAvatar(username, radiusInDp); return createNamedAvatar(username, radiusInDp);
} }
@ -132,11 +131,8 @@ public class TextDrawable extends Drawable {
*/ */
@NonNull @NonNull
public static TextDrawable createNamedAvatar(String name, float radiusInDp) throws NoSuchAlgorithmException { public static TextDrawable createNamedAvatar(String name, float radiusInDp) throws NoSuchAlgorithmException {
int[] hsl = BitmapUtils.calculateHSL(name); BitmapUtils.Color color = BitmapUtils.usernameToColor(name);
int[] rgb = BitmapUtils.HSLtoRGB(hsl[0], hsl[1], hsl[2], 1); return new TextDrawable(extractCharsFromDisplayName(name), color, radiusInDp);
return new TextDrawable(extractCharsFromDisplayName(name), rgb[0], rgb[1], rgb[2],
radiusInDp);
} }
@VisibleForTesting @VisibleForTesting

View file

@ -337,8 +337,7 @@ public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedV
ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable =
new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable( new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable(
context.getResources(), context.getResources(),
ThumbnailsCacheManager.mDefaultImg, ThumbnailsCacheManager.mDefaultImg
task
); );
holder.image.setImageDrawable(asyncDrawable); holder.image.setImageDrawable(asyncDrawable);

View file

@ -39,9 +39,11 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Locale; import java.util.Locale;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable; import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/** /**
* Utility class with methods for decoding Bitmaps. * Utility class with methods for decoding Bitmaps.
@ -49,24 +51,17 @@ import androidx.exifinterface.media.ExifInterface;
public final class BitmapUtils { public final class BitmapUtils {
public static final String TAG = BitmapUtils.class.getSimpleName(); public static final String TAG = BitmapUtils.class.getSimpleName();
private static final int INDEX_RED = 0;
private static final int INDEX_GREEN = 1;
private static final int INDEX_BLUE = 2;
private static final int INDEX_HUE = 0;
private static final int INDEX_SATURATION = 1;
private static final int INDEX_LUMINATION = 2;
private BitmapUtils() { private BitmapUtils() {
// utility class -> private constructor // utility class -> private constructor
} }
/** /**
* Decodes a bitmap from a file containing it minimizing the memory use, known that the bitmap * Decodes a bitmap from a file containing it minimizing the memory use, known that the bitmap will be drawn in a
* will be drawn in a surface of reqWidth x reqHeight * surface of reqWidth x reqHeight
* *
* @param srcPath Absolute path to the file containing the image. * @param srcPath Absolute path to the file containing the image.
* @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels. * @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels.
* @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels. * @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels.
* @return decoded bitmap * @return decoded bitmap
*/ */
public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) { public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) {
@ -93,16 +88,14 @@ public final class BitmapUtils {
/** /**
* Calculates a proper value for options.inSampleSize in order to decode a Bitmap minimizing * Calculates a proper value for options.inSampleSize in order to decode a Bitmap minimizing the memory overload and
* the memory overload and covering a target surface of reqWidth x reqHeight if the original * covering a target surface of reqWidth x reqHeight if the original image is big enough.
* image is big enough.
* *
* @param options Bitmap decoding options; options.outHeight and options.inHeight should * @param options Bitmap decoding options; options.outHeight and options.inHeight should be set.
* be set. * @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels.
* @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels. * @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels.
* @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels. * @return The largest inSampleSize value that is a power of 2 and keeps both height and width larger than reqWidth
* @return The largest inSampleSize value that is a power of 2 and keeps both * and reqHeight.
* height and width larger than reqWidth and reqHeight.
*/ */
public static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) { public static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) {
@ -142,9 +135,9 @@ public final class BitmapUtils {
} }
/** /**
* Rotate bitmap according to EXIF orientation. * Rotate bitmap according to EXIF orientation. Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
* Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ *
* @param bitmap Bitmap to be rotated * @param bitmap Bitmap to be rotated
* @param storagePath Path to source file of bitmap. Needed for EXIF information. * @param storagePath Path to source file of bitmap. Needed for EXIF information.
* @return correctly EXIF-rotated bitmap * @return correctly EXIF-rotated bitmap
*/ */
@ -201,167 +194,115 @@ public final class BitmapUtils {
return resultBitmap; return resultBitmap;
} }
/** public static Color usernameToColor(String name) throws NoSuchAlgorithmException {
* Convert HSL values to a RGB Color. String hash = name.toLowerCase(Locale.ROOT);
*
* @param h Hue is specified as degrees in the range 0 - 360.
* @param s Saturation is specified as a percentage in the range 1 - 100.
* @param l Luminance is specified as a percentage in the range 1 - 100.
* @param alpha the alpha value between 0 - 1
* adapted from https://svn.codehaus.org/griffon/builders/gfxbuilder/tags/GFXBUILDER_0.2/
* gfxbuilder-core/src/main/com/camick/awt/HSLColor.java
*/
@SuppressWarnings("PMD.MethodNamingConventions")
public static int[] HSLtoRGB(float h, float s, float l, float alpha) {
if (s < 0.0f || s > 100.0f) {
String message = "Color parameter outside of expected range - Saturation";
throw new IllegalArgumentException(message);
}
if (l < 0.0f || l > 100.0f) { // already a md5 hash?
String message = "Color parameter outside of expected range - Luminance"; if (!hash.matches("([0-9a-f]{4}-?){8}$")) {
throw new IllegalArgumentException(message);
}
if (alpha < 0.0f || alpha > 1.0f) {
String message = "Color parameter outside of expected range - Alpha";
throw new IllegalArgumentException(message);
}
// Formula needs all values between 0 - 1.
h = h % 360.0f;
h /= 360f;
s /= 100f;
l /= 100f;
float q;
if (l < 0.5) {
q = l * (1 + s);
} else {
q = (l + s) - (s * l);
}
float p = 2 * l - q;
int r = Math.round(Math.max(0, HueToRGB(p, q, h + (1.0f / 3.0f)) * 256));
int g = Math.round(Math.max(0, HueToRGB(p, q, h) * 256));
int b = Math.round(Math.max(0, HueToRGB(p, q, h - (1.0f / 3.0f)) * 256));
return new int[]{r, g, b};
}
@SuppressWarnings("PMD.MethodNamingConventions")
private static float HueToRGB(float p, float q, float h) {
if (h < 0) {
h += 1;
}
if (h > 1) {
h -= 1;
}
if (6 * h < 1) {
return p + ((q - p) * 6 * h);
}
if (2 * h < 1) {
return q;
}
if (3 * h < 2) {
return p + ((q - p) * 6 * (2.0f / 3.0f - h));
}
return p;
}
/**
* calculates the RGB value based on a given account name.
*
* @param name The name
* @return corresponding RGB color
* @throws NoSuchAlgorithmException if the specified algorithm is not available
*/
public static int[] calculateHSL(String name) throws NoSuchAlgorithmException {
// using adapted algorithm from https://github.com/nextcloud/server/blob/master/core/js/placeholder.js#L126
String[] result = new String[]{"0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"};
double[] rgb = new double[]{0, 0, 0};
int sat = 70;
int lum = 68;
int modulo = 16;
String hash = name.toLowerCase(Locale.ROOT).replaceAll("[^0-9a-f]", "");
if (!hash.matches("^[0-9a-f]{32}")) {
hash = md5(hash); hash = md5(hash);
} }
// Splitting evenly the string hash = hash.replaceAll("[^0-9a-f]", "");
for (int i = 0; i < hash.length(); i++) { int steps = 6;
result[i % modulo] = result[i % modulo] + Integer.parseInt(hash.substring(i, i + 1), 16);
}
// Converting our data into a usable rgb format Color[] finalPalette = generateColors(steps);
// Start at 1 because 16%3=1 but 15%3=0 and makes the repartition even
for (int count = 1; count < modulo; count++) {
rgb[count % 3] += Integer.parseInt(result[count]);
}
// Reduce values bigger than rgb requirements return finalPalette[hashToInt(hash, steps * 3)];
rgb[INDEX_RED] = rgb[INDEX_RED] % 255;
rgb[INDEX_GREEN] = rgb[INDEX_GREEN] % 255;
rgb[INDEX_BLUE] = rgb[INDEX_BLUE] % 255;
double[] hsl = rgbToHsl(rgb[INDEX_RED], rgb[INDEX_GREEN], rgb[INDEX_BLUE]);
// Classic formula to check the brightness for our eye
// If too bright, lower the sat
double bright = Math.sqrt(0.299 * Math.pow(rgb[INDEX_RED], 2) + 0.587 * Math.pow(rgb[INDEX_GREEN], 2) + 0.114
* Math.pow(rgb[INDEX_BLUE], 2));
if (bright >= 200) {
sat = 60;
}
return new int[]{(int) (hsl[INDEX_HUE] * 360), sat, lum};
} }
private static double[] rgbToHsl(double rUntrimmed, double gUntrimmed, double bUntrimmed) { private static int hashToInt(String hash, int maximum) {
double r = rUntrimmed / 255; int finalInt = 0;
double g = gUntrimmed / 255; int[] result = new int[hash.length()];
double b = bUntrimmed / 255;
double max = Math.max(r, Math.max(g, b)); // splitting evenly the string
double min = Math.min(r, Math.min(g, b)); for (int i = 0; i < hash.length(); i++) {
double h = (max + min) / 2; // chars in md5 goes up to f, hex: 16
double s; result[i] = Integer.parseInt(String.valueOf(hash.charAt(i)), 16) % 16;
double l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
double d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
if (max == r) {
h = (g - b) / d + (g < b ? 6 : 0);
} else if (max == g) {
h = (b - r) / d + 2;
} else if (max == b) {
h = (r - g) / d + 4;
}
h /= 6;
} }
double[] hsl = new double[]{0.0, 0.0, 0.0}; // adds up all results
hsl[INDEX_HUE] = h; for (int value : result) {
hsl[INDEX_SATURATION] = s; finalInt += value;
hsl[INDEX_LUMINATION] = l; }
return hsl; // chars in md5 goes up to f, hex:16
// make sure we're always using int in our operation
return Integer.parseInt(String.valueOf(Integer.parseInt(String.valueOf(finalInt), 10) % maximum), 10);
}
private static Color[] generateColors(int steps) {
Color red = new Color(182, 70, 157);
Color yellow = new Color(221, 203, 85);
Color blue = new Color(0, 130, 201); // Nextcloud blue
Color[] palette1 = mixPalette(steps, red, yellow);
Color[] palette2 = mixPalette(steps, yellow, blue);
Color[] palette3 = mixPalette(steps, blue, red);
Color[] resultPalette = new Color[palette1.length + palette2.length + palette3.length];
System.arraycopy(palette1, 0, resultPalette, 0, palette1.length);
System.arraycopy(palette2, 0, resultPalette, palette1.length, palette2.length);
System.arraycopy(palette3,
0,
resultPalette,
palette1.length + palette2.length,
palette1.length);
return resultPalette;
}
@SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
private static Color[] mixPalette(int steps, Color color1, Color color2) {
Color[] palette = new Color[steps];
palette[0] = color1;
float[] step = stepCalc(steps, color1, color2);
for (int i = 1; i < steps; i++) {
int r = (int) (color1.r + step[0] * i);
int g = (int) (color1.g + step[1] * i);
int b = (int) (color1.b + step[2] * i);
palette[i] = new Color(r, g, b);
}
return palette;
}
private static float[] stepCalc(int steps, Color color1, Color color2) {
float[] step = new float[3];
step[0] = (color2.r - color1.r) / (float) steps;
step[1] = (color2.g - color1.g) / (float) steps;
step[2] = (color2.b - color1.b) / (float) steps;
return step;
}
public static class Color {
public int r;
public int g;
public int b;
public Color(int r, int g, int b) {
this.r = r;
this.g = g;
this.b = b;
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Color)) {
return false;
}
Color other = (Color) obj;
return this.r == other.r && this.g == other.g && this.b == other.b;
}
@Override
public int hashCode() {
return r * 10000 + g * 1000 + b;
}
} }
public static String md5(String string) throws NoSuchAlgorithmException { public static String md5(String string) throws NoSuchAlgorithmException {
@ -372,11 +313,11 @@ public final class BitmapUtils {
} }
/** /**
* Returns a new circular bitmap drawable by creating it from a bitmap, setting initial target density based on * Returns a new circular bitmap drawable by creating it from a bitmap, setting initial target density based on the
* the display metrics of the resources. * display metrics of the resources.
* *
* @param resources the resources for initial target density * @param resources the resources for initial target density
* @param bitmap the original bitmap * @param bitmap the original bitmap
* @return the circular bitmap * @return the circular bitmap
*/ */
public static RoundedBitmapDrawable bitmapToCircularBitmapDrawable(Resources resources, public static RoundedBitmapDrawable bitmapToCircularBitmapDrawable(Resources resources,
@ -420,7 +361,7 @@ public final class BitmapUtils {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
} else { } else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888); Bitmap.Config.ARGB_8888);
} }
Canvas canvas = new Canvas(bitmap); Canvas canvas = new Canvas(bitmap);
@ -436,14 +377,14 @@ public final class BitmapUtils {
imageView); imageView);
} }
public static void setRoundedBitmapForGridMode(Bitmap thumbnail, ImageView imageView){ public static void setRoundedBitmapForGridMode(Bitmap thumbnail, ImageView imageView) {
BitmapUtils.setRoundedBitmap(getResources(), BitmapUtils.setRoundedBitmap(getResources(),
thumbnail, thumbnail,
getResources().getDimension(R.dimen.file_icon_rounded_corner_radius_for_grid_mode), getResources().getDimension(R.dimen.file_icon_rounded_corner_radius_for_grid_mode),
imageView); imageView);
} }
private static Resources getResources(){ private static Resources getResources() {
return MainApp.getAppContext().getResources(); return MainApp.getAppContext().getResources();
} }
} }