template<typename tc>
    CImg<T>& draw_line(const int x0, const int y0, const float z0,
                       const int x1, const int y1, const float z1,
                       const CImg<tc>& texture,
                       const int tx0, const int ty0,
                       const int tx1, const int ty1,
                       const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (is_empty() && z0<=0 && z1<=0) return *this;
      if (texture._depth>1 || texture._spectrum<_spectrum)
        throw CImgArgumentException(_cimg_instance
                                    "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).",
                                    cimg_instance,
                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
      if (is_overlapped(texture))
        return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
      static unsigned int hatch = ~0U - (~0U>>1);
      if (init_hatch) hatch = ~0U - (~0U>>1);
      const bool xdir = x0<x1, ydir = y0<y1;
      int
        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
        &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
        &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
      float
        Tx0 = tx0/z0, Tx1 = tx1/z1,
        Ty0 = ty0/z0, Ty1 = ty1/z1,
        Z0 = 1/z0, Z1 = 1/z1,
        dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
        tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
        &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
        &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
        &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
        &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
      if (xright<0 || xleft>=width()) return *this;
      if (xleft<0) {
        const float D = (float)xright - xleft;
        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
        zleft-=(float)xleft*(zright - zleft)/D;
        txleft-=(float)xleft*(txright - txleft)/D;
        tyleft-=(float)xleft*(tyright - tyleft)/D;
        xleft = 0;
      }
      if (xright>=width()) {
        const float d = (float)xright - width(), D = (float)xright - xleft;
        yright-=(int)(d*((float)yright - yleft)/D);
        zright-=d*(zright - zleft)/D;
        txright-=d*(txright - txleft)/D;
        tyright-=d*(tyright - tyleft)/D;
        xright = width() - 1;
      }
      if (ydown<0 || yup>=height()) return *this;
      if (yup<0) {
        const float D = (float)ydown - yup;
        xup-=(int)((float)yup*((float)xdown - xup)/D);
        zup-=(float)yup*(zdown - zup)/D;
        txup-=(float)yup*(txdown - txup)/D;
        tyup-=(float)yup*(tydown - tyup)/D;
        yup = 0;
      }
      if (ydown>=height()) {
        const float d = (float)ydown - height(), D = (float)ydown - yup;
        xdown-=(int)(d*((float)xdown - xup)/D);
        zdown-=d*(zdown - zup)/D;
        txdown-=d*(txdown - txup)/D;
        tydown-=d*(tydown - tyup)/D;
        ydown = height() - 1;
      }
      T *ptrd0 = data(nx0,ny0);
      int dx = xright - xleft, dy = ydown - yup;
      const bool steep = dy>dx;
      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
      const longT
        offx = (longT)(nx0<nx1?1:-1)*(steep?width():1),
        offy = (longT)(ny0<ny1?1:-1)*(steep?1:width()),
        ndx = (longT)(dx>0?dx:1);
      const ulongT
        whd = (ulongT)_width*_height*_depth,
        twh = (ulongT)texture._width*texture._height;

      if (opacity>=1) {
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          if (pattern&hatch) {
            const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
            const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
            T *ptrd = ptrd0;
            cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
          }
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
          const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
          T *ptrd = ptrd0;
          cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      } else {
        const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          if (pattern&hatch) {
            const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
            const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
            T *ptrd = ptrd0;
            cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
          }
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
          const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
          T *ptrd = ptrd0;
          cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      }
      return *this;