diff --git a/toonz/sources/include/tools/inputmanager.h b/toonz/sources/include/tools/inputmanager.h
index f3e9548..50563d8 100644
--- a/toonz/sources/include/tools/inputmanager.h
+++ b/toonz/sources/include/tools/inputmanager.h
@@ -272,6 +272,9 @@ private:
   std::vector<TTrackList> m_tracks;
   std::vector<THoverList> m_hovers;
   TInputSavePoint::List m_savePoints;
+  TRectD m_prevBounds;
+  TRectD m_nextBounds;
+  bool m_started;
   int m_savePointsSent;
 
   static TInputState::TouchId m_lastTouchId;
diff --git a/toonz/sources/include/tools/modifiers/modifierline.h b/toonz/sources/include/tools/modifiers/modifierline.h
new file mode 100644
index 0000000..a6966a2
--- /dev/null
+++ b/toonz/sources/include/tools/modifiers/modifierline.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#ifndef MODIFIERLINE_INCLUDED
+#define MODIFIERLINE_INCLUDED
+
+// TnzTools includes
+#include <tools/inputmanager.h>
+
+
+#undef DVAPI
+#undef DVVAR
+#ifdef TNZTOOLS_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+
+//===================================================================
+
+//*****************************************************************************************
+//    TModifierLine definition
+//*****************************************************************************************
+
+class TModifierLine: public TInputModifier {
+public:
+  class Modifier: public TTrackModifier {
+  public:
+    explicit Modifier(TTrackHandler &handler):
+      TTrackModifier(handler), fixAngle(), maxPressure() { }
+
+    bool fixAngle;
+    double maxPressure;
+    TInputSavePoint::Holder savePoint;
+
+    TTrackPoint calcPoint(double originalIndex) override;
+  };
+
+  void modifyTrack(
+    const TTrack &track,
+    const TInputSavePoint::Holder &savePoint,
+    TTrackList &outTracks ) override;
+};
+
+#endif
diff --git a/toonz/sources/include/tools/tooltimer.h b/toonz/sources/include/tools/tooltimer.h
index 60f6df5..d20513b 100644
--- a/toonz/sources/include/tools/tooltimer.h
+++ b/toonz/sources/include/tools/tooltimer.h
@@ -39,8 +39,7 @@ public:
   static const double step;
   static const double epsilon;
 
-  static inline TTimerTicks ticks()
-    { return m_instance.m_timer.nsecsElapsed(); }
+  static TTimerTicks ticks();
 };
 
 
diff --git a/toonz/sources/include/tools/track.h b/toonz/sources/include/tools/track.h
index dd83e29..52efd44 100644
--- a/toonz/sources/include/tools/track.h
+++ b/toonz/sources/include/tools/track.h
@@ -37,10 +37,12 @@ class TTrack;
 class TTrackPoint;
 class TTrackTangent;
 class TTrackHandler;
+class TTrackToolHandler;
 class TTrackModifier;
 
 typedef TSmartPointerT<TTrack> TTrackP;
 typedef TSmartPointerT<TTrackHandler> TTrackHandlerP;
+typedef TSmartPointerT<TTrackToolHandler> TTrackToolHandlerP;
 typedef TSmartPointerT<TTrackModifier> TTrackModifierP;
 
 typedef std::vector<TTrackPoint> TTrackPointList;
@@ -51,6 +53,13 @@ typedef std::vector<TTrackP> TTrackList;
 
 
 //*****************************************************************************************
+//    TTrackToolHandler definition
+//*****************************************************************************************
+
+class DVAPI TTrackToolHandler : public TSmartObject { };
+
+
+//*****************************************************************************************
 //    TTrackPoint definition
 //*****************************************************************************************
 
@@ -159,6 +168,7 @@ public:
   const TTrackModifierP modifier;
 
   mutable TTrackHandlerP handler;
+  mutable TTrackToolHandlerP toolHandler;
   mutable int pointsRemoved;
   mutable int pointsAdded;
 
@@ -242,6 +252,11 @@ public:
 
   inline const TTrackPoint& current() const
     { return point(size() - pointsAdded); }
+  inline const TTrackPoint& previous() const
+    { return point(size() - pointsAdded - 1); }
+  inline const TTrackPoint& next() const
+    { return point(size() - pointsAdded + 1); }
+
   inline TInputState::KeyState::Holder getKeyState(double time) const
     { return keyHistory.get(time); }
   inline TInputState::KeyState::Holder getKeyState(const TTrackPoint &point) const
diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index 9e67138..8f0d9fc 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -49,10 +49,11 @@ set(HEADERS
     ../include/tools/track.h
     ../include/tools/inputmanager.h
     ../include/tools/assistant.h
-    ../include/tools/modifiers/modifiertest.h
+    ../include/tools/modifiers/modifierassistants.h
+    ../include/tools/modifiers/modifierline.h
     ../include/tools/modifiers/modifiertangents.h
+    ../include/tools/modifiers/modifiertest.h
     ../include/tools/modifiers/modifiersegmentation.h
-    ../include/tools/modifiers/modifierassistants.h
 )
 
 set(SOURCES
@@ -121,10 +122,11 @@ set(SOURCES
     track.cpp
     inputmanager.cpp
     assistant.cpp
-    modifiers/modifiertest.cpp
+    modifiers/modifierassistants.cpp
+    modifiers/modifierline.cpp
     modifiers/modifiertangents.cpp
+    modifiers/modifiertest.cpp
     modifiers/modifiersegmentation.cpp
-    modifiers/modifierassistants.cpp
     assistants/guidelineline.cpp
     assistants/assistantvanishingpoint.cpp
     editassistantstool.cpp
diff --git a/toonz/sources/tnztools/fullcolorbrushtool.cpp b/toonz/sources/tnztools/fullcolorbrushtool.cpp
index 771a9d6..f1dc3e0 100644
--- a/toonz/sources/tnztools/fullcolorbrushtool.cpp
+++ b/toonz/sources/tnztools/fullcolorbrushtool.cpp
@@ -126,16 +126,17 @@ FullColorBrushTool::FullColorBrushTool(std::string name)
     , m_enabledPressure(false)
     , m_minCursorThick(0)
     , m_maxCursorThick(0)
-    , m_toonz_brush(0)
     , m_tileSet(0)
     , m_tileSaver(0)
     , m_notifier(0)
     , m_presetsLoaded(false)
-    , m_firstTime(true) {
+    , m_firstTime(true)
+    , m_started(false) {
   bind(TTool::RasterImage | TTool::EmptyTarget);
 
   m_inputmanager.setHandler(this);
   m_modifierTest = new TModifierTest(5, 40);
+  m_modifierLine = new TModifierLine();
   m_modifierTangents = new TModifierTangents();
   m_modifierAssistants = new TModifierAssistants();
   m_modifierSegmentation = new TModifierSegmentation(0.25);
@@ -156,8 +157,6 @@ FullColorBrushTool::FullColorBrushTool(std::string name)
   m_modifierEraser.setId("RasterEraser");
   m_modifierLockAlpha.setId("LockAlpha");
   m_pressure.setId("PressureSensitivity");
-
-  m_brushTimer.start();
 }
 
 //---------------------------------------------------------------------------------------------------
@@ -320,6 +319,13 @@ void FullColorBrushTool::handleMouseEvent(MouseEventType type, const TPointD &po
   bool alt = e.getModifiersMask() & TMouseEvent::ALT_KEY;
   bool shift = e.getModifiersMask() & TMouseEvent::SHIFT_KEY;
   bool control = e.getModifiersMask() & TMouseEvent::CTRL_KEY;
+  
+  if ((control || shift) && type == ME_DOWN && e.button() == Qt::LeftButton && !m_started) {
+    m_inputmanager.clearModifiers();
+    m_inputmanager.addModifier( TInputModifierP(m_modifierLine.getPointer()) );
+    m_inputmanager.addModifier( TInputModifierP(m_modifierSegmentation.getPointer()) );
+  }
+  
   if (alt != m_inputmanager.state.isKeyPressed(TKey::alt))
     m_inputmanager.keyEvent(alt, TKey::alt, t, nullptr);
   if (shift != m_inputmanager.state.isKeyPressed(TKey::shift))
@@ -350,220 +356,6 @@ void FullColorBrushTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e)
 void FullColorBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e)
   { handleMouseEvent(ME_MOVE, pos, e); }
 
-//---------------------------------------------------------------------------------------------------
-
-void FullColorBrushTool::inputLeftButtonDown(
-  const TTrackPoint &point, const TTrack &track )
-{
-  const TPointD &pos = point.position;
-  TPointD previousBrushPos = m_brushPos;
-  m_brushPos = m_mousePos = pos;
-  m_mousePressed          = true;
-  TToolViewer *viewer     = getViewer();
-  if (!viewer) return;
-
-  TRasterImageP ri = (TRasterImageP)getImage(true);
-  if (!ri) ri = (TRasterImageP)touchImage();
-
-  if (!ri) return;
-
-  // Modifier to do straight line
-  TInputState::KeyState::Holder keys = track.getKeyState(point);
-  if (keys.isPressed(TKey::shift) || keys.isPressed(TKey::control)) {
-    m_isStraight = true;
-    m_firstPoint = pos;
-    m_lastPoint  = pos;
-  }
-
-  /* update color here since the current style might be switched with numpad
-   * shortcut keys */
-  updateCurrentStyle();
-
-  TRasterP ras = ri->getRaster();
-
-  if (!(m_workRaster && m_backUpRas)) setWorkAndBackupImages();
-
-  m_workRaster->lock();
-
-  TPointD rasCenter = ras->getCenterD();
-  TPointD pt(pos + rasCenter);
-
-  double defPressure = 1.0;
-  if (getApplication()->getCurrentLevelStyle()->getTagId() == 4001) // mypaint brush case
-    defPressure = 0.5;
-  double pressure = m_enabledPressure && track.hasPressure ? point.pressure : defPressure;
-
-  m_tileSet   = new TTileSetFullColor(ras->getSize());
-  m_tileSaver = new TTileSaverFullColor(ras, m_tileSet);
-
-  mypaint::Brush mypaintBrush;
-  applyToonzBrushSettings(mypaintBrush);
-  m_toonz_brush = new MyPaintToonzBrush(m_workRaster, *this, mypaintBrush, false);
-
-  m_strokeRect.empty();
-  m_strokeSegmentRect.empty();
-  m_toonz_brush->beginStroke();
-  m_toonz_brush->strokeTo(pt, pressure, restartBrushTimer());
-  TRect updateRect = m_strokeSegmentRect * ras->getBounds();
-  if (!updateRect.isEmpty())
-    ras->extract(updateRect)->copy(m_workRaster->extract(updateRect));
-
-  TPointD thickOffset(m_maxCursorThick * 0.5, m_maxCursorThick * 0.5);
-  TRectD invalidateRect = convert(m_strokeSegmentRect) - rasCenter;
-  invalidateRect += TRectD(m_brushPos - thickOffset, m_brushPos + thickOffset);
-  invalidateRect +=
-      TRectD(previousBrushPos - thickOffset, previousBrushPos + thickOffset);
-  invalidate(invalidateRect.enlarge(2.0));
-}
-
-//-------------------------------------------------------------------------------------------------------------
-
-void FullColorBrushTool::inputLeftButtonDrag(
-  const TTrackPoint &point, const TTrack &track )
-{
-  const TPointD &pos = point.position;
-  TRectD invalidateRect;
-  TPointD previousLastPoint = m_lastPoint;
-  m_lastPoint = pos;
-
-  TPointD previousBrushPos = m_brushPos;
-  m_brushPos = m_mousePos = pos;
-  TRasterImageP ri        = (TRasterImageP)getImage(true);
-  if (!ri) return;
-
-  if (!m_toonz_brush) return;
-
-  TRasterP ras      = ri->getRaster();
-  TPointD rasCenter = ras->getCenterD();
-  TPointD pt(pos + rasCenter);
-
-  double defPressure = 1.0;
-  if (getApplication()->getCurrentLevelStyle()->getTagId() == 4001) // mypaint brush case
-    defPressure = 0.5;
-  double pressure = m_enabledPressure && track.hasPressure ? point.pressure : defPressure;
-
-  if (m_maxPressure < pressure) m_maxPressure = pressure;
-
-  if (m_isStraight) {
-    if (track.getKeyState(point).isPressed(TKey::control)) {
-      TPointD dist = m_lastPoint - m_firstPoint;
-      double angle = fabs(atan(dist)) * (180 / 3.14159);
-      if (angle > 90) angle = 180 - angle;
-      if (angle > 90 - 22.5)
-        m_lastPoint.x = m_firstPoint.x;
-      else if (angle < 22.5)
-        m_lastPoint.y = m_firstPoint.y;
-      else {
-        double xDistance = m_lastPoint.x - m_firstPoint.x;
-        double yDistance = m_lastPoint.y - m_firstPoint.y;
-        if (abs(xDistance) > abs(yDistance)) {
-          if (abs(yDistance) == yDistance)
-            m_lastPoint.y = m_firstPoint.y + abs(xDistance);
-          else
-            m_lastPoint.y = m_firstPoint.y - abs(xDistance);
-        } else {
-          if (abs(xDistance) == xDistance)
-            m_lastPoint.x = m_firstPoint.x + abs(yDistance);
-          else
-            m_lastPoint.x = m_firstPoint.x - abs(yDistance);
-        }
-      }
-    }
-    double cursorSize = m_maxCursorThick/2 + 2;
-    TPointD cursorSize2d(cursorSize, cursorSize);
-    invalidateRect = TRectD(m_firstPoint, previousLastPoint).enlarge(2);
-    invalidateRect += TRectD(previousBrushPos - cursorSize2d, previousBrushPos + cursorSize2d);
-    invalidateRect += TRectD(m_brushPos - cursorSize2d, m_brushPos + cursorSize2d);
-    invalidateRect += TRectD(m_firstPoint, m_lastPoint).enlarge(2);
-    invalidate(invalidateRect);
-    return;
-  }
-
-  m_strokeSegmentRect.empty();
-  m_toonz_brush->strokeTo(pt, pressure, restartBrushTimer());
-  TRect updateRect = m_strokeSegmentRect * ras->getBounds();
-  if (!updateRect.isEmpty())
-    ras->extract(updateRect)->copy(m_workRaster->extract(updateRect));
-
-  TPointD thickOffset(m_maxCursorThick * 0.5, m_maxCursorThick * 0.5);
-  invalidateRect = convert(m_strokeSegmentRect) - rasCenter;
-  invalidateRect += TRectD(m_brushPos - thickOffset, m_brushPos + thickOffset);
-  invalidateRect +=
-      TRectD(previousBrushPos - thickOffset, previousBrushPos + thickOffset);
-  invalidate(invalidateRect.enlarge(2.0));
-}
-
-//---------------------------------------------------------------------------------------------------------------
-
-void FullColorBrushTool::inputLeftButtonUp(
-  const TTrackPoint &point, const TTrack &track )
-{
-  const TPointD &pos = point.position;
-  TPointD previousBrushPos = m_brushPos;
-  m_brushPos = m_mousePos = pos;
-
-  TRasterImageP ri = (TRasterImageP)getImage(true);
-  if (!ri) return;
-
-  if (!m_toonz_brush) return;
-
-  TRasterP ras      = ri->getRaster();
-  TPointD rasCenter = ras->getCenterD();
-  TPointD pt;
-  if (m_isStraight)
-    pt = TPointD(m_lastPoint + rasCenter);
-  else
-    pt = TPointD(pos + rasCenter);
-
-  double defPressure = 1.0;
-  if (getApplication()->getCurrentLevelStyle()->getTagId() == 4001) // mypaint brush case
-    defPressure = 0.5;
-  double pressure = m_enabledPressure && track.hasPressure ? point.pressure : defPressure;
-
-  if (m_isStraight && m_maxPressure > 0.0)
-    pressure = m_maxPressure;
-
-  m_strokeSegmentRect.empty();
-  m_toonz_brush->strokeTo(pt, pressure, restartBrushTimer());
-  m_toonz_brush->endStroke();
-  TRect updateRect = m_strokeSegmentRect * ras->getBounds();
-  if (!updateRect.isEmpty())
-    ras->extract(updateRect)->copy(m_workRaster->extract(updateRect));
-
-  TPointD thickOffset(m_maxCursorThick * 0.5, m_maxCursorThick * 0.5);
-  TRectD invalidateRect = convert(m_strokeSegmentRect) - rasCenter;
-  invalidateRect += TRectD(m_brushPos - thickOffset, m_brushPos + thickOffset);
-  invalidateRect +=
-      TRectD(previousBrushPos - thickOffset, previousBrushPos + thickOffset);
-  invalidate(invalidateRect.enlarge(2.0));
-
-  if (m_toonz_brush) {
-    delete m_toonz_brush;
-    m_toonz_brush = 0;
-  }
-
-  m_lastRect.empty();
-  m_workRaster->unlock();
-
-  if (m_tileSet->getTileCount() > 0) {
-    delete m_tileSaver;
-    TTool::Application *app   = TTool::getApplication();
-    TXshLevel *level          = app->getCurrentLevel()->getLevel();
-    TXshSimpleLevelP simLevel = level->getSimpleLevel();
-    TFrameId frameId          = getCurrentFid();
-    TRasterP subras           = ras->extract(m_strokeRect)->clone();
-    TUndoManager::manager()->add(new FullColorBrushUndo(
-        m_tileSet, simLevel.getPointer(), frameId, m_isFrameCreated, subras,
-        m_strokeRect.getP00()));
-  }
-
-  notifyImageChanged();
-  m_strokeRect.empty();
-  m_mousePressed = false;
-  m_isStraight   = false;
-  m_maxPressure  = -1.0;
-}
-
 //---------------------------------------------------------------------------------------------------------------
 
 void FullColorBrushTool::inputMouseMove(
@@ -621,6 +413,98 @@ void FullColorBrushTool::inputMouseMove(
 
 //-------------------------------------------------------------------------------------------------------------
 
+void FullColorBrushTool::inputSetBusy(bool busy) {
+  if (m_started == busy) return;
+  if (busy) {
+    // begin paint
+    TRasterImageP ri = (TRasterImageP)getImage(true);
+    if (!ri) ri      = (TRasterImageP)touchImage();
+    if (!ri) return;
+    TRasterP ras = ri->getRaster();
+
+    if (!(m_workRaster && m_backUpRas)) setWorkAndBackupImages();
+    m_workRaster->lock();
+    m_tileSet   = new TTileSetFullColor(ras->getSize());
+    m_tileSaver = new TTileSaverFullColor(ras, m_tileSet);
+
+    // update color here since the current style might be switched
+    // with numpad shortcut keys
+    updateCurrentStyle();
+  } else {
+    // end paint
+    if (TRasterImageP ri = (TRasterImageP)getImage(true)) {
+      TRasterP ras = ri->getRaster();
+
+      m_lastRect.empty();
+      m_workRaster->unlock();
+
+      if (m_tileSet->getTileCount() > 0) {
+        delete m_tileSaver;
+        TTool::Application *app   = TTool::getApplication();
+        TXshLevel *level          = app->getCurrentLevel()->getLevel();
+        TXshSimpleLevelP simLevel = level->getSimpleLevel();
+        TFrameId frameId          = getCurrentFid();
+        TRasterP subras           = ras->extract(m_strokeRect)->clone();
+        TUndoManager::manager()->add(new FullColorBrushUndo(
+            m_tileSet, simLevel.getPointer(), frameId, m_isFrameCreated, subras,
+            m_strokeRect.getP00()));
+      }
+
+      notifyImageChanged();
+      m_strokeRect.empty();
+    }
+  }
+  m_started = busy;
+}
+
+//-------------------------------------------------------------------------------------------------------------
+
+void FullColorBrushTool::inputPaintTrackPoint(const TTrackPoint &point, const TTrack &track, bool firstTrack) {
+  // get raster
+  if (!m_started || !getViewer()) return;
+  TRasterImageP ri = (TRasterImageP)getImage(true);
+  if (!ri) return;
+  TRasterP ras = ri->getRaster();
+  TPointD rasCenter = ras->getCenterD();
+
+  // init brush
+  TrackHandler *handler;
+  if (track.size() == track.pointsAdded && !track.toolHandler && m_workRaster) {
+    mypaint::Brush mypaintBrush;
+    applyToonzBrushSettings(mypaintBrush);
+    handler = new TrackHandler(m_workRaster, *this, mypaintBrush);
+    handler->brush.beginStroke();
+    track.toolHandler = handler;
+  }
+  handler = dynamic_cast<TrackHandler*>(track.toolHandler.getPointer());
+  if (!handler) return;
+
+  // paint stroke
+  m_strokeSegmentRect.empty();
+  handler->brush.strokeTo(
+    point.position + rasCenter,
+    m_enabledPressure ? point.pressure : 0.5,
+    point.tilt,
+    point.time - track.previous().time );
+  if (track.pointsAdded == 1 && track.finished())
+    handler->brush.endStroke();
+
+  // update affected area
+  TRect updateRect = m_strokeSegmentRect * ras->getBounds();
+  if (!updateRect.isEmpty())
+    ras->extract(updateRect)->copy(m_workRaster->extract(updateRect));
+  TRectD invalidateRect = convert(m_strokeSegmentRect) - rasCenter;
+  if (firstTrack) {
+    TPointD thickOffset(m_maxCursorThick * 0.5, m_maxCursorThick * 0.5);
+    invalidateRect += TRectD(m_brushPos - thickOffset, m_brushPos + thickOffset);
+    invalidateRect += TRectD(point.position - thickOffset, point.position + thickOffset);
+    m_brushPos = m_mousePos = point.position;
+  }
+  invalidate(invalidateRect.enlarge(2.0));
+}
+
+//-------------------------------------------------------------------------------------------------------------
+
 void FullColorBrushTool::inputInvalidateRect(const TRectD &bounds)
   { invalidate(bounds); }
 
@@ -628,11 +512,6 @@ void FullColorBrushTool::inputInvalidateRect(const TRectD &bounds)
 
 void FullColorBrushTool::draw() {
   if (TRasterImageP ri = TRasterImageP(getImage(false))) {
-    // Draw line segment on straight line mode
-    if (m_isStraight) {
-      tglDrawSegment(m_firstPoint, m_lastPoint);
-    }
-
     // If toggled off, don't draw brush outline
     if (!Preferences::instance()->isCursorOutlineEnabled()) return;
 
@@ -906,14 +785,6 @@ void FullColorBrushTool::updateCurrentStyle() {
 
 //------------------------------------------------------------------
 
-double FullColorBrushTool::restartBrushTimer() {
-  double dtime = m_brushTimer.nsecsElapsed() * 1e-9;
-  m_brushTimer.restart();
-  return dtime;
-}
-
-//------------------------------------------------------------------
-
 TMyPaintBrushStyle *FullColorBrushTool::getBrushStyle() {
   if (TTool::Application *app = getApplication())
     return dynamic_cast<TMyPaintBrushStyle *>(app->getCurrentLevelStyle());
diff --git a/toonz/sources/tnztools/fullcolorbrushtool.h b/toonz/sources/tnztools/fullcolorbrushtool.h
index 67dd22d..50fdfb1 100644
--- a/toonz/sources/tnztools/fullcolorbrushtool.h
+++ b/toonz/sources/tnztools/fullcolorbrushtool.h
@@ -7,6 +7,7 @@
 
 #include <tools/inputmanager.h>
 #include <tools/modifiers/modifiertest.h>
+#include <tools/modifiers/modifierline.h>
 #include <tools/modifiers/modifiertangents.h>
 #include <tools/modifiers/modifierassistants.h>
 #include <tools/modifiers/modifiersegmentation.h>
@@ -36,9 +37,21 @@ class Brush;
 
 class FullColorBrushTool final : public TTool, public RasterController, public TInputHandler {
   Q_DECLARE_TR_FUNCTIONS(FullColorBrushTool)
+public:
+  class TrackHandler: public TTrackToolHandler {
+  public:
+    MyPaintToonzBrush brush;
+
+    TrackHandler(
+      const TRaster32P &ras,
+      RasterController &controller,
+      const mypaint::Brush &brush
+    ):
+      brush(ras, controller, brush) { }
+  };
 
+private:
   void updateCurrentStyle();
-  double restartBrushTimer();
   void applyClassicToonzBrushSettings(mypaint::Brush &mypaintBrush);
   void applyToonzBrushSettings(mypaint::Brush &mypaintBrush);
 
@@ -63,10 +76,9 @@ public:
   void leftButtonUp(const TPointD &pos, const TMouseEvent &e) override;
   void mouseMove(const TPointD &pos, const TMouseEvent &e) override;
 
-  void inputLeftButtonDown(const TTrackPoint &point, const TTrack &track) override;
-  void inputLeftButtonDrag(const TTrackPoint &point, const TTrack &track) override;
-  void inputLeftButtonUp(const TTrackPoint &point, const TTrack &track) override;
   void inputMouseMove(const TPointD &position, const TInputState &state) override;
+  void inputSetBusy(bool busy) override;
+  void inputPaintTrackPoint(const TTrackPoint &point, const TTrack &track, bool firstTrack) override;
   void inputInvalidateRect(const TRectD &bounds) override;
   TTool* inputGetTool() override { return this; };
 
@@ -103,21 +115,22 @@ private:
 protected:
   TInputManager m_inputmanager;
   TSmartPointerT<TModifierTest> m_modifierTest;
+  TSmartPointerT<TModifierLine> m_modifierLine;
   TSmartPointerT<TModifierTangents> m_modifierTangents;
   TSmartPointerT<TModifierAssistants> m_modifierAssistants;
   TSmartPointerT<TModifierSegmentation> m_modifierSegmentation;
   
   TPropertyGroup m_prop;
 
-  TIntPairProperty m_thickness;
-  TBoolProperty m_pressure;
+  TIntPairProperty    m_thickness;
+  TBoolProperty       m_pressure;
   TDoublePairProperty m_opacity;
-  TDoubleProperty m_hardness;
-  TDoubleProperty m_modifierSize;
-  TDoubleProperty m_modifierOpacity;
-  TBoolProperty m_modifierEraser;
-  TBoolProperty m_modifierLockAlpha;
-  TEnumProperty m_preset;
+  TDoubleProperty     m_hardness;
+  TDoubleProperty     m_modifierSize;
+  TDoubleProperty     m_modifierOpacity;
+  TBoolProperty       m_modifierEraser;
+  TBoolProperty       m_modifierLockAlpha;
+  TEnumProperty       m_preset;
 
   TPixel32 m_currentColor;
   bool m_enabledPressure;
@@ -131,9 +144,6 @@ protected:
 
   TRect m_strokeRect, m_strokeSegmentRect, m_lastRect;
 
-  MyPaintToonzBrush *m_toonz_brush;
-  QElapsedTimer m_brushTimer;
-
   TTileSetFullColor *m_tileSet;
   TTileSaverFullColor *m_tileSaver;
 
@@ -143,12 +153,8 @@ protected:
 
   bool m_presetsLoaded;
   bool m_firstTime;
-  bool m_mousePressed = false;
 
-  bool m_isStraight = false;
-  TPointD m_firstPoint;
-  TPointD m_lastPoint;
-  double m_maxPressure = -1.0;
+  bool m_started;
 
   bool m_propertyUpdating = false;
 };
diff --git a/toonz/sources/tnztools/inputmanager.cpp b/toonz/sources/tnztools/inputmanager.cpp
index 1ddbf00..58546f2 100644
--- a/toonz/sources/tnztools/inputmanager.cpp
+++ b/toonz/sources/tnztools/inputmanager.cpp
@@ -204,6 +204,7 @@ TInputManager::TInputManager():
   m_handler(),
   m_tracks(1),
   m_hovers(1),
+  m_started(),
   m_savePointsSent()
 { }
 
@@ -293,6 +294,12 @@ TInputManager::paintTracks() {
     }
     TTrackList &subTracks = m_tracks.back();
 
+    // is paint started?
+    if (!m_started && !subTracks.empty()) {
+      m_started = true;
+      if (m_handler) m_handler->inputSetBusy(true);
+    }
+
     // create handlers
     for(TTrackList::const_iterator i = subTracks.begin(); i != subTracks.end(); ++i)
       if (!(*i)->handler)
@@ -339,7 +346,10 @@ TInputManager::paintTracks() {
         }
         for(std::vector<TTrackList>::iterator i = m_tracks.begin(); i != m_tracks.end(); ++i)
           i->clear();
-        if (m_handler) m_handler->inputSetBusy(false);
+        if (m_started) {
+          if (m_handler) m_handler->inputSetBusy(false);
+          m_started = false;
+        }
       }
       break;
     }
@@ -473,8 +483,10 @@ void
 TInputManager::processTracks() {
   paintTracks();
   TRectD bounds = calcDrawBounds();
-  if (!bounds.isEmpty())
-    m_handler->inputInvalidateRect(bounds);
+  if (!bounds.isEmpty()) {
+    m_handler->inputInvalidateRect(m_prevBounds + bounds);
+    m_nextBounds += bounds;
+  }
 }
 
 
@@ -489,6 +501,7 @@ void
 TInputManager::reset() {
   // forget about handler paint stack
   // assuime it was already reset by outside
+  m_started = false;
   m_savePointsSent = 0;
 
   // reset save point
@@ -559,9 +572,6 @@ TInputManager::trackEvent(
   bool final,
   TTimerTicks ticks )
 {
-  if (getInputTracks().empty() && m_handler)
-    m_handler->inputSetBusy(true);
-
   ticks = fixTicks(ticks);
   TTrackP track = getTrack(deviceId, touchId, ticks, (bool)pressure, (bool)tilt);
   if (!track->finished()) {
@@ -629,8 +639,10 @@ TInputManager::hoverEvent(const THoverList &hovers) {
   }
   if (m_handler) {
     TRectD bounds = calcDrawBounds();
-    if (!bounds.isEmpty())
-      m_handler->inputInvalidateRect(bounds);
+    if (!bounds.isEmpty()) {
+      m_handler->inputInvalidateRect(m_prevBounds + bounds);
+      m_nextBounds += bounds;
+    }
     m_handler->inputHoverEvent(*this);
   }
 }
@@ -667,6 +679,9 @@ TInputManager::calcDrawBounds() {
 
 void
 TInputManager::draw() {
+  m_prevBounds = m_nextBounds;
+  m_nextBounds = TRectD();
+  
   // paint not sent sub-tracks
   if (debugInputManager || m_savePointsSent < (int)m_savePoints.size()) {
     glPushAttrib(GL_ALL_ATTRIB_BITS);
diff --git a/toonz/sources/tnztools/modifiers/modifierline.cpp b/toonz/sources/tnztools/modifiers/modifierline.cpp
new file mode 100644
index 0000000..561f636
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifierline.cpp
@@ -0,0 +1,95 @@
+
+
+#include <tools/modifiers/modifierline.h>
+
+
+//*****************************************************************************************
+//    TModifierLine implementation
+//*****************************************************************************************
+
+
+static inline void calcFixedAngle(const TTrackPoint &p0, TTrackPoint &p1) {
+  TPointD p = p1.position - p0.position;
+  double l = sqrt(p.x*p.x + p.y*p.y);
+  if (l < TConsts::epsilon) {
+    p = TPointD();
+  } else {
+    double a = atan2(p.y, p.x);
+    a = round(a*4/M_PI)*M_PI/4;
+    p.x = cos(a)*l;
+    p.y = sin(a)*l;
+  }
+  p1.position = p0.position + p;
+}
+
+
+TTrackPoint TModifierLine::Modifier::calcPoint(double originalIndex) {
+  if (original.empty()) return TTrackPoint();
+  if (original.size() < 2) return original.front();
+  TTrackPoint p0 = original.front();
+  TTrackPoint p1 = original.back();
+  if (fixAngle) calcFixedAngle(p0, p1);
+  return TTrack::interpolationLinear(p0, p1, originalIndex/(original.size() - 1));
+}
+
+void
+TModifierLine::modifyTrack(
+  const TTrack &track,
+  const TInputSavePoint::Holder &savePoint,
+  TTrackList &outTracks )
+{
+  if (!track.handler) {
+    track.handler = new TTrackHandler(track);
+    Modifier *modifier = new Modifier(*track.handler);
+    modifier->savePoint = savePoint;
+    track.handler->tracks.push_back( new TTrack(modifier) );
+  }
+
+  if (!track.changed())
+    return;
+  if (track.handler->tracks.empty())
+    return;
+  
+  TTrack &subTrack = *track.handler->tracks.front();
+  Modifier* modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer());
+  if (!modifier)
+    { track.resetChanges(); return; }
+  
+  bool fixAngle = track.getKeyState(track.back()).isPressed(TKey::shift);
+  outTracks.push_back(track.handler->tracks.front());
+
+  int i1 = track.size();
+  int i0 = i1 - track.pointsAdded;
+  double maxPressure = modifier->maxPressure;
+  if (track.pointsRemoved)
+    { maxPressure = 0; i0 = 0; }
+  for(int i = i0; i < i1; ++i) {
+    double p = track[i].pressure;
+    if (maxPressure < p) maxPressure = p;
+  }
+  modifier->maxPressure = maxPressure;
+  modifier->fixAngle = fixAngle;
+  if (track.finished())
+    modifier->savePoint.reset();
+
+  subTrack.truncate(0);
+  
+  if (track.size() > 0) {
+    TTrackPoint p = track.front();
+    p.originalIndex = 0;
+    p.pressure = maxPressure;
+    p.tilt = TPointD();
+    subTrack.push_back(p);
+  }
+  
+  if (track.size() > 1) {
+    TTrackPoint p = track.back();
+    p.originalIndex = track.size() - 1;
+    p.pressure = maxPressure;
+    p.tilt = TPointD();
+    if (fixAngle) calcFixedAngle(subTrack.front(), p);
+    subTrack.push_back(p);
+  }
+  
+  track.resetChanges();
+}
diff --git a/toonz/sources/tnztools/mypainttoonzbrush.cpp b/toonz/sources/tnztools/mypainttoonzbrush.cpp
index 3f97202..38c3b43 100644
--- a/toonz/sources/tnztools/mypainttoonzbrush.cpp
+++ b/toonz/sources/tnztools/mypainttoonzbrush.cpp
@@ -59,9 +59,9 @@ class Raster32PMyPaintSurface::Internal
 public:
   typedef SurfaceCustom Parent;
   Internal(Raster32PMyPaintSurface &owner)
-      : SurfaceCustom(owner.m_ras->pixels(), owner.m_ras->getLx(),
-                      owner.m_ras->getLy(), owner.m_ras->getPixelSize(),
-                      owner.m_ras->getRowSize(), &owner) {}
+      : SurfaceCustom(owner.ras->pixels(), owner.ras->getLx(),
+                      owner.ras->getLy(), owner.ras->getPixelSize(),
+                      owner.ras->getRowSize(), &owner) {}
 };
 
 //=======================================================
@@ -71,14 +71,14 @@ public:
 //=======================================================
 
 Raster32PMyPaintSurface::Raster32PMyPaintSurface(const TRaster32P &ras)
-    : m_ras(ras), controller(), internal() {
+    : ras(ras), controller(), internal() {
   assert(ras);
   internal = new Internal(*this);
 }
 
 Raster32PMyPaintSurface::Raster32PMyPaintSurface(const TRaster32P &ras,
                                                  RasterController &controller)
-    : m_ras(ras), controller(&controller), internal() {
+    : ras(ras), controller(&controller), internal() {
   assert(ras);
   internal = new Internal(*this);
 }
@@ -115,15 +115,15 @@ MyPaintToonzBrush::MyPaintToonzBrush(
   const mypaint::Brush &brush,
   bool interpolation
 )
-    : m_ras(ras)
-    , m_mypaintSurface(m_ras, controller)
+    : ras(ras)
+    , mypaintSurface(ras, controller)
     , brush(brush)
     , reset(true)
     , interpolation(interpolation)
 {
   // read brush antialiasing settings
   float aa = this->brush.getBaseValue(MYPAINT_BRUSH_SETTING_ANTI_ALIASING);
-  m_mypaintSurface.setAntialiasing(aa > 0.5f);
+  mypaintSurface.setAntialiasing(aa > 0.5f);
 
   // reset brush antialiasing to zero to avoid radius and hardness correction
   this->brush.setBaseValue(MYPAINT_BRUSH_SETTING_ANTI_ALIASING, 0.f);
@@ -141,14 +141,14 @@ void MyPaintToonzBrush::beginStroke() {
 void MyPaintToonzBrush::endStroke() {
   if (!reset) {
     if (interpolation)
-      strokeTo(TPointD(current.x, current.y), current.pressure, 0.f);
+      strokeTo(TPointD(current.x, current.y), current.pressure, TPointD(current.tx, current.ty), 0.f);
     beginStroke();
   }
 }
 
-void MyPaintToonzBrush::strokeTo(const TPointD &point, double pressure,
-                                 double dtime) {
-  Params next(point.x, point.y, pressure, 0.0);
+void MyPaintToonzBrush::strokeTo(const TPointD &position, double pressure,
+                                 const TPointD &tilt, double dtime) {
+  Params next(position.x, position.y, pressure, tilt.x, tilt.y, 0.0);
 
   if (reset) {
     current  = next;
@@ -190,8 +190,8 @@ void MyPaintToonzBrush::strokeTo(const TPointD &point, double pressure,
         sub->p2.setMedian(sub->p1, segment->p1);
         segment = sub;
       } else {
-        brush.strokeTo(m_mypaintSurface, segment->p2.x, segment->p2.y,
-                      segment->p2.pressure, 0.f, 0.f,
+        brush.strokeTo(mypaintSurface, segment->p2.x, segment->p2.y,
+                      segment->p2.pressure, segment->p2.tx, segment->p2.ty,
                       segment->p2.time - p0.time);
         if (segment == stack) break;
         p0 = segment->p2;
@@ -207,7 +207,7 @@ void MyPaintToonzBrush::strokeTo(const TPointD &point, double pressure,
     previous.time = 0.0;
     current.time  = dtime;
   } else {
-    brush.strokeTo(m_mypaintSurface, point.x, point.y, pressure, 0.f, 0.f, dtime);
+    brush.strokeTo(mypaintSurface, position.x, position.y, pressure, tilt.x, tilt.y, dtime);
   }
 }
 
@@ -224,6 +224,6 @@ void MyPaintToonzBrush::updateDrawing(const TRasterCM32P rasCM,
   if (targetRect.isEmpty()) return;
 
   rasCM->copy(rasBackupCM->extract(targetRect), targetRect.getP00());
-  putOnRasterCM(rasCM->extract(targetRect), m_ras->extract(targetRect), styleId,
+  putOnRasterCM(rasCM->extract(targetRect), ras->extract(targetRect), styleId,
                 lockAlpha);
 }
diff --git a/toonz/sources/tnztools/mypainttoonzbrush.h b/toonz/sources/tnztools/mypainttoonzbrush.h
index 5304fd7..3eab55b 100644
--- a/toonz/sources/tnztools/mypainttoonzbrush.h
+++ b/toonz/sources/tnztools/mypainttoonzbrush.h
@@ -27,7 +27,7 @@ class Raster32PMyPaintSurface : public mypaint::Surface {
 private:
   class Internal;
 
-  TRaster32P m_ras;
+  TRaster32P ras;
   RasterController *controller;
   Internal *internal;
 
@@ -80,6 +80,9 @@ public:
 
   bool getAntialiasing() const;
   void setAntialiasing(bool value);
+
+  RasterController* getController() const
+    { return controller; }
 };
 
 //=======================================================
@@ -93,16 +96,16 @@ private:
   struct Params {
     union {
       struct {
-        double x, y, pressure, time;
+        double x, y, pressure, tx, ty, time;
       };
       struct {
-        double values[4];
+        double values[6];
       };
     };
 
-    inline explicit Params(double x = 0.0, double y = 0.0,
-                           double pressure = 0.0, double time = 0.0)
-        : x(x), y(y), pressure(pressure), time(time) {}
+    inline explicit Params(double x = 0.0, double y = 0.0, double pressure = 0.0,
+                           double tx = 0.0, double ty = 0.0, double time = 0.0)
+        : x(x), y(y), pressure(pressure), tx(tx), ty(ty), time(time) {}
 
     inline void setMedian(Params &a, Params &b) {
       for (int i  = 0; i < (int)sizeof(values) / sizeof(values[0]); ++i)
@@ -114,8 +117,8 @@ private:
     Params p1, p2;
   };
 
-  TRaster32P m_ras;
-  Raster32PMyPaintSurface m_mypaintSurface;
+  TRaster32P ras;
+  Raster32PMyPaintSurface mypaintSurface;
   mypaint::Brush brush;
 
   bool reset;
@@ -129,12 +132,23 @@ public:
     const mypaint::Brush &brush,
     bool interpolation = false );
   void beginStroke();
+  void strokeTo(
+    const TPointD &position,
+    double pressure,
+    const TPointD &tilt,
+    double dtime );
   void endStroke();
-  void strokeTo(const TPointD &p, double pressure, double dtime);
 
   // colormapped
   void updateDrawing(const TRasterCM32P rasCM, const TRasterCM32P rasBackupCM,
                      const TRect &bbox, int styleId, bool lockAlpha) const;
+
+  const TRaster32P& getRaster() const
+    { return ras; }
+  RasterController& getController()
+    { return *mypaintSurface.getController(); }
+  const mypaint::Brush& getBrush() const
+    { return brush; }
 };
 
 #endif  // T_BLUREDBRUSH
diff --git a/toonz/sources/tnztools/tooltimer.cpp b/toonz/sources/tnztools/tooltimer.cpp
index 5dfdc15..bebd4db 100644
--- a/toonz/sources/tnztools/tooltimer.cpp
+++ b/toonz/sources/tnztools/tooltimer.cpp
@@ -10,7 +10,6 @@
 const TTimerTicks TToolTimer::frequency = 1000000000;
 const double TToolTimer::step = 1e-9;
 const double TToolTimer::epsilon = 1e-10;
-TToolTimer TToolTimer::m_instance;
 
 
 //*****************************************************************************************
@@ -19,3 +18,9 @@ TToolTimer TToolTimer::m_instance;
 
 TToolTimer::TToolTimer()
   { m_timer.start(); }
+
+ 
+TTimerTicks TToolTimer::ticks() {
+  static TToolTimer timer;
+  return timer.m_timer.nsecsElapsed();
+}
diff --git a/toonz/sources/tnztools/toonzrasterbrushtool.cpp b/toonz/sources/tnztools/toonzrasterbrushtool.cpp
index 2542d20..0b256e9 100644
--- a/toonz/sources/tnztools/toonzrasterbrushtool.cpp
+++ b/toonz/sources/tnztools/toonzrasterbrushtool.cpp
@@ -1357,7 +1357,7 @@ void ToonzRasterBrushTool::leftButtonDown(const TPointD &pos,
       m_strokeRect.empty();
       m_strokeSegmentRect.empty();
       m_toonz_brush->beginStroke();
-      m_toonz_brush->strokeTo(point, pressure, restartBrushTimer());
+      m_toonz_brush->strokeTo(point, pressure, TPointD(), restartBrushTimer());
       TRect updateRect = m_strokeSegmentRect * ras->getBounds();
       if (!updateRect.isEmpty()) {
         // ras->extract(updateRect)->copy(m_workRas->extract(updateRect));
@@ -1510,7 +1510,7 @@ void ToonzRasterBrushTool::leftButtonDrag(const TPointD &pos,
     TPointD point(centeredPos + rasCenter);
 
     m_strokeSegmentRect.empty();
-    m_toonz_brush->strokeTo(point, pressure, restartBrushTimer());
+    m_toonz_brush->strokeTo(point, pressure, TPointD(), restartBrushTimer());
     TRect updateRect = m_strokeSegmentRect * ras->getBounds();
     if (!updateRect.isEmpty()) {
       // ras->extract(updateRect)->copy(m_workRaster->extract(updateRect));
@@ -1681,7 +1681,7 @@ void ToonzRasterBrushTool::finishRasterBrush(const TPointD &pos,
     double pressure = m_pressure.getValue() ? pressureVal : 0.5;
 
     m_strokeSegmentRect.empty();
-    m_toonz_brush->strokeTo(point, pressure, restartBrushTimer());
+    m_toonz_brush->strokeTo(point, pressure, TPointD(), restartBrushTimer());
     m_toonz_brush->endStroke();
     TRect updateRect = m_strokeSegmentRect * ras->getBounds();
     if (!updateRect.isEmpty()) {
diff --git a/toonz/sources/tnztools/track.cpp b/toonz/sources/tnztools/track.cpp
index ae32131..8d048d1 100644
--- a/toonz/sources/tnztools/track.cpp
+++ b/toonz/sources/tnztools/track.cpp
@@ -107,6 +107,10 @@ TTrack::pop_back(int count) {
   if (count > size()) count = size();
   if (count <= 0) return;
   m_points.resize(size() - count);
+  if (pointsAdded > count)
+    { pointsAdded -= count; return; }
+  if (pointsAdded > 0)
+    { count -= pointsAdded; pointsAdded = 0; }
   pointsRemoved += count;
 }