software copyright
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 自然写互动课堂应用开发SDK软件 V1.0
|
||||
* StrokePath - 笔迹路径数据模型
|
||||
*
|
||||
* 描述:封装一条完整笔画的坐标序列、属性和元数据
|
||||
*/
|
||||
|
||||
package com.writech.sdk.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 笔迹路径模型
|
||||
* 代表从落笔到抬笔的一条完整笔画数据
|
||||
*/
|
||||
public class StrokePath implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/* ========== 采样点内部类 ========== */
|
||||
|
||||
/** 单个笔迹采样点 */
|
||||
public static class Point implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** X坐标(屏幕像素或物理mm,取决于坐标空间) */
|
||||
public float x;
|
||||
|
||||
/** Y坐标 */
|
||||
public float y;
|
||||
|
||||
/** 压力值(归一化 0.0~1.0) */
|
||||
public float pressure;
|
||||
|
||||
/** 时间戳(相对于笔画开始时间的毫秒偏移) */
|
||||
public long timeOffset;
|
||||
|
||||
/** 笔尖倾斜角度(度,0-90,0为垂直,部分笔支持) */
|
||||
public float tiltAngle;
|
||||
|
||||
/** 笔尖方位角(度,0-360,部分笔支持) */
|
||||
public float azimuthAngle;
|
||||
|
||||
public Point() {}
|
||||
|
||||
public Point(float x, float y, float pressure, long timeOffset) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pressure = pressure;
|
||||
this.timeOffset = timeOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + x + "," + y + ",p=" + pressure + ",t=" + timeOffset + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 笔画属性 ========== */
|
||||
|
||||
/** 笔画唯一ID */
|
||||
private String strokeId;
|
||||
|
||||
/** 来源笔设备MAC地址 */
|
||||
private String penMac;
|
||||
|
||||
/** 学生ID */
|
||||
private String studentId;
|
||||
|
||||
/** 页面ID(标识书写所在页面) */
|
||||
private String pageId;
|
||||
|
||||
/** 笔画开始时间(绝对时间戳毫秒) */
|
||||
private long startTimestamp;
|
||||
|
||||
/** 笔画结束时间 */
|
||||
private long endTimestamp;
|
||||
|
||||
/** 笔画颜色(ARGB) */
|
||||
private int color = 0xFF000000;
|
||||
|
||||
/** 笔画基础线宽(像素) */
|
||||
private float baseWidth = 3.0f;
|
||||
|
||||
/** 采样点列表 */
|
||||
private List<Point> points;
|
||||
|
||||
/* ========== 分析结果(由OCR/AI引擎填充) ========== */
|
||||
|
||||
/** 识别的文字内容 */
|
||||
private String recognizedText;
|
||||
|
||||
/** 识别置信度 */
|
||||
private float recognitionConfidence;
|
||||
|
||||
/** 笔顺序号(在整个书写序列中的顺序) */
|
||||
private int strokeOrder;
|
||||
|
||||
/** 是否为有效笔画(排除误触等) */
|
||||
private boolean isValid = true;
|
||||
|
||||
/* ========== 构造函数 ========== */
|
||||
|
||||
public StrokePath() {
|
||||
this.points = new ArrayList<>();
|
||||
}
|
||||
|
||||
public StrokePath(String strokeId, String penMac) {
|
||||
this.strokeId = strokeId;
|
||||
this.penMac = penMac;
|
||||
this.points = new ArrayList<>();
|
||||
this.startTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/* ========== 点操作方法 ========== */
|
||||
|
||||
/** 添加采样点 */
|
||||
public void addPoint(float x, float y, float pressure, long timeOffset) {
|
||||
points.add(new Point(x, y, pressure, timeOffset));
|
||||
}
|
||||
|
||||
/** 添加采样点(含倾斜角) */
|
||||
public void addPointWithTilt(float x, float y, float pressure,
|
||||
long timeOffset, float tilt, float azimuth) {
|
||||
Point p = new Point(x, y, pressure, timeOffset);
|
||||
p.tiltAngle = tilt;
|
||||
p.azimuthAngle = azimuth;
|
||||
points.add(p);
|
||||
}
|
||||
|
||||
/** 获取采样点数量 */
|
||||
public int getPointCount() {
|
||||
return points.size();
|
||||
}
|
||||
|
||||
/** 获取指定索引的采样点 */
|
||||
public Point getPoint(int index) {
|
||||
if (index >= 0 && index < points.size()) {
|
||||
return points.get(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 获取所有采样点 */
|
||||
public List<Point> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
/* ========== 笔画几何计算 ========== */
|
||||
|
||||
/** 计算笔画总长度(像素) */
|
||||
public float calculateLength() {
|
||||
float length = 0;
|
||||
for (int i = 1; i < points.size(); i++) {
|
||||
Point p0 = points.get(i - 1);
|
||||
Point p1 = points.get(i);
|
||||
float dx = p1.x - p0.x;
|
||||
float dy = p1.y - p0.y;
|
||||
length += (float) Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/** 计算笔画包围盒 */
|
||||
public float[] getBoundingBox() {
|
||||
if (points.isEmpty()) return new float[]{0, 0, 0, 0};
|
||||
|
||||
float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE;
|
||||
float maxX = Float.MIN_VALUE, maxY = Float.MIN_VALUE;
|
||||
|
||||
for (Point p : points) {
|
||||
if (p.x < minX) minX = p.x;
|
||||
if (p.y < minY) minY = p.y;
|
||||
if (p.x > maxX) maxX = p.x;
|
||||
if (p.y > maxY) maxY = p.y;
|
||||
}
|
||||
|
||||
return new float[]{minX, minY, maxX, maxY};
|
||||
}
|
||||
|
||||
/** 计算平均书写速度(像素/毫秒) */
|
||||
public float calculateAverageSpeed() {
|
||||
if (points.size() < 2) return 0;
|
||||
|
||||
float totalLength = calculateLength();
|
||||
long duration = points.get(points.size() - 1).timeOffset - points.get(0).timeOffset;
|
||||
|
||||
return duration > 0 ? totalLength / duration : 0;
|
||||
}
|
||||
|
||||
/** 计算平均压力 */
|
||||
public float calculateAveragePressure() {
|
||||
if (points.isEmpty()) return 0;
|
||||
float sum = 0;
|
||||
for (Point p : points) {
|
||||
sum += p.pressure;
|
||||
}
|
||||
return sum / points.size();
|
||||
}
|
||||
|
||||
/** 获取书写持续时间(毫秒) */
|
||||
public long getDuration() {
|
||||
if (points.size() < 2) return 0;
|
||||
return points.get(points.size() - 1).timeOffset - points.get(0).timeOffset;
|
||||
}
|
||||
|
||||
/* ========== 序列化方法 ========== */
|
||||
|
||||
/**
|
||||
* 将笔画数据序列化为紧凑的二进制格式
|
||||
* 用于BLE传输和本地缓存
|
||||
*
|
||||
* 格式:
|
||||
* [4字节 点数][每个点: 4字节x + 4字节y + 2字节pressure + 4字节timeOffset]
|
||||
*/
|
||||
public byte[] toBytes() {
|
||||
int pointCount = points.size();
|
||||
byte[] data = new byte[4 + pointCount * 14];
|
||||
|
||||
/* 写入点数(大端序) */
|
||||
data[0] = (byte) ((pointCount >> 24) & 0xFF);
|
||||
data[1] = (byte) ((pointCount >> 16) & 0xFF);
|
||||
data[2] = (byte) ((pointCount >> 8) & 0xFF);
|
||||
data[3] = (byte) (pointCount & 0xFF);
|
||||
|
||||
int offset = 4;
|
||||
for (Point p : points) {
|
||||
/* 写入X坐标(float → 4字节) */
|
||||
int fx = Float.floatToIntBits(p.x);
|
||||
data[offset++] = (byte) ((fx >> 24) & 0xFF);
|
||||
data[offset++] = (byte) ((fx >> 16) & 0xFF);
|
||||
data[offset++] = (byte) ((fx >> 8) & 0xFF);
|
||||
data[offset++] = (byte) (fx & 0xFF);
|
||||
|
||||
/* 写入Y坐标 */
|
||||
int fy = Float.floatToIntBits(p.y);
|
||||
data[offset++] = (byte) ((fy >> 24) & 0xFF);
|
||||
data[offset++] = (byte) ((fy >> 16) & 0xFF);
|
||||
data[offset++] = (byte) ((fy >> 8) & 0xFF);
|
||||
data[offset++] = (byte) (fy & 0xFF);
|
||||
|
||||
/* 写入压力值(归一化后*65535转uint16) */
|
||||
int pressure16 = (int) (p.pressure * 65535);
|
||||
data[offset++] = (byte) ((pressure16 >> 8) & 0xFF);
|
||||
data[offset++] = (byte) (pressure16 & 0xFF);
|
||||
|
||||
/* 写入时间偏移(uint32) */
|
||||
long t = p.timeOffset;
|
||||
data[offset++] = (byte) ((t >> 24) & 0xFF);
|
||||
data[offset++] = (byte) ((t >> 16) & 0xFF);
|
||||
data[offset++] = (byte) ((t >> 8) & 0xFF);
|
||||
data[offset++] = (byte) (t & 0xFF);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/* ========== Getter / Setter ========== */
|
||||
|
||||
public String getStrokeId() { return strokeId; }
|
||||
public void setStrokeId(String strokeId) { this.strokeId = strokeId; }
|
||||
|
||||
public String getPenMac() { return penMac; }
|
||||
public void setPenMac(String penMac) { this.penMac = penMac; }
|
||||
|
||||
public String getStudentId() { return studentId; }
|
||||
public void setStudentId(String studentId) { this.studentId = studentId; }
|
||||
|
||||
public String getPageId() { return pageId; }
|
||||
public void setPageId(String pageId) { this.pageId = pageId; }
|
||||
|
||||
public long getStartTimestamp() { return startTimestamp; }
|
||||
public void setStartTimestamp(long t) { this.startTimestamp = t; }
|
||||
|
||||
public long getEndTimestamp() { return endTimestamp; }
|
||||
public void setEndTimestamp(long t) { this.endTimestamp = t; }
|
||||
|
||||
public int getColor() { return color; }
|
||||
public void setColor(int color) { this.color = color; }
|
||||
|
||||
public float getBaseWidth() { return baseWidth; }
|
||||
public void setBaseWidth(float w) { this.baseWidth = w; }
|
||||
|
||||
public String getRecognizedText() { return recognizedText; }
|
||||
public void setRecognizedText(String text) { this.recognizedText = text; }
|
||||
|
||||
public float getRecognitionConfidence() { return recognitionConfidence; }
|
||||
public void setRecognitionConfidence(float c) { this.recognitionConfidence = c; }
|
||||
|
||||
public int getStrokeOrder() { return strokeOrder; }
|
||||
public void setStrokeOrder(int order) { this.strokeOrder = order; }
|
||||
|
||||
public boolean isValid() { return isValid; }
|
||||
public void setValid(boolean valid) { isValid = valid; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StrokePath{id='" + strokeId + "', points=" + points.size()
|
||||
+ ", duration=" + getDuration() + "ms"
|
||||
+ ", text='" + recognizedText + "'}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user