Show client avatars and let the user set their own

Avatars are fetched from the server's file repository as /avatar_<id>,
verified against client_flag_avatar and cached on disk; animated GIFs play in
the info panel. Self > Set avatar... uploads or removes our own picture within
the server's size limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 19:03:00 +00:00
parent dbb425403a
commit 2a73900dcc
20 changed files with 886 additions and 5 deletions

View File

@@ -0,0 +1,65 @@
package com.ts3client.ui;
import javax.swing.JComponent;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
/**
* Paints an avatar scaled to fit, keeping its aspect ratio. The image is a Toolkit
* image drawn with this component as its observer, so an animated GIF keeps playing:
* each decoded frame triggers a repaint.
*/
final class AvatarView extends JComponent {
private final int boxSize;
private Image image;
private int imageWidth = -1;
private int imageHeight = -1;
AvatarView(int boxSize) {
this.boxSize = boxSize;
setPreferredSize(new Dimension(boxSize, boxSize));
setMinimumSize(getPreferredSize());
}
/** @param data the encoded image, or {@code null} to show nothing */
void setImage(byte[] data) {
if (image != null) image.flush();
image = data == null ? null : Toolkit.getDefaultToolkit().createImage(data);
imageWidth = imageHeight = -1;
repaint();
}
boolean hasImage() {
return image != null;
}
@Override
protected void paintComponent(Graphics g) {
if (image == null) return;
if (imageWidth < 0 || imageHeight < 0) {
// Unknown until the header is decoded; the observer repaints once it is.
imageWidth = image.getWidth(this);
imageHeight = image.getHeight(this);
if (imageWidth < 0 || imageHeight < 0) return;
}
double scale = Math.min(1.0, Math.min((double) getWidth() / imageWidth, (double) getHeight() / imageHeight));
int w = Math.max(1, (int) Math.round(imageWidth * scale));
int h = Math.max(1, (int) Math.round(imageHeight * scale));
Graphics2D g2 = (Graphics2D) g.create();
try {
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, (getWidth() - w) / 2, (getHeight() - h) / 2, w, h, this);
} finally {
g2.dispose();
}
}
int boxSize() {
return boxSize;
}
}