Last active
December 31, 2018 04:11
-
-
Save seraphy/f428874e84a2378293a9a889d2d5f388 to your computer and use it in GitHub Desktop.
SwingのJava9以降で、Hi-DPI環境下で暗黙で適用されるスケールを明示的に解除する実験例。 および、スケールされている環境下でロードしたイメージの実ピクセルサイズでの描画を行う実験例。およびスケール環境下でのマウス座標の確認。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jp.seraphyware.java10learn; | |
import java.awt.BasicStroke; | |
import java.awt.BorderLayout; | |
import java.awt.Color; | |
import java.awt.Container; | |
import java.awt.Font; | |
import java.awt.FontMetrics; | |
import java.awt.Graphics; | |
import java.awt.Graphics2D; | |
import java.awt.GraphicsConfiguration; | |
import java.awt.Image; | |
import java.awt.Point; | |
import java.awt.RenderingHints; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.MouseEvent; | |
import java.awt.event.MouseMotionListener; | |
import java.awt.event.WindowAdapter; | |
import java.awt.event.WindowEvent; | |
import java.awt.font.LineMetrics; | |
import java.awt.geom.AffineTransform; | |
import java.awt.geom.Line2D; | |
import java.awt.image.BufferedImage; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.UncheckedIOException; | |
import java.util.Base64; | |
import java.util.Base64.Decoder; | |
import javax.imageio.ImageIO; | |
import javax.swing.AbstractAction; | |
import javax.swing.JCheckBoxMenuItem; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JMenu; | |
import javax.swing.JMenuBar; | |
import javax.swing.JMenuItem; | |
import javax.swing.JPanel; | |
import javax.swing.SwingUtilities; | |
import javax.swing.UIManager; | |
/** | |
* SwingのJava9以降で、Hi-DPI環境下では自動的にスケールがかかるようになっている。 | |
* このスケールを明示的に解除して描画させるための実験。 | |
* | |
* スケールはGraphis2Dに既定でかかっているので、これを明示的に解除すればよい。 | |
* ただし、メニューバーのオフセットなど、transformがかかっている可能性があるので解除は注意のこと。 | |
* | |
* スケールを解除せず、画像だけスケールさせたくない場合は、、 | |
* 画像サイズをスケールで割った小さなサイズを指定すればスケールで相殺されて、実ピクセルサイズで描画されることになる。 | |
* | |
* https://stackoverflow.com/questions/43057457/jdk-9-high-dpi-disable-for-specific-panel | |
* https://bugs.openjdk.java.net/browse/JDK-8189416 | |
* https://superuser.com/questions/988379/how-do-i-run-java-apps-upscaled-on-a-high-dpi-display | |
* | |
* System Propertyで、 | |
* -Dsun.java2d.uiScale=2.5 | |
* のように明示的にスケールを指定して実験することができる。 | |
*/ | |
public class SwingScaleExample extends JFrame { | |
public SwingScaleExample() { | |
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); | |
addWindowListener(new WindowAdapter() { | |
@Override | |
public void windowClosing(WindowEvent e) { | |
onClosing(); | |
} | |
}); | |
try { | |
initLayout(); | |
} catch (Exception ex) { | |
dispose(); | |
throw ex; | |
} | |
} | |
protected void onClosing() { | |
dispose(); | |
} | |
private MyPanel myPanel = new MyPanel(); | |
private void initLayout() { | |
setTitle(getClass().getSimpleName()); | |
setSize(400, 200); | |
Container container = getContentPane(); | |
container.setLayout(new BorderLayout()); | |
container.add(myPanel, BorderLayout.CENTER); | |
JMenuBar menubar = new JMenuBar(); | |
JMenu menuFile = new JMenu("File"); | |
JMenuItem menuFileClose = new JMenuItem(new AbstractAction("Close") { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
onClosing(); | |
} | |
}); | |
menuFile.add(menuFileClose); | |
menubar.add(menuFile); | |
JMenu menuView = new JMenu("View"); | |
JCheckBoxMenuItem menuViewScaleOff = new JCheckBoxMenuItem(new AbstractAction("Scale Off") { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) e.getSource(); | |
myPanel.setScaleOff(menuItem.isSelected()); | |
} | |
}); | |
menuView.add(menuViewScaleOff); | |
menubar.add(menuView); | |
setJMenuBar(menubar); | |
Image img = readImageFromBase64("box.png.asc"); | |
System.out.println("img=" + img); | |
myPanel.setImage(img); | |
JLabel status = new JLabel(); | |
container.add(status, BorderLayout.NORTH); | |
// スケールされた環境でのマウス座標のとりかた | |
myPanel.addMouseMotionListener(new MouseMotionListener() { | |
private int marker; | |
@Override | |
public void mouseMoved(MouseEvent e) { | |
print("move", e); | |
} | |
@Override | |
public void mouseDragged(MouseEvent e) { | |
print("drag", e); | |
} | |
private void print(String prefix, MouseEvent e) { | |
// デバイスのデフォルトのスケールを取得する | |
GraphicsConfiguration gconf = getGraphicsConfiguration(); | |
AffineTransform at = gconf.getDefaultTransform(); | |
double scalex = at.getScaleX(); | |
double scaley = at.getScaleY(); | |
// マウス座標 | |
Point pt = e.getPoint(); | |
double x = pt.getX(); | |
double y = pt.getX(); | |
// デバイス上の実ピクセル位置 | |
int real_x = (int) (x * scalex); | |
int real_y = (int) (x * scaley); | |
// 通知回数をみるためのカウンタ | |
marker++; | |
status.setText((marker % 100) + " " + prefix + " x=" + x + ", y=" + y + ", scalex=" + scalex | |
+ ", real_x=" + real_x + ", real_y=" + real_y); | |
} | |
}); | |
} | |
/** | |
* base64で格納されているリソースからイメージを構築する | |
* (gistにアップロードする都合上、バイナリを避けているだけ) | |
* @param name | |
* @return | |
*/ | |
private BufferedImage readImageFromBase64(String name) { | |
BufferedImage img = null; | |
try (InputStream is = getClass().getResourceAsStream(name)) { | |
if (is != null) { | |
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
byte[] buf = new byte[4096]; | |
for (;;) { | |
int rd = is.read(buf); | |
if (rd < 0) { | |
break; | |
} | |
bos.write(buf, 0, rd); | |
} | |
Decoder decoder = Base64.getMimeDecoder(); | |
byte[] data = decoder.decode(bos.toByteArray()); | |
try (InputStream is2 = new ByteArrayInputStream(data)) { | |
img = ImageIO.read(is2); | |
} | |
} | |
} catch (IOException ex) { | |
throw new UncheckedIOException(ex); | |
} | |
return img; | |
} | |
public static void main(String[] args) throws Exception { | |
// システムデフォルトのL&Fに設定 | |
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); | |
SwingUtilities.invokeLater(() -> { | |
SwingScaleExample inst = new SwingScaleExample(); | |
inst.setLocationByPlatform(true); | |
inst.setVisible(true); | |
}); | |
} | |
} | |
/** | |
* 画像表示パネル | |
*/ | |
class MyPanel extends JPanel { | |
/** | |
* スケール解除フラグ | |
*/ | |
private boolean scaleOff; | |
/** | |
* イメージ | |
*/ | |
private Image image; | |
public void setImage(Image image) { | |
this.image = image; | |
repaint(); | |
} | |
public Image getImage() { | |
return image; | |
} | |
public void setScaleOff(boolean scaleOff) { | |
this.scaleOff = scaleOff; | |
repaint(); | |
} | |
public boolean isScaleOff() { | |
return scaleOff; | |
} | |
@Override | |
protected void paintComponent(Graphics g0) { | |
Graphics2D g = (Graphics2D) g0; | |
// サブピクセルのレンダリング (このヒントがあるとなめらかになる) | |
// https://stackoverflow.com/questions/12415640/is-there-any-way-to-draw-lines-with-subpixel-precision-in-swing | |
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); | |
super.paintComponent(g); | |
AffineTransform at = g.getTransform(); | |
double scalex = at.getScaleX(); | |
double scaley = at.getScaleY(); | |
System.out.print("at=" + at); | |
int w = getWidth(); | |
int h = getHeight(); | |
int real_w = (int) (w * scalex); | |
int real_h = (int) (h * scaley); | |
String msg = "virtual w=" + w + ",h=" + h + ", scale x=" + scalex + ", y=" + scaley + ", real w=" + real_w | |
+ ",h=" + real_h; | |
System.out.println(msg); | |
Font font = g.getFont(); | |
FontMetrics fm = g.getFontMetrics(); | |
LineMetrics lm = fm.getLineMetrics(msg, g); | |
float fontHeight = lm.getHeight(); | |
int dw, dh; | |
if (scaleOff) { | |
// 暗黙で適用されているスケールを解除する。 | |
// ※ メニューバー分のオフセットがかかっているので | |
// スケール解除時にオフセットは残すようにする | |
double offsetx = at.getTranslateX(); | |
double offsety = at.getTranslateY(); | |
at.setToTranslation(offsetx, offsety); | |
//at.scale(1, 1); | |
g.setTransform(at); | |
// スケールが解除されるので描画領域を実サイズとする | |
dw = real_w; | |
dh = real_h; | |
} else { | |
// スケールされた状態の描画範囲 | |
dw = w; | |
dh = h; | |
} | |
// 論理10pxごとの矩形と、情報の表示 | |
Color oldColor = g.getColor(); | |
g.setColor(Color.RED); | |
for (int x = 0; x < dw; x += 10) { | |
g.drawLine(x, 0, x, dh); | |
} | |
for (int y = 0; y < dh; y += 10) { | |
g.drawLine(0, y, dw, y); | |
} | |
// スケール環境でのサブピクセルの描画 | |
// (scaleがx2の場合に10pxの範囲に10本の線が見えていればサブピクセルの描画成功) | |
g.setColor(Color.LIGHT_GRAY); | |
g.setStroke(new BasicStroke((float) (1 / scalex))); | |
for (double x = 0; x < 10; x += 2 / scalex) { // 1論理ドットおきのラインをスケールで割った単位 | |
g.draw(new Line2D.Double(x, 0, x, dh)); | |
} | |
for (double y = 0; y < 10; y += 2 / scaley) { // 1論理ドットおきのラインをスケールで割った単位 | |
g.draw(new Line2D.Double(0, y, dw, y)); | |
} | |
g.setColor(Color.BLACK); | |
for (int x = 0; x < dw; x += 10) { | |
if (x % 3 == 0) { | |
g.drawString(Integer.toString(x), x + 5, fontHeight); | |
} | |
} | |
for (int y = 0; y < dh; y += 10) { | |
if (y % 3 == 0) { | |
g.drawString(Integer.toString(y), 5, y + fontHeight); | |
} | |
} | |
g.setColor(Color.BLUE); | |
g.drawString(msg, 30, fontHeight * 2); | |
String msg2 = "font size=" + font.getSize2D() + ", height=" + fontHeight; | |
g.drawString(msg2, 30, fontHeight * 3); | |
g.setColor(oldColor); | |
// 画像(ラスター)のスケール調整の実験 | |
if (image != null) { | |
int img_real_w = image.getWidth(null); | |
int img_real_h = image.getHeight(null); | |
System.out.println("image imgw=" + img_real_w + "px, imgh=" + img_real_h + "px"); | |
//g.drawImage(image, 50, 50, null); // 通常 | |
g.drawImage(image, 50, 50, img_real_w, img_real_h, null); // 通常指定。幅、高さもスケールされる | |
int draw_w, draw_h; | |
if (!scaleOff) { | |
// スケールがかかっている場合の画像表示を、スケール解除して描画するためには、 | |
// 画像サイズをスケールで割って、倍率のかかっていない場合の画像サイズを求める。 | |
draw_w = (int) (img_real_w / scalex); | |
draw_h = (int) (img_real_h / scaley); | |
} else { | |
// スケール解除されているので、実サイズそのままで良い | |
draw_w = img_real_w; | |
draw_h = img_real_h; | |
} | |
g.drawImage(image, 50, 150, draw_w, draw_h, null); // 画像の範囲を明示的にスケール補正して指定 | |
//g.drawImage(image, 50, 150, 50 + draw_w, 150 + draw_h, 0, 0, img_real_w, img_real_h, null); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment