bboxHint.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. const BBOX_MAX_NUM = 16;
  2. const BBOX_WARNING_SIZE = 1280;
  3. const DEFAULT_X = 0.4;
  4. const DEFAULT_Y = 0.4;
  5. const DEFAULT_H = 0.2;
  6. const DEFAULT_W = 0.2;
  7. // ref: https://html-color.codes/
  8. const COLOR_MAP = [
  9. ['#ff0000', 'rgba(255, 0, 0, 0.3)'], // red
  10. ['#ff9900', 'rgba(255, 153, 0, 0.3)'], // orange
  11. ['#ffff00', 'rgba(255, 255, 0, 0.3)'], // yellow
  12. ['#33cc33', 'rgba(51, 204, 51, 0.3)'], // green
  13. ['#33cccc', 'rgba(51, 204, 204, 0.3)'], // indigo
  14. ['#0066ff', 'rgba(0, 102, 255, 0.3)'], // blue
  15. ['#6600ff', 'rgba(102, 0, 255, 0.3)'], // purple
  16. ['#cc00cc', 'rgba(204, 0, 204, 0.3)'], // dark pink
  17. ['#ff6666', 'rgba(255, 102, 102, 0.3)'], // light red
  18. ['#ffcc66', 'rgba(255, 204, 102, 0.3)'], // light orange
  19. ['#99cc00', 'rgba(153, 204, 0, 0.3)'], // lime green
  20. ['#00cc99', 'rgba(0, 204, 153, 0.3)'], // teal
  21. ['#0099cc', 'rgba(0, 153, 204, 0.3)'], // steel blue
  22. ['#9933cc', 'rgba(153, 51, 204, 0.3)'], // lavender
  23. ['#ff3399', 'rgba(255, 51, 153, 0.3)'], // hot pink
  24. ['#996633', 'rgba(153, 102, 51, 0.3)'], // brown
  25. ];
  26. const RESIZE_BORDER = 5;
  27. const MOVE_BORDER = 5;
  28. const t2i_bboxes = new Array(BBOX_MAX_NUM).fill(null);
  29. const i2i_bboxes = new Array(BBOX_MAX_NUM).fill(null);
  30. function getUpscalerFactor() {
  31. let upscalerInput = parseFloat(gradioApp().querySelector('#MD-upscaler-factor input').value);
  32. if (!isNaN(upscalerInput)) {
  33. return upscalerInput;
  34. }
  35. }
  36. function updateInput(target){
  37. let e = new Event("input", { bubbles: true })
  38. Object.defineProperty(e, "target", {value: target})
  39. target.dispatchEvent(e);
  40. }
  41. function onBoxEnableClick(is_t2i, idx, enable) {
  42. let canvas = null;
  43. let bboxes = null;
  44. if (is_t2i) {
  45. ref_div = gradioApp().querySelector('#MD-bbox-ref-t2i');
  46. bboxes = t2i_bboxes;
  47. } else {
  48. ref_div = gradioApp().querySelector('#MD-bbox-ref-i2i');
  49. bboxes = i2i_bboxes;
  50. }
  51. canvas = ref_div.querySelector('img');
  52. if (!canvas) { return false; }
  53. if (enable) {
  54. // Check if the bounding box already exists
  55. if (!bboxes[idx]) {
  56. // Initialize bounding box
  57. const bbox = [DEFAULT_X, DEFAULT_Y, DEFAULT_W, DEFAULT_H];
  58. const colorMap = COLOR_MAP[idx % COLOR_MAP.length];
  59. const div = document.createElement('div');
  60. div.id = 'MD-bbox-' + (is_t2i ? 't2i-' : 'i2i-') + idx;
  61. div.style.left = '0px';
  62. div.style.top = '0px';
  63. div.style.width = '0px';
  64. div.style.height = '0px';
  65. div.style.position = 'absolute';
  66. div.style.border = '2px solid ' + colorMap[0];
  67. div.style.background = colorMap[1];
  68. div.style.zIndex = '900';
  69. div.style.display = 'none';
  70. // A text tip to warn the user if bbox is too large
  71. const tip = document.createElement('span');
  72. tip.id = 'MD-tip-' + (is_t2i ? 't2i-' : 'i2i-') + idx;
  73. tip.style.position = 'absolute';
  74. tip.style.fontSize = '12px';
  75. tip.style.color = colorMap[0];
  76. tip.style.fontWeight = 'bold';
  77. tip.style.zIndex = '901';
  78. tip.style.display = 'none';
  79. tip.style.transform = 'translate(-50%, -50%)';
  80. tip.style.left = '50%';
  81. tip.style.top = '50%';
  82. tip.innerHTML = 'Warning: Region very large!<br>Take care of VRAM usage!';
  83. tip.style.textAlign = 'center';
  84. div.appendChild(tip);
  85. div.addEventListener('mousedown', function (e) {
  86. if (e.button === 0) {
  87. onBoxMouseDown(e, is_t2i, idx);
  88. }
  89. });
  90. div.addEventListener('mousemove', function (e) {
  91. updateCursorStyle(e, is_t2i, idx);
  92. });
  93. ref_div.appendChild(div);
  94. bboxes[idx] = [div, bbox];
  95. }
  96. // Show the bounding box
  97. let [div, bbox] = bboxes[idx];
  98. let [x, y, w, h] = bbox;
  99. let vpScale = Math.min(canvas.clientWidth / canvas.naturalWidth, canvas.clientHeight / canvas.naturalHeight);
  100. displayBox(canvas, is_t2i, vpScale, div, x, y, w, h);
  101. return true;
  102. } else {
  103. if (!bboxes[idx]) { return false; }
  104. const [div, _] = bboxes[idx];
  105. div.style.display = 'none';
  106. }
  107. return false;
  108. }
  109. function displayBox(canvas, is_t2i, vpScale, div, x, y, w, h) {
  110. // check null input
  111. if (!canvas || !div || x == null || y == null || w == null || h == null) { return; }
  112. // client: canvas widget display size
  113. // natural: content image real size
  114. let canvasCenterX = canvas.clientWidth / 2;
  115. let canvasCenterY = canvas.clientHeight / 2;
  116. let scaledX = canvas.naturalWidth * vpScale;
  117. let scaledY = canvas.naturalHeight * vpScale;
  118. let viewRectLeft = canvasCenterX - scaledX / 2;
  119. let viewRectRight = canvasCenterX + scaledX / 2;
  120. let viewRectTop = canvasCenterY - scaledY / 2;
  121. let viewRectDown = canvasCenterY + scaledY / 2;
  122. let xDiv = viewRectLeft + scaledX * x;
  123. let yDiv = viewRectTop + scaledY * y;
  124. let wDiv = Math.min(scaledX * w, viewRectRight - xDiv);
  125. let hDiv = Math.min(scaledY * h, viewRectDown - yDiv);
  126. // Calculate warning bbox size
  127. let upscalerFactor = 1.0;
  128. if (!is_t2i){
  129. upscalerFactor = getUpscalerFactor();
  130. }
  131. let maxSize = BBOX_WARNING_SIZE / upscalerFactor * vpScale;
  132. let maxW = maxSize / scaledX;
  133. let maxH = maxSize / scaledY;
  134. if (w > maxW || h > maxH) {
  135. div.querySelector('span').style.display = 'block';
  136. } else {
  137. div.querySelector('span').style.display = 'none';
  138. }
  139. // update <div> when not equal
  140. div.style.left = xDiv + 'px';
  141. div.style.top = yDiv + 'px';
  142. div.style.width = wDiv + 'px';
  143. div.style.height = hDiv + 'px';
  144. div.style.display = 'block';
  145. }
  146. function onBoxChange(is_t2i, idx, what, v) {
  147. // This function handles all the changes of the bounding box
  148. // Including the rendering and python slider update
  149. let bboxes = null;
  150. let canvas = null;
  151. if (is_t2i) {
  152. bboxes = t2i_bboxes;
  153. canvas = gradioApp().querySelector('#MD-bbox-ref-t2i img');
  154. } else {
  155. bboxes = i2i_bboxes;
  156. canvas = gradioApp().querySelector('#MD-bbox-ref-i2i img');
  157. }
  158. if (!bboxes[idx] || !canvas) {
  159. switch (what) {
  160. case 'x': return DEFAULT_X;
  161. case 'y': return DEFAULT_Y;
  162. case 'w': return DEFAULT_W;
  163. case 'h': return DEFAULT_H;
  164. }
  165. }
  166. let [div, bbox] = bboxes[idx];
  167. if (div.style.display === 'none') { return v; }
  168. let [x, y, w, h] = bbox;
  169. // parse trigger
  170. switch (what) {
  171. case 'x':
  172. x = v;
  173. bboxes[idx][1][0] = x;
  174. break;
  175. case 'y':
  176. y = v;
  177. bboxes[idx][1][1] = y;
  178. break;
  179. case 'w':
  180. w = v;
  181. bboxes[idx][1][2] = w;
  182. break;
  183. case 'h':
  184. h = v;
  185. bboxes[idx][1][3] = h;
  186. break;
  187. }
  188. let vpScale = Math.min(canvas.clientWidth / canvas.naturalWidth, canvas.clientHeight / canvas.naturalHeight);
  189. displayBox(canvas, is_t2i, vpScale, div, x, y, w, h);
  190. return v
  191. }
  192. function onBoxMouseDown(e, is_t2i, idx) {
  193. let bboxes = null;
  194. let canvas = null;
  195. if (is_t2i) {
  196. bboxes = t2i_bboxes;
  197. canvas = gradioApp().querySelector('#MD-bbox-ref-t2i img');
  198. } else {
  199. bboxes = i2i_bboxes;
  200. canvas = gradioApp().querySelector('#MD-bbox-ref-i2i img');
  201. }
  202. // Get the bounding box
  203. if (!canvas || !bboxes[idx]) return;
  204. let [div, bbox] = bboxes[idx];
  205. // Check if the click is inside the bounding box
  206. let boxRect = div.getBoundingClientRect();
  207. let mouseX = e.clientX;
  208. let mouseY = e.clientY;
  209. let resizeLeft = mouseX >= boxRect.left && mouseX <= boxRect.left + RESIZE_BORDER;
  210. let resizeRight = mouseX >= boxRect.right - RESIZE_BORDER && mouseX <= boxRect.right;
  211. let resizeTop = mouseY >= boxRect.top && mouseY <= boxRect.top + RESIZE_BORDER;
  212. let resizeBottom = mouseY >= boxRect.bottom - RESIZE_BORDER && mouseY <= boxRect.bottom;
  213. let moveHorizontal = mouseX >= boxRect.left + MOVE_BORDER && mouseX <= boxRect.right - MOVE_BORDER;
  214. let moveVertical = mouseY >= boxRect.top + MOVE_BORDER && mouseY <= boxRect.bottom - MOVE_BORDER;
  215. if (!resizeLeft && !resizeRight && !resizeTop && !resizeBottom && !moveHorizontal && !moveVertical) {
  216. return;
  217. }
  218. const horizontalPivot = resizeLeft ? bbox[0] + bbox[2] : bbox[0];
  219. const verticalPivot = resizeTop ? bbox[1] + bbox[3] : bbox[1];
  220. // Canvas can be regarded as invariant during the drag operation
  221. // Calculate in advance to reduce overhead
  222. // Calculate viewport scale based on the current canvas size and the natural image size
  223. let vpScale = Math.min(canvas.clientWidth / canvas.naturalWidth, canvas.clientHeight / canvas.naturalHeight);
  224. let vpOffset = canvas.getBoundingClientRect();
  225. // Calculate scaled dimensions of the canvas
  226. let scaledX = canvas.naturalWidth * vpScale;
  227. let scaledY = canvas.naturalHeight * vpScale;
  228. // Calculate the canvas center and view rectangle coordinates
  229. let canvasCenterX = (vpOffset.left + window.scrollX) + canvas.clientWidth / 2;
  230. let canvasCenterY = (vpOffset.top + window.scrollY) + canvas.clientHeight / 2;
  231. let viewRectLeft = canvasCenterX - scaledX / 2 - window.scrollX;
  232. let viewRectRight = canvasCenterX + scaledX / 2 - window.scrollX;
  233. let viewRectTop = canvasCenterY - scaledY / 2 - window.scrollY;
  234. let viewRectDown = canvasCenterY + scaledY / 2 - window.scrollY;
  235. mouseX = Math.min(Math.max(mouseX, viewRectLeft), viewRectRight);
  236. mouseY = Math.min(Math.max(mouseY, viewRectTop), viewRectDown);
  237. // The querySelector is not very efficient, so we query it once and reuse it
  238. const sliderIds = ['x', 'y', 'w', 'h'];
  239. const sliderSelectors = sliderIds.map(id => `#MD-${is_t2i ? 't2i' : 'i2i'}-${idx}-${id} input`);
  240. const sliderInputs = gradioApp().querySelectorAll(sliderSelectors.join(', '));
  241. // Move or resize the bounding box on mousemove
  242. function onMouseMove(e) {
  243. // Prevent selecting anything irrelevant
  244. e.preventDefault();
  245. // Get the new mouse position
  246. let newMouseX = e.clientX;
  247. let newMouseY = e.clientY;
  248. // clamp the mouse position to the view rectangle
  249. newMouseX = Math.min(Math.max(newMouseX, viewRectLeft), viewRectRight);
  250. newMouseY = Math.min(Math.max(newMouseY, viewRectTop), viewRectDown);
  251. // Calculate the mouse movement delta
  252. let dx = (newMouseX - mouseX) / scaledX;
  253. let dy = (newMouseY - mouseY) / scaledY;
  254. // Update the mouse position
  255. mouseX = newMouseX;
  256. mouseY = newMouseY;
  257. // if no move just return
  258. if (dx === 0 && dy === 0) return;
  259. // Update the mouse position
  260. let [x, y, w, h] = bbox;
  261. if (moveHorizontal && moveVertical) {
  262. // If moving the bounding box
  263. x = Math.min(Math.max(x + dx, 0), 1 - w);
  264. y = Math.min(Math.max(y + dy, 0), 1 - h);
  265. } else {
  266. // If resizing the bounding box
  267. if (resizeLeft || resizeRight) {
  268. if (x < horizontalPivot) {
  269. if (dx <= w) {
  270. // If still within the left side of the pivot
  271. x = x + dx;
  272. w = w - dx;
  273. } else {
  274. // If crossing the pivot
  275. w = dx - w;
  276. x = horizontalPivot;
  277. }
  278. } else {
  279. if (w + dx < 0) {
  280. // If still within the right side of the pivot
  281. x = horizontalPivot + w + dx;
  282. w = - dx - w;
  283. } else {
  284. // If crossing the pivot
  285. x = horizontalPivot;
  286. w = w + dx;
  287. }
  288. }
  289. // Clamp the bounding box to the image
  290. if (x < 0) {
  291. w = w + x;
  292. x = 0;
  293. } else if (x + w > 1) {
  294. w = 1 - x;
  295. }
  296. }
  297. // Same as above, but for the vertical axis
  298. if (resizeTop || resizeBottom) {
  299. if (y < verticalPivot) {
  300. if (dy <= h) {
  301. y = y + dy;
  302. h = h - dy;
  303. } else {
  304. h = dy - h;
  305. y = verticalPivot;
  306. }
  307. } else {
  308. if (h + dy < 0) {
  309. y = verticalPivot + h + dy;
  310. h = - dy - h;
  311. } else {
  312. y = verticalPivot;
  313. h = h + dy;
  314. }
  315. }
  316. if (y < 0) {
  317. h = h + y;
  318. y = 0;
  319. } else if (y + h > 1) {
  320. h = 1 - y;
  321. }
  322. }
  323. }
  324. let old_bbox = bboxes[idx][1];
  325. // If all the values are the same, just return
  326. if (old_bbox[0] === x && old_bbox[1] === y && old_bbox[2] === w && old_bbox[3] === h) return;
  327. // else update the bbox
  328. let event = new Event('input');
  329. let coords = [x, y, w, h];
  330. for (let i = 0; i < 4; i++) {
  331. if (old_bbox[i] !== coords[i]) {
  332. sliderInputs[2*i].value = coords[i];
  333. sliderInputs[2*i].dispatchEvent(event);
  334. }
  335. }
  336. }
  337. // Remove the mousemove and mouseup event listeners
  338. function onMouseUp() {
  339. document.removeEventListener('mousemove', onMouseMove);
  340. document.removeEventListener('mouseup', onMouseUp);
  341. }
  342. // Add the event listeners
  343. document.addEventListener('mousemove', onMouseMove);
  344. document.addEventListener('mouseup', onMouseUp);
  345. }
  346. function updateCursorStyle(e, is_t2i, idx) {
  347. // This function changes the cursor style when hovering over the bounding box
  348. let bboxes = is_t2i ? t2i_bboxes : i2i_bboxes;
  349. if (!bboxes[idx]) return;
  350. let [div, _] = bboxes[idx];
  351. let boxRect = div.getBoundingClientRect();
  352. let mouseX = e.clientX;
  353. let mouseY = e.clientY;
  354. let resizeLeft = mouseX >= boxRect.left && mouseX <= boxRect.left + RESIZE_BORDER;
  355. let resizeRight = mouseX >= boxRect.right - RESIZE_BORDER && mouseX <= boxRect.right;
  356. let resizeTop = mouseY >= boxRect.top && mouseY <= boxRect.top + RESIZE_BORDER;
  357. let resizeBottom = mouseY >= boxRect.bottom - RESIZE_BORDER && mouseY <= boxRect.bottom;
  358. if ((resizeLeft && resizeTop) || (resizeRight && resizeBottom)) {
  359. div.style.cursor = 'nwse-resize';
  360. } else if ((resizeLeft && resizeBottom) || (resizeRight && resizeTop)) {
  361. div.style.cursor = 'nesw-resize';
  362. } else if (resizeLeft || resizeRight) {
  363. div.style.cursor = 'ew-resize';
  364. } else if (resizeTop || resizeBottom) {
  365. div.style.cursor = 'ns-resize';
  366. } else {
  367. div.style.cursor = 'move';
  368. }
  369. }
  370. function updateTabBoxes(is_t2i) {
  371. // This function redraw all bounding boxes
  372. let bboxes = null;
  373. let canvas = null;
  374. if (is_t2i) {
  375. bboxes = t2i_bboxes;
  376. canvas = gradioApp().querySelector('#MD-bbox-ref-t2i img');
  377. } else {
  378. bboxes = i2i_bboxes;
  379. canvas = gradioApp().querySelector('#MD-bbox-ref-i2i img');
  380. }
  381. if (!canvas) return;
  382. let vpScale = Math.min(canvas.clientWidth / canvas.naturalWidth, canvas.clientHeight / canvas.naturalHeight);
  383. for (let idx = 0; idx < bboxes.length; idx++) {
  384. if (!bboxes[idx]) continue;
  385. let [div, bbox] = bboxes[idx];
  386. if (div.style.display === 'none') continue;
  387. let [x, y, w, h] = bbox;
  388. displayBox(canvas, is_t2i, vpScale, div, x, y, w, h);
  389. }
  390. }
  391. function updateAllBoxes() {
  392. // This function redraw all bounding boxes
  393. updateTabBoxes(true);
  394. updateTabBoxes(false);
  395. }
  396. window.addEventListener('resize', updateAllBoxes);
  397. function onCreateT2IRefClick(overwrite) {
  398. let width, height;
  399. if (overwrite) {
  400. const overwriteInputs = gradioApp().querySelectorAll('#MD-overwrite-width-t2i input, #MD-overwrite-height-t2i input');
  401. width = parseInt(overwriteInputs[0].value);
  402. height = parseInt(overwriteInputs[2].value);
  403. } else {
  404. const sizeInputs = gradioApp().querySelectorAll('#txt2img_width input, #txt2img_height input');
  405. width = parseInt(sizeInputs[0].value);
  406. height = parseInt(sizeInputs[2].value);
  407. }
  408. if (isNaN(width)) width = 512;
  409. if (isNaN(height)) height = 512;
  410. // Concat it to string to bypass the gradio bug
  411. // 向黑恶势力低头
  412. return width.toString() + 'x' + height.toString();
  413. }
  414. function onCreateI2IRefClick(){
  415. let canvas = gradioApp().querySelector('#img2img_image img');
  416. return canvas.src;
  417. }