Added settings for generated icons saturation and brightness

This commit is contained in:
M66B 2019-10-13 11:14:06 +02:00
parent 89041dc5ef
commit ec75c1f8cb
6 changed files with 232 additions and 62 deletions

View File

@ -27,13 +27,6 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@ -168,54 +161,20 @@ public class ContactInfo {
boolean identicon = false;
if (info.bitmap == null) {
int dp = Helper.dp2pixels(context, 96);
boolean dark = Helper.isDarkTheme(context);
boolean generated = prefs.getBoolean("generated_icons", true);
if (generated) {
boolean identicons = prefs.getBoolean("identicons", false);
if (identicons) {
identicon = true;
info.bitmap = ImageHelper.generateIdenticon(key, dp, 5, dark);
info.bitmap = ImageHelper.generateIdenticon(key, dp, 5, context);
} else
info.bitmap = ImageHelper.generateLetterIcon(key, dp, dark);
info.bitmap = ImageHelper.generateLetterIcon(key, dp, context);
}
}
boolean circular = prefs.getBoolean("circular", true);
if (info.bitmap != null) {
int w = info.bitmap.getWidth();
int h = info.bitmap.getHeight();
Rect source;
if (w > h) {
int off = (w - h) / 2;
source = new Rect(off, 0, w - off, h);
} else if (w < h) {
int off = (h - w) / 2;
source = new Rect(0, off, w, h - off);
} else
source = new Rect(0, 0, w, h);
Rect dest = new Rect(0, 0, source.width(), source.height());
Bitmap round = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(round);
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(Color.GRAY);
if (circular && !identicon)
canvas.drawOval(new RectF(dest), paint);
else {
float radius = Helper.dp2pixels(context, 3);
canvas.drawRoundRect(new RectF(dest), radius, radius, paint);
}
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(info.bitmap, source, dest, paint);
info.bitmap.recycle();
info.bitmap = round;
}
info.bitmap = ImageHelper.makeCircular(info.bitmap,
circular && !identicon ? null : Helper.dp2pixels(context, 3));
if (info.displayName == null)
info.displayName = address.getPersonal();

View File

@ -40,7 +40,7 @@ public class FragmentOptions extends FragmentBase {
static String[] OPTIONS_RESTART = new String[]{
"subscriptions",
"startup", "cards", "date", "threading", "indentation", "highlight_unread",
"avatars", "generated_icons", "identicons", "circular",
"avatars", "generated_icons", "identicons", "circular", "saturation", "brightness",
"name_email", "distinguish_contacts", "authentication",
"subject_top", "subject_italic", "subject_ellipsize",
"flags", "flags_background", "preview", "preview_italic",

View File

@ -20,7 +20,9 @@ package eu.faircode.email;
*/
import android.app.Dialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
@ -31,7 +33,9 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.Toast;
@ -54,6 +58,11 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
private SwitchCompat swGeneratedIcons;
private SwitchCompat swIdenticons;
private SwitchCompat swCircular;
private ImageView ivRed;
private ImageView ivGreen;
private ImageView ivBlue;
private SeekBar sbSaturation;
private SeekBar sbBrightness;
private SwitchCompat swNameEmail;
private SwitchCompat swDistinguishContacts;
private SwitchCompat swAuthentication;
@ -77,7 +86,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
private final static String[] RESET_OPTIONS = new String[]{
"theme", "startup", "cards", "date", "threading", "indentation", "highlight_unread",
"avatars", "generated_icons", "identicons", "circular", "name_email", "distinguish_contacts", "authentication",
"avatars", "generated_icons", "identicons", "circular", "saturation", "brightness",
"name_email", "distinguish_contacts", "authentication",
"subject_top", "subject_italic", "subject_ellipsize",
"flags", "flags_background", "preview", "preview_italic", "addresses", "attachments_alt",
"contrast", "monospaced", "text_color",
@ -105,6 +115,11 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swGeneratedIcons = view.findViewById(R.id.swGeneratedIcons);
swIdenticons = view.findViewById(R.id.swIdenticons);
swCircular = view.findViewById(R.id.swCircular);
ivRed = view.findViewById(R.id.ivRed);
ivGreen = view.findViewById(R.id.ivGreen);
ivBlue = view.findViewById(R.id.ivBlue);
sbSaturation = view.findViewById(R.id.sbSaturation);
sbBrightness = view.findViewById(R.id.sbBrightness);
swNameEmail = view.findViewById(R.id.swNameEmail);
swDistinguishContacts = view.findViewById(R.id.swDistinguishContacts);
swAuthentication = view.findViewById(R.id.swAuthentication);
@ -216,10 +231,49 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("circular", checked).apply();
updateColor();
ContactInfo.clearCache();
}
});
sbSaturation.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
prefs.edit().putInt("saturation", progress).apply();
updateColor();
ContactInfo.clearCache();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Do nothing
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// Do nothing
}
});
sbBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
prefs.edit().putInt("brightness", progress).apply();
updateColor();
ContactInfo.clearCache();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Do nothing
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// Do nothing
}
});
swNameEmail.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@ -426,6 +480,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swIdenticons.setChecked(prefs.getBoolean("identicons", false));
swIdenticons.setEnabled(swGeneratedIcons.isChecked());
swCircular.setChecked(prefs.getBoolean("circular", true));
sbSaturation.setProgress(prefs.getInt("saturation", 100));
sbBrightness.setProgress(prefs.getInt("brightness", 100));
swNameEmail.setChecked(prefs.getBoolean("name_email", false));
swDistinguishContacts.setChecked(prefs.getBoolean("distinguish_contacts", false));
swAuthentication.setChecked(prefs.getBoolean("authentication", true));
@ -454,6 +510,29 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swImagesInline.setChecked(prefs.getBoolean("inline_images", false));
swSeekbar.setChecked(prefs.getBoolean("seekbar", false));
swActionbar.setChecked(prefs.getBoolean("actionbar", true));
updateColor();
}
private void updateColor() {
Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean circular = prefs.getBoolean("circular", true);
int size = Helper.dp2pixels(context, 36);
Integer radius = (circular ? null : Helper.dp2pixels(context, 3));
Bitmap red = ImageHelper.generateLetterIcon(0f, "A", size, context);
Bitmap green = ImageHelper.generateLetterIcon(120f, "B", size, context);
Bitmap blue = ImageHelper.generateLetterIcon(240f, "C", size, context);
red = ImageHelper.makeCircular(red, radius);
green = ImageHelper.makeCircular(green, radius);
blue = ImageHelper.makeCircular(blue, radius);
ivRed.setImageBitmap(red);
ivGreen.setImageBitmap(green);
ivBlue.setImageBitmap(blue);
}
public static class FragmentDialogTheme extends FragmentDialogBase {

View File

@ -28,6 +28,8 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageDecoder;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
@ -64,10 +66,17 @@ class ImageHelper {
private static final ExecutorService executor =
Helper.getBackgroundExecutor(1, "image");
static Bitmap generateIdenticon(@NonNull String email, int size, int pixels, boolean dark) {
static Bitmap generateIdenticon(@NonNull String email, int size, int pixels, Context context) {
byte[] hash = getHash(email);
int color = Color.HSVToColor(127, new float[]{Math.abs(email.hashCode()) % 360, 1, 1});
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int saturation = prefs.getInt("saturation", 100);
int brightness = prefs.getInt("brightness", 100);
int color = Color.HSVToColor(new float[]{
Math.abs(email.hashCode()) % 360,
saturation / 100f,
brightness / 100f});
Paint paint = new Paint();
paint.setColor(color);
@ -91,34 +100,43 @@ class ImageHelper {
return bitmap;
}
static Bitmap generateLetterIcon(@NonNull String email, int size, boolean dark) {
String text = null;
static Bitmap generateLetterIcon(@NonNull String email, int size, Context context) {
String letter = null;
for (int i = 0; i < email.length(); i++) {
char kar = email.charAt(i);
if (Character.isAlphabetic(kar)) {
text = email.substring(i, i + 1).toUpperCase();
letter = email.substring(i, i + 1).toUpperCase();
break;
}
}
if (text == null)
if (letter == null)
return null;
int color = Color.HSVToColor(127, new float[]{Math.abs(email.hashCode()) % 360, 1, 1});
float h = Math.abs(email.hashCode()) % 360f;
return generateLetterIcon(h, letter, size, context);
}
static Bitmap generateLetterIcon(float h, String letter, int size, Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
float s = prefs.getInt("saturation", 100) / 100f;
float v = prefs.getInt("brightness", 100) / 100f;
int bg = Color.HSVToColor(new float[]{h, s, v});
double lum = ColorUtils.calculateLuminance(bg);
int fg = Color.HSVToColor(new float[]{0, 0, lum < 0.5 ? v : 0});
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(color);
double lum = ColorUtils.calculateLuminance(color);
canvas.drawColor(bg);
Paint paint = new Paint();
paint.setColor(lum < 0.5 ? Color.WHITE : Color.BLACK);
paint.setColor(fg);
paint.setTextSize(size / 2f);
paint.setTypeface(Typeface.DEFAULT_BOLD);
canvas.drawText(
text,
size / 2f - paint.measureText(text) / 2,
canvas.drawText(letter,
size / 2f - paint.measureText(letter) / 2,
size / 2f - (paint.descent() + paint.ascent()) / 2, paint);
return bitmap;
@ -132,6 +150,43 @@ class ImageHelper {
}
}
static Bitmap makeCircular(Bitmap bitmap, Integer radius) {
if (bitmap == null)
return null;
int w = bitmap.getWidth();
int h = bitmap.getHeight();
Rect source;
if (w > h) {
int off = (w - h) / 2;
source = new Rect(off, 0, w - off, h);
} else if (w < h) {
int off = (h - w) / 2;
source = new Rect(0, off, w, h - off);
} else
source = new Rect(0, 0, w, h);
Rect dest = new Rect(0, 0, source.width(), source.height());
Bitmap round = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(round);
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(Color.GRAY);
if (radius == null)
canvas.drawOval(new RectF(dest), paint); // round
else
canvas.drawRoundRect(new RectF(dest), radius, radius, paint); // rounded
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, source, dest, paint);
bitmap.recycle();
return round;
}
static Drawable decodeImage(final Context context, final long id, String source, boolean show, final TextView view) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean compact = prefs.getBoolean("compact", false);

View File

@ -187,6 +187,81 @@
app:layout_constraintTop_toBottomOf="@id/swIdenticons"
app:switchPadding="12dp" />
<ImageView
android:id="@+id/ivRed"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginTop="12dp"
android:src="@drawable/baseline_person_24"
app:layout_constraintEnd_toStartOf="@+id/ivGreen"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swCircular" />
<ImageView
android:id="@+id/ivGreen"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginTop="12dp"
android:src="@drawable/baseline_person_24"
app:layout_constraintEnd_toStartOf="@+id/ivBlue"
app:layout_constraintStart_toEndOf="@id/ivRed"
app:layout_constraintTop_toBottomOf="@id/swCircular" />
<ImageView
android:id="@+id/ivBlue"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginTop="12dp"
android:src="@drawable/baseline_person_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivGreen"
app:layout_constraintTop_toBottomOf="@id/swCircular" />
<TextView
android:id="@+id/tvSaturation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="48dp"
android:text="@string/title_advanced_color_saturation"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivRed" />
<SeekBar
android:id="@+id/sbSaturation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:max="100"
android:min="0"
android:progress="50"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSaturation" />
<TextView
android:id="@+id/tvBrightness"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginEnd="48dp"
android:text="@string/title_advanced_color_value"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sbSaturation" />
<SeekBar
android:id="@+id/sbBrightness"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:max="100"
android:min="0"
android:progress="50"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvBrightness" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swNameEmail"
android:layout_width="0dp"
@ -195,7 +270,7 @@
android:text="@string/title_advanced_name_email"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swCircular"
app:layout_constraintTop_toBottomOf="@id/sbBrightness"
app:switchPadding="12dp" />
<TextView

View File

@ -250,6 +250,8 @@
<string name="title_advanced_generated_icons">Show generated icons</string>
<string name="title_advanced_identicons">Show identicons</string>
<string name="title_advanced_circular">Show round icons</string>
<string name="title_advanced_color_saturation">Saturation</string>
<string name="title_advanced_color_value">Brightness</string>
<string name="title_advanced_name_email">Show names and email addresses</string>
<string name="title_advanced_authentication">Show a warning when the receiving server could not authenticate the message</string>
<string name="title_advanced_subject_top">Show subject above sender</string>