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:
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user