How to "correctly" detect DPI of display with Java?

Frank picture Frank · Jan 16, 2011 · Viewed 9.8k times · Source

I have the following app that draws a rule :

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

It worked fine on my old 17" and 20" displays, now I've noticed on my new 27" LCD, it's inaccurate, so I have to change the 4th line to be more accurate, isn't Toolkit.getDefaultToolkit().getScreenResolution() supposed to get the accurate DPI, why it's not correct, for my app to work on other machines with different display sizes and DPIs, how to automatically adjust the 1.15 I have manually put in ?

PS : Not only my Java app's DPI is inaccurate, but also when I looked at several other apps on Windows 7 such as paint.exe or paint.net, their inch and cm are also in correct. You can try them on your machine.

Answer

Vladimir Dyuzhev picture Vladimir Dyuzhev · Mar 25, 2011

Driver problem.

The real DPI is known only to driver, which reports it to the OS, which reports it to Java and other applications. Since not only Java has the wrong scale, it must be the driver.

This is ages old problem. Many graphical applications have a global setting "screen DPI" where users can adjust for their actual monitor.

Besides, since you're writing in Java, keep in mind that some OSes have no means of telling Java the real DPI (because they don't know themselves). The need for DPI configuration setting is thus even more clear.