123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- <template>
- <div>
- <div
- class="vue-crop-layout"
- ref="layoutRef"
- :style="{
- width: sWidth + 'px',
- height: sHeight + 'px',
- '--filter': `hue-rotate(${hue}deg)`,
- }"
- >
- <VueCropper
- class="cropper-cls"
- ref="cropperRef"
- :img="url"
- :outputSize="1"
- canScale
- autoCrop
- centerBox
- :fixed="!!fixed"
- :fixedNumber="fixed"
- />
- </div>
- <div class="control">
- <div class="slider-demo-block">
- <span class="demonstration">色相调整</span>
- <el-slider v-model="hue" :max="360" :min="0" show-input />
- </div>
- <el-button type="primary" @click="cropperRef.rotateRight()"> 旋转 </el-button>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import "vue-cropper/dist/index.css";
- import { ref, computed } from "vue";
- import { VueCropper } from "vue-cropper";
- import { QuiskExpose } from "@/helper/mount";
- import { getDomMatrix, rotateHue } from "@/util";
- import { inverse, multiply, positionTransform, rotateZ, translate } from "@/util/mt4";
- type CropperProps = {
- img: Blob | string;
- fixed: [number, number];
- };
- const props = defineProps<CropperProps>();
- const hue = ref(90);
- // 样式控制
- const sWidth = 500;
- const sHeight = (props.fixed[1] / props.fixed[0]) * sWidth;
- const realImage = new Image();
- const url = computed(() => {
- const url = typeof props.img === "string" ? props.img : URL.createObjectURL(props.img);
- realImage.src = url;
- return url;
- });
- const layoutRef = ref<HTMLDivElement>();
- const cropperRef = ref<any>();
- const getDrawInfo = () => {
- const imgDom = layoutRef.value?.querySelector(".cropper-box-canvas") as HTMLElement;
- const cropDom = layoutRef.value?.querySelector(".cropper-crop-box") as HTMLElement;
- const imgMatrix = getDomMatrix(imgDom);
- const cropMatrix = getDomMatrix(cropDom);
- const cropSize = [cropperRef.value.cropW, cropperRef.value.cropH];
- // 屏幕位置
- const cropBox = [
- positionTransform([-cropSize[0] / 2, -cropSize[1] / 2, 0], cropMatrix),
- positionTransform([cropSize[0] / 2, cropSize[1] / 2, 0], cropMatrix),
- ];
- const scale = [
- realImage.width / imgDom.offsetWidth,
- realImage.height / imgDom.offsetHeight,
- ];
- const invImageMatrix = inverse(imgMatrix);
- const lt = positionTransform(cropBox[0], invImageMatrix);
- const rb = positionTransform(cropBox[1], invImageMatrix);
- const imgBound = [
- lt[0] * scale[0] + realImage.width / 2,
- lt[1] * scale[1] + realImage.height / 2,
- rb[0] * scale[0] + realImage.width / 2,
- rb[1] * scale[1] + realImage.height / 2,
- ];
- const realBound = {
- left: Math.round(imgBound[0]),
- top: Math.round(imgBound[1]),
- right: Math.round(imgBound[2]),
- bottom: Math.round(imgBound[3]),
- };
- // 旋转过
- if (realBound.left > realBound.right) {
- [realBound.left, realBound.right] = [realBound.right, realBound.left];
- }
- if (realBound.top > realBound.bottom) {
- [realBound.top, realBound.bottom] = [realBound.bottom, realBound.top];
- }
- return {
- ...realBound,
- rotate: (cropperRef.value.rotate * Math.PI) / 2,
- };
- };
- const clipImage = () => {
- const data = getDrawInfo();
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d")!;
- const w = data.right - data.left;
- const h = data.bottom - data.top;
- const boxMatrix = multiply(
- translate(-w / 2, -h / 2, 0),
- rotateZ(data.rotate),
- translate(w / 2, h / 2, 0)
- );
- const start = positionTransform([0, 0, 0], boxMatrix);
- const end = positionTransform([w, h, 0], boxMatrix);
- const cw = (canvas.width = Math.abs(end[0] - start[0]));
- const ch = (canvas.height = Math.abs(end[1] - start[1]));
- ctx.translate(cw / 2, ch / 2);
- ctx.rotate(data.rotate);
- ctx.drawImage(realImage, data.left, data.top, w, h, -w / 2, -h / 2, w, h);
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- rotateHue(imageData.data, hue.value / 360);
- ctx.putImageData(imageData, 0, 0);
- console.log(imageData.data);
- return new Promise<Blob | null>((resolve) => canvas.toBlob(resolve));
- };
- defineExpose<QuiskExpose>({
- submit: clipImage,
- });
- </script>
- <style lang="scss" scoped>
- .vue-crop-layout {
- width: 100%;
- }
- .control {
- margin-top: 20px;
- text-align: center;
- }
- .slider-demo-block {
- max-width: 600px;
- display: flex;
- align-items: center;
- }
- .slider-demo-block .el-slider {
- margin-top: 0;
- margin-left: 12px;
- }
- .demonstration {
- width: 100px;
- }
- </style>
- <style lang="scss">
- .vue-crop-layout {
- .cropper-view-box {
- outline-color: var(--el-color-primary);
- }
- img {
- filter: var(--filter);
- }
- }
- </style>
|