canvas实现图像裁剪

1.技术背景

随着都市类程序开发业务推进,在各类活动、个人信息等模块逐渐依赖图像裁剪、上传等功能,那么如何实现图像缩放、裁剪就成了前端工程师们关注优化的重点。

2.应用技术

本次使用的技术主要是依赖canvas,又叫画布,是H5新增内容,可以使用js脚本在其中绘制图像的元素
可以完成图片处理、动画渲染、图表渲染等操作。
最近用uni-app的推流组件做了个图片裁切、上传的组件,拍照、选择照片完成后还需要对图片进行裁切,才能真正使用。

3.处理思路

使用两个canvas分别为操作canvas目标canvas,操作canvas通过touchstart、touchmove、touchend、touchcancel实时跟进图片缩放、移动状态,点击确认后,使用canvas.drawImage()将前面的操作canvas绘制到目标canvas,最终调用canvas.toDataUrl()将目标canvas转为base64编码的图片传给接口

3.1 关键方法
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
参数值
参数描述
img规定要使用的图像、画布或视频。
sx可选。开始剪切的 x 坐标位置。
sy可选。开始剪切的 y 坐标位置。
swidth可选。被剪切图像的宽度。
sheight可选。被剪切图像的高度。
x在画布上放置图像的 x 坐标位置。
y在画布上放置图像的 y 坐标位置。
width可选。要使用的图像的宽度。(伸展或缩小图像)
height可选。要使用的图像的高度。(伸展或缩小图像)

![image.png](https://img-blog.csdnimg.cn/img_convert/de12ebb4d31f1c4df11fdc63938fb755.png#clientId=u1b516e45-d140-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=747&id=uf4632ce1&margin=[object Object]&name=image.png&originHeight=1828&originWidth=1576&originalType=binary&ratio=1&rotation=0&showTitle=false&size=440563&status=done&style=none&taskId=u2234db4a-eebb-4ed0-a5d8-cb4c6aa94a0&title=&width=644)

4. 具体实现

4.1创建操作canvas、目标canvas
mounted() {
    // 防止H5用户在数据挂在前移动图片
    //#ifdef H5
    this.$el.addEventListener('touchmove', (ev) => {
      ev.preventDefault();
    });
    // #endif
    // 创建canvas
    this.context = uni.createCanvasContext('canvas', this);//操作canvas
    this.targetContext = uni.createCanvasContext('target', this);//目标canvas
  },
4.2 图片加载初始化

其实这一部分就是数据计算,也就是开始涉及到一些图像缩放的算法逻辑,这一部分理解的时候需要花一点时间。
这边其实最要关注的就是一个时间点,两个主体。
时间点:图片加载完成后的如何对图片位置、大小以及对应的裁剪框位置、大小初始化,即图片和裁剪框的相对关系;
两个主体:图像、裁剪框

<template>
  <view v-show="url" class="ksp-image-cutter">
    <!-- 目标图像载体 -->
    <canvas
      id="target"
      :style="{ width: target.width + 'px', height: target.height + 'px' }"
      canvas-id="target"
    ></canvas>
    <!-- 主体 -->
    <view class="body">
      <!-- 裁剪的图片 -->
      <image
        v-if="url"
        lazy-load
        class="image"
        :style="{
          left: image.left + 'px',
          top: image.top + 'px',
          width: image.width + 'px',
          height: image.height + 'px',
        }"
        :src="url"
        @load="imageLoad"
      ></image>
      <!-- <view v-if="mask.show" class="mask"></view> -->
      <!-- 裁剪区域 -->
      <view
        class="plank"
        @touchstart="touchStart($event, 'plank')"
        @touchmove="touchMove"
        @touchend="touchEnd"
        @touchcancel="touchCancel"
      >
        <view
          class="frame"
          :style="{
            left: frame.left + 'px',
            top: frame.top + 'px',
            width: frame.width + 'px',
            height: frame.height + 'px',
          }"
          @touchstart="touchStart($event, 'frame')"
          @touchstart.stop.prevent="touchHandle"
        >
          <!-- 裁剪载体 -->
          <canvas
            v-if="mask.show"
            class="canvas"
            :style="{ width: frame.width + 'px', height: frame.height + 'px' }"
            canvas-id="canvas"
          ></canvas>
          <!-- 窗口 -->
          <view class="rect"></view>
          <!-- 分割线 -->
          <!-- <view class="line-one"></view>
				<view class="line-two"></view>
				<view class="line-three"></view>
				<view class="line-four"></view>  -->
          <!-- 窗口调节键 -->
          <view
            class="frame-left"
            @touchstart="touchStart($event, 'left')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-right"
            @touchstart="touchStart($event, 'right')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-top"
            @touchstart="touchStart($event, 'top')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-bottom"
            @touchstart="touchStart($event, 'bottom')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-left-top"
            @touchstart="touchStart($event, 'left-top')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-left-bottom"
            @touchstart="touchStart($event, 'left-bottom')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-right-top"
            @touchstart="touchStart($event, 'right-top')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-right-bottom"
            @touchstart="touchStart($event, 'right-bottom')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
        </view>
      </view>
    </view>
    <!-- 操作区域 -->
    <view class="toolbar">
      <button class="btn-cancel" @tap="oncancel">取消</button>
      <button class="btn-ok" @tap="onok">选取</button>
    </view>
  </view>
</template>
  methods: {
    // 图片首次加载
    imageLoad(ev) {
      // 加载动画
      this.mask.show = true;
      //图像原本宽高获取 (关键数据:图片缩放依赖)
      this.real.width = ev.detail.width;
      this.real.height = ev.detail.height;
      //图片目前的宽高。(关键数据:图片缩放后的大小)
      this.image.width = ev.detail.width;
      this.image.height = ev.detail.height;
      // 裁剪区域的大小
      this.frame.width = this.width;
      this.frame.height = this.height;
      // 裁剪大小是否固定
      if (!this.fixed) {
        this.frame.width = this.image.width;
        this.frame.height = this.image.height;
      }
      // 获取相关元素基本信息
      const query = uni.createSelectorQuery().in(this);
      query
        .select('.body')
        .boundingClientRect((data) => {
        //容器宽高
          const bw = data.width;
          const bh = data.height;
        // 裁剪大小
          const fw = this.frame.width;
          const fh = this.frame.height;
        
        // 计算缩放比例
          let tw = bw * 0.8;
          let th = bh * 0.8;
          let sx = tw / fw;
          let sy = th / fh;
          let scale = sx;
          if (sx < sy) {
            scale = sy;
          }
        //计算矢量位移
          tw = fw * scale;
          th = fh * scale;
          const tx = (bw - tw) / 2;
          const ty = (bh - th) / 2;
          this.frame.width = tw;
          this.frame.height = th;
          this.frame.left = tx;
          this.frame.top = ty;

          const iw = this.image.width;
          const ih = this.image.height;
          sx = tw / iw;
          sy = th / ih;
          scale = sx;
          if (sx < sy) {
            scale = sy;
          }
          this.image.width = iw * scale;
          this.image.height = ih * scale;
          this.image.left = (bw - this.image.width) / 2;
          this.image.top = (bh - this.image.height) / 2;
          setTimeout(() => {
            // 图片大小
            this.trimImage();
          }, 100);
        })
        .exec();
    },
     // 初始化图片大小、位置
    trimImage() {
      this.mask.show = true;
      const query = uni.createSelectorQuery().in(this);
      query
        .select('.body')
        .boundingClientRect((data) => {
          const bw = data.width;
          const bh = data.height;
          const fw = this.frame.width;
          const fh = this.frame.height;
          let tw = bw;
          let th = bh;
          // let tw = bw * 0.8;
          // let th = bh * 0.8;
          const sx = tw / fw;
          const sy = th / fh;
          let scale = sx;
          if (sx > sy) {
            scale = sy;
          }
          tw = fw * scale;
          th = fh * scale;
          const tx = (bw - tw) / 2;
          const ty = (bh - th) / 2;
          const ax =
            tx -
            this.frame.left +
            (this.frame.left - this.image.left) * (1 - scale);
          const ay =
            ty -
            this.frame.top +
            (this.frame.top - this.image.top) * (1 - scale);
          this.frame.width = tw;
          this.frame.height = th;
          this.frame.left = tx;
          this.frame.top = ty;
          this.image.width *= scale;
          this.image.height *= scale;
          this.image.left += ax;
          this.image.top += ay;
        })
        .exec();
      setTimeout(() => {
        // 计算缩放、位移数据
        const scale = this.image.width / this.real.width;
        const x = (this.frame.left - this.image.left) / scale;
        const y = (this.frame.top - this.image.top) / scale;
        const width = this.frame.width / scale;
        const height = this.frame.height / scale;
        // 将图片绘制到操作canvas
        this.context.drawImage(
          this.url,
          x,
          y,
          width,
          height,
          0,
          0,
          this.frame.width,
          this.frame.height
        );
        // 开始绘制
        this.context.draw(false);
      }, 100);
    },
  }

4.3 触摸事件处理

    // 阻止手指事件冒泡传递
    touchHandle() {},
    // 手指开始触摸事件
    touchStart(ev, type) {
      this.stopTime();
      this.mask.show = false;
      if (this.touches.length === 0) {
        this.type = type;
        this.start.frame.left = this.frame.left;
        this.start.frame.top = this.frame.top;
        this.start.frame.width = this.frame.width;
        this.start.frame.height = this.frame.height;
        this.start.image.left = this.image.left;
        this.start.image.top = this.image.top;
        this.start.image.width = this.image.width;
        this.start.image.height = this.image.height;
      }
      const touches = ev.changedTouches;
      for (let i = 0; i < touches.length; i++) {
        const touch = touches[i];
        // this.touches[touch.identifier] = touch;
        this.touches.push(touch);
      }
    },
    // 手指移动事件
    touchMove(ev) {
      this.stopTime();
      ev.preventDefault();
      const touches = ev.touches;
      if (this.touches.length === 1) {
        if (this.type === 'plank' || this.type === 'frame' || this.fixed) {
          this.moveImage(this.touches[0], touches[0]);
        } else {
          this.scaleFrame(this.touches[0], touches[0], this.type);
        }
      } else if (this.touches.length === 2 && touches.length === 2) {
        const ta = this.touches[0];
        const tb = this.touches[1];
        let tc = touches[0];
        let td = touches[1];
        if (ta.identifier !== tc.identifier) {
          const temp = tc;
          tc = td;
          td = temp;
        }
        this.scaleImage(ta, tb, tc, td);
      }
    },
    touchEnd(ev) {
      this.type = '';
      this.touches = [];
      this.startTime();
    },
    touchCancel(ev) {
      this.type = '';
      this.touches = [];
      this.startTime();
    },
    startTime() {
      this.stopTime();
      this.timeoutId = setTimeout(() => {
        this.trimImage();
      }, 800);
    },
    stopTime() {
      if (this.timeoutId >= 0) {
        clearTimeout(this.timeoutId);
        this.timeoutId = -1;
      }
    },

4.4 图片放大事件

这里图片移动主要代码难点、注意点有以下两点:
1.移动范围的控制,即保证裁剪框保证在图片区域内。
2.图片缩放过程中会存在位移,即图像缩放一定是矢量位移。

// 图片移动
    moveImage(ta, tb) {
      const ax = tb.clientX - ta.clientX;
      const ay = tb.clientY - ta.clientY;
      this.image.left = this.start.image.left + ax;
      this.image.top = this.start.image.top + ay;
      if (this.image.left > this.frame.left) {
        this.image.left = this.frame.left;
      }
      if (this.image.top > this.frame.top) {
        this.image.top = this.frame.top;
      }
      if (
        this.image.left + this.image.width <
        this.frame.left + this.frame.width
      ) {
        this.image.left = this.frame.left + this.frame.width - this.image.width;
      }
      if (
        this.image.top + this.image.height <
        this.frame.top + this.frame.height
      ) {
        this.image.top = this.frame.top + this.frame.height - this.image.height;
      }
    },
         // 图片放大
    scaleImage(ta, tb, tc, td) {
      const x1 = ta.clientX;
      const y1 = ta.clientY;
      const x2 = tb.clientX;
      const y2 = tb.clientY;
      const x3 = tc.clientX;
      const y3 = tc.clientY;
      const x4 = td.clientX;
      const y4 = td.clientY;
      const ol = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
      const el = Math.sqrt((x3 - x4) * (x3 - x4) + (y3 - y4) * (y3 - y4));
      const ocx = (x1 + x2) / 2;
      const ocy = (y1 + y2) / 2;
      const ecx = (x3 + x4) / 2;
      const ecy = (y3 + y4) / 2;
      const ax = ecx - ocx;
      const ay = ecy - ocy;
      let scale = el / ol;
      if (this.start.image.width * scale < this.frame.width) {
        scale = this.frame.width / this.start.image.width;
      }
      if (this.start.image.height * scale < this.frame.height) {
        scale = this.frame.height / this.start.image.height;
      }
      if (this.start.image.width * scale < this.frame.width) {
        scale = this.frame.width / this.start.image.width;
      }
      this.image.left =
        this.start.image.left +
        ax -
        (ocx - this.start.image.left) * (scale - 1);
      this.image.top =
        this.start.image.top + ay - (ocy - this.start.image.top) * (scale - 1);
      this.image.width = this.start.image.width * scale;
      this.image.height = this.start.image.height * scale;
      if (this.image.left > this.frame.left) {
        this.image.left = this.frame.left;
      }
      if (this.image.top > this.frame.top) {
        this.image.top = this.frame.top;
      }
      if (
        this.image.left + this.image.width <
        this.frame.left + this.frame.width
      ) {
        this.image.left = this.frame.left + this.frame.width - this.image.width;
      }
      if (
        this.image.top + this.image.height <
        this.frame.top + this.frame.height
      ) {
        this.image.top = this.frame.top + this.frame.height - this.image.height;
      }
    },

4.5 图片绘制上传

理解前面代码后,下面的代码没有难点了,重点掌握CanvasContext.toDataURL({})方法
canvas.toDataURL(type, encoderOptions

type 可选
图片格式,默认为 image/png
encoderOptions 可选
在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略
**方法返回一个包含图片展示的 data URI **。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi

// 确认按钮事件
    onok() {
      const scale = this.image.width / this.real.width;
      const x = (this.frame.left - this.image.left) / scale;
      const y = (this.frame.top - this.image.top) / scale;
      const width = this.frame.width / scale;
      const height = this.frame.height / scale;
      let tw = width;
      let th = height;
      if (this.fixed) {
        tw = this.width / 2;
        th = this.height / 2;
      } else {
        if (tw > this.maxWidth / 2) {
          const sc = this.maxWidth / 2 / tw;
          tw = tw * sc;
          th = th * sc;
        }
        if (th > this.maxHeight / 2) {
          let sc = this.maxHeight / 2 / th;
          th = th * sc;
          tw = tw * sc;
        }
      }
      this.target.width = tw;
      this.target.height = th;
      // uni.showLoading({
      //   title: '正在裁剪',
      // });
      setTimeout(() => {
        this.targetContext.drawImage(
          this.url,
          x,
          y,
          width,
          height,
          0,
          0,
          tw,
          th
        );
        this.targetContext.draw(false, () => {
          const CanvasContext = my.createCanvasContext('target');
          CanvasContext.toDataURL({}).then((dataURL) => {
            this.upLoadPic(dataURL);
          });
        });
      }, 100);
    },

5.完整代码

组件式开发,开箱即用,注意这边依赖uni框架、小程序下的native交互,H5慎用!!

<template>
  <view v-show="url" class="ksp-image-cutter">
    <!-- 目标图像载体 -->
    <canvas
      id="target"
      :style="{ width: target.width + 'px', height: target.height + 'px' }"
      canvas-id="target"
    ></canvas>
    <!-- 主体 -->
    <view class="body">
      <!-- 裁剪的图片 -->
      <image
        v-if="url"
        lazy-load
        class="image"
        :style="{
          left: image.left + 'px',
          top: image.top + 'px',
          width: image.width + 'px',
          height: image.height + 'px',
        }"
        :src="url"
        @load="imageLoad"
      ></image>
      <!-- <view v-if="mask.show" class="mask"></view> -->
      <!-- 裁剪区域 -->
      <view
        class="plank"
        @touchstart="touchStart($event, 'plank')"
        @touchmove="touchMove"
        @touchend="touchEnd"
        @touchcancel="touchCancel"
      >
        <view
          class="frame"
          :style="{
            left: frame.left + 'px',
            top: frame.top + 'px',
            width: frame.width + 'px',
            height: frame.height + 'px',
          }"
          @touchstart="touchStart($event, 'frame')"
          @touchstart.stop.prevent="touchHandle"
        >
          <!-- 裁剪载体 -->
          <canvas
            v-if="mask.show"
            class="canvas"
            :style="{ width: frame.width + 'px', height: frame.height + 'px' }"
            canvas-id="canvas"
          ></canvas>
          <!-- 窗口 -->
          <view class="rect"></view>
          <!-- 分割线 -->
          <!-- <view class="line-one"></view>
				<view class="line-two"></view>
				<view class="line-three"></view>
				<view class="line-four"></view>  -->
          <!-- 窗口调节键 -->
          <view
            class="frame-left"
            @touchstart="touchStart($event, 'left')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-right"
            @touchstart="touchStart($event, 'right')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-top"
            @touchstart="touchStart($event, 'top')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-bottom"
            @touchstart="touchStart($event, 'bottom')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-left-top"
            @touchstart="touchStart($event, 'left-top')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-left-bottom"
            @touchstart="touchStart($event, 'left-bottom')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-right-top"
            @touchstart="touchStart($event, 'right-top')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
          <view
            class="frame-right-bottom"
            @touchstart="touchStart($event, 'right-bottom')"
            @touchstart.stop.prevent="touchHandle"
          ></view>
        </view>
      </view>
    </view>
    <!-- 操作区域 -->
    <view class="toolbar">
      <button class="btn-cancel" @tap="oncancel">取消</button>
      <button class="btn-ok" @tap="onok">选取</button>
    </view>
  </view>
