如何在Retina显示屏上的Java Swing中双缓冲而不丢失更高的分辨率?

MiguelMunoz:

我在JLayer子类中使用双缓冲图形来在Java Swing应用程序中实现简单的滑动动画。它在较旧的显示器上可以正常工作,但是当我在Retina显示器上运行时,动画开始时屏幕会失去两倍的分辨率,结束时会恢复原来的分辨率。我不确定如何在动画过程中保持更高的分辨率。

我的动画方法最初看起来像这样:

private void animate() {
  Timer timer = new Timer(frameMillis, null);
  final ActionListener actionListener = (evt) -> { /* omitted for brevity */ };
  timer.addActionListener(actionListener);

  int imageType = BufferedImage.TYPE_INT_ARGB;
  upcomingScreen = new BufferedImage(liveComponent.getWidth(), liveComponent.getHeight(), imageType);
  Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
  liveComponent.paint(graphics2D); // liveComponent is a JComponent
  graphics2D.dispose();

  timer.start();
}

我尝试将图像大小加倍,但这没有帮助。

upcomingScreen = new BufferedImage(liveComponent.getWidth()*2, liveComponent.getHeight()*2, imageType);

为了反映这些变化,我改变了我的绘制代码在LayerUI加倍xLimitwidthheight

public void paint(final Graphics g, final JComponent c) {
  if (isAnimating) {
    int xLimit = (c.getWidth()*2 * frame) / maxFrames;
    int width = c.getWidth()*2;
    int height = c.getHeight()*2;

    g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
    g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
  } else {
    super.paint(g, c);
  }
}

这没有帮助。无论有没有最后的更改,它都绘制相同的内容,这没有任何意义。

这是一个说明问题的类:

/**
 * <p>Created by IntelliJ IDEA.
 * <p>Date: 5/2/20
 * <p>Time: 10:25 AM
 *
 * @author Miguel Mu\u00f1oz
 */
@SuppressWarnings({"HardcodedLineSeparator", "StringConcatenation", "HardCodedStringLiteral", "DuplicatedCode"})
public final class SwipeViewTest extends JPanel {
  public static final String text1 = "Demo of Swipe View.\n\nThe swipe button will toggle between two pages of text. It has a built-in " +
      "special effect, which is a swipe. When you hit the swipe button, it should flip between two pages of text. This worked fine on " +
      "the older displays, but for some reason, on a Retina display, the text briefly switches to low resolution as the swipe proceeds, " +
      "then switches back once it has finished. This code is written for retina displays. I don't know if it will work for the older, " +
      "low resolution displays.\n\nYou can watch it swipe by hitting the space bar or by clicking the swipe button.";
  public static final String text2 = "Demo of Swipe View.\n\nThis is the second page of the swipe-text demo. The change in resolution is " +
      "most easily noticed when watching the line at the top, which doesn't change as the swipe is performed.";
  private final SwipeView<TestView> swipeView;
  private final TestView testView;

  public static void main(String[] args) {
    JFrame frame = new JFrame("SwipeView demo");
    SwipeViewTest comp = new SwipeViewTest();
    comp.install();
    frame.add(comp);
    frame.setLocationByPlatform(true);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setVisible(true);
  }

  private boolean page1 = true;

  private SwipeViewTest() {
    super(new BorderLayout());
    testView = new TestView();
    swipeView = SwipeView.wrap(testView, 1000);
    add(BorderLayout.CENTER, swipeView.getLayer());
  }

  private void install() {
    JButton jButton = new JButton("Swipe");
    jButton.addActionListener(this::doSwipe);
    add(jButton, BorderLayout.PAGE_END);
    AncestorListener ancestorListener = new AncestorListener() {
      @Override
      public void ancestorAdded(final AncestorEvent event) {
        JComponent button = event.getComponent();
        button.requestFocus();
        button.removeAncestorListener(this); // execute only once.
      }

      @Override public void ancestorRemoved(final AncestorEvent event) { }
      @Override public void ancestorMoved(final AncestorEvent event) { }
    };
    jButton.addAncestorListener(ancestorListener);
  }

  private void doSwipe(ActionEvent ignored) {
    swipeView.swipeLeft(this::flipPage);
  }

  private void flipPage() {
    page1 = !page1;
    if (page1) {
      testView.setText(text1);
    } else {
      testView.setText(text2);
    }
  }

  private static class TestView extends JPanel {

    private final JTextArea textArea;

    TestView() {
      super(new BorderLayout());
      textArea = new JTextArea(20, 40);
      JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
      textArea.setLineWrap(true);
      textArea.setWrapStyleWord(true);
      textArea.setEditable(false);
      textArea.setText(text1);
      add(scrollPane, BorderLayout.CENTER);
    }

    private void setText(String text) {
      textArea.setText(text);
    }
  }

  /**
   * SwipeView adds a swipe special effect to a Component. This draws a swipe-right or swipe-left effect on a chosen
   * action. It also optionally supports a repeated action when the mouse is held down.
   * <p>
   * This class is very specific right now, but I hope to generalize it for other special effects later.
   * <p>Created by IntelliJ IDEA.
   * <p>Date: 4/4/18
   * <p>Time: 12:38 AM
   *
   * @author Miguel Mu\u00f1oz
   */
  @SuppressWarnings("MagicNumber")
  public static final class SwipeView<C extends JComponent> extends LayerUI<C> {

    public static <J extends JComponent> SwipeView<J> wrap(J view, int durationMillis) {
      JLayer<J> jLayer = new JLayer<>(view);
      final SwipeView<J> ui = new SwipeView<>(view, jLayer, durationMillis);
      jLayer.setUI(ui);
      return ui;
    }

    private final C liveComponent;
    private Image priorScreen = null;
    private Image upcomingScreen = null;
    private final JLayer<C> layer;

    private boolean isAnimating = false;
    private SwipeDirection swipeDirection = SwipeDirection.SWIPE_RIGHT;
    private final int maxFrames;
    // Calculated:
    @SuppressWarnings("FieldCanBeLocal")
    private final int frameMillis;
    private int frame = 0;
    private final long startTime = System.currentTimeMillis();

    private SwipeView(C view, JLayer<C> theLayer, int animationDurationMillis) {
      super();
      liveComponent = view;
      layer = theLayer;
      maxFrames = (30 * animationDurationMillis) / 1000;
      frameMillis = animationDurationMillis / maxFrames;
    }

    public JLayer<C> getLayer() { return layer; }

    /**
     * Perform the specified operation with a swipe-right special effect. This is often used in an ActionListener:
     * <pre>
     *   first.addActionListener((e) -> swipeView.swipeRight(recordModel::goFirst));
     * </pre>
     * Here, the Action listener will perform a Swipe-right after executing the goFirst() method of recordModel.
     *
     * @param operation The operation
     */
    @SuppressWarnings("WeakerAccess")
    public void swipeRight(Runnable operation) {
      swipe(operation, SwipeDirection.SWIPE_RIGHT);
    }

    /**
     * Perform the specified operation with a swipe-left special effect. This is often used in an ActionListener:
     * <pre>
     *   first.addActionListener((e) -> swipeView.swipeLeft(recordModel::goFirst));
     * </pre>
     * Here, the Action listener will perform a Swipe-Left after executing the goFirst() method of recordModel.
     *
     * @param operation The operation
     */
    @SuppressWarnings("WeakerAccess")
    public void swipeLeft(Runnable operation) {
      swipe(operation, SwipeDirection.SWIPE_LEFT);
    }

    private void swipe(Runnable operation, SwipeDirection swipeDirection) {
      prepareToAnimate(swipeDirection);
      operation.run();
      animate();
    }

    //  @SuppressWarnings({"HardCodedStringLiteral", "HardcodedFileSeparator"})
    @Override
    public void paint(final Graphics g, final JComponent c) {
      if (isAnimating) {
        int xLimit = (c.getWidth() * 2 * frame) / maxFrames;
        if (swipeDirection == SwipeDirection.SWIPE_LEFT) {
          xLimit = (c.getWidth() * 2) - xLimit;
        }
        int width = c.getWidth() * 2;
        int height = c.getHeight() * 2;

//      //noinspection UseOfSystemOutOrSystemErr
//      System.out.printf("Dimensions:  Frame:  %d/%d (at %d)  xLimit: %4d  (%4d x %4d) (from %4d x %4d)  Animating: %b%n",
//          frame, maxFrames, System.currentTimeMillis() - startTime, xLimit, width, height, c.getWidth(), c.getHeight(), isAnimating);

        assert upcomingScreen != null;
        assert priorScreen != null;
        Image pScreen = Objects.requireNonNull(priorScreen);
        Image uScreen = Objects.requireNonNull(upcomingScreen);
        if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
          g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
          g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
        } else {
          g.drawImage(uScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
          g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
        }
      } else {
        super.paint(g, c);
      }
    }

    private void prepareToAnimate(SwipeDirection swipeDirection) {
      this.swipeDirection = swipeDirection;
      isAnimating = true;
      frame = 0;

      // Save current state
      priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
      Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
      liveComponent.paint(graphics2D);
      graphics2D.dispose();
    }

    private void animate() {
      Timer timer = new Timer(frameMillis, null);
      final ActionListener actionListener = (evt) -> {
        frame++;
        layer.repaint();
        if (frame == maxFrames) {
          frame = 0;
          isAnimating = false;
          timer.stop(); // Investigate: Am I leaking timers?
        }
      };
      timer.addActionListener(actionListener);
      upcomingScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
      Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
      liveComponent.paint(graphics2D);
      graphics2D.dispose();

      timer.start();
    }
  }

  public static enum SwipeDirection {
    @SuppressWarnings("JavaDoc") SWIPE_RIGHT,
    @SuppressWarnings("JavaDoc") SWIPE_LEFT
  }
}
MiguelMunoz:

事实证明,我需要更改框架动画的方式,以解决缩放比例增加一倍的问题。

首先,我需要检测规模。我添加了此代码,需要Java 9或更高版本才能正常工作。(它在Java 8下编译,但是无法正确执行,对于任何屏幕始终返回1。)

private static final int SCALE = calculateScaleForDefaultScreen();

private static int calculateScaleForDefaultScreen() {
  // scale will be 2.0 for a Retina screen, and 1.0 for an older screen
  double scale = GraphicsEnvironment.getLocalGraphicsEnvironment()
      .getDefaultScreenDevice()
      .getDefaultConfiguration()
      .getDefaultTransform()
      .getScaleX(); // Requires Java 9+ to work. Compiles under Java 8 but always returns 1.0.
  //noinspection NumericCastThatLosesPrecision
  return (int) Math.round(scale);
}

当准备两个屏幕外图形时,我需要以两倍的比例进行操作:

Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the current state of liveComponent into the image
graphics2D.dispose();

和…

Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the upcoming state of liveComponent into the image
graphics2D.dispose();

然后,当我制作动画时,我需要在图形中包括SCALE。

  if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
    g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
    g.drawImage(pScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
  } else {
    g.drawImage(uScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
    g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
  }

在其他几个地方,我将宽度和高度乘以2。我也将宽度和高度也更改为SCALE。

我希望有一个更优雅的解决方案,但这可行。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

Java JDK 1.7中的Apple Retina显示屏支持AWT / Swing

在4k分辨率显示屏上运行1080p

在VLC上以更高的分辨率播放视频时,是什么导致帧丢失/丢失?

如何使Ubuntu支持单显示屏5120x1440分辨率?

如何在带有小显示屏的Ubuntu 14.04中处理BTsync GUI?

如果设置1920 * 1080分辨率,为什么4K显示屏上的图像模糊?

如何在没有实际Retina显示屏的情况下在Windows上测试Retina网站?

在13英寸显示屏上以3200x1800分辨率在Jitsi中配置更大的字体

如何在不丢失数据的情况下更改.tif光栅文件的分辨率

如何强制Chrome在辅助显示屏中打开?

我如何在点击时听到声音并在计算器显示屏中显示7

有什么方法可以使用比最大分辨率更高的显示分辨率?

拍摄比实际显示分辨率更高的分辨率的屏幕截图

如何获得比LCD物理分辨率更高的分辨率?

为iPad Pro显示屏优化的Web图像分辨率

19.04 MATE 和 QT:超大显示屏,低分辨率

较大的标题文字可显示更高的分辨率

宽度未在更高的屏幕分辨率上对齐

Windows 10中的双显示器分辨率问题

Windows 7中:双显示器削减壁纸如果分辨率不同

如何在显示屏上显示当前用户的姓名?

如何在不丢失图像信息的情况下将位图调整为低分辨率?

如何在Docky中获得Google Chrome浏览器的更高分辨率的图标?

在显示屏上显示图形并将其同时保存到gnuplot中的文件中

背景不覆盖整个显示屏?

如何在MacOS上获得物理显示分辨率?

降低Java中的图像分辨率

Java中的SVG圆分辨率

如何在Java中获得屏幕分辨率?