fix: round caption edges the same way as the editor

Python round() uses ties-to-even, so a coordinate on a half step was one
unit off from Copy JSON, which rounds halves toward +inf.
This commit is contained in:
by-ae
2026-11-02 07:21:23 +10:00
parent c4975e62b1
commit f0ddd9b5fd
3 changed files with 28 additions and 4 deletions
+14 -4
View File
@@ -2,6 +2,7 @@
import json
import logging
import math
import os
import re
@@ -341,17 +342,26 @@ def _render_preview(boxes, width, height, bg_guid="", background_opacity=0.5):
return torch.from_numpy(arr).unsqueeze(0)
def _round_half_up(value):
"""Nearest integer, halves toward +inf. Matches JS Math.round.
Python round() uses ties-to-even, so a caption edge on x.5 disagreed
with Copy JSON by one unit.
"""
return int(math.floor(value + 0.5))
def _scale_axis(value):
"""Map one finite axis onto Ideogram's integer 0-1000 range.
Clamp before rounding. round() raises OverflowError once value * 1000
exceeds the float range, which a hand-edited region can do.
Values outside 0-1 are clamped before scaling. A huge finite coordinate
used to overflow inside round().
"""
if value >= 1:
return 1000
if value <= 0:
return 0
return int(round(value * 1000))
return _round_half_up(value * 1000)
def _box_coords(box):
@@ -399,7 +409,7 @@ def _preview_edges(box, rw, rh):
return scale
if value <= 0:
return 0
return max(0, min(scale, int(round(value * scale))))
return max(0, min(scale, _round_half_up(value * scale)))
x1, y1, x2, y2 = edge(x, rw), edge(y, rh), edge(x + w, rw), edge(y + h, rh)
if x2 < x1:
+2
View File
@@ -104,6 +104,8 @@ describe("ideogram bbox import", () => {
it("exports a usable bbox and drops coordinates that are not finite", () => {
assert.deepEqual(rectToIdeogramBbox({ x: 0.1, y: 0.2, w: 0.3, h: 0.4 }), [200, 100, 600, 400]);
// Halves go toward +inf, the same rule the prompt node uses.
assert.deepEqual(rectToIdeogramBbox({ x: 0.0025, y: 0.0005, w: 0.001, h: 0.001 }), [1, 3, 2, 4]);
assert.deepEqual(rectToIdeogramBbox({ x: "0.1", y: "0.2", w: "0.3", h: "0.4" }), [200, 100, 600, 400]);
assert.deepEqual(rectToIdeogramBbox({ x: 0.5, y: 0.6, w: -0.2, h: -0.1 }), [500, 300, 600, 500]);
assert.equal(rectToIdeogramBbox({ x: Number.NaN, y: 0.2, w: 0.3, h: 0.4 }), null);
+12
View File
@@ -86,6 +86,12 @@ class TestPromptHelpers(unittest.TestCase):
result = _norm_bbox({"x": 1e308, "y": 0.0, "w": 1e308, "h": 0.2})
self.assertEqual(result, [0, 1000, 200, 1000])
def test_norm_bbox_half_steps_round_like_the_editor(self):
# 2.5 and 0.5. Copy JSON uses JS Math.round (half toward +inf).
# Python round() uses ties-to-even, which made the prompt one unit off.
box = {"x": 0.0025, "y": 0.0005, "w": 0.001, "h": 0.001}
self.assertEqual(_norm_bbox(box), [1, 3, 2, 4])
def test_elements_from_boxes_omits_unusable_bbox(self):
elements = _elements_from_boxes([
{"type": "obj", "desc": "bad", "x": "nope", "y": 0, "w": 0.2, "h": 0.2},
@@ -111,6 +117,12 @@ class TestPromptHelpers(unittest.TestCase):
(10, 10, 40, 30),
)
def test_preview_edges_half_pixel_rounds_like_the_editor(self):
self.assertEqual(
_preview_edges({"x": 0.005, "y": 0, "w": 0.01, "h": 0.02}, 100, 100),
(1, 0, 2, 2),
)
def test_elements_keep_schema_types_when_fields_are_not_strings(self):
elements = _elements_from_boxes([
{