CutImageDialog.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <template>
  2. <a-modal
  3. v-model:open="visible"
  4. width="100%"
  5. :footer="false"
  6. :closable="false"
  7. :maskClosable="false"
  8. wrapClassName="cut-image-dialog full-modal"
  9. >
  10. <div ref="imgContainRef" class="cut-image">
  11. <div v-if="visible" class="cut-image-body" :style="imageStyle">
  12. <img ref="imgRef" :src="sheetUrl" alt="原图" @load="initImageSize" />
  13. <element-resize
  14. v-if="selection.w"
  15. v-model="selection"
  16. class="element-resize-act"
  17. :active="['r', 'rb', 'b', 'lb', 'l', 'lt', 't', 'rt']"
  18. >
  19. <div class="image-selection" :style="selectionStyle"></div>
  20. </element-resize>
  21. </div>
  22. <div class="cut-image-action">
  23. <div class="cut-close" @click="close"><CloseOutlined /></div>
  24. <div class="cut-save" @click="confirm"><SaveOutlined /></div>
  25. </div>
  26. </div>
  27. </a-modal>
  28. </template>
  29. <script setup lang="ts">
  30. import { computed, ref, watch } from "vue";
  31. import { SaveOutlined, CloseOutlined } from "@ant-design/icons-vue";
  32. import useModal from "@/hooks/useModal";
  33. import { objAssign } from "@/utils/tool";
  34. import ElementResize from "@/components/ElementResize/index.vue";
  35. defineOptions({
  36. name: "CutImageDialog",
  37. });
  38. /* modal */
  39. const { visible, open, close } = useModal();
  40. defineExpose({ open, close });
  41. open();
  42. const props = defineProps<{
  43. sheetUrl: string;
  44. sliceSelection?: AreaSize;
  45. }>();
  46. const emit = defineEmits(["confirm"]);
  47. const initSelection = {
  48. w: 0,
  49. h: 0,
  50. x: 0,
  51. y: 0,
  52. };
  53. const originImgRef = ref();
  54. const curCroppper = ref();
  55. const showCanvas = ref(false);
  56. const selection = ref({
  57. ...initSelection,
  58. });
  59. const selectionStyle = computed(() => {
  60. return {
  61. width: `${selection.value.w}px`,
  62. height: `${selection.value.h}px`,
  63. top: `${selection.value.y}px`,
  64. left: `${selection.value.x}px`,
  65. };
  66. });
  67. const imageSize = ref({
  68. width: 0,
  69. height: 0,
  70. left: 0,
  71. top: 0,
  72. });
  73. const imageStyle = computed(() => {
  74. return {
  75. width: `${imageSize.value.width}px`,
  76. height: `${imageSize.value.height}px`,
  77. top: `${imageSize.value.top}px`,
  78. left: `${imageSize.value.left}px`,
  79. };
  80. });
  81. const imgContainRef = ref();
  82. const imgRef = ref();
  83. function initImageSize() {
  84. const imgDom = imgRef.value as HTMLImageElement;
  85. const elDom = imgContainRef.value as HTMLDivElement;
  86. const imgSize = getImageSizePos({
  87. win: {
  88. width: elDom.clientWidth,
  89. height: elDom.clientHeight,
  90. },
  91. img: {
  92. width: imgDom.naturalWidth,
  93. height: imgDom.naturalHeight,
  94. },
  95. rotate: 0,
  96. });
  97. imageSize.value = objAssign(imageSize.value, imgSize);
  98. if (!props.sliceSelection) return;
  99. const rate = imgDom.naturalWidth / imageSize.value.width;
  100. selection.value = {
  101. x: props.sliceSelection.x / rate,
  102. y: props.sliceSelection.y / rate,
  103. w: props.sliceSelection.w / rate,
  104. h: props.sliceSelection.h / rate,
  105. };
  106. }
  107. interface AreaSize {
  108. width: number;
  109. height: number;
  110. }
  111. function getImageSizePos({
  112. win,
  113. img,
  114. rotate,
  115. }: {
  116. win: AreaSize;
  117. img: AreaSize;
  118. rotate: number;
  119. }) {
  120. const imageSize = {
  121. width: 0,
  122. height: 0,
  123. top: 0,
  124. left: 0,
  125. };
  126. const isHorizontal = !!(rotate % 180);
  127. const rateWin = isHorizontal
  128. ? win.height / win.width
  129. : win.width / win.height;
  130. const hwin = isHorizontal
  131. ? {
  132. width: win.height,
  133. height: win.width,
  134. }
  135. : win;
  136. const rateImg = img.width / img.height;
  137. if (rateImg <= rateWin) {
  138. imageSize.height = Math.min(hwin.height, img.height);
  139. imageSize.width = Math.floor((imageSize.height * img.width) / img.height);
  140. } else {
  141. imageSize.width = Math.min(hwin.width, img.width);
  142. imageSize.height = Math.floor((imageSize.width * img.height) / img.width);
  143. }
  144. imageSize.left = (win.width - imageSize.width) / 2;
  145. imageSize.top = (win.height - imageSize.height) / 2;
  146. return imageSize;
  147. }
  148. async function confirm() {
  149. const imgDom = imgRef.value as HTMLImageElement;
  150. const rate = imageSize.value.width / imgDom.naturalWidth;
  151. const selectionArea: AreaSize = {
  152. x: selection.value.x / rate,
  153. y: selection.value.y / rate,
  154. w: selection.value.w / rate,
  155. h: selection.value.h / rate,
  156. };
  157. const file = await getSliceImage(imgDom, selectionArea).catch((e) => {
  158. console.error(e);
  159. });
  160. if (!file) return;
  161. console.log(file);
  162. emit("confirm", file);
  163. close();
  164. }
  165. function getSliceImage(
  166. imgDom: HTMLImageElement,
  167. area: AreaSize
  168. ): Promise<File> {
  169. return new Promise((resolve, reject) => {
  170. const canvas = document.createElement("canvas");
  171. const ctx = canvas.getContext("2d");
  172. if (!ctx) return reject(new Error("不支持canvas"));
  173. canvas.width = area.w;
  174. canvas.height = area.h;
  175. ctx.drawImage(
  176. imgDom,
  177. area.x,
  178. area.y,
  179. area.w,
  180. area.h,
  181. 0,
  182. 0,
  183. canvas.width,
  184. canvas.height
  185. );
  186. canvas.toBlob((blob) => {
  187. if (blob) {
  188. resolve(new File([blob], "slice.png", { type: "image/png" }));
  189. } else {
  190. reject(new Error("构建文件失败"));
  191. }
  192. });
  193. });
  194. }
  195. // init
  196. watch(
  197. () => visible.value,
  198. (val) => {
  199. if (!val) {
  200. selection.value = {
  201. ...initSelection,
  202. };
  203. }
  204. },
  205. {
  206. immediate: true,
  207. }
  208. );
  209. </script>