墨水操作
矢量和光栅墨水操控的处理方式各不相同。 矢量操控修改生成的几何图形,而光栅擦除器仅“覆盖”笔划。
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
}
}