</template>

<script>
import { getIn } from '@/common/script/utils';
import { myYaYunService } from '@/pages/myyayun/common/myYaYunService';
import { log } from 'lodash-decorators/utils';
export default {
  props: {
    url: {
      type: String,
      default: '',
    },
    fixed: {
      type: Boolean,
      default: false,
    },
    width: {
      type: Number,
      default: 200,
    },
    height: {
      type: Number,
      default: 200,
    },
    maxWidth: {
      type: Number,
      default: 1024,
    },
    maxHeight: {
      type: Number,
      default: 1024,
    },
    blob: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      mask: {
        show: false,
      },
      frame: {
        left: 50,
        top: 50,
        width: this.width,
        height: this.height,
      },
      image: {
        left: 20,
        top: 20,
        width: 300,
        height: 400,
      },
      real: {
        width: 100,
        height: 100,
      },
      target: {
        width: this.width,
        height: this.height,
      },
      touches: [],
      type: '',
      start: {
        frame: {
          left: 0,
          top: 0,
          width: 0,
          height: 0,
        },
        image: {
          left: 0,
          top: 0,
          width: 0,
          height: 0,
        },
      },
      timeoutId: -1,
      context: null,
    };
  },
  mounted() {
    //#ifdef H5
    this.$el.addEventListener('touchmove', (ev) => {
      ev.preventDefault();
    });
    // #endif
    // 创建canvas
    this.context = uni.createCanvasContext('canvas', this);
    this.targetContext = uni.createCanvasContext('target', this);
  },
  methods: {
    // 图片首次加载
    imageLoad(ev) {
      this.mask.show = true;
      this.real.width = ev.detail.width;
      this.real.height = ev.detail.height;
      this.image.width = ev.detail.width;
      this.image.height = ev.detail.height;
      this.frame.width = this.width;
      this.frame.height = this.height;
      if (!this.fixed) {
        this.frame.width = this.image.width;
        this.frame.height = this.image.height;
      }
      const query = uni.createSelectorQuery().in(this);
      query
        .select('.body')
        .boundingClientRect((data) => {
          const bw = data.width;
          const bh = data.height;
          const fw = this.frame.width;
          const fh = this.frame.height;
          let tw = bw * 0.8;
          let th = bh * 0.8;
          let sx = tw / fw;
          let sy = th / fh;
          let scale = sx;
          if (sx < sy) {
            scale = sy;
          }
          tw = fw * scale;
          th = fh * scale;
          const tx = (bw - tw) / 2;
          const ty = (bh - th) / 2;
          this.frame.width = tw;
          this.frame.height = th;
          this.frame.left = tx;
          this.frame.top = ty;

          const iw = this.image.width;
          const ih = this.image.height;
          sx = tw / iw;
          sy = th / ih;
          scale = sx;
          if (sx < sy) {
            scale = sy;
          }
          this.image.width = iw * scale;
          this.image.height = ih * scale;
          this.image.left = (bw - this.image.width) / 2;
          this.image.top = (bh - this.image.height) / 2;
          setTimeout(() => {
            this.trimImage();
          }, 100);
        })
        .exec();
    },
    // 阻止手指事件冒泡传递
    touchHandle() {},
    // 手指开始触摸事件
    touchStart(ev, type) {
      this.stopTime();
      this.mask.show = false;
      if (this.touches.length === 0) {
        this.type = type;
        this.start.frame.left = this.frame.left;
        this.start.frame.top = this.frame.top;
        this.start.frame.width = this.frame.width;
        this.start.frame.height = this.frame.height;
        this.start.image.left = this.image.left;
        this.start.image.top = this.image.top;
        this.start.image.width = this.image.width;
        this.start.image.height = this.image.height;
      }
      const touches = ev.changedTouches;
      for (let i = 0; i < touches.length; i++) {
        const touch = touches[i];
        // this.touches[touch.identifier] = touch;
        this.touches.push(touch);
      }
    },
    // 手指移动事件
    touchMove(ev) {
      this.stopTime();
      ev.preventDefault();
      const touches = ev.touches;
      if (this.touches.length === 1) {
        if (this.type === 'plank' || this.type === 'frame' || this.fixed) {
          this.moveImage(this.touches[0], touches[0]);
        } else {
          this.scaleFrame(this.touches[0], touches[0], this.type);
        }
      } else if (this.touches.length === 2 && touches.length === 2) {
        const ta = this.touches[0];
        const tb = this.touches[1];
        let tc = touches[0];
        let td = touches[1];
        if (ta.identifier !== tc.identifier) {
          const temp = tc;
          tc = td;
          td = temp;
        }
        this.scaleImage(ta, tb, tc, td);
      }
    },
    touchEnd(ev) {
      this.type = '';
      this.touches = [];
      this.startTime();
    },
    touchCancel(ev) {
      this.type = '';
      this.touches = [];
      this.startTime();
    },
    startTime() {
      this.stopTime();
      this.timeoutId = setTimeout(() => {
        this.trimImage();
      }, 800);
    },
    stopTime() {
      if (this.timeoutId >= 0) {
        clearTimeout(this.timeoutId);
        this.timeoutId = -1;
      }
    },
    // 初始化图片大小、位置
    trimImage() {
      this.mask.show = true;
      const query = uni.createSelectorQuery().in(this);
      query
        .select('.body')
        .boundingClientRect((data) => {
          const bw = data.width;
          const bh = data.height;
          const fw = this.frame.width;
          const fh = this.frame.height;
          let tw = bw;
          let th = bh;
          // let tw = bw * 0.8;
          // let th = bh * 0.8;
          const sx = tw / fw;
          const sy = th / fh;
          let scale = sx;
          if (sx > sy) {
            scale = sy;
          }
          tw = fw * scale;
          th = fh * scale;
          const tx = (bw - tw) / 2;
          const ty = (bh - th) / 2;
          const ax =
            tx -
            this.frame.left +
            (this.frame.left - this.image.left) * (1 - scale);
          const ay =
            ty -
            this.frame.top +
            (this.frame.top - this.image.top) * (1 - scale);
          this.frame.width = tw;
          this.frame.height = th;
          this.frame.left = tx;
          this.frame.top = ty;
          this.image.width *= scale;
          this.image.height *= scale;
          this.image.left += ax;
          this.image.top += ay;
        })
        .exec();
      setTimeout(() => {
        const scale = this.image.width / this.real.width;
        const x = (this.frame.left - this.image.left) / scale;
        const y = (this.frame.top - this.image.top) / scale;
        const width = this.frame.width / scale;
        const height = this.frame.height / scale;
        this.context.drawImage(
          this.url,
          x,
          y,
          width,
          height,
          0,
          0,
          this.frame.width,
          this.frame.height
        );
        this.context.draw(false);
      }, 100);
    },
    // 图片移动
    moveImage(ta, tb) {
      const ax = tb.clientX - ta.clientX;
      const ay = tb.clientY - ta.clientY;
      this.image.left = this.start.image.left + ax;
      this.image.top = this.start.image.top + ay;
      if (this.image.left > this.frame.left) {
        this.image.left = this.frame.left;
      }
      if (this.image.top > this.frame.top) {
        this.image.top = this.frame.top;
      }
      if (
        this.image.left + this.image.width <
        this.frame.left + this.frame.width
      ) {
        this.image.left = this.frame.left + this.frame.width - this.image.width;
      }
      if (
        this.image.top + this.image.height <
        this.frame.top + this.frame.height
      ) {
        this.image.top = this.frame.top + this.frame.height - this.image.height;
      }
    },
    // 图片放大
    scaleImage(ta, tb, tc, td) {
      const x1 = ta.clientX;
      const y1 = ta.clientY;
      const x2 = tb.clientX;
      const y2 = tb.clientY;
      const x3 = tc.clientX;
      const y3 = tc.clientY;
      const x4 = td.clientX;
      const y4 = td.clientY;
      const ol = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
      const el = Math.sqrt((x3 - x4) * (x3 - x4) + (y3 - y4) * (y3 - y4));
      const ocx = (x1 + x2) / 2;
      const ocy = (y1 + y2) / 2;
      const ecx = (x3 + x4) / 2;
      const ecy = (y3 + y4) / 2;
      const ax = ecx - ocx;
      const ay = ecy - ocy;
      let scale = el / ol;
      if (this.start.image.width * scale < this.frame.width) {
        scale = this.frame.width / this.start.image.width;
      }
      if (this.start.image.height * scale < this.frame.height) {
        scale = this.frame.height / this.start.image.height;
      }
      if (this.start.image.width * scale < this.frame.width) {
        scale = this.frame.width / this.start.image.width;
      }
      this.image.left =
        this.start.image.left +
        ax -
        (ocx - this.start.image.left) * (scale - 1);
      this.image.top =
        this.start.image.top + ay - (ocy - this.start.image.top) * (scale - 1);
      this.image.width = this.start.image.width * scale;
      this.image.height = this.start.image.height * scale;
      if (this.image.left > this.frame.left) {
        this.image.left = this.frame.left;
      }
      if (this.image.top > this.frame.top) {
        this.image.top = this.frame.top;
      }
      if (
        this.image.left + this.image.width <
        this.frame.left + this.frame.width
      ) {
        this.image.left = this.frame.left + this.frame.width - this.image.width;
      }
      if (
        this.image.top + this.image.height <
        this.frame.top + this.frame.height
      ) {
        this.image.top = this.frame.top + this.frame.height - this.image.height;
      }
    },
    // 放大事件
    scaleFrame(ta, tb, type) {
      const ax = tb.clientX - ta.clientX;
      const ay = tb.clientY - ta.clientY;
      let x1 = this.start.frame.left;
      let y1 = this.start.frame.top;
      let x2 = this.start.frame.left + this.start.frame.width;
      let y2 = this.start.frame.top + this.start.frame.height;
      if (type === 'left') {
        x1 += ax;
      } else if (type === 'right') {
        x2 += ax;
      } else if (type === 'top') {
        y1 += ay;
      } else if (type === 'bottom') {
        y2 += ay;
      } else if (type === 'left-top') {
        x1 += ax;
        y1 += ay;
      } else if (type === 'left-bottom') {
        x1 += ax;
        y2 += ay;
      } else if (type === 'right-top') {
        x2 += ax;
        y1 += ay;
      } else if (type === 'right-bottom') {
        x2 += ax;
        y2 += ay;
      }
      if (x1 < this.image.left) {
        x1 = this.image.left;
      }
      if (y1 < this.image.top) {
        y1 = this.image.top;
      }
      if (x2 > this.image.left + this.image.width) {
        x2 = this.image.left + this.image.width;
      }
      if (y2 > this.image.top + this.image.height) {
        y2 = this.image.top + this.image.height;
      }
      this.frame.left = x1;
      this.frame.top = y1;
      this.frame.width = x2 - x1;
      this.frame.height = y2 - y1;
    },
    // 确认按钮事件
    onok() {
      const scale = this.image.width / this.real.width;
      const x = (this.frame.left - this.image.left) / scale;
      const y = (this.frame.top - this.image.top) / scale;
      const width = this.frame.width / scale;
      const height = this.frame.height / scale;
      let tw = width;
      let th = height;
      if (this.fixed) {
        tw = this.width / 2;
        th = this.height / 2;
      } else {
        if (tw > this.maxWidth / 2) {
          const sc = this.maxWidth / 2 / tw;
          tw = tw * sc;
          th = th * sc;
        }
        if (th > this.maxHeight / 2) {
          let sc = this.maxHeight / 2 / th;
          th = th * sc;
          tw = tw * sc;
        }
      }
      this.target.width = tw;
      this.target.height = th;
      // uni.showLoading({
      //   title: '正在裁剪',
      // });
      setTimeout(() => {
        this.targetContext.drawImage(
          this.url,
          x,
          y,
          width,
          height,
          0,
          0,
          tw,
          th
        );
        this.targetContext.draw(false, () => {
          const CanvasContext = my.createCanvasContext('target');
          CanvasContext.toDataURL({}).then((dataURL) => {
            this.upLoadPic(dataURL);
          });
        });
      }, 100);
    },
    // 取消
    oncancel() {
      // 抛出失败事件
      this.$emit('cancel');
    },
    // 上传图片
    async upLoadPic(base64Data) {
      const [err, res] = await myYaYunService.upLoadPicApi(base64Data);
      if (res) {
        let url = res.data;
        // console.log('url',url);
        if (url && url.length > 0) {
          this.imgCallBack('', url);
        }
      }
    },
    // 抛出成功事件
    imgCallBack(action, url) {
      this.$emit('ok', {
        path: url,
      });
    },
  },
};
</script>

<style scoped>
page {
  background-color: black;
}
.ksp-image-cutter {
  /* position: absolute; */
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  bottom: 0;
  z-index: 1000;
  background-color: black;
}
.toolbar {
  position: absolute;
  width: 100%;
  height: 134rpx;
  left: 0rpx;
  bottom: 10rpx;
  box-sizing: border-box;
}
.btn-cancel {
  position: absolute;
  width: 339rpx;
  height: 98rpx;
  left: 24rpx;
  font-size: 36rpx;
  color: #fff;
  border: none;
  background: none;
}
.btn-ok {
  position: absolute;
  width: 339rpx;
  height: 98rpx;
  right: 24rpx;
  font-size: 36rpx;
  color: #fff;
  border: none;
  background: none;
}
.body {
  position: absolute;
  left: 0rpx;
  right: 0rpx;
  top: 0rpx;
  bottom: 0rpx;
  background: black;
  overflow: hidden;
}
.mask {
  position: absolute;
  left: 0rpx;
  right: 0rpx;
  top: 0rpx;
  bottom: 0rpx;
  background: black;
  opacity: 0.4;
}
.plank {
  position: absolute;
  left: 0rpx;
  right: 0rpx;
  top: 0rpx;
  bottom: 0rpx;
}
.image {
  position: absolute;
}
.frame {
  position: absolute;
}
.canvas {
  position: absolute;
  display: block;
  left: 0px;
  top: 0px;
}
.rect {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  /* width: 600rpx;
	height: 600rpx; */
  padding: 211rpx 375rpx;
  border: 1000rpx solid rgba(0, 0, 0, 0.5);
  /* border-radius: 50%; */
}

.line-one {
  position: absolute;
  width: 100%;
  height: 1px;
  background: white;
  left: 0;
  top: 33.3%;
}
.line-two {
  position: absolute;
  width: 100%;
  height: 1px;
  background: white;
  left: 0;
  top: 66.7%;
}
.line-three {
  position: absolute;
  width: 1px;
  height: 100%;
  background: white;
  top: 0;
  left: 33.3%;
}
.line-four {
  position: absolute;
  width: 1px;
  height: 100%;
  background: white;
  top: 0;
  left: 66.7%;
}
.frame-left {
  position: absolute;
  height: 100%;
  width: 8px;
  left: -4px;
  top: 0;
}
.frame-right {
  position: absolute;
  height: 100%;
  width: 8px;
  right: -4px;
  top: 0;
}
.frame-top {
  position: absolute;
  width: 100%;
  height: 8px;
  top: -4px;
  left: 0;
}
.frame-bottom {
  position: absolute;
  width: 100%;
  height: 8px;
  bottom: -4px;
  left: 0;
}
.frame-left-top {
  position: absolute;
  width: 20px;
  height: 20px;
  left: -6px;
  top: -6px;
}
.frame-left-bottom {
  position: absolute;
  width: 20px;
  height: 20px;
  left: -6px;
  bottom: -6px;
}
.frame-right-top {
  position: absolute;
  width: 20px;
  height: 20px;
  right: -6px;
  top: -6px;
}
.frame-right-bottom {
  position: absolute;
  width: 20px;
  height: 20px;
  right: -6px;
  bottom: -6px;
}
</style>

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Python中的tkinter库的canvas组件来实现图片裁剪。以下是一个基本的示例代码: ```python import tkinter as tk from PIL import Image, ImageTk class ImageCropper: def __init__(self, image_path): self.root = tk.Tk() self.canvas = tk.Canvas(self.root, width=500, height=500) self.canvas.pack() self.image = Image.open(image_path) self.tk_image = ImageTk.PhotoImage(self.image) self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_image) self.rect = None self.start_x = None self.start_y = None self.end_x = None self.end_y = None self.canvas.bind("<Button-1>", self.on_button_press) self.canvas.bind("<B1-Motion>", self.on_move_press) self.canvas.bind("<ButtonRelease-1>", self.on_button_release) self.root.mainloop() def on_button_press(self, event): self.start_x = event.x self.start_y = event.y if not self.rect: self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, 1, 1, outline='red', width=3) def on_move_press(self, event): if self.rect: self.end_x = event.x self.end_y = event.y self.canvas.coords(self.rect, self.start_x, self.start_y, self.end_x, self.end_y) def on_button_release(self, event): if self.rect: self.image = self.image.crop((self.start_x, self.start_y, self.end_x, self.end_y)) self.tk_image = ImageTk.PhotoImage(self.image) self.canvas.delete(self.rect) self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_image) if __name__ == '__main__': cropper = ImageCropper('image.jpg') ``` 在这个示例代码中,我们首先创建了一个名为ImageCropper的类,它接收一个图像路径作为参数。我们创建了一个tkinter的canvas组件,并将图像加载到canvas中。 我们还定义了一个on_button_press方法,用于在鼠标左键按下时创建一个矩形。on_move_press方法用于跟踪鼠标移动,从而更新矩形的位置和大小。最后,on_button_release方法用于在鼠标释放时裁剪图像,删除矩形并更新canvas中的图像。 在main函数中,我们实例化ImageCropper类并传递要裁剪图像路径。运行程序后,我们可以使用鼠标选择要裁剪的区域,然后按下鼠标左键并拖动以选择区域。释放鼠标左键后,程序将裁剪图像并更新canvas中的图像

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值