M Katz
M Katz

Reputation: 5446

can the CImg library draw thick lines

I have been using the CImg library, and have been pleased with how easy it is to integrate and use. However, I now want to draw thick lines (i.e., more than one pixel thick). It is not clear from the API documentation of the draw_line function (here) how this can be done. A second version of the function (just below the first in the documentation) even takes a texture as input, but again no width. It seems strange that such a comprehensive library would not have this feature. Perhaps it's supposed to be done using some kind of transformation? I know I could do it using a polygon (i.e., a rectangle where I would compute the corners of the polygon using a normal to the line), but I fear that would be significantly slower.

Upvotes: 6

Views: 4078

Answers (3)

VLL
VLL

Reputation: 10185

This function can be used to draw thick lines as polygons.

The function converts a line (two points) to a rectangle (four points). Each endpoint p is converted to two points pa and pb by adding w_diff that is perpendicular to the line. x_adj and y_adj are the difference to the original point.

void draw_line(cimg_library::CImg<uint8_t>& image,
    const int x1, const int y1,
    const int x2, const int y2,
    const uint8_t* const color,
    const unsigned int line_width)
{
    if (x1 == x2 && y1 == y2) {
        return;
    }

    // Convert line (p1, p2) to polygon (pa, pb, pc, pd)
    const double x_diff = x1 - x2;
    const double y_diff = y1 - y2;
    const double w_diff = line_width / 2.0;

    // Triangle between pa and p1: x_adj^2 + y_adj^2 = w_diff^2
    // Triangle between p1 and p2: x_diff^2 + y_diff^2 = length^2
    // Similar triangles: y_adj / x_diff = x_adj / y_diff = w_diff / length
    // -> y_adj / x_diff = w_diff / sqrt(x_diff^2 + y_diff^2)
    const int x_adj = y_diff * w_diff / std::sqrt(std::pow(x_diff, 2) + std::pow(y_diff, 2));
    const int y_adj = x_diff * w_diff / std::sqrt(std::pow(x_diff, 2) + std::pow(y_diff, 2));

    // Points are listed in clockwise order, starting from top-left
    cimg_library::CImg<int> points(4, 2);
    points(0, 0) = x1 - x_adj;
    points(0, 1) = y1 + y_adj;
    points(1, 0) = x1 + x_adj;
    points(1, 1) = y1 - y_adj;
    points(2, 0) = x2 + x_adj;
    points(2, 1) = y2 - y_adj;
    points(3, 0) = x2 - x_adj;
    points(3, 1) = y2 + y_adj;

    image.draw_polygon(points, color);
}

Benchmarks with line_width 20 and 3 colors. First time is using this function, second time is drawing single 1 px wide line using image.draw_line().

  • 1000,1000 to 2000,2000: 216 µs / 123 µs
  • 2000,2000 to 8000,4000: 588 µs / 151 µs
  • 3000,1000 to 3020,1000: 21 µs / 5 µs

Upvotes: 5

lamductan
lamductan

Reputation: 136

Basically this code does the same as @vll's answer, but also handles the case when (x1-x2)/(y1-y2) < 0 (I remove the abs function).

void draw_line(cimg_library::CImg<uint8_t>& image,
      const int x1, const int y1,
      const int x2, const int y2,
      const uint8_t* const color,
      const uint8_t line_width,
      const double opacity=1.0)
   {
      if (x1 == x2 && y1 == y2) {
         return;
      }
      // Convert line (p1, p2) to polygon (pa, pb, pc, pd)
      const double x_diff = (x1 - x2);
      const double y_diff = (y1 - y2);
      const double w_diff = line_width / 2.0;

      // Triangle between pa and p1: x_adj^2 + y_adj^2 = w_diff^2
      // Triangle between p1 and p2: x_diff^2 + y_diff^2 = length^2 
      // Similar triangles: y_adj / x_diff = x_adj / y_diff = w_diff / length
      // -> y_adj / x_diff = w_diff / sqrt(x_diff^2 + y_diff^2) 
      const int x_adj = y_diff * w_diff / std::sqrt(std::pow(x_diff, 2) + std::pow(y_diff, 2));
      const int y_adj = x_diff * w_diff / std::sqrt(std::pow(x_diff, 2) + std::pow(y_diff, 2));

      // Points are listed in clockwise order, starting from top-left
      cimg_library::CImg<int> points(4, 2);
      points(0, 0) = x1 - x_adj;
      points(0, 1) = y1 + y_adj;
      points(1, 0) = x1 + x_adj;
      points(1, 1) = y1 - y_adj;
      points(2, 0) = x2 + x_adj;
      points(2, 1) = y2 - y_adj;
      points(3, 0) = x2 - x_adj;
      points(3, 1) = y2 + y_adj;

      image.draw_polygon(points, color, opacity);
   }

Upvotes: 2

Tournevissette
Tournevissette

Reputation: 66

Apparently, it is not possible 'out-of-the-box', but creating your own routine that calls multiple times the 'draw_line()' routine of CImg, with one or two pixels shifts should give you the result you want, without much work.

Upvotes: 4

Related Questions