Manipulation of Digital Ink
ベクトルインクとラスターインクでは、加工時の処理が異なります。 ラスターイレーサがストロークを単に「誇張して」描くのに対し、ベクトル加工は生成された形状を修正します。
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
}
}
},
...
注意: Strokeはモデルから削除されず、単に上書きされます。
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
}
}