annotated-image.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <template>
  2. <div class="annotated-image" v-on="events">
  3. <img alt="media" ref="image" draggable="false"
  4. :src="mediaUrl" :srcset="mediaUrlSet" :sizes="mediaUrlSizes"/>
  5. <div style="position: absolute; top: 0.5rem; left: 0.5rem">{{ data }}</div>
  6. <annotation-box v-if="current"
  7. :image="image"
  8. :position="current"
  9. :immutable="true"/>
  10. <annotation-box v-for="(result, index) in predictions"
  11. :type="result.origin"
  12. :key="index"
  13. :id="result.id"
  14. :image="image"
  15. :position="result"
  16. :socket="socket"
  17. :immutable="false"
  18. :box-url="mediaUrl + '/' + result.id"
  19. @resize="resize"/>
  20. </div>
  21. </template>
  22. <script>
  23. import AnnotationBox from "@/components/media/annotation-box";
  24. export default {
  25. name: "annotated-image",
  26. components: {AnnotationBox},
  27. props: ['project', 'data', 'socket'],
  28. mounted: function () {
  29. window.addEventListener("resize", this.resizeEvent);
  30. if (this.$refs.image.complete)
  31. this.resizeEvent();
  32. else
  33. this.$refs.image.addEventListener('load', this.resizeEvent);
  34. },
  35. destroyed() {
  36. window.removeEventListener("resize", this.resizeEvent);
  37. },
  38. watch: {
  39. data: function () {
  40. this.current = false;
  41. this.resizeEvent();
  42. }
  43. },
  44. data: function () {
  45. return {
  46. sizes: [600, 800, 1200, 1600, 2000, 3000],
  47. image: {
  48. left: 0,
  49. top: 0,
  50. width: 0,
  51. height: 0
  52. },
  53. start: false,
  54. fixed: false,
  55. current: false,
  56. callback: false
  57. }
  58. },
  59. computed: {
  60. mediaUrl: function () {
  61. return this.socket.media(this.project.id, this.data.id);
  62. },
  63. mediaUrlSet: function () {
  64. return this.sizes.map(e => this.mediaUrl + '/' + e + ' ' + e + 'w').join(',');
  65. },
  66. mediaUrlSizes: function () {
  67. return this.sizes.map(e => '(max-width: ' + e + 'px) ' + e + 'px').join(',');
  68. },
  69. predictions: function () {
  70. return Object.keys(this.data.predictionResults).map(k => this.data.predictionResults[k]);
  71. },
  72. events: function () {
  73. return {
  74. 'touchstart': this.press,
  75. 'touchmove': this.track,
  76. 'touchend': this.release,
  77. 'mousedown': this.press,
  78. 'mousemove': this.track,
  79. 'mouseup': this.release,
  80. 'dragstart': e => e.stopPropagation()
  81. }
  82. }
  83. },
  84. methods: {
  85. resizeEvent: function () {
  86. const element = this.$refs.image.getBoundingClientRect();
  87. const parent = this.$refs.image.parentElement.getBoundingClientRect();
  88. this.image.left = element.x - parent.x;
  89. this.image.top = element.y - parent.y;
  90. this.image.width = element.width;
  91. this.image.height = element.height;
  92. },
  93. get: function (event) {
  94. if ('clientX' in event)
  95. return event;
  96. if ('touches' in event && event.touches.length > 0)
  97. return event.touches[0];
  98. if ('changedTouches' in event && event.changedTouches.length > 0)
  99. return event.changedTouches[0];
  100. },
  101. getX: function (event) {
  102. const bcr = this.$refs.image.getBoundingClientRect();
  103. return (this.get(event).clientX - bcr.left) / bcr.width;
  104. },
  105. getY: function (event) {
  106. const bcr = this.$refs.image.getBoundingClientRect();
  107. return (this.get(event).clientY - bcr.top) / bcr.height;
  108. },
  109. buildRectangle: function (x1, y1, x2, y2) {
  110. const lx = this.fixed && 'lx' in this.fixed ? this.fixed.lx : Math.max(Math.min(x1, x2), 0);
  111. const hx = this.fixed && 'hx' in this.fixed ? this.fixed.hx : Math.min(Math.max(x1, x2), 1);
  112. const ly = this.fixed && 'ly' in this.fixed ? this.fixed.ly : Math.max(Math.min(y1, y2), 0);
  113. const hy = this.fixed && 'hy' in this.fixed ? this.fixed.hy : Math.min(Math.max(y1, y2), 1);
  114. return {
  115. x: lx,
  116. y: ly,
  117. w: hx - lx,
  118. h: hy - ly
  119. }
  120. },
  121. press: function (event) {
  122. this.start = {
  123. x: this.getX(event),
  124. y: this.getY(event)
  125. };
  126. },
  127. track: function (event) {
  128. if (this.start) {
  129. const x = this.getX(event);
  130. const y = this.getY(event);
  131. this.current = this.buildRectangle(this.start.x, this.start.y, x, y);
  132. }
  133. },
  134. release: function () {
  135. if (this.start) {
  136. if (this.callback)
  137. this.callback(this.current);
  138. else
  139. this.socket.post(this.mediaUrl, this.current);
  140. this.start = false;
  141. this.fixed = false;
  142. this.current = false;
  143. this.callback = false;
  144. }
  145. },
  146. resize: function (position, mode, callback) {
  147. this.callback = callback;
  148. switch (mode) {
  149. case 'nw':
  150. this.start = {x: position.x + position.w, y: position.y + position.h};
  151. this.fixed = false;
  152. break;
  153. case 'ne':
  154. this.start = {x: position.x, y: position.y + position.h};
  155. this.fixed = false;
  156. break;
  157. case 'sw':
  158. this.start = {x: position.x + position.w, y: position.y};
  159. this.fixed = false;
  160. break;
  161. case 'se':
  162. this.start = {x: position.x, y: position.y};
  163. this.fixed = false;
  164. break;
  165. case 'nn':
  166. this.start = {x: position.x, y: position.y + position.h};
  167. this.fixed = {lx: position.x, hx: position.x + position.w};
  168. break;
  169. case 'ww':
  170. this.start = {x: position.x + position.w, y: position.y};
  171. this.fixed = {ly: position.y, hy: position.y + position.h};
  172. break;
  173. case 'ee':
  174. this.start = {x: position.x, y: position.y};
  175. this.fixed = {ly: position.y, hy: position.y + position.h};
  176. break;
  177. case 'ss':
  178. this.start = {x: position.x, y: position.y};
  179. this.fixed = {lx: position.x, hx: position.x + position.w};
  180. break;
  181. }
  182. }
  183. }
  184. }
  185. </script>
  186. <style scoped>
  187. img {
  188. max-width: 100%;
  189. max-height: 100%;
  190. }
  191. </style>