UAT-8b: axis UI — mask carry-forward, remove axis line, slider
1. drawOverlay: checkerboard mask overlay now drawn in axis + measure states (mask carry-forward — slab is isolated) 2. Removed the separate red axis line + arrowhead — the rect's long side IS the axis 3. Rect rotates: uses axisState.rect.angle (from UAT-8a) 4. Replace three rotate buttons with slider (-180..180, step 1) + minus/plus nudge buttons (same pattern as Sensitivity slider) 587 lines, 109/109 tests, build OK.
This commit is contained in:
@@ -154,6 +154,15 @@
|
||||
axisState = reduceAxisState(axisState, { type: 'rotate', angle: rad });
|
||||
}
|
||||
|
||||
function onRotateSlider(deg) {
|
||||
if (!axisState) return;
|
||||
const currentDeg = axisState.rect.angle * 180 / Math.PI;
|
||||
const deltaDeg = deg - currentDeg;
|
||||
if (deltaDeg === 0) return;
|
||||
const rad = (deltaDeg * Math.PI) / 180;
|
||||
axisState = reduceAxisState(axisState, { type: 'rotate', angle: rad });
|
||||
}
|
||||
|
||||
function approveAxis() {
|
||||
if (!axisState) return;
|
||||
axisState = reduceAxisState(axisState, { type: 'approve' });
|
||||
@@ -239,7 +248,7 @@
|
||||
// Draw photo
|
||||
ctx.drawImage(photo, 0, 0, w, h);
|
||||
|
||||
if (state === 'mask' && maskResult) {
|
||||
if ((state === 'mask' || state === 'axis' || state === 'measure') && maskResult) {
|
||||
// Checkerboard on removed pixels
|
||||
const weights = maskResult.weights;
|
||||
const mask = maskResult.mask;
|
||||
@@ -263,42 +272,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
if ((state === 'axis' || state === 'measure') && rect) {
|
||||
// Draw bounding rectangle
|
||||
const cx = (rect.center.x / photoW) * w;
|
||||
const cy = (rect.center.y / photoH) * h;
|
||||
const rw = (rect.width / photoW) * w;
|
||||
const rh = (rect.height / photoH) * h;
|
||||
const angle = rect.angle;
|
||||
if ((state === 'axis' || state === 'measure') && axisState) {
|
||||
// Draw bounding rectangle — uses the ROTATED rect from axisState.
|
||||
// The rect's long side IS the length axis (no separate line).
|
||||
const r = axisState.rect;
|
||||
const cx = (r.center.x / photoW) * w;
|
||||
const cy = (r.center.y / photoH) * h;
|
||||
const rw = (r.width / photoW) * w;
|
||||
const rh = (r.height / photoH) * h;
|
||||
ctx.save();
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate(angle);
|
||||
ctx.rotate(r.angle);
|
||||
ctx.strokeStyle = '#00ff88';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(-rw / 2, -rh / 2, rw, rh);
|
||||
// Draw axis line on long side
|
||||
const axis = axisState?.axis;
|
||||
if (axis) {
|
||||
const axLen = (Math.max(rect.width, rect.height) / photoW) * w;
|
||||
const axScreenX = axis.x * axLen;
|
||||
const axScreenY = axis.y * axLen;
|
||||
ctx.strokeStyle = '#ff4444';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-axScreenX / 2, -axScreenY / 2);
|
||||
ctx.lineTo(axScreenX / 2, axScreenY / 2);
|
||||
ctx.stroke();
|
||||
// Arrowhead
|
||||
const tipX = axScreenX / 2;
|
||||
const tipY = axScreenY / 2;
|
||||
const aAng = Math.atan2(axScreenY, axScreenX);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tipX, tipY);
|
||||
ctx.lineTo(tipX - 12 * Math.cos(aAng - 0.4), tipY - 12 * Math.sin(aAng - 0.4));
|
||||
ctx.moveTo(tipX, tipY);
|
||||
ctx.lineTo(tipX - 12 * Math.cos(aAng + 0.4), tipY - 12 * Math.sin(aAng + 0.4));
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
@@ -404,9 +391,13 @@
|
||||
<div class="controls">
|
||||
<div class="control-row">
|
||||
<label>Rotate</label>
|
||||
<button class="nudge" onclick={() => rotateAxisBy(-5)}>−5°</button>
|
||||
<button class="nudge" onclick={() => rotateAxisBy(5)}>+5°</button>
|
||||
<button class="nudge" onclick={() => rotateAxisBy(90)}>90°</button>
|
||||
<button class="nudge" onclick={() => rotateAxisBy(-5)}>−</button>
|
||||
<input type="range" min="-180" max="180" step="1"
|
||||
value={Math.round(axisState.rect.angle * 180 / Math.PI)}
|
||||
oninput={(e) => onRotateSlider(Number(e.target.value))}
|
||||
aria-label="Rotate axis" />
|
||||
<span class="val">{Math.round(axisState.rect.angle * 180 / Math.PI)}°</span>
|
||||
<button class="nudge" onclick={() => rotateAxisBy(5)}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
|
||||
Reference in New Issue
Block a user