I think I fixed it.. The image scaling issue

Added prototype for media descriptions
This commit is contained in:
Snowyfox 2022-06-01 20:44:06 -04:00
parent e7aeee7ba9
commit 39cb81d758
7 changed files with 166 additions and 109 deletions

View File

@ -130,7 +130,9 @@ ComposeWindow extends JFrame {
if (a.id != null) continue;
// Assume it had already been uploaded.
api.uploadFile(a.uploadee, new RequestListener() {
api.uploadFile(
a.uploadee, a.description,
new RequestListener() {
public void
connectionFailed(IOException eIo)
@ -561,9 +563,6 @@ implements ActionListener {
private JPanel
selections;
private ButtonGroup
selectionsGroup;
private JToggleButton
attachment1,
attachment2,
@ -621,29 +620,29 @@ implements ActionListener {
if (working.size() > 3)
{
attachment4.doClick();
Image i = working.get(3).image;
attachment4.setIcon(new ImageIcon(i));
}
else if (working.size() > 2)
{
attachment3.doClick();
Image i = working.get(2).image;
attachment3.setIcon(new ImageIcon(i));
}
else if (working.size() > 1)
{
attachment2.doClick();
Image i = working.get(1).image;
attachment2.setIcon(new ImageIcon(i));
}
else if (working.size() > 0)
{
attachment1.doClick();
Image i = working.get(0).image;
attachment1.setIcon(new ImageIcon(i));
}
else selectionsGroup.clearSelection();
attachment4.setSelected(working.size() > 3);
attachment3.setSelected(working.size() > 2);
attachment2.setSelected(working.size() > 1);
attachment1.setSelected(working.size() > 0);
int bw = sz.width;
int hgap = 4;
@ -661,11 +660,6 @@ implements ActionListener {
{
Object src = eA.getSource();
if (false)
{
// Clicked on filled attachment button.
}
if (src == add)
{
int r = chooser.showOpenDialog(this);
@ -697,40 +691,93 @@ implements ActionListener {
if (src == delete)
{
Object sm = selectionsGroup.getSelection();
if (sm == attachment1.getModel())
if (attachment1.isSelected())
{
assert working.size() > 0;
working.remove(0);
updateButtons();
}
if (sm == attachment2.getModel())
if (attachment2.isSelected())
{
assert working.size() > 1;
working.remove(1);
updateButtons();
}
if (sm == attachment3.getModel())
if (attachment3.isSelected())
{
assert working.size() > 2;
working.remove(2);
updateButtons();
}
if (sm == attachment4.getModel())
if (attachment4.isSelected())
{
assert working.size() > 3;
working.remove(3);
updateButtons();
}
updateButtons();
return;
}
if (src != add && selections.isAncestorOf((Component)src))
{
int iCurr = getSelectedIndex();
int iNext = getIndex(src);
System.err.println(iCurr + ":" + iNext);
assert iNext != 0;
if (iCurr == iNext) {
save();
return;
}
if (iCurr != 0) save();
load(iNext);
}
if (src == revert)
{
return;
assert getSelectedIndex() != 0;
// Should the controls be disabled until
// there is an attachment?
load(getSelectedIndex());
}
}
private int
getIndex(Object src)
{
if (src == attachment4) return 4;
if (src == attachment3) return 3;
if (src == attachment2) return 2;
if (src == attachment1) return 1;
return 0;
}
private int
getSelectedIndex()
{
if (attachment4.isSelected()) return 4;
if (attachment3.isSelected()) return 3;
if (attachment2.isSelected()) return 2;
if (attachment1.isSelected()) return 1;
return 0;
}
private void
save()
{
int index = getSelectedIndex();
assert index != 0;
Attachment a = working.get(index - 1);
a.description = this.description.getText();
}
private void
load(int index)
{
assert index > 0;
assert working.size() >= index;
Attachment a = working.get(index - 1);
this.description.setText(a.description);
}
// ---%-@-%---
AttachmentsComponent(ComposeWindow primaire)
@ -762,17 +809,20 @@ implements ActionListener {
attachment2.setMargin(add.getMargin());
attachment3.setMargin(add.getMargin());
attachment4.setMargin(add.getMargin());
attachment1.addActionListener(this);
attachment2.addActionListener(this);
attachment3.addActionListener(this);
attachment4.addActionListener(this);
selections = new JPanel();
selections.setOpaque(false);
selections.setLayout(new GridLayout(1, 0, 4, 0));
working = new ArrayList<Attachment>();
selectionsGroup = new ButtonGroup();
ButtonGroup selectionsGroup = new ButtonGroup();
selectionsGroup.add(attachment1);
selectionsGroup.add(attachment2);
selectionsGroup.add(attachment3);
selectionsGroup.add(attachment4);
// Have to add selection listener to button group
updateButtons();
JButton del = new JButton("D");

View File

@ -2,6 +2,11 @@
import javax.swing.ImageIcon;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.MediaTracker;
import java.awt.Graphics;
import java.awt.Component;
import java.awt.image.ImageObserver;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.net.MalformedURLException;

View File

@ -435,9 +435,10 @@ MastodonApi {
}
public void
uploadFile(File file, RequestListener handler)
uploadFile(File file, String alt, RequestListener handler)
{
assert file != null;
assert alt != null;
assert file.canRead();
String bct =
@ -496,10 +497,12 @@ MastodonApi {
String url = instanceUrl + "/api/v1/media/";
try
{
URL endpoint = new URL(url);
String s1 = "?description=" + encode(alt);
URL endpoint = new URL(url + s1);
HttpURLConnection conn = cast(endpoint.openConnection());
String s1 = "Bearer " + token;
conn.setRequestProperty("Authorization", s1);
String s2 = "Bearer " + token;
conn.setRequestProperty("Authorization", s2);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setFixedLengthStreamingMode(contentLength);

View File

@ -429,54 +429,7 @@ implements ActionListener {
setAuthorId(String n) { authorId.setText(n); }
public void
setAuthorAvatar(Image n)
{
try {
MediaTracker mt = new MediaTracker(this);
mt.addImage(n, 0);
mt.waitForID(0);
}
catch (InterruptedException eIt) {
assert false;
}
/*
* Blocks until the image loads. We have to do this because
* there is an unfixed JDK bug in Image#getScaledInstance
* (which RoundButton uses) where when you pass it an AWT
* image, it incorrectly sets the ColorModel's transfer type,
* causing a ClassCastException, and returns a valid image
* with blank data. It seems using loaded images does not
* replicate the issue, which would explain why the bug
* hasn't been found and fixed..
*
* For future readers wondering. JDK 11, up to JDK 15.
* java.awt.image.ColorModel#getAlpha(:845), who was called
* sun.awt.image.ImageRepresentation#convertToRGB, who was
* called by AreaAveragingScaleFilter trying to #setPixels.
* Cause RoundButton was using Image.SCALE_SMOOTH.
*
* I guess it kind of makes sense, why would I try to get a
* scaled instance of an image that hadn't finished loading.
* An intelligent class would render the awt.Image as-is until
* it has completely loaded, then call #getScaledInstance
* instead. I won't have RoundButton do that for now, as
* my other classes like RichTextPane3 might have this too.
*
* If you asked me how I'd generalise the solution. My
* expectation had been that #getScaledInstance waits for
* the image too, returning a null image until it's done.
* So I'd put a #getScaledInstance in ImageApi, which does
* this, using MediaTracker. That assumes what I'm doing
* right now definitely works.
*/
/*
* () It doesn't work. I might be forced to use
* BufferedImage with ImageIO. Or have
* ImageApi#getScaledInstance spam until it works.
*/
profile.setImage(n);
}
setAuthorAvatar(Image n) { profile.setImage(n); }
public void
setDate(String n) { date.setText(n); }

View File

@ -8,6 +8,8 @@ import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.Component;
import java.awt.image.BufferedImage;
import java.awt.geom.Ellipse2D;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
@ -255,6 +257,7 @@ implements KeyListener, MouseListener, FocusListener {
// - -%- -
private Image
copy,
scaled;
private int
@ -267,22 +270,78 @@ implements KeyListener, MouseListener, FocusListener {
selectedOverlay,
disabledOverlay;
private static Shape
roundClip;
// ---%-@-%---
public void
setImage(Image n)
{
image = n;
copy = null;
scaled = null;
if (image != null)
{
image.flush();
prepareImage(image, this);
}
}
// - -%- -
public boolean
imageUpdate(Image i, int f, int x, int y, int w, int h)
imageUpdate(Image img, int f, int x, int y, int w, int h)
{
if ((f | WIDTH) != 0) repaint();
return super.imageUpdate(i, f, x, y, w, h);
// AbstractButton overrode this to refuse updates for
// images that aren't the button's icon. We don't use
// the icon, so we're overriding it back. Also, we have
// some async work to do regarding the images.
if ((f & (ABORT|ERROR)) != 0) return false;
boolean all = (f & ALLBITS) != 0;
boolean frame = (f & FRAMEBITS) != 0;
boolean some = (f & SOMEBITS) != 0;
if (frame && img != this.image) return false;
if (img == this.image && (all || frame))
{
int ow = img.getWidth(null);
int oh = img.getHeight(null);
if (copy == null)
{
int type = BufferedImage.TYPE_INT_ARGB;
copy = new BufferedImage(ow, oh, type);
}
Graphics g = copy.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
int algo = Image.SCALE_SMOOTH;
Rectangle b = roundClip.getBounds();
int sw = ow > oh ? -1 : b.width;
int sh = oh > ow ? -1 : b.height;
scaled = copy.getScaledInstance(sw, sh, algo);
/*
* We create a scaled instance from a BufferedImage copy
* rather than this.image directly, to avoid a ClassCast
* Exception bug in the JDK, where ColorModel was
* incorrectly casting an int array of input data into
* a byte array. I'm not sure why that bug exists nor
* why they haven't noticed it, but.
*/
repaint();
}
if (img == scaled && (some || all))
{
repaint();
}
return all ? false : true;
}
protected void
@ -294,34 +353,13 @@ implements KeyListener, MouseListener, FocusListener {
if (isFocusOwner())
g.drawImage(selectedOverlay, 0, 0, this);
if (image == null) return;
int ow = image.getWidth(this);
int oh = image.getHeight(this);
int nx = 6;
int ny = 6;
int nw = button.getWidth(this) - 12;
int nh = button.getHeight(this) - 12;
Shape defaultClip, roundClip;
defaultClip = g.getClip();
roundClip = new Ellipse2D.Float(nx, ny, nw, nh);
if (scaled == null)
{
int sw = ow > oh ? -1 : nw;
int sh = oh > ow ? -1 : nh;
// We do not defend against unloaded images. There is
// a bug on those anyways, see comment in PostWindow.
scaled = image.getScaledInstance(
sw, sh,
Image.SCALE_SMOOTH
);
}
// () Be careful to be idempotent when calling
// async methods like #drawImage.
if (scaled == null) return;
Rectangle b = roundClip.getBounds();
Shape defaultClip = g.getClip();
g.setClip(roundClip);
g.drawImage(scaled, nx, ny, getParent());
g.drawImage(scaled, b.x, b.y, this);
getParent().repaint();
// I don't know why, but when we repaint ourselves, our
// parent doesn't repaint, so nothing seems to happen.
g.setClip(defaultClip);
@ -383,8 +421,8 @@ implements KeyListener, MouseListener, FocusListener {
setFocusable(true);
setOpaque(false);
int w = button.getWidth(this);
int h = button.getHeight(this);
int w = button.getWidth(null);
int h = button.getHeight(null);
setPreferredSize(new Dimension(w, h));
this.addKeyListener(this);
@ -405,6 +443,14 @@ implements KeyListener, MouseListener, FocusListener {
button = new ImageIcon(u1).getImage();
disabledOverlay = new ImageIcon(u2).getImage();
selectedOverlay = new ImageIcon(u3).getImage();
int radius = 6;
roundClip = new Ellipse2D.Float(
radius,
radius,
button.getWidth(null) - (2 * radius),
button.getHeight(null) - (2 * radius)
);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB