Skip to content

feat: Improve onyxsdk-pen integration #1452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions lib/components/canvas/canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import 'package:saber/components/canvas/image/editor_image.dart';
import 'package:saber/components/canvas/inner_canvas.dart';
import 'package:saber/data/editor/editor_core_info.dart';
import 'package:saber/data/editor/page.dart';
import 'package:saber/data/tools/_tool.dart';
import 'package:saber/data/tools/eraser.dart';
import 'package:saber/data/tools/highlighter.dart';
import 'package:saber/data/tools/laser_pointer.dart';
import 'package:saber/data/tools/pen.dart';
import 'package:saber/data/tools/pencil.dart';
import 'package:saber/data/tools/select.dart';

class Canvas extends StatelessWidget {
Expand All @@ -20,7 +26,7 @@ class Canvas extends StatelessWidget {
required this.currentStrokeDetectedShape,
required this.currentSelection,
required this.setAsBackground,
required this.currentToolIsSelect,
required this.currentTool,
required this.currentScale,
this.placeholder = false,
});
Expand All @@ -37,10 +43,53 @@ class Canvas extends StatelessWidget {

final void Function(EditorImage image)? setAsBackground;

final bool currentToolIsSelect;
final Tool currentTool;
final double currentScale;
final bool placeholder;

int toolToOnyx(Tool currentTool) {
if (placeholder) return 5;
if (currentTool is Pencil) {
return 3;
} else if (currentTool is Highlighter) {
return 4;
} else if (currentTool is Eraser) {
return 1;
} else if (currentTool is Select) {
return 1;
} else if (currentTool is LaserPointer) {
return 1;
} else if (currentTool is Pen) {
if (currentTool.isPressureEnabled()) {
return 2;
} else {
return 1;
}
} else {
return 5;
}
}

Comment on lines +50 to +72
Copy link
Member

@adil192 adil192 May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can then use the enum's values instead of ints

int getColor() {
if (currentTool is Pen) {
return (currentTool as Pen).color.toARGB32();
} else {
return Colors.black.toARGB32();
}
}
double getWidth() {
if (currentTool is Pen) {
double baseSize = (currentTool as Pen).getSize() * currentScale;
if ((currentTool as Pen).isPressureEnabled()) {
return baseSize;
} else {
return baseSize * 2;
}
} else {
return 3;
}
}
Comment on lines +73 to +91
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these methods would be best named getOnyxTool, getOnyxColor, getOnyxWidth so their purpose is more clear


@override
Widget build(BuildContext context) {
return Center(
Expand All @@ -62,6 +111,10 @@ class Canvas extends StatelessWidget {
width: page.size.width,
height: page.size.height,
child: OnyxSdkPenArea(
refreshDelay: const Duration(seconds: 1),
strokeStyle: toolToOnyx(currentTool),
strokeColor: getColor(),
strokeWidth: getWidth(),
child: InnerCanvas(
key: page.innerCanvasKey,
pageIndex: pageIndex,
Expand All @@ -74,7 +127,7 @@ class Canvas extends StatelessWidget {
currentStrokeDetectedShape: currentStrokeDetectedShape,
currentSelection: currentSelection,
setAsBackground: setAsBackground,
currentToolIsSelect: currentToolIsSelect,
currentToolIsSelect: currentTool is Select,
currentScale: currentScale,
),
),
Expand Down
7 changes: 7 additions & 0 deletions lib/data/tools/pen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ class Pen extends Tool {
_currentPen = currentPen;
}

bool isPressureEnabled() {
return pressureEnabled;
}
double getSize() {
return options.size;
}

Comment on lines +75 to +81
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem like redundant getters, we can just use the underlying fields themselves

void onDragStart(
Offset position, EditorPage page, int pageIndex, double? pressure) {
currentStroke = Stroke(
Expand Down
4 changes: 2 additions & 2 deletions lib/pages/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1393,7 +1393,7 @@ class EditorState extends State<Editor> {
currentSelection: null,
placeholder: true,
setAsBackground: null,
currentToolIsSelect: currentTool is Select,
currentTool: currentTool,
currentScale: double.minPositive,
);
},
Expand Down Expand Up @@ -1853,7 +1853,7 @@ class EditorState extends State<Editor> {
autosaveAfterDelay();
setState(() {});
},
currentToolIsSelect: currentTool is Select,
currentTool: currentTool,
currentScale: _transformationController.value.approxScale,
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/onyxsdk_pen/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ android {
}

dependencies {
implementation('com.onyx.android.sdk:onyxsdk-device:1.2.29')
implementation('com.onyx.android.sdk:onyxsdk-pen:1.4.10.1')
implementation('com.onyx.android.sdk:onyxsdk-device:1.2.32')
implementation('com.onyx.android.sdk:onyxsdk-pen:1.4.12')
implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,110 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.util.Log
import android.view.SurfaceView
import android.view.View
import com.onyx.android.sdk.data.note.TouchPoint
import com.onyx.android.sdk.pen.RawInputCallback
import com.onyx.android.sdk.pen.TouchHelper
import com.onyx.android.sdk.pen.data.TouchPointList
import com.onyx.android.sdk.api.device.epd.EpdController
import com.onyx.android.sdk.api.device.epd.UpdateMode
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import java.util.Timer
import java.util.TimerTask
import androidx.annotation.NonNull

internal class OnyxsdkPenArea(context: Context, messenger: BinaryMessenger, id: Int, creationParams: Map<String?, Any?>?) : PlatformView, MethodCallHandler {

enum class StrokeStyle(val Id: Int) {
FountainPen(0),
Pen(1),
Brush(2),
Pencil(3),
Marker(4)
}
Comment on lines +28 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should define this enum in the dart package too


private val channel: MethodChannel = MethodChannel(messenger, "onyxsdk_pen_area")

internal class OnyxsdkPenArea(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
companion object {
private val pointsToRedraw = 20
private val strokeWidth = 3f
}

private var strokeWidth = 0.0f
private var strokeColor = Color.BLACK
private var strokeStyle = StrokeStyle.FountainPen

private fun updateStroke(paramsRef: Map<String, Any>?) {
/*
Flutter ints are variable width
Personally, I think it is utterly dumb. I hope there is a way to fix
int size in flutter (why would you need 64 bits to store srgb??),
but unless or until there isn't, this stupidity should be performed
*/
val pureColor = (paramsRef?.get("strokeColor") as? Long)?.toInt()
?: paramsRef?.get("strokeColor") as? Int
?: Color.BLACK
Comment on lines +47 to +55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the fixnum package for fixed size ints, though I think I prefer this hack over polluting the dart code

/*
ONYXSDK Pen saturates the colors. Whatever it deems as "light" (luma < 128?)
gets automagically turned to white. Transparency is also accounted apparently.
Unless this is some case-by-case situation, if resulting color, assuming
being drawn on absolutely white background, with a fully opaque brush
is lighter than a certain unknown arbitrary threshold, it turns white,
otherwise, is saturated to max.
These thoughts are, however, purely observational. Nothing in documentation
says this and no binary artifacts have been decompiled to come to these
conclusions. Thus, feel free to correct my incompetence if I turn out wrong
*/
var dest = FloatArray(3)
Color.RGBToHSV(
Color.red(pureColor),
Color.green(pureColor),
Color.blue(pureColor),
dest
)
/*
Saturation
I suppose clamping is a sound idea, but the extremes to which the lib
takes them are unacceptable. Make it white if visually indistinguishable
from white and colorful otherwise.
*/
if (dest[1] < 0.05) {
dest[1] = 0.0f
} else {
dest[1] = 1.0f
}
/*
Value
I want color to show
So clamp to black if it's visually indistinguishable from black
and to colorful otherwise. E.g. brown will look red instead of black
*/
if (dest[2] < 0.2) {
dest[2] = 0.0f
} else {
dest[2] = 1.0f
}
strokeColor = Color.HSVToColor(dest)
strokeWidth = (paramsRef?.get("strokeWidth") as? Double ?: 3.0).toFloat()
strokeStyle = when (paramsRef?.get("strokeStyle") as? Int ?: 0) {
0 -> StrokeStyle.FountainPen
1 -> StrokeStyle.Pen
2 -> StrokeStyle.Brush
3 -> StrokeStyle.Pencil
4 -> StrokeStyle.Marker
else -> StrokeStyle.Pen
}
touchHelper.setStrokeStyle(strokeStyleToOnyx(strokeStyle))
touchHelper.setStrokeWidth(strokeWidth)
touchHelper.setStrokeColor(strokeColor)
}


private val touchHelper: TouchHelper by lazy { TouchHelper.create(view, callback) }

private val view: SurfaceView = SurfaceView(context)
Expand Down Expand Up @@ -59,6 +146,7 @@ internal class OnyxsdkPenArea(context: Context, id: Int, creationParams: Map<Str
refreshTimerTask = object : TimerTask() {
override fun run() {
touchHelper.setRawDrawingEnabled(false)
EpdController.invalidate(view, UpdateMode.GC);
touchHelper.setRawDrawingEnabled(true)
}
}
Expand Down Expand Up @@ -102,21 +190,22 @@ internal class OnyxsdkPenArea(context: Context, id: Int, creationParams: Map<Str
}
}

fun drawPreview() {
val canvas: Canvas = view.getHolder().lockCanvas() ?: return
canvas.drawColor(Color.WHITE)

for (i in 0 until currentStroke.size - 1) {
val p1 = currentStroke[i]
val p2 = currentStroke[i + 1]

canvas.drawLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), paint)
private fun strokeStyleToOnyx(style: StrokeStyle): Int {
return when (style) {
StrokeStyle.FountainPen -> TouchHelper.STROKE_STYLE_FOUNTAIN
StrokeStyle.Pen -> TouchHelper.STROKE_STYLE_PENCIL
StrokeStyle.Brush -> TouchHelper.STROKE_STYLE_NEO_BRUSH
StrokeStyle.Pencil -> TouchHelper.STROKE_STYLE_CHARCOAL
StrokeStyle.Marker -> TouchHelper.STROKE_STYLE_MARKER
}
}

view.getHolder().unlockCanvasAndPost(canvas)
fun drawPreview() {
currentStroke.clear()
}

init {
channel.setMethodCallHandler(this)
view.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
val limit = Rect()
val exclude = emptyList<Rect>()
Expand All @@ -128,16 +217,35 @@ internal class OnyxsdkPenArea(context: Context, id: Int, creationParams: Map<Str
}

paint.setStrokeWidth(strokeWidth)
paint.setColor(Color.BLACK)
paint.setColor(strokeColor)

touchHelper.setStrokeWidth(strokeWidth)
touchHelper.openRawDrawing()
touchHelper.setRawDrawingEnabled(true)
touchHelper.setRawDrawingRenderEnabled(false)

drawPreview()
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "updateStroke") {
val params = call.arguments<Map<String, Any>?>()
updateStroke(params)
result.success(null)
} else if (call.method == "setDraw") {
if (call.arguments<Boolean>()!!) {
touchHelper.openRawDrawing()
touchHelper.setRawDrawingEnabled(true)
touchHelper.setRawDrawingRenderEnabled(true)
} else {
touchHelper.closeRawDrawing()
}

result.success(null)
} else {
result.notImplemented()
}
}

override fun dispose() {
touchHelper.closeRawDrawing()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package com.example.onyxsdk_pen
import android.content.Context
import android.view.View
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory

class OnyxsdkPenAreaFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
class OnyxsdkPenAreaFactory(private val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return OnyxsdkPenArea(context, viewId, creationParams)
return OnyxsdkPenArea(context, messenger, viewId, creationParams)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class OnyxsdkPenPlugin: FlutterPlugin, MethodCallHandler {
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
private lateinit var channel : MethodChannel

override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "onyxsdk_pen")
Expand All @@ -28,7 +28,10 @@ class OnyxsdkPenPlugin: FlutterPlugin, MethodCallHandler {

binding
.platformViewRegistry
.registerViewFactory("onyxsdk_pen_area", OnyxsdkPenAreaFactory())
.registerViewFactory(
"onyxsdk_pen_area",
OnyxsdkPenAreaFactory(binding.binaryMessenger)
)
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
Expand Down
Loading