Merge pull request #6817 from nextcloud/avatar
Avatar: use display name & adapted algorithm to server one
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -1 +1 @@
|
|||
326
|
||||
320
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -20,12 +20,15 @@
|
|||
package com.nextcloud.client.account;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.nextcloud.java.util.Optional;
|
||||
import com.owncloud.android.MainApp;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudAccount;
|
||||
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
||||
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -154,13 +157,14 @@ public interface UserAccountManager extends CurrentAccountProvider {
|
|||
|
||||
/**
|
||||
* Extract username from account.
|
||||
*
|
||||
* <p>
|
||||
* Full account name is in form of "username@nextcloud.domain".
|
||||
*
|
||||
* @param account Account instance
|
||||
* @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) {
|
||||
return account.name.substring(0, account.name.lastIndexOf('@'));
|
||||
} 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.
|
||||
*
|
||||
* <p>
|
||||
* This method returns immediately. Authenticator activity will be launched asynchronously.
|
||||
*
|
||||
* @param activity Activity used to launch authenticator flow via {@link Activity#startActivity(Intent)}
|
||||
|
|
|
@ -1136,13 +1136,10 @@ public final class ThumbnailsCacheManager {
|
|||
}
|
||||
|
||||
public static class AsyncMediaThumbnailDrawable extends BitmapDrawable {
|
||||
private final WeakReference<MediaThumbnailGenerationTask> bitmapWorkerTaskReference;
|
||||
|
||||
public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap,
|
||||
MediaThumbnailGenerationTask bitmapWorkerTask) {
|
||||
public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap) {
|
||||
|
||||
super(res, bitmap);
|
||||
bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,19 +69,17 @@ public class TextDrawable extends Drawable {
|
|||
* Create a TextDrawable with the given radius.
|
||||
*
|
||||
* @param text the text to be rendered
|
||||
* @param r rgb red value
|
||||
* @param g rgb green value
|
||||
* @param b rgb blue value
|
||||
* @param color color
|
||||
* @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;
|
||||
mText = text;
|
||||
|
||||
mBackground = new Paint();
|
||||
mBackground.setStyle(Paint.Style.FILL);
|
||||
mBackground.setAntiAlias(true);
|
||||
mBackground.setColor(Color.rgb(r, g, b));
|
||||
mBackground.setColor(Color.rgb(color.r, color.g, color.b));
|
||||
|
||||
mTextPaint = new Paint();
|
||||
mTextPaint.setColor(Color.WHITE);
|
||||
|
@ -101,8 +99,9 @@ public class TextDrawable extends Drawable {
|
|||
*/
|
||||
@NonNull
|
||||
@NextcloudServer(max = 12)
|
||||
public static TextDrawable createAvatar(Account account, float radiusInDp) throws NoSuchAlgorithmException {
|
||||
String username = UserAccountManager.getUsername(account);
|
||||
public static TextDrawable createAvatar(Account account, float radiusInDp) throws
|
||||
NoSuchAlgorithmException {
|
||||
String username = UserAccountManager.getDisplayName(account);
|
||||
return createNamedAvatar(username, radiusInDp);
|
||||
}
|
||||
|
||||
|
@ -132,11 +131,8 @@ public class TextDrawable extends Drawable {
|
|||
*/
|
||||
@NonNull
|
||||
public static TextDrawable createNamedAvatar(String name, float radiusInDp) throws NoSuchAlgorithmException {
|
||||
int[] hsl = BitmapUtils.calculateHSL(name);
|
||||
int[] rgb = BitmapUtils.HSLtoRGB(hsl[0], hsl[1], hsl[2], 1);
|
||||
|
||||
return new TextDrawable(extractCharsFromDisplayName(name), rgb[0], rgb[1], rgb[2],
|
||||
radiusInDp);
|
||||
BitmapUtils.Color color = BitmapUtils.usernameToColor(name);
|
||||
return new TextDrawable(extractCharsFromDisplayName(name), color, radiusInDp);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
|
@ -337,8 +337,7 @@ public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedV
|
|||
ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable =
|
||||
new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable(
|
||||
context.getResources(),
|
||||
ThumbnailsCacheManager.mDefaultImg,
|
||||
task
|
||||
ThumbnailsCacheManager.mDefaultImg
|
||||
);
|
||||
holder.image.setImageDrawable(asyncDrawable);
|
||||
|
||||
|
|
|
@ -39,9 +39,11 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
/**
|
||||
* Utility class with methods for decoding Bitmaps.
|
||||
|
@ -49,20 +51,13 @@ import androidx.exifinterface.media.ExifInterface;
|
|||
public final class BitmapUtils {
|
||||
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() {
|
||||
// utility class -> private constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a bitmap from a file containing it minimizing the memory use, known that the bitmap
|
||||
* will be drawn in a surface of reqWidth x reqHeight
|
||||
* Decodes a bitmap from a file containing it minimizing the memory use, known that the bitmap will be drawn in a
|
||||
* surface of reqWidth x reqHeight
|
||||
*
|
||||
* @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.
|
||||
|
@ -93,16 +88,14 @@ public final class BitmapUtils {
|
|||
|
||||
|
||||
/**
|
||||
* Calculates a proper value for options.inSampleSize in order to decode a Bitmap minimizing
|
||||
* the memory overload and covering a target surface of reqWidth x reqHeight if the original
|
||||
* image is big enough.
|
||||
* Calculates a proper value for options.inSampleSize in order to decode a Bitmap minimizing the memory overload and
|
||||
* covering a target surface of reqWidth x reqHeight if the original image is big enough.
|
||||
*
|
||||
* @param options Bitmap decoding options; options.outHeight and options.inHeight should
|
||||
* be set.
|
||||
* @param options Bitmap decoding options; options.outHeight and options.inHeight should be set.
|
||||
* @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.
|
||||
* @return The largest inSampleSize value that is a power of 2 and keeps both
|
||||
* height and width larger than reqWidth and reqHeight.
|
||||
* @return The largest inSampleSize value that is a power of 2 and keeps both height and width larger than reqWidth
|
||||
* and 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.
|
||||
* Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
|
||||
* Rotate bitmap according to EXIF orientation. Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
|
||||
*
|
||||
* @param bitmap Bitmap to be rotated
|
||||
* @param storagePath Path to source file of bitmap. Needed for EXIF information.
|
||||
* @return correctly EXIF-rotated bitmap
|
||||
|
@ -201,167 +194,115 @@ public final class BitmapUtils {
|
|||
return resultBitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL values to a RGB Color.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
public static Color usernameToColor(String name) throws NoSuchAlgorithmException {
|
||||
String hash = name.toLowerCase(Locale.ROOT);
|
||||
|
||||
if (l < 0.0f || l > 100.0f) {
|
||||
String message = "Color parameter outside of expected range - Luminance";
|
||||
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}")) {
|
||||
// already a md5 hash?
|
||||
if (!hash.matches("([0-9a-f]{4}-?){8}$")) {
|
||||
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++) {
|
||||
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
|
||||
// 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]);
|
||||
// adds up all results
|
||||
for (int value : result) {
|
||||
finalInt += value;
|
||||
}
|
||||
|
||||
// Reduce values bigger than rgb requirements
|
||||
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;
|
||||
// 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
double r = rUntrimmed / 255;
|
||||
double g = gUntrimmed / 255;
|
||||
double b = bUntrimmed / 255;
|
||||
@SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
|
||||
private static Color[] mixPalette(int steps, Color color1, Color color2) {
|
||||
Color[] palette = new Color[steps];
|
||||
palette[0] = color1;
|
||||
|
||||
double max = Math.max(r, Math.max(g, b));
|
||||
double min = Math.min(r, Math.min(g, b));
|
||||
double h = (max + min) / 2;
|
||||
double s;
|
||||
double l = (max + min) / 2;
|
||||
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);
|
||||
|
||||
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;
|
||||
palette[i] = new Color(r, g, b);
|
||||
}
|
||||
|
||||
double[] hsl = new double[]{0.0, 0.0, 0.0};
|
||||
hsl[INDEX_HUE] = h;
|
||||
hsl[INDEX_SATURATION] = s;
|
||||
hsl[INDEX_LUMINATION] = l;
|
||||
return palette;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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
|
||||
* the display metrics of the resources.
|
||||
* Returns a new circular bitmap drawable by creating it from a bitmap, setting initial target density based on the
|
||||
* display metrics of the resources.
|
||||
*
|
||||
* @param resources the resources for initial target density
|
||||
* @param bitmap the original bitmap
|
||||
|
|