software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -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 + "'}";
}
}