墨水操作
矢量和光栅墨水操控的处理方式各不相同。 矢量操控修改生成的几何图形,而光栅擦除器仅“覆盖”笔划。
Raster Ink
目前的光栅墨水仅提供部分擦除器功能。
橡皮擦
擦除器被配置为一种类似于刷子的工具。
它使用特定的模糊模式BlendMode.DESTINATION_OUT覆盖笔划,以达到擦除的目的。
- Kotlin
- JavaScript
import com.wacom.ink.Calculator
import com.wacom.ink.PathPoint
import com.wacom.ink.PathPointLayout
import com.wacom.ink.rendering.BlendMode
...
class EraserRasterTool(context: Context) : RasterTool(context) {
    companion object {
        val uri = URIBuilder.getToolURI("raster", "eraser")
    }
    override var brush = BrushPalette.eraser(context)
    override fun getLayout(): PathPointLayout {
        return PathPointLayout(
            PathPoint.Property.X,
            PathPoint.Property.Y,
            PathPoint.Property.SIZE,
            PathPoint.Property.RED,
            PathPoint.Property.GREEN,
            PathPoint.Property.BLUE,
            PathPoint.Property.ALPHA
        )
    }
    override val touchCalculator: Calculator = { previous, current, next ->
        // Use the following to compute size based on speed:
        var size = current.computeValueBasedOnSpeed(
            previous,
            next,
            minValue = 8f,
            maxValue = 112f,
            minSpeed = 720f,
            maxSpeed = 3900f
        )
        if (size == null) size = 8f
        PathPoint(current.x, current.y, size = size,
                  red = 1f, green = 1f, blue = 1f, alpha = 1f)
    }
    override val stylusCalculator: Calculator = { previous, current, next ->
        // Use the following to compute size based on speed:
        var size = current.computeValueBasedOnSpeed(
            previous,
            next,
            minValue = 8f,
            maxValue = 112f,
            minSpeed = 720f,
            maxSpeed = 3900f
        )
        if (size == null) size = 8f
        PathPoint(current.x, current.y, size = size,
                  red = 1f, green = 1f, blue = 1f, alpha = 1f)
    }
    override fun getBlendMode(): BlendMode {
        return BlendMode.DESTINATION_OUT
    }
}
...
eraserRaster: {
  brush: BrushPalette.circle,
  blendMode: BlendMode.DESTINATION_OUT,
  dynamics: {
    size: {
      value: {
        min: 8,
        max: 112
      },
      velocity: {
        min: 720,
        max: 3900
      }
    }
  },
  ...
**注意:**笔划并未从模型中擦除,仅是被覆盖了。
Vector Ink
矢量墨水提供更多的笔划操控功能:
- 部分笔划擦除器
- 完整笔划擦除器
- 操控选项
部分擦除 / 分割
擦除器工具的工作原理与刷子工具类似。 因此需要定义它的计算器。 可在此确定擦除器宽 度是否随压力或速度而改变。 还可定义不同手指或触控笔输入行为。
- Kotlin
- C#
- JavaScript
class EraserVectorTool : VectorTool() {
    companion object {
        val uri = "tool@erase_vector"
    }
    override var brush = BrushPalette.circle()
    override fun getLayout(): PathPointLayout {
        return PathPointLayout(
            PathPoint.Property.X,
            PathPoint.Property.Y,
            PathPoint.Property.SIZE,
            PathPoint.Property.RED,
            PathPoint.Property.GREEN,
            PathPoint.Property.BLUE
        )
    }
    override var drawingMode = DrawingMode.ERASING_PARTIAL_STROKE
    override val touchCalculator: Calculator = { previous, current, next ->
        // Use the following to compute size based on speed:
        var size = current.computeValueBasedOnSpeed(
            previous,
            next,
            minValue = 8f,
            maxValue = 112f,
            minSpeed = 720f,
            maxSpeed = 3900f
        )
        if (size == null) size = 1.0f
        PathPoint(current.x, current.y, size = size, red = 1f, green = 1f, blue = 1f, alpha = 0.5f)
    }
    override val stylusCalculator: Calculator = { previous, current, next ->
        // Use the following to compute size based on speed:
        var size = current.computeValueBasedOnSpeed(
            previous,
            next,
            minValue = 8f,
            maxValue = 112f,
            minSpeed = 720f,
            maxSpeed = 3900f
        )
        if (size == null) size = 1.0f
        PathPoint(current.x, current.y, size = size, red = 1f, green = 1f, blue = 1f)
    }
}
/// <summary>
 /// Vector "drawing" tool for erasing ink storkes
 /// </summary>
 public class VectorEraserTool : VectorSelectionTool
 {
     private static readonly ToolConfig mConfig = new ToolConfig()
     {
         initValue = 2,
         minSpeed = 100,
         maxSpeed = 4000,
         minValue = 2,
         maxValue = 24
     };
     public VectorEraserTool(ManipulationMode mode)
         : base(mode)
     {
     }
     public override bool BlendCurrentStroke => false;
     public override VectorBrush Shape => mCircleBrush;
     protected override ToolConfig SizeConfig => mConfig;
     protected override float PreviousSize { get; set; } = 2f;
     public override void OnReleased(UIElement uiElement, PointerRoutedEventArgs args)
     {
         base.OnReleased(uiElement, args);
     }
     public override PathPointLayout GetLayout(Windows.Devices.Input.PointerDeviceType deviceType)
     {
         switch (deviceType)
         {
             case Windows.Devices.Input.PointerDeviceType.Mouse:
             case Windows.Devices.Input.PointerDeviceType.Touch:
             case Windows.Devices.Input.PointerDeviceType.Pen:
                 return new PathPointLayout(PathPoint.Property.X,
                                         PathPoint.Property.Y,
                                         PathPoint.Property.Size);
             default:
                 throw new Exception("Unknown input device type");
         }
     }
     public override Calculator GetCalculator(PointerDeviceType deviceType)
     {
         switch (deviceType)
         {
             case Windows.Devices.Input.PointerDeviceType.Mouse:
             case Windows.Devices.Input.PointerDeviceType.Touch:
             case Windows.Devices.Input.PointerDeviceType.Pen:
                 return CalculatorForMouseAndTouch;
             default:
                 throw new Exception("Unknown input device type");
         }
     }
  }
}
eraserVector: {
	brush: BrushPalette.circle,
	intersector: new Intersector(Intersector.Mode.PARTIAL_STROKE),
	dynamics: {
		size: {
			value: {
				min: 8,
				max: 12
			},
			velocity: {
				min: 720,
				max: 3900
			}
		}
	},
完整笔划橡皮擦
- Kotlin
- C#
- JavaScript
class EraserWholeStrokeTool : VectorTool() {
    companion object {
        val uri = URIBuilder.getToolURI("vector", "eraser_whole_stroke")
    }
    override var brush = BrushPalette.basic()
    override fun getLayout(): PathPointLayout {
        return PathPointLayout(
            PathPoint.Property.X,
            PathPoint.Property.Y,
            PathPoint.Property.SIZE
        )
    }
    override var drawingMode = DrawingMode.ERASING_WHOLE_STROKE
    override val touchCalculator: Calculator = { previous, current, next ->
        //Use the following to compute size based on speed:
        PathPoint(current.x, current.y, size = 3f)
    }
    override val stylusCalculator: Calculator = { previous, current, next ->
        //Use the following to compute size based on speed:
        PathPoint(current.x, current.y, size = 3f)
    }
}
/// <summary>
 /// Vector "drawing" tool for erasing ink storkes
 /// </summary>
 public class VectorEraserTool : VectorSelectionTool
 {
     private static readonly ToolConfig mConfig = new ToolConfig()
     {
         initValue = 2,
         minSpeed = 100,
         maxSpeed = 4000,
         minValue = 2,
         maxValue = 24
     };
     public VectorEraserTool(ManipulationMode mode)
         : base(mode)
     {
     }
     public override bool BlendCurrentStroke => false;
     public override VectorBrush Shape => mCircleBrush;
     protected override ToolConfig SizeConfig => mConfig;
     protected override float PreviousSize { get; set; } = 2f;
     public override void OnReleased(UIElement uiElement, PointerRoutedEventArgs args)
     {
         base.OnReleased(uiElement, args);
     }
     public override PathPointLayout GetLayout(Windows.Devices.Input.PointerDeviceType deviceType)
     {
         switch (deviceType)
         {
             case Windows.Devices.Input.PointerDeviceType.Mouse:
             case Windows.Devices.Input.PointerDeviceType.Touch:
             case Windows.Devices.Input.PointerDeviceType.Pen:
                 return new PathPointLayout(PathPoint.Property.X,
                                         PathPoint.Property.Y,
                                         PathPoint.Property.Size);
             default:
                 throw new Exception("Unknown input device type");
         }
     }
     public override Calculator GetCalculator(PointerDeviceType deviceType)
     {
         switch (deviceType)
         {
             case Windows.Devices.Input.PointerDeviceType.Mouse:
             case Windows.Devices.Input.PointerDeviceType.Touch:
             case Windows.Devices.Input.PointerDeviceType.Pen:
                 return CalculatorForMouseAndTouch;
             default:
                 throw new Exception("Unknown input device type");
         }
     }
  }
}
eraserWholeStroke: {
			brush: BrushPalette.basic2,
			intersector: new Intersector(Intersector.Mode.WHOLE_STROKE),
			statics: {
				size: 3,
				red: 255,
				green: 255,
				blue: 255,
				alpha: 0.5
			}
		},
选择
如果需要通过下列操作操控墨水笔划:
- 平移
- 旋转
- 缩放
您很可能会使用套框工具选择笔划的一部分或全部。 而使用这项技术,我们对部分选择笔划和完整选择笔划作了区分。
部分选择
还是一样,我们需要定义工具。
- Kotlin
- C#
- JavaScript
class SelectorPartialStrokeTool : VectorTool() {
  companion object {
      val uri = "tool@selector_parcial_stroke"
  }
  override var brush = BrushPalette.basic()
  override fun getLayout(): PathPointLayout {
      return PathPointLayout(
          PathPoint.Property.X,
          PathPoint.Property.Y,
          PathPoint.Property.SIZE,
          PathPoint.Property.RED,
          PathPoint.Property.GREEN,
          PathPoint.Property.BLUE
      )
  }
  override var drawingMode = DrawingMode.SELECTING_PARTIAL_STROKE
  override val touchCalculator: Calculator = { previous, current, next ->
      // Use the following to compute size based on speed:
      PathPoint(current.x, current.y, size = 3f, red = 1f, green = 1f, blue = 1f)
  }
  override val stylusCalculator: Calculator = { previous, current, next ->
      // Use the following to compute size based on speed:
      PathPoint(current.x, current.y, size = 3f, red = 1f, green = 1f, blue = 1f)
  }
}
ManipulationMode manipulation = ManipulationMode.PartialStroke
..
/// <summary>
/// Vector "drawing" tool for selecting ink strokes
/// </summary>
public class VectorManipulationTool : VectorSelectionTool
{
   private static readonly ToolConfig mConfig = new ToolConfig()
   {
       minSpeed = 5,
       maxSpeed = 210,
       minValue = 0.5f,
       maxValue = 1.6f,
       remap = v => (1 + 0.62f) * v / ((float)Math.Abs(v) + 0.62f)
   };
   private Point m_startPosition;
   private Point m_prevPosition;
   private bool m_isTranslating = false;
   private VectorStrokeHandler m_strokeHandler;
   public VectorManipulationTool(VectorStrokeHandler strokeHandler, ManipulationMode mode)
       : base(mode)
   {
       m_strokeHandler = strokeHandler;
   }
   public override bool BlendCurrentStroke => false;
   public override VectorBrush Shape => mCircleBrush;
   protected override ToolConfig SizeConfig => mConfig;
   protected override float PreviousSize { get; set; } = 1.5f;
   public Rect DestRect { get; set; } = Rect.Empty;
   public Rect SourceRect { get; set; } = Rect.Empty;
   public event EventHandler OnTranslate;
   public event EventHandler<Matrix3x2> TranslateFinished;
   public override void OnPressed(UIElement uiElement, PointerRoutedEventArgs args)
   {
       var currentPt = args.GetCurrentPoint(uiElement).Position;
       m_isTranslating = CurrentPointIntersectsSourceRect(currentPt);
       if (m_isTranslating)
       {
           m_startPosition = currentPt;
           m_prevPosition = m_startPosition;
           OnTranslate?.Invoke(this, null);
       }
       else
       {
           base.OnPressed(uiElement, args);
       }
   }
   public override void OnMoved(UIElement uiElement, PointerRoutedEventArgs args)
   {
       if (m_isTranslating)
       {
           var pt = args.GetCurrentPoint(uiElement).Position;
           double dX = pt.X - m_prevPosition.X;
           double dY = pt.Y - m_prevPosition.Y;
           Matrix3x2 translation = Matrix3x2.CreateTranslation((float)dX, (float)dY);
           m_prevPosition = pt;
           Vector2 newPosition = Vector2.Transform(new Vector2((float)DestRect.X, (float)DestRect.Y), translation);
           DestRect = new Rect(newPosition.X, newPosition.Y, DestRect.Width, DestRect.Height);
           OnTranslate?.Invoke(this, null);
       }
       else
       {
           base.OnMoved(uiElement, args);
       }
   }
   public override void OnReleased(UIElement uiElement, PointerRoutedEventArgs args)
   {
       if (m_isTranslating)
       {
           //m_isTranslating = false;
           Matrix3x2 translation = Matrix3x2.Identity;
           var pt = args.GetCurrentPoint(uiElement).Position;
           if (!m_strokeHandler.TransformationMatrix.IsIdentity)
           {
               bool res = Matrix3x2.Invert(m_strokeHandler.TransformationMatrix, out Matrix3x2 modelTransformationMatrix);
               if (!res)
               {
                   throw new InvalidOperationException("Transform matrix could not be inverted.");
               }
               Vector2 currentPointPos = new Vector2((float)pt.X, (float)pt.Y);
               Vector2 startPos = new Vector2((float)m_startPosition.X, (float)m_startPosition.Y);
               Vector2 modelCurrentPointPos = Vector2.Transform(currentPointPos, modelTransformationMatrix);
               Vector2 modelStartPos = Vector2.Transform(startPos, modelTransformationMatrix);
               double dX = modelCurrentPointPos.X - modelStartPos.X;
               double dY = modelCurrentPointPos.Y - modelStartPos.Y;
               translation = Matrix3x2.CreateTranslation((float)dX, (float)dY);
           }
           else
           {
               double dX = pt.X - m_startPosition.X;
               double dY = pt.Y - m_startPosition.Y;
               translation = Matrix3x2.CreateTranslation((float)dX, (float)dY);
           }
           TranslateFinished?.Invoke(this, translation);
       }
       else
       {
           base.OnReleased(uiElement, args);
       }
       m_isTranslating = false;
   }
   private bool CurrentPointIntersectsSourceRect(Point currentPoint)
   {
       if (SourceRect.IsEmpty)
           return false;
       return SourceRect.Contains(currentPoint);
   }
   public override PathPointLayout GetLayout(Windows.Devices.Input.PointerDeviceType deviceType)
   {
       switch (deviceType)
       {
           case Windows.Devices.Input.PointerDeviceType.Mouse:
           case Windows.Devices.Input.PointerDeviceType.Touch:
           case Windows.Devices.Input.PointerDeviceType.Pen:
               return new PathPointLayout(PathPoint.Property.X,
                                       PathPoint.Property.Y,
                                       PathPoint.Property.Size);
           default:
               throw new Exception("Unknown input device type");
       }
   }
   public override Calculator GetCalculator(PointerDeviceType deviceType)
   {
       switch (deviceType)
       {
           case Windows.Devices.Input.PointerDeviceType.Mouse:
           case Windows.Devices.Input.PointerDeviceType.Touch:
           case Windows.Devices.Input.PointerDeviceType.Pen:
               return CalculatorForMouseAndTouch;
           default:
               throw new Exception("Unknown input device type");
       }
   }
}
selector: {
  brush: BrushPalette.circle,
  selector: new Selector(Selector.Mode.PARTIAL_STROKE),
  statics: {
    size: 2,
    red: 0,
    green: 151,
    blue: 212,
    alpha: 1
  }
}
完整笔划选择器
- Kotlin
- C#
- JavaScript
class SelectorWholeStrokeTool : VectorTool() {
    companion object {
        val uri = "tool@selector_whole_stroke"
    }
    override var brush = BrushPalette.basic()
    override fun getLayout(): PathPointLayout {
        return PathPointLayout(
            PathPoint.Property.X,
            PathPoint.Property.Y,
            PathPoint.Property.SIZE,
            PathPoint.Property.RED,
            PathPoint.Property.GREEN,
            PathPoint.Property.BLUE
        )
    }
    override var drawingMode = DrawingMode.SELECTING_WHOLE_STROKE
    override val touchCalculator: Calculator = { previous, current, next ->
        //Use the following to compute size based on speed:
        PathPoint(current.x, current.y, size = 3f, red = 1f, green = 1f, blue = 1f)
    }
    override val stylusCalculator: Calculator = { previous, current, next ->
        //Use the following to compute size based on speed:
        PathPoint(current.x, current.y, size = 3f, red = 1f, green = 1f, blue = 1f)
    }
}
// Only use a different manipulation mode for the manipluation tool
ManipulationMode manipulation = ManipulationMode.WholeStroke
selectorWholeStroke: {
			brush: BrushPalette.circle,
			selector: new Selector(Selector.Mode.WHOLE_STROKE),
			statics: {
				size: 2,
				red: 0,
				green: 151,
				blue: 212,
				alpha: 1
			}
		}