Как правильно определить DPI дисплея с помощью Java?

У меня есть следующее приложение, которое рисует правило:

public class Rule extends JComponent
{
  public static final long serialVersionUID=26362862L;
//  public static final int INCH=Toolkit.getDefaultToolkit().getScreenResolution();
  public static final int INCH=(int)(Toolkit.getDefaultToolkit().getScreenResolution()*1.15);  // Auto adjust this 1.15 ?
  public static final int HORIZONTAL=0;
  public static final int VERTICAL=1;
  public static final int SIZE=35;
  public int orientation;
  public boolean isMetric;
  private int increment;
  private int units;
//  private Color rule_color=new Color(0,135,235);
  private Color rule_color=new Color(120,170,230);
  static JFrame frame=new JFrame("Rule");
  static Dimension screenSize=Toolkit.getDefaultToolkit().getScreenSize();  // 1600 x 1200  ,  1024 x 768
  static JPanel rulerPanel=new JPanel(new BorderLayout());

  public Rule(int o,boolean m)
  {
    orientation=o;
    isMetric=m;
    setIncrementAndUnits();
  }

  public void setIsMetric(boolean isMetric)
  {
    this.isMetric=isMetric;
    setIncrementAndUnits();
    repaint();
  }

  private void setIncrementAndUnits()
  {
    if (isMetric)
    {
      units=(int)((double)INCH/(double)2.54); // dots per centimeter
      increment=units;
    }
    else
    {
      units=INCH;
      increment=units/2;
    }
  }

  public boolean isMetric() { return this.isMetric; }

  public int getIncrement() { return increment; }

  public void setPreferredHeight(int ph) { setPreferredSize(new Dimension(SIZE,ph)); }

  public void setPreferredWidth(int pw) { setPreferredSize(new Dimension(pw,SIZE)); }

  public void setColor(Color color) { rule_color=color; }

  public void paintComponent(Graphics g)
  {
    Rectangle drawHere=g.getClipBounds();

    // Fill clipping area with blue-gray.
    g.setColor(rule_color);
    g.fillRect(drawHere.x,drawHere.y,drawHere.width,drawHere.height);

    // Do the ruler labels in a small font that's black.
    g.setFont(new Font("SansSerif",Font.PLAIN,10));
    g.setColor(Color.black);

    // Some vars we need.
    int end=0;
    int start=0;
    int tickLength=0;
    String text=null;

    // Use clipping bounds to calculate first tick and last tick location.
    if (orientation==HORIZONTAL)
    {
      start=(drawHere.x/increment)*increment;
      end=(((drawHere.x+drawHere.width)/increment)+1)*increment;
    }
    else
    {
      start=(drawHere.y/increment)*increment;
      end=(((drawHere.y+drawHere.height)/increment)+1)*increment;
    }

    // Make a special case of 0 to display the number within the rule and draw a units label.
    if (start==0)
    {
      text=Integer.toString(0)+(isMetric?" cm":" in");
      tickLength=10;
      if (orientation==HORIZONTAL)
      {
        g.drawLine(0,SIZE-1,0,SIZE-tickLength-1);
        g.drawString(text,2,21);
      }
      else
      {
        g.drawLine(SIZE-1,0,SIZE-tickLength-1,0);
        g.drawString(text,9,10);
      }
      text=null;
      start=increment;
    }

    // ticks and labels
    for (int i=start;i<end;i+=increment)
    {
      if (i%units==0)
      {
        tickLength=10;
        text=Integer.toString(i/units);
      }
      else
      {
        tickLength=5;
        text=null;
      }

      if (tickLength!=0)
      {
        if (orientation==HORIZONTAL)
        {
          g.drawLine(i,SIZE-1,i,SIZE-tickLength-1);
          if (text!=null) g.drawString(text,i-3,21);
        }
        else
        {
          g.drawLine(SIZE-1,i,SIZE-tickLength-1,i);
          if (text!=null) g.drawString(text,9,i+3);
        }
      }
    }
  }

  // Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread.
  static void createAndShowGUI()
  {
    rulerPanel.setPreferredSize(new Dimension(570,78));

    Rule cmView=new Rule(Rule.HORIZONTAL,true);
    int H=35;
    cmView.setPreferredHeight(H);
    cmView.setColor(new Color(128,200,235));
    JScrollPane cmScrollPane=new JScrollPane();
    cmScrollPane.setViewportBorder(BorderFactory.createLineBorder(Color.black));
    cmScrollPane.setColumnHeaderView(cmView);

    rulerPanel.add("North",cmScrollPane);

    Rule inchView=new Rule(Rule.HORIZONTAL,true);
    inchView.setPreferredHeight(H);
    inchView.setColor(new Color(168,200,235)); //238,238,238
    inchView.setIsMetric(false);
    JScrollPane inchScrollPane=new JScrollPane();
    inchScrollPane.setViewportBorder(BorderFactory.createLineBorder(Color.black));
    inchScrollPane.setColumnHeaderView(inchView);

    rulerPanel.add("South",inchScrollPane);
    frame.getContentPane().add(rulerPanel);
    frame.addWindowListener(new WindowAdapter()
    {
      public void windowActivated(WindowEvent e) { }
      public void windowClosed(WindowEvent e) { }
      public void windowClosing(WindowEvent e) { System.exit(0); }
      public void windowDeactivated(WindowEvent e) { }
      public void windowDeiconified(WindowEvent e) { rulerPanel.repaint(); }
      public void windowGainedFocus(WindowEvent e) { rulerPanel.repaint(); }
      public void windowIconified(WindowEvent e) { }
      public void windowLostFocus(WindowEvent e) { }
      public void windowOpening(WindowEvent e) { rulerPanel.repaint(); }
      public void windowOpened(WindowEvent e) { }
      public void windowResized(WindowEvent e) { rulerPanel.repaint(); }
      public void windowStateChanged(WindowEvent e) { rulerPanel.repaint(); }
    });
    frame.pack();
    frame.setBounds((screenSize.width-rulerPanel.getWidth())/2,(screenSize.height-rulerPanel.getHeight())/2-19,rulerPanel.getWidth()+20,rulerPanel.getHeight()+38);
    frame.setVisible(true);
  }

  public static void main(String[] args)
  {
    // Schedule a job for the event-dispatching thread : creating and showing this application's GUI.
    SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } });
  }
}

Он отлично работал на моих старых 17-дюймовых и 20-дюймовых дисплеях, теперь я заметил, что на моем новом 27-дюймовом ЖК-дисплее он неточен, поэтому мне нужно изменить 4-ю строку, чтобы она была более точной, не Toolkit.getDefaultToolkit(). getScreenResolution() должен получить точный DPI, почему это неправильно, чтобы мое приложение работало на других машинах с разными размерами экрана и DPI, как автоматически настроить 1.15, который я вставил вручную?

PS: не только DPI моего Java-приложения неточны, но и когда я просматривал несколько других приложений в Windows 7, таких как paint.exe или paint.net, их дюймы и см также были правильными. Вы можете попробовать их на своей машине.


person Frank    schedule 16.01.2011    source источник
comment
Информация о DPI, сообщаемая операционной системой или даже самим драйвером дисплея, часто неточна. Вам, вероятно, не повезло для действительно общего случая.   -  person Carl Norum    schedule 17.01.2011
comment
Я не очень уверен, но вместо Toolkit.getDefaultToolkit().getScreenSize(); попробуйте использовать это для получения размера GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();. Последний оператор даст вам режим отображения, используйте его для получения высоты и ширины.   -  person Favonius    schedule 17.01.2011
comment
@Favonius: Спасибо, но как только я их получу, как получить нужный мне точный DPI? Числа выглядят следующим образом: Default_device Ширина: 2560 Высота: 1440 Частота обновления: 59 Глубина бит: 32 Практически та же информация, которую я получил от getScreenSize();   -  person Frank    schedule 17.01.2011
comment
Как по этой ссылке ( weblogs.java.net/blog/kirillcool /archive/2007/03/ ) похоже, что в текущей реализации поддержки высокого DPI в Vista есть некоторые проблемы, и она не идеальна, особенно для диапазона 144+. Про win7 ничего не сказано.   -  person Favonius    schedule 17.01.2011


Ответы (1)


Проблема с драйвером.

Реальный DPI известен только драйверу, который сообщает его ОС, которая сообщает его Java и другим приложениям. Так как не только у Java неправильный масштаб, он должен быть драйвером.

Это давняя проблема. Многие графические приложения имеют глобальную настройку «DPI экрана», с помощью которой пользователи могут настроить свой фактический монитор.

Кроме того, поскольку вы пишете на Java, имейте в виду, что некоторые операционные системы не имеют возможности сообщить Java реальный DPI (потому что они сами не знают). Таким образом, необходимость настройки конфигурации DPI становится еще более очевидной.

person Vladimir Dyuzhev    schedule 25.03.2011