/*
* Kontalk Java client
* Copyright (C) 2016 Kontalk Devteam <devteam@kontalk.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.view;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang.ObjectUtils;
import org.kontalk.model.Avatar;
import org.kontalk.model.chat.Chat;
import org.kontalk.model.Contact;
import org.kontalk.model.chat.SingleChat;
import org.kontalk.util.MediaUtils;
import org.kontalk.util.Tr;
/**
* Static functions for loading avatar pictures.
* @author Alexander Bikadorov {@literal <bikaejkb@mail.tu-berlin.de>}
*/
final class AvatarLoader {
private static final Color LETTER_COLOR = new Color(255, 255, 255);
private static final Color FALLBACK_COLOR = new Color(220, 220, 220);
private static final Color GROUP_COLOR = new Color(160, 160, 160);
private static final Map<Item, AvatarImg> CACHE = new HashMap<>();
static AvatarImg load(Chat chat, int size) {
return load(new Item(chat, size));
}
static AvatarImg load(Contact contact, int size) {
return load(new Item(contact, size));
}
static AvatarImg loadFallback(int size) {
return load(new Item(size));
}
private AvatarLoader() {}
private static AvatarImg load(Item item) {
if (!CACHE.containsKey(item)) {
CACHE.put(item, item.createImage());
}
return CACHE.get(item);
}
static class AvatarImg {
final BufferedImage image;
final boolean isFallback;
AvatarImg(BufferedImage img, boolean fallback) {
this.image = img;
this.isFallback = fallback;
}
}
private static class Item {
private final int mSize;
private final Avatar mAvatar;
private final String mLetter;
private final Color mColor;
private Item(int size) {
mSize = size;
mAvatar = null;
mLetter = fallbackLetter();
mColor = FALLBACK_COLOR;
}
private Item(Contact contact, int size) {
mSize = size;
mAvatar = contact.getDisplayAvatar().orElse(null);
if (mAvatar == null) {
String name = contact.getName();
mLetter = labelToLetter(name);
if (contact.isDeleted()) {
mColor = FALLBACK_COLOR;
} else {
int colorcode = hash(contact.getID());
int hue = Math.abs(colorcode) % 360;
mColor = Color.getHSBColor(hue / 360.0f, 0.9f, 1);
}
} else {
// not used
mLetter = "";
mColor = new Color(0);
}
}
private Item(Chat chat, int size) {
mSize = size;
String l;
if (chat.isGroupChat()) {
// nice to have: group picture
mAvatar = null;
// or use number of contacts here?
l = chat.getSubject();
mColor = GROUP_COLOR;
} else {
Contact c = ((SingleChat) chat).getMember().getContact();
Item i = new Item(c, size);
mAvatar = i.mAvatar;
l = i.mLetter;
mColor = i.mColor;
}
mLetter = labelToLetter(l);
}
private String labelToLetter(String label) {
return label.length() >= 1 ?
label.substring(0, 1).toUpperCase() :
fallbackLetter();
}
private AvatarImg createImage() {
if (mAvatar != null) {
BufferedImage img = mAvatar.loadImage().orElse(null);
if (img != null) {
return new AvatarImg(
MediaUtils.scale(img, mSize, mSize),
false);
}
}
return fallback(mLetter, mColor, mSize);
}
@Override
public final boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Item))
return false;
Item oItem = (Item) o;
return mSize == oItem.mSize &&
ObjectUtils.equals(mAvatar, oItem.mAvatar) &&
mLetter.equals(oItem.mLetter) &&
mColor.equals(oItem.mColor);
}
@Override
public int hashCode() {
return Objects.hash(mSize, mAvatar, mLetter, mColor);
}
}
private static String fallbackLetter() {
return Tr.tr("?");
}
// uniform hash
// Source: https://stackoverflow.com/a/12996028
private static int hash(int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x);
return x;
}
private static AvatarImg fallback(String letter, Color color, int size) {
BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
graphics.setColor(color);
graphics.fillRect(0, 0, size, size);
graphics.setFont(new Font(Font.DIALOG, Font.PLAIN, size));
graphics.setColor(LETTER_COLOR);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
FontMetrics fm = graphics.getFontMetrics();
Rectangle2D r = fm.getStringBounds(letter, graphics);
graphics.drawString(letter,
(size - (int) r.getWidth()) / 2.0f,
// adjust to font baseline
// Note: not centered for letters with descent (drawing under
// the baseline), dont know how to get that
(size - (int) r.getHeight()) / 2.0f + fm.getAscent());
return new AvatarImg(img, true);
}
}