- use display name for avatar generation

- use new server algorithm
Ref: https://github.com/nextcloud/nextcloud-vue/blob/master/src/functions/usernameToColor/usernameToColor.js

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2020-08-26 08:19:30 +02:00
parent ebd39db114
commit e07c2d169a
No known key found for this signature in database
GPG key ID: 0E00D4D47D0C5AF7
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,20 +51,13 @@ 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.
@ -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 * @return The largest inSampleSize value that is a power of 2 and keeps both height and width larger than reqWidth
* height and width larger than reqWidth and reqHeight. * and reqHeight.
*/ */
public static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) { public static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) {
@ -142,8 +135,8 @@ 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]", "");
int steps = 6;
Color[] finalPalette = generateColors(steps);
return finalPalette[hashToInt(hash, steps * 3)];
}
private static int hashToInt(String hash, int maximum) {
int finalInt = 0;
int[] result = new int[hash.length()];
// splitting evenly the string
for (int i = 0; i < hash.length(); i++) { for (int i = 0; i < hash.length(); i++) {
result[i % modulo] = result[i % modulo] + Integer.parseInt(hash.substring(i, i + 1), 16); // chars in md5 goes up to f, hex: 16
result[i] = Integer.parseInt(String.valueOf(hash.charAt(i)), 16) % 16;
} }
// Converting our data into a usable rgb format // adds up all results
// Start at 1 because 16%3=1 but 15%3=0 and makes the repartition even for (int value : result) {
for (int count = 1; count < modulo; count++) { finalInt += value;
rgb[count % 3] += Integer.parseInt(result[count]);
} }
// Reduce values bigger than rgb requirements // chars in md5 goes up to f, hex:16
rgb[INDEX_RED] = rgb[INDEX_RED] % 255; // make sure we're always using int in our operation
rgb[INDEX_GREEN] = rgb[INDEX_GREEN] % 255; return Integer.parseInt(String.valueOf(Integer.parseInt(String.valueOf(finalInt), 10) % maximum), 10);
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 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;
} }
private static double[] rgbToHsl(double rUntrimmed, double gUntrimmed, double bUntrimmed) { @SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
double r = rUntrimmed / 255; private static Color[] mixPalette(int steps, Color color1, Color color2) {
double g = gUntrimmed / 255; Color[] palette = new Color[steps];
double b = bUntrimmed / 255; palette[0] = color1;
double max = Math.max(r, Math.max(g, b)); float[] step = stepCalc(steps, color1, color2);
double min = Math.min(r, Math.min(g, b)); for (int i = 1; i < steps; i++) {
double h = (max + min) / 2; int r = (int) (color1.r + step[0] * i);
double s; int g = (int) (color1.g + step[1] * i);
double l = (max + min) / 2; int b = (int) (color1.b + step[2] * i);
if (max == min) { palette[i] = new Color(r, g, b);
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}; return palette;
hsl[INDEX_HUE] = h; }
hsl[INDEX_SATURATION] = s;
hsl[INDEX_LUMINATION] = l;
return hsl; 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,8 +313,8 @@ 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