前端调起摄像头拍摄增加引导框及相片裁剪和旋转处理

时间:2022-12-23 17:52:18

标题调起摄像头拍摄

  1. html部分
    <div @click="getCamera()">
    	<span>拍照</span>
    </div>
    
    <div
      class="video-box"
      v-show="showImg"
    >
    	<video
    		ref="videos"
    		v-show="showImg && isShowVideo == true"
    		webkit-playsinline="true"
    		playsinline
    	></video>
    	
    	<!-- 取景框 -->
    	<div class="img_warp">
    		<img :src="../../assets/image/xxx.png" class="cover-img"/>
    	</div>
    	
    	<!-- 关闭按钮 -->
    	<van-button
    		class="close-take-photo"
    		@click.native="closeTakePhoto"
    	>×</van-button>
    	
    	<!-- 拍照按钮 -->
    	<van-button
    		id="snap"
    		class="snap-btn"
    		@click.native="closeVideo"
    	></van-button>
    </div>
    
    
  2. css部分
// 样式可根据聚体需求调整 

.video-box {
  width: 100%;
  height: 100%;
  /* background-color: rgba(75, 75, 75); */
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}

/* 摄像头 */
video {
  /* z-index: 10; */
  /* margin-right: 50px; */
  height: 100%;
  /* 拍摄镜头以Y轴为轴线旋转180度 */
  // transform: rotateY(180deg);
  // -ms-transform: rotateY(180deg); /* IE 9 */
  // -moz-transform: rotateY(180deg); /* Firefox */
  // -webkit-transform: rotateY(180deg); /* Safari 和 Chrome */
  // -o-transform: rotateY(180deg); /* Opera */
  /* width: 100%; */
  /* margin: auto; */
}

/** 右上角关闭按钮 */
.close-take-photo {
  width: 30px;
  height: 30px;
  color: black;
  font-size: 20px;
  background-color: white;
  box-shadow: 0 0 10px #fff;
  border-radius: 15px;
  line-height: 30px;
  text-align: center;
  /** 定位 */
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 10;
}

/* 取景框 */
.img_warp {
  width: 80%;
  height: 70%;
  /* border: 1px black solid; */
  /* background-image: url("../../../assets/images/cover3.png");
  background-repeat: no-repeat;
  background-size: cover; */
  // position: fixed;
  // left: 0;
  // bottom: 0;
  // top: 0;
  // right: 0;
  z-index: 10;
}

.img_warp img {
  width: 80%;
  height: 65%;
  margin-left: 50%;
  transform: translateX(-50%);
  margin-top: 80px;
}

.cover-img {
  /* width: 300px; */
  /* height: 100%; */
  width: 100%;

  position: fixed;
  left: 0;
  bottom: 0;
  top: 0;
  right: 0;
}

/* 拍照按钮 */
.snap-btn {
  width: 50px;
  height: 50px;
  background: #fff;
  box-shadow: 0 0 5px #fff;
  border-radius: 50px;
  position: fixed;
  bottom: 20px;
  left: 50%;
  margin-left: -25px;
  z-index: 10;
}

/* 画布 */
canvas {
  width: 100%;
  /* height: 100%; */
  border: 1px;
}
  1. js部分
// 调用摄像头 开始拍照
getCamera () {
	// 注意本例需要在HTTPS协议网站中运行,新版本Chrome中getUserMedia接口在http下不再支持。
	// 设置事件监听器
	// window.addEventListener('DOMContentLoaded', function () {

	// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
	if (navigator.mediaDevices === undefined) {
 		navigator.mediaDevices = {}
 	}

	// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
	// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
	if (navigator.mediaDevices.getUserMedia === undefined) {
		navigator.mediaDevices.getUserMedia = function (constraints) {
			// 首先,如果有getUserMedia的话,就获得它
			// var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
			var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia

			// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
			if (!getUserMedia) {
				alert('该浏览器不支持getUserMedia,请使用其他浏览器')
				return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
			}

			// 否则,为老的navigator.getUserMedia方法包裹一个Promise
			return new Promise(function (resolve, reject) {
				getUserMedia.call(navigator, constraints, resolve, reject)
			})
		}
	}
	
	// 获取手机屏幕宽度 赋给video
	let h = document.documentElement.clientHeight
	let w = document.documentElement.clientWidth
	var constraints = {
		audio: false,
		video: {
			// facingMode: 'user', // 调前置摄像头
			facingMode: 'environment', // 调后置摄像头
			//调整焦距及清晰度
			advanced: [
				{ width: 1500, height: 2000 },
				{ aspectRatio: 1 }
			]
		}
	}

	let _this = this

	navigator.mediaDevices.getUserMedia(constraints)
		.then((stream) => {
			_this.MediaStreamTrack = typeof stream.stop === 'function' ? stream : stream.getTracks()[0]
			// 显示取景框
			this.showImg = true
			// 显示画布 关闭摄像头
			this.isShowVideo = true
			this.isShowCanvas = false
			var video = document.querySelector('video')

      // 旧的浏览器可能没有srcObject
      if ('srcObject' in video) {
        video.srcObject = stream
      } else {
        // 防止在新的浏览器里使用它,应为它已经不再支持了
        video.src = window.URL.createObjectURL(stream)
      }

      //   var tracker = this.tracking.ObjectTracker('face')

      video.onloadedmetadata = function () {
        // 屏幕宽高
        video.style.height = h + 'px'
        // video实际宽高
        // console.log(video.videoWidth + ',' + video.videoHeight)

        // 偏移  video.videoWidth / video.videoHeight
        var videoW = (video.videoWidth / video.videoHeight) * h
        this.margin_left = (w - videoW) / 2

        // 控制左边不会出现空白的同时向右偏移
        // if (videoW - w > 60) {
        //   this.margin_left += 30
        // } else {
        //   this.margin_left += (videoW - w) / 2
        // }

        video.style.marginLeft = this.margin_left + 'px'

        // video实际宽度

        // 根据实际宽度 查找中间线
        // let mainLine = (video.videoWidth / 2 - w)
        // let mainLine = (video.videoWidth / 2 - w / 2)

        // // video.style.marginLeft = (w - video.videoWidth) / 2 + 'px'
        // video.style.marginLeft = -mainLine + 'px'

        video.play()
      }
    })
    .catch(function (err) { alert(err.name + ': ' + err.message) })

  // }, false)
},

// 点击拍照
closeVideo () {
	// 点击拍照 -> 关闭摄像头 ->显示画布
	this.isShowVideo = false
	this.isShowCanvas = true
	this.showImg = false

	var canvas = document.querySelector('#mycanvas')
	var ctx = canvas.getContext('2d')
	var video = document.querySelector('video')
	// var ctx = this.$refs.mycanvas.getContext('2d')

	canvas.height = 600
	canvas.width = (video.videoWidth * 600) / video.videoHeight

	ctx.drawImage(video, 0, 0, canvas.width, 600)
	this.dealVideoPhoto(canvas.toDataURL("image/jpeg", .8))
},

// 处理拍摄相片
dealVideoPhoto (base64Img) {
	// 输出相片base64
	console.log(base64Img);
},

// 取消拍照
closeTakePhoto () {
	// 刷新当前页面
	this.$router.go(0)
}

处理拍摄的相片,进行裁剪和旋转处理

由于处理拍摄的相片并进行进行裁剪和旋转处理需要进行以下三个步骤:

  1. 将图片base64转化为img对象
  2. 对图片进行裁剪
  3. 对图片进行旋转
    将图片base64转化为img对象的方法进行封装,文件吗命名为xxx1.js,放于utils文件夹下
    // 将base64转换成img对象
    /**
     * 
     * @param {*} str 图片base64
     */
    export default function readBase64 (str) {
      return new Promise((resolve, reject) => {
        const img = new Image()
        img.src = 'data:image/jpeg;base64,' + str
        img.onload = function () {
          resolve(img)
        }
        img.onerror = function (e) {
          reject(e)
        }
      })
    }
    

将对图片进行裁剪和旋转的方法进行封装,文件吗命名为xxx2.js,放于utils文件夹下

var img = null
var canvas = document.createElement("canvas");// 创建canvas对象
var ctx = canvas.getContext('2d');
/**
 * 
 * @param {*} imgObj 
 * @param {*} leftX 左上角X坐标
 * @param {*} leftY 左上角Y坐标
 * @param {*} rightX 右下角X坐标
 * @param {*} rightY 右下角Y坐标
 * @param {*} isRotate 是否旋转
 * @returns 
 */
export default function dealBase64Img (imgObj, leftX, leftY, rightX, rightY, isRotate) {
	return new Promise(resolve => {
		// 获取原图宽高
		var height = imgObj.height;
		var width = imgObj.width;
		//设置canvas大小与原图宽高一致
		canvas.height = height;
		canvas.width = width;
		// 在canvas绘制图片
		ctx.drawImage(imgObj, 0, 0, width, height);
		// 截图:
		drawRect(leftX, leftY, rightX, rightY);

		if (isRotate) {
			rotateBase64Img(img, 270).then(res => {
				resolve(res)
			})
		} else {
			resolve(img)
		}
	})
}

// 绘制截图矩阵
function drawRect (leftX, leftY, rightX, rightY) {
	// 截图宽度
	var w = rightX - leftX;
	// 截图高度
	var h = rightY - leftY;
	// 获取截图区域内容,截图区域的像素点矩阵
	var cutImage = ctx.getImageData(leftX, leftY, w, h);
	// 裁剪后的base64数据
	var newImage = createNewCanvas(cutImage, w, h);
	img = newImage;
	// console.log(newImage);// 裁剪后的base64数据
}

//创建新的空白canvas画布将矩阵渲染截图
function createNewCanvas (content, width, height) {
	var nCanvas = document.createElement('canvas');
	var nCtx = nCanvas.getContext('2d');
	nCanvas.width = width;
	nCanvas.height = height;
	nCtx.putImageData(content, 0, 0);// 将画布上指定矩形的像素数据,通过 putImageData() 方法将图像数据放回画布
	return nCanvas.toDataURL('image/png');
}

// 旋转图片
/**
 * 
 * @param {*} src 图片base64
 * @param {*} edg 旋转角度
 * @returns 
 */
function rotateBase64Img (src, edg) {
	return new Promise(resolve => {
		var canvas = document.createElement("canvas");
	    var ctx = canvas.getContext("2d");
		var imgW;//图片宽度
		var imgH;//图片高度
		var size;//canvas初始大小
		if (edg % 90 != 0) {
			console.error("旋转角度必须是90的倍数!");
			throw '旋转角度必须是90的倍数!';
		}
		(edg < 0) && (edg = (edg % 360) + 360)
		const quadrant = (edg / 90) % 4; //旋转象限
		const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 }; //裁剪坐标
		var image = new Image();
		image.crossOrigin = "anonymous"
		image.src = src;
		image.onload = function () {
			imgW = image.width;
			imgH = image.height;
			size = imgW > imgH ? imgW : imgH;
			canvas.width = size * 2;
			canvas.height = size * 2;
			switch (quadrant) {
				case 0:
					cutCoor.sx = size;
					cutCoor.sy = size;
					cutCoor.ex = size + imgW;
					cutCoor.ey = size + imgH;
					break;
				case 1:
					cutCoor.sx = size - imgH;
					cutCoor.sy = size;
					cutCoor.ex = size;
					cutCoor.ey = size + imgW;
					break;
				case 2:
					cutCoor.sx = size - imgW;
					cutCoor.sy = size - imgH;
					cutCoor.ex = size;
					cutCoor.ey = size;
					break;
				case 3:
					cutCoor.sx = size;
					cutCoor.sy = size - imgW;
					cutCoor.ex = size + imgH;
					cutCoor.ey = size + imgW;
					break;
			}
			ctx.translate(size, size);
			ctx.rotate(edg * Math.PI / 180);
			ctx.drawImage(image, 0, 0);
			var imgData = ctx.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
			if (quadrant % 2 == 0) {
				canvas.width = imgW;
				canvas.height = imgH;
			} else {
				canvas.width = imgH;
				canvas.height = imgW;
			}
			ctx.putImageData(imgData, 0, 0);

			resolve(canvas.toDataURL())
		};
	})
}

最后引入xxx1.js的readBase64方法和xxx2.js文件的dealBase64Img方法,处理拍摄后的相片base64

// 注意引入路径不要出错
import readBase64 from '@/utils/xxx1.js'
import dealBase64Img from '@/utils/xxx2.js'

dealVideoPhoto (base64Img) {
	// console.log(base64Img);
	const img = base64Img.replace(/^data:image\/\w+;base64,/, '')
      
	// 将base64转换成img对象
	readBase64(img).then(imgObj => {

		dealBase64Img(imgObj, 250, 60, 550, 480, true).then(res => {
			const img = res.replace(/^data:image\/\w+;base64,/, '')
			
			// 输出处理后的相片base64
			console.log(img)
        })
	}
}