Как сделать двойной буфер в Java Swing на дисплее Retina без потери более высокого разрешения? - PullRequest
1 голос
/ 02 мая 2020

Я использую графику с двойной буферизацией в своем подклассе 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();
}

Я пытался удвоить размер изображения, но это не help.

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

Чтобы отразить эти изменения, я изменил свой код рисования в LayerUI, удвоив xLimit, width, height:

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);
  }
}

Это не так Помогите. Это dr aws то же самое с последним изменением или без него, что не имеет смысла.

Вот класс, который иллюстрирует проблему:

<code>/**
 * <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));
     * 
* Здесь слушатель Action выполнит Swipe -после выполнения метода goFirst () объекта recordModel. * * @param operation Операция * / @SuppressWarnings ("WeakerAccess") publi c void swipeRight (Runnable операция) {swipe (операция, SwipeDirection.SWIPE_RIGHT); } / ** * Выполнение указанной операции со специальным эффектом смахивания влево. Это часто используется в ActionListener: *
     *   first.addActionListener((e) -> swipeView.swipeLeft(recordModel::goFirst));
     * 
* Здесь слушатель Action будет выполнять Swipe-Left после выполнения метода goFirst () для recordModel. * * @param operation Операция * / @SuppressWarnings ("WeakerAccess") publi c void swipeLeft (Операция Runnable) {swipe (операция, SwipeDirection.SWIPE_LEFT); } закрытое частное сканирование (операция Runnable, SwipeDirection swipeDirection) {prepareToAnimate (swipeDirection); operation.run (); одушевленные (); } // @SuppressWarnings ({"HardCodedStringLiteral", "HardcodedFileSeparator"}) @Override publi c 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 ("Размеры: фрейм:% d /% d (при% d) xLimit:% 4d (% 4d x% 4d) (из% 4d x% 4d) Анимация: % b% n ", // frame, maxFrames, System.currentTimeMillis () - startTime, xLimit, ширина, высота, c .getWidth (), c .getHeight (), isAnimating); assert upcomingScreen! = null; assert priorScreen! = null; Изображение pScreen = Objects.requireNonNull (priorScreen); Изображение uScreen = Objects.requireNonNull (предстоящий экран); if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {g.drawImage (uScreen, 0, 0, xLimit, высота, 0, 0, xLimit, высота, c); g.drawImage (pScreen, xLimit, 0, ширина, высота, xLimit, 0, ширина, высота, c); } else {g.drawImage (uScreen, xLimit, 0, ширина, высота, xLimit, 0, ширина, высота, c); g.drawImage (pScreen, 0, 0, xLimit, высота, 0, 0, xLimit, высота, c); }} else {super.paint (g, c); }} private void prepareToAnimate (SwipeDirection swipeDirection) {this.swipeDirection = swipeDirection; isAnimating = true; кадр = 0; // Сохранение текущего состояния 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.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 (); }} publi c stati c enum SwipeDirection {@SuppressWarnings ("JavaDo c") SWIPE_RIGHT, @SuppressWarnings ("JavaDo c") SWIPE_LEFT}}

Ответы [ 2 ]

0 голосов
/ 05 мая 2020

Оказывается, мне нужно было изменить способ анимации кадра, чтобы учесть удвоение масштаба.

Сначала мне нужно было определить масштаб. Я добавил этот код, который требует 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();

Затем, когда я сделал свою анимацию, мне нужно было включить масштаб в чертеж.

  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.

У меня будет sh, было более элегантное решение, но это работает.

0 голосов
/ 04 мая 2020

Я не использую дисплей сетчатки, но я заметил небольшую разницу в рисовании, когда анимация началась.

Я изменил оба ваших BufferedImage, чтобы избавиться от значения альфа, и я больше не обратите внимание на разницу в окраске:

//priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_RGB);
...