fix(paperjs): fix scale bug

This commit is contained in:
2026-03-04 10:36:24 +08:00
parent 448a64df3b
commit 19010c4b41
3 changed files with 325 additions and 140 deletions

View File

@@ -72,6 +72,8 @@ type Action =
const initialState: LabelState = new Map()
const operationTypeList = [101, 102, 103, 104]
let latestProjectDetailRequestSeq = 0
let latestTaskDetailRequestSeq = 0
// utils
const getRectPoints = ([sx, sy, w, h]: number[]) => {
@@ -200,8 +202,17 @@ const LabelPage = ({
const asyncGetProjectDetail = useCallback(async () => {
if (projectId) {
const requestSeq = ++latestProjectDetailRequestSeq
try {
const res = await getProjectDetail(projectId)
if (requestSeq !== latestProjectDetailRequestSeq) {
console.warn("[LabelNossr] ignore stale project detail response", {
projectId,
requestSeq,
latestRequestSeq: latestProjectDetailRequestSeq,
})
return
}
setProjectDetail(res)
let operations: Project.LabelSchemaList[] = []
let textOperations: Project.LabelSchemaList[] = []
@@ -362,6 +373,7 @@ const LabelPage = ({
const asyncGetTaskDetail = useCallback(async () => {
if (taskId) {
const requestSeq = ++latestTaskDetailRequestSeq
try {
usePaperStore.getState().setLoadingData(true)
// 获取当前任务全量数据
@@ -371,8 +383,24 @@ const LabelPage = ({
page_number: 1,
page_size: 1,
})
if (requestSeq !== latestTaskDetailRequestSeq) {
console.warn("[LabelNossr] ignore stale task list response", {
taskId,
requestSeq,
latestRequestSeq: latestTaskDetailRequestSeq,
})
return
}
let detail = res.task_list[0]
detail = await normalizeVideoTaskData(detail)
if (requestSeq !== latestTaskDetailRequestSeq) {
console.warn("[LabelNossr] ignore stale normalized task response", {
taskId,
requestSeq,
latestRequestSeq: latestTaskDetailRequestSeq,
})
return
}
setTaskDetail(detail)
// 帧列表
useBottomToolsStore
@@ -383,6 +411,14 @@ const LabelPage = ({
let turn1time = 1
let turn2time = 1
const flowRes = await getTaskWorkFlow(taskId)
if (requestSeq !== latestTaskDetailRequestSeq) {
console.warn("[LabelNossr] ignore stale workflow response", {
taskId,
requestSeq,
latestRequestSeq: latestTaskDetailRequestSeq,
})
return
}
flowRes.forEach((item) => {
if (item.task_status_src !== 4 && item.task_status_dst === 4) {
flowCommentArr.push({
@@ -424,6 +460,14 @@ const LabelPage = ({
})
// 获取并处理当前任务标注结果数据
const dataRes = await getLabelResult(taskId)
if (requestSeq !== latestTaskDetailRequestSeq) {
console.warn("[LabelNossr] ignore stale label result response", {
taskId,
requestSeq,
latestRequestSeq: latestTaskDetailRequestSeq,
})
return
}
const { results } = dataRes
const resultImageEntries = results.flatMap((item) =>
expandLabelResultImages(item)
@@ -714,6 +758,7 @@ const LabelPage = ({
useDescToolsStore.getState().updateFlag(true)
useRightToolsStore.getState().setPathGroupMap(pathGroupData)
} catch (err) {
if (requestSeq !== latestTaskDetailRequestSeq) return
console.log(err)
setOldWorkLoad(null)
setTaskDetail(undefined)
@@ -729,7 +774,9 @@ const LabelPage = ({
})
}
} finally {
usePaperStore.getState().setLoadingData(false)
if (requestSeq === latestTaskDetailRequestSeq) {
usePaperStore.getState().setLoadingData(false)
}
}
}
}, [

View File

@@ -633,6 +633,52 @@ const PaperContainer = (
let operationSchema = getOperationSchema(operationId)
if (operationSchema && imageData) {
const toPaperPoint = (segment: any): paper.Point | null => {
if (segment instanceof paper.Point) return segment
if (segment instanceof paper.Segment) {
return new paper.Point(segment.point)
}
if (
Array.isArray(segment) &&
segment.length >= 2 &&
typeof segment[0] === "number" &&
typeof segment[1] === "number"
) {
return new paper.Point(segment[0], segment[1])
}
if (
Array.isArray(segment) &&
Array.isArray(segment[0]) &&
segment[0].length >= 2 &&
typeof segment[0][0] === "number" &&
typeof segment[0][1] === "number"
) {
return new paper.Point(segment[0][0], segment[0][1])
}
if (
segment?.point &&
typeof segment.point.x === "number" &&
typeof segment.point.y === "number"
) {
return new paper.Point(segment.point.x, segment.point.y)
}
if (
segment?.point &&
Array.isArray(segment.point) &&
segment.point.length >= 2 &&
typeof segment.point[0] === "number" &&
typeof segment.point[1] === "number"
) {
return new paper.Point(segment.point[0], segment.point[1])
}
return null
}
const toPaperPoints = (segments: any[]) => {
return segments
.map((segment) => toPaperPoint(segment))
.filter((point): point is paper.Point => point !== null)
}
let strokeColor = `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})`
let fillColor = `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.3)`
let blankColor = `rgba(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]},0.01)`
@@ -646,69 +692,65 @@ const PaperContainer = (
case "多边形":
paths?.forEach((item) => {
let outerPaths =
item[1]?.map((child) => {
return addPolygon(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Point) {
return segment
} else {
return new paper.Point(segment[0], segment[1])
item[1]
?.map((child) => {
const points = toPaperPoints(child)
if (!points.length) return null
return addPolygon(
renderPath,
points,
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "polygon",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
}
}),
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "polygon",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
}
)
}) || []
)
})
.filter((path): path is paper.Path => !!path) || []
let innerPaths =
item[3]?.map((child) => {
return addPolygon(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Point) {
return segment
} else {
return new paper.Point(segment[0], segment[1])
item[3]
?.map((child) => {
const points = toPaperPoints(child)
if (!points.length) return null
return addPolygon(
renderPath,
points,
{
strokeColor: strokeColor,
strokeWidth: 2,
fillColor: fillColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "polygon",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
isHollowPolygon: true,
}
}),
{
strokeColor: strokeColor,
strokeWidth: 2,
fillColor: fillColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "polygon",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
isHollowPolygon: true,
}
)
}) || []
)
})
.filter((path): path is paper.Path => !!path) || []
addCompoundPath(
renderCompoundPath,
@@ -767,10 +809,8 @@ const PaperContainer = (
item[1]?.forEach((child) => {
// 保存前排好序
child.forEach((segment: any, segmentIndex: number) => {
let point =
segment instanceof paper.Segment
? new paper.Point(segment.point)
: new paper.Point(segment[0], segment[1])
let point = toPaperPoint(segment)
if (!point) return
addPoint(
renderCircle,
point,
@@ -788,15 +828,11 @@ const PaperContainer = (
}
)
})
const points = toPaperPoints(child)
if (!points.length) return
addPointLine(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Segment) {
return new paper.Point(segment.point)
} else {
return new paper.Point(segment[0], segment[1])
}
}),
points,
{
strokeColor: strokeColor,
strokeWidth: 2,
@@ -819,37 +855,35 @@ const PaperContainer = (
case "多线段":
paths?.forEach((item) => {
let outerPaths =
item[1]?.map((child) => {
return addBrush(
renderPath,
child.map((segment: any) => {
if (segment instanceof paper.Point) {
return segment
} else {
return new paper.Point(segment[0], segment[1])
item[1]
?.map((child) => {
const points = toPaperPoints(child)
if (!points.length) return null
return addBrush(
renderPath,
points,
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "brush",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
}
}),
{
strokeColor: strokeColor,
strokeWidth: 2,
// fillColor: fillColor,
fillColor: blankColor,
visible:
!paperPathStatus[activeImage + item[0]]?.["HIDE"],
},
{
type: "brush",
id: item[0],
imageId: activeImage,
operationId: operationId,
fillColor: fillColor,
blankColor: blankColor,
selected: paperSelectedPath[activeImage]?.includes(
item[0]
),
}
)
}) || []
)
})
.filter((path): path is paper.Path => !!path) || []
addCompoundPath(
renderCompoundPath,
@@ -876,13 +910,18 @@ const PaperContainer = (
case "2D框":
paths?.forEach((item) => {
item[1].forEach((child) => {
let flag = child[0] instanceof paper.Point
let p = flag
? child[1]
: new paper.Point(child[1][0], child[1][1])
let dp = flag
? child[3]
: new paper.Point(child[3][0], child[3][1])
let p = toPaperPoint(child[1])
let dp = toPaperPoint(child[3])
if (!p || !dp) {
console.warn(
"[PaperContainer] skip invalid rectangle segment",
{
id: item[0],
child,
}
)
return
}
addRectangle(
renderRectangle,
@@ -1216,6 +1255,7 @@ const PaperContainer = (
}, [activeImage])
const [rasterInited, setRasterInited] = useState<boolean>(false)
const rasterLoadRequestIdRef = useRef(0)
const renderAndScaleRaster = useCallback(
async (paperGroup: paper.Group) => {
@@ -1224,6 +1264,24 @@ const PaperContainer = (
// });
if (activeImage && projectDetail?.id) {
const requestId = ++rasterLoadRequestIdRef.current
const requestImage = activeImage
const isStaleRasterRequest = () => {
const currentImage = useBottomToolsStore.getState().activeImage
const stale =
requestId !== rasterLoadRequestIdRef.current ||
currentImage !== requestImage
if (stale) {
console.warn("[PaperContainer] ignore stale raster callback", {
requestId,
latestRequestId: rasterLoadRequestIdRef.current,
requestImage,
currentImage,
})
}
return stale
}
console.log(usePaperStore.getState().paperScope)
// console.log(activeImage, projectDetail?.rpath);
notifications.show({
@@ -1245,10 +1303,12 @@ const PaperContainer = (
data_type: 0,
project_id: projectDetail?.id,
})
if (isStaleRasterRequest()) return
text = `data:image/jpeg;base64,${response}`
images.set(activeImage, text)
useImagesStore.getState().setImages(images)
}
if (isStaleRasterRequest()) return
if (!text) return
raster = renderRaster(paperGroup, {
source: text,
@@ -1259,9 +1319,29 @@ const PaperContainer = (
if (!raster) return
raster.onLoad = function () {
if (isStaleRasterRequest()) {
raster.remove()
return
}
// Scale the raster to fit the canvas while maintaining aspect ratio
let canvasWidth = paperScope.current?.view.bounds.width
let canvasHeight = paperScope.current?.view.bounds.height
let canvasWidth =
imgSize?.clientWidth ?? paperScope.current?.view.bounds.width
let canvasHeight =
imgSize?.clientHeight ?? paperScope.current?.view.bounds.height
if (!canvasWidth || !canvasHeight) {
console.warn(
"[PaperContainer] skip raster scale: invalid canvas",
{
requestId,
requestImage,
canvasWidth,
canvasHeight,
}
)
raster.remove()
return
}
let scaleX = canvasWidth! / raster.bounds.width
let scaleY = canvasHeight! / raster.bounds.height
@@ -1271,11 +1351,35 @@ const PaperContainer = (
])
let scale = Math.min(scaleX, scaleY) // Maintain aspect ratio
if (!Number.isFinite(scale) || scale <= 0) {
console.warn(
"[PaperContainer] skip raster scale: invalid scale",
{
requestId,
requestImage,
scale,
scaleX,
scaleY,
}
)
raster.remove()
return
}
// 将raster以00为中心点进行缩放
raster.scale(scale, new paper.Point(0, 0))
const currentImgScale =
usePaperStore.getState().rasterScale[activeImage]
console.debug("[PaperContainer] raster scale reconcile", {
image: activeImage,
currentImgScale,
nextScale: scale,
branch: !currentImgScale
? "init-scale"
: currentImgScale !== scale
? "rescale"
: "keep",
})
// 当前图片无缩放比例或缩放比例不等于上次记录比例时
if (!currentImgScale) {
setRasterScale(activeImage, scale)
@@ -1338,6 +1442,8 @@ const PaperContainer = (
},
[
activeImage,
imgSize?.clientHeight,
imgSize?.clientWidth,
projectDetail?.id,
setRasterPath,
@@ -1367,6 +1473,7 @@ const PaperContainer = (
useEffect(() => {
if (loadingData) return
if (!imgSize?.clientWidth || !imgSize?.clientHeight) return
const currentGroup = usePaperStore.getState().group
if (currentGroup && projectDetail) {
if (!useTopToolsStore.getState().saveCurrentScale) {
@@ -1382,7 +1489,14 @@ const PaperContainer = (
currentGroup
)
}
}, [activeImage, loadImageAndPolygons, loadingData, projectDetail])
}, [
activeImage,
imgSize?.clientHeight,
imgSize?.clientWidth,
loadImageAndPolygons,
loadingData,
projectDetail,
])
useEffect(() => {
if (rasterInited) {

View File

@@ -1,5 +1,53 @@
import paper from "paper"
const scaleFlatPoint = (point: any, scale: number): [number, number] => {
return [point[0] * scale, point[1] * scale]
}
const scaleSegmentToPoint = (seg: any, scale: number): any => {
if (seg instanceof paper.Point) {
return [seg.x * scale, seg.y * scale]
}
if (seg instanceof paper.Segment) {
return [seg.point.x * scale, seg.point.y * scale]
}
if (
Array.isArray(seg) &&
seg.length >= 2 &&
typeof seg[0] === "number" &&
typeof seg[1] === "number"
) {
return scaleFlatPoint(seg, scale)
}
if (
Array.isArray(seg) &&
Array.isArray(seg[0]) &&
seg[0].length >= 2 &&
typeof seg[0][0] === "number" &&
typeof seg[0][1] === "number"
) {
// Support paper JSON segment format: [[x,y], [handleInX, handleInY], [handleOutX, handleOutY]]
return scaleFlatPoint(seg[0], scale)
}
if (
seg?.point &&
typeof seg.point.x === "number" &&
typeof seg.point.y === "number"
) {
return [seg.point.x * scale, seg.point.y * scale]
}
if (
seg?.point &&
Array.isArray(seg.point) &&
seg.point.length >= 2 &&
typeof seg.point[0] === "number" &&
typeof seg.point[1] === "number"
) {
return [seg.point[0] * scale, seg.point[1] * scale]
}
return seg
}
// 调整原始比例点位到当前图片的缩放比例
export const adjustPoints: (
id: string,
@@ -34,25 +82,13 @@ export const adjustPoints: (
let adjustedPoints = points.map((child) => {
return child.map((seg: any) => {
if (seg instanceof paper.Point) {
return [seg.x * scale, seg.y * scale]
} else if (seg instanceof paper.Segment) {
return [seg.point.x * scale, seg.point.y * scale]
} else {
return seg.map((item: any) => item * scale)
}
return scaleSegmentToPoint(seg, scale)
})
})
let adjustedHollowPoints = hollowPoints.map((child) => {
return child.map((seg: any) => {
if (seg instanceof paper.Point) {
return [seg.x * scale, seg.y * scale]
} else if (seg instanceof paper.Segment) {
return [seg.point.x * scale, seg.point.y * scale]
} else {
return seg.map((item: any) => item * scale)
}
return scaleSegmentToPoint(seg, scale)
})
})
@@ -103,25 +139,13 @@ export const adjustAllPoints: (
let adjustedPoints = points.map((child) => {
return child.map((seg: any) => {
if (seg instanceof paper.Point) {
return [seg.x * scale, seg.y * scale]
} else if (seg instanceof paper.Segment) {
return [seg.point.x * scale, seg.point.y * scale]
} else {
return seg.map((item: any) => item * scale)
}
return scaleSegmentToPoint(seg, scale)
})
})
let adjustedHollowPoints = hollowPoints.map((child) => {
return child.map((seg: any) => {
if (seg instanceof paper.Point) {
return [seg.x * scale, seg.y * scale]
} else if (seg instanceof paper.Segment) {
return [seg.point.x * scale, seg.point.y * scale]
} else {
return seg.map((item: any) => item * scale)
}
return scaleSegmentToPoint(seg, scale)
})
})