2009/7/22 Florian Echtler <f...@butterbrot.org>:
> I've recently written an input event driver for a touchscreen, and it's
> working out-of-the box with the evdev driver. However, the scaling is a
> bit off, so I'd like to do a four-point calibration and set the "Evdev
> Axis Calibration" property accordingly. Is there a tool which does that?
> I've only found ts_calibrate so far, which is just for framebuffer
> devices.

I wrote some code a while back that should compute the calibration
parameters, and it's just running inside X (I'm attaching the code).
It doesn't actually set the parameters as that wasn't supported at the
time, but you should be able to do this in run-time these days.

Søren
/*
 * Copyright © 2008 Soren Hauberg
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of Red Hat
 * not be used in advertising or publicity pertaining to distribution
 * of the software without specific, written prior permission.  Red
 * Hat makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors:
 *	Soren Hauberg (haub...@gmail.com)
 */

/*
 * This program performs calibration of a touchscreen that uses the 'usbtouchscreen'
 * Linux kernel module. A calibration consists of finding the following parameters:
 *
 *   'flip_x'
 *      a boolean parameter that determines if the x-coordinate should be flipped.
 * 
 *   'flip_y'
 *      same as 'flip_x' except for the y-coordinate.
 *
 *   'swap_xy'
 *      a boolean parameter that determines if the x and y-coordinates should
 *      be swapped.
 *
 *   'min_x', 'max_x', 'min_y', and 'max_y'
 *      the minimum and maximum x and y values that the screen can report. In principle
 *      we could get these parameters by letting the user press the corners of the
 *      screen. In practice this doesn't work, because the screen doesn't always have
 *      sensors at the corners. So, what we do is we ask the user to press points that
 *      have been pushed a bit closer to the center, and then we extrapolate the
 *      parameters.
 */

#include <cstring>

#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <gtkmm/drawingarea.h>
#include <cairomm/context.h>

/* Number of points the user should click */
const int num_points = 4;

/* Number of blocks. We partition we screen into 'num_blocks' x 'num_blocks'
 * rectangles of equal size. We then ask the user to press points that are
 * located at the corner closes to the center of the four blocks in the corners
 * of the screen. The following ascii art illustrates the situation. We partition
 * the screen into 8 blocks in each direction. We then let the user press the
 * points marked with 'O'.
 * 
 *   +--+--+--+--+--+--+--+--+
 *   |  |  |  |  |  |  |  |  |
 *   +--O--+--+--+--+--+--O--+
 *   |  |  |  |  |  |  |  |  |
 *   +--+--+--+--+--+--+--+--+
 *   |  |  |  |  |  |  |  |  |
 *   +--+--+--+--+--+--+--+--+
 *   |  |  |  |  |  |  |  |  |
 *   +--+--+--+--+--+--+--+--+
 *   |  |  |  |  |  |  |  |  |
 *   +--+--+--+--+--+--+--+--+
 *   |  |  |  |  |  |  |  |  |
 *   +--+--+--+--+--+--+--+--+
 *   |  |  |  |  |  |  |  |  |
 *   +--O--+--+--+--+--+--O--+
 *   |  |  |  |  |  |  |  |  |
 *   +--+--+--+--+--+--+--+--+
 */
const int num_blocks = 8;

/* Names of the points */
enum {
  UL = 0, /* Upper-left */
  UR = 1, /* Upper-right */
  LL = 2, /* Lower-left */
  LR = 3  /* Lower-right */
};

/* Output ranges. The final output will be scaled to [0, range_x] x [0, range_y] */
const int range_x = 1023;
const int range_y = 1023;

/* The file to which the calibration parameters are saved. (XXX: is this distribution dependend?) */
const char *modprobe_conf_local = "/etc/modprobe.conf.local";

/* Prefix to the kernel path where we can set the parameters */
const char *module_prefix = "/sys/module/usbtouchscreen/parameters";

/* Names of kernel parameters */
const char *p_range_x = "range_x";
const char *p_range_y = "range_y";
const char *p_min_x = "min_x";
const char *p_min_y = "min_y";
const char *p_max_x = "max_x";
const char *p_max_y = "max_y";
const char *p_transform_xy = "transform_xy";
const char *p_flip_x = "flip_x";
const char *p_flip_y = "flip_y";
const char *p_swap_xy = "swap_xy";

/* Threshold to keep the same point from being clicked twice. Set to zero if you don't
 * want this check */
const int click_threshold = 7;

/* Timeout parameters */
const int time_step = 100; /* 500 = 0.1 sec */
const int max_time = 5000; /* 5000 = 5 sec */

/* Clock Appereance */
const int clock_radius = 25;
const int clock_line_width = 10;

/* Globals for kernel parameters from startup. We revert to these if the program aborts */
bool val_transform_xy, val_flip_x, val_flip_y, val_swap_xy;

/* Text printed on screen */
const int font_size = 20;
const std::string help_text = "Press the point with the stylus";

/* Helper functions */
char yesno (const bool value)
{
  if (value)
    return 'Y';
  else
    return 'N';
}

void read_int_parameter (const char *param, int &value)
{
  char filename [100];
  sprintf (filename, "%s/%s", module_prefix, param);
  FILE *fid = fopen (filename, "r");
  if (fid == NULL)
    {
      fprintf (stderr, "Could not read parameter '%s'\n", param);
      return;
    }

  fscanf (fid, "%d", &value);
  fclose (fid);
}

void read_bool_parameter (const char *param, bool &value)
{
  char filename [100];
  sprintf (filename, "%s/%s", module_prefix, param);
  FILE *fid = fopen (filename, "r");
  if (fid == NULL)
    {
      fprintf (stderr, "Could not read parameter '%s'\n", param);
      return;
    }

  char val [3];
  fgets (val, 2, fid);
  fclose (fid);

  value = (val [0] == yesno (true));
}

void write_int_parameter (const char *param, const int value)
{
  char filename [100];
  sprintf (filename, "%s/%s", module_prefix, param);
  FILE *fid = fopen (filename, "w");
  if (fid == NULL)
    {
      fprintf (stderr, "Could not save parameter '%s'\n", param);
      return;
    }

  fprintf (fid, "%d", value);
  fclose (fid);
}

void write_bool_parameter (const char *param, const bool value)
{
  char filename [100];
  sprintf (filename, "%s/%s", module_prefix, param);
  FILE *fid = fopen (filename, "w");
  if (fid == NULL)
    {
      fprintf (stderr, "Could not save parameter '%s'\n", param);
      return;
    }

  fprintf (fid, "%c", yesno (value));
  fclose (fid);
}

void quit (int status)
{
  if (status != 0)
    {
      // Dirty exit, so we restore the parameters of the running kernel
      write_bool_parameter (p_transform_xy, val_transform_xy);
      write_bool_parameter (p_flip_x, val_flip_x);
      write_bool_parameter (p_flip_y, val_flip_y);
      write_bool_parameter (p_swap_xy, val_swap_xy);
    }

  Gtk::Main::quit ();
}

/* Class for writing output parameters to running kernel and to modprobe,conf */
class CalibrationWriter
{
public:
  CalibrationWriter ();

  bool add_click (double cx, double cy);
  void finish ();
  void set_width (int w);
  void set_height (int h);

protected:
  double clicked_x [num_points], clicked_y [num_points];
  int num_clicks, width, height;
};

CalibrationWriter::CalibrationWriter ()
  : num_clicks (0)
{
}

void CalibrationWriter::set_width (int w)
{
  width = w;
}

void CalibrationWriter::set_height (int h)
{
  height = h;
}

bool CalibrationWriter::add_click (double cx, double cy)
{
  /* Check that we don't click the same point twice */
  if (num_clicks > 0 && click_threshold > 0
  && abs (cx - clicked_x [num_clicks-1]) < click_threshold
  && abs (cy - clicked_y [num_clicks-1]) < click_threshold)
    return false;

  clicked_x [num_clicks] = cx;
  clicked_y [num_clicks] = cy;
  num_clicks ++;

  return true;
}

void CalibrationWriter::finish ()
{
  /* Should x and y be swapped? */
  const bool swap_xy = (abs (clicked_x [UL] - clicked_x [UR]) < abs (clicked_y [UL] - clicked_y [UR]));
  if (swap_xy)
    {
      std::swap (clicked_x [LL], clicked_x [UR]);
      std::swap (clicked_y [LL], clicked_y [UR]);
    }

  /* Compute min/max parameters. These are scaled to [0, range_x] x [0, range_y] */
  int min_x = (range_x * (clicked_x [UL] + clicked_x [LL])) / (2 * width);
  int max_x = (range_x * (clicked_x [UR] + clicked_x [LR])) / (2 * width);
  int min_y = (range_y * (clicked_y [UL] + clicked_y [UR])) / (2 * height);
  int max_y = (range_y * (clicked_y [LL] + clicked_y [LR])) / (2 * height);

  /* Should x and y be flipped? */
  const bool flip_x = (min_x > max_x);
  const bool flip_y = (min_y > max_y);

  /* Add / subtract the ofset that comes from not having the points in the corners */
  const int delta_x = (max_x - min_x) / (num_blocks - 2);
  min_x -= delta_x;
  max_x += delta_x;
  const int delta_y = (max_y - min_y) / (num_blocks - 2);
  min_y -= delta_y;
  max_y += delta_y;

  /* If x and y has to be swapped we also have to swap the parameters */
  if (swap_xy)
    {
      std::swap (min_x, max_y);
      std::swap (min_y, max_x);
    }

  /* Send the estimated parameters to the currently running kernel */
  write_int_parameter (p_range_x, range_x);
  write_int_parameter (p_range_y, range_y);
  write_int_parameter (p_min_x, min_x);
  write_int_parameter (p_min_y, min_y);
  write_int_parameter (p_max_x, max_x);
  write_int_parameter (p_max_y, max_y);
  write_bool_parameter (p_transform_xy, true);
  write_bool_parameter (p_flip_x, flip_x);
  write_bool_parameter (p_flip_y, flip_y);
  write_bool_parameter (p_swap_xy, swap_xy);

  /* Write calibration parameters to modprobe_conf_local to keep the for the next boot */
  FILE *fid = fopen (modprobe_conf_local, "r");
  if (fid == NULL)
    {
      fprintf (stderr, "Can't open '%s' for reading. Make sure you have the necesary rights\n",
               modprobe_conf_local);
      quit (1);
    }
  std::string new_contents;
  const int len = 1024; // XXX: we currently don't handle lines that are longer than this
  char line [len];
  const char *opt = "options usbtouchscreen";
  const int opt_len = strlen (opt);
  while (fgets (line, len, fid))
    {
      if (strncmp (line, opt, opt_len) == 0) // This is the line we want to remove
        continue;
      new_contents += line;
    }
  fclose (fid);
  char new_opt [opt_len];
  sprintf (new_opt, "%s %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%c %s=%c %s=%c %s=%c\n",
           opt, p_range_x, range_x, p_range_y, range_y, p_min_x, min_x, p_min_y, min_y,
           p_max_x, max_x, p_max_y, max_y, p_transform_xy, yesno (true),
           p_flip_x, yesno (flip_x), p_flip_y, yesno (flip_y), p_swap_xy, yesno (swap_xy));
  new_contents += new_opt;

  fid = fopen (modprobe_conf_local, "w");
  if (fid == NULL)
    {
      fprintf (stderr, "Can't open '%s' for writing. Make sure you have the necesary rights\n",
               modprobe_conf_local);
      quit (1);
    }
  fprintf (fid, "%s", new_contents.c_str ());
  fclose (fid);
}

/* Class for creating the calibration GUI */
class CalibrationArea : public Gtk::DrawingArea
{
public:
  CalibrationArea ();

protected:
  /* Helper functions */
  void redraw ();

  /* Signal handlers */
  bool on_timeout ();
  bool on_expose_event (GdkEventExpose *event);
  bool on_button_press_event (GdkEventButton *event);

  /* Data */
  double X [num_points], Y [num_points];
  int num_clicks, width, height;
  CalibrationWriter W;
  int time_elapsed;
};

CalibrationArea::CalibrationArea ()
  : num_clicks (0), time_elapsed (0)
{
  /* Listen for mouse events */
  add_events (Gdk::BUTTON_PRESS_MASK);

  /* Compute absolute circle centers */
  const Glib::RefPtr<Gdk::Screen> S = get_screen ();
  width = S->get_width ();
  height = S->get_height ();
  W.set_width (width);
  W.set_height (height);

  const int delta_x = width/num_blocks;
  const int delta_y = height/num_blocks;
  X [UL] = delta_x;             Y [UL] = delta_y;
  X [UR] = width - delta_x - 1; Y [UR] = delta_y;
  X [LL] = delta_x;             Y [LL] = height - delta_y - 1;
  X [LR] = width - delta_x - 1; Y [LR] = height - delta_y - 1;

  /* Reset the currently running kernel */
  read_bool_parameter (p_transform_xy, val_transform_xy);
  read_bool_parameter (p_flip_x, val_flip_x);
  read_bool_parameter (p_flip_y, val_flip_y);
  read_bool_parameter (p_swap_xy, val_swap_xy);

  write_bool_parameter (p_transform_xy, false);
  write_bool_parameter (p_flip_x, false);
  write_bool_parameter (p_flip_y, false);
  write_bool_parameter (p_swap_xy, false);

  /* Setup timer for animation */
  sigc::slot<bool> slot = sigc::mem_fun (*this, &CalibrationArea::on_timeout);
  Glib::signal_timeout().connect (slot, time_step);
}

bool CalibrationArea::on_button_press_event (GdkEventButton *event)
{
  /* Handle click */
  time_elapsed = 0;
  const bool accepted = W.add_click (event->x_root, event->y_root);
  if (accepted)
    num_clicks ++;

  /* Are we done yet? */
  if (num_clicks >= num_points)
    {
      /* Save data to disk */
      W.finish ();

      /* Quit */
      quit (0);
    }

  /* Force a redraw */
  redraw ();

  return true;
}

void CalibrationArea::redraw ()
{
  Glib::RefPtr<Gdk::Window> win = get_window ();
  if (win)
    {
      const Gdk::Rectangle rect (0, 0, width, height);
      win->invalidate_rect (rect, false);
    }
}

bool CalibrationArea::on_expose_event (GdkEventExpose *event)
{
  const double radius = 4;
  Glib::RefPtr<Gdk::Window> window = get_window ();
  if (window)
    {
      Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context ();
      cr->save ();

      cr->rectangle (event->area.x, event->area.y, event->area.width, event->area.height);
      cr->clip ();

      /* Print the text */
      Cairo::TextExtents extent;
      cr->set_font_size (font_size);
      cr->get_text_extents (help_text, extent);
      cr->move_to ((width-extent.width)/2, 0.3*height);
      cr->show_text (help_text);
      cr->stroke ();

      /* Draw the points */
      for (int i = 0; i < num_clicks; i++)
        {
          cr->arc (X [i], Y [i], radius, 0.0, 2.0 * M_PI);
          cr->set_source_rgb (1.0, 1.0, 1.0);
          cr->fill_preserve ();
          cr->stroke ();
        }

      cr->set_line_width (2);
      cr->arc (X [num_clicks], Y [num_clicks], radius, 0.0, 2.0 * M_PI);
      cr->set_source_rgb (0.8, 0.0, 0.0);
      //cr->fill_preserve ();
      cr->stroke ();

      /* Draw the clock */
      cr->arc (width/2, height/2, clock_radius, 0.0, 2.0 * M_PI);
      cr->set_source_rgb (0.5, 0.5, 0.5);
      cr->fill_preserve ();
      cr->stroke ();

      cr->set_line_width (clock_line_width);
      cr->arc (width/2, height/2, clock_radius - clock_line_width/2, 0.0,
               2.0 * M_PI * (double)time_elapsed/(double)max_time);
      cr->set_source_rgb (0.0, 0.0, 0.0);
      cr->stroke ();

      cr->restore ();
    }

  return true;
}

bool CalibrationArea::on_timeout ()
{
  time_elapsed += time_step;
  if (time_elapsed > max_time)
    quit (1);

  // Update clock
  Glib::RefPtr<Gdk::Window> win = get_window ();
  if (win)
    {
      const Gdk::Rectangle rect (width/2 - clock_radius - clock_line_width,
                                 height/2 - clock_radius - clock_line_width,
                                 2 * clock_radius + 1 + 2 * clock_line_width,
                                 2 * clock_radius + 1 + 2 * clock_line_width);
      win->invalidate_rect (rect, false);
    }

  return true;
}

int main(int argc, char** argv)
{
  Gtk::Main kit(argc, argv);

  Gtk::Window win;
  win.fullscreen ();

  CalibrationArea area;
  win.add (area);
  area.show ();

  Gtk::Main::run (win);

  return 0;
}

Attachment: Makefile
Description: Binary data

_______________________________________________
xorg mailing list
xorg@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/xorg

Reply via email to