这两天撸了一个需求,通过 JS 调用手机后置相机,进行拍照扫码。前台实现调用手机相机,然后截取图片并上传到后台的功能。后台接收传过来的图片后,通过调用开源二维码识别库 ZXing 进行二维码数据解析。刚开始在电脑上截图,上传到后台进行识别,测试了几个没有问题。但是发布外网后,一直解析失败。我把手机拍的照片,放到电脑上也是识别不了。后来通过对比图片,发现手机拍的照片有15M大。怀疑是图片过大导致解析二维码失败,才想着把图片无损压缩后再进行二维码识别,压缩后果然见效。
1.图片无损压缩方法
/// <summary>
/// 无损压缩图片
/// </summary>
/// <param name="sFile">原图片地址</param>
/// <param name="dFile">压缩后保存图片地址</param>
/// <param name="flag">压缩质量(数字越小压缩率越高)1-100</param>
/// <param name="size">压缩后图片的最大大小</param>
/// <param name="sfsc">是否是第一次调用</param>
/// <returns></returns>
public static bool CompressImage(string sFile, string dFile, int flag = , int size = , bool sfsc = true)
{
Image iSource = Image.FromFile(sFile);
ImageFormat tFormat = iSource.RawFormat;
//如果是第一次调用,原始图像的大小小于要压缩的大小,则直接复制文件,并且返回true
FileInfo firstFileInfo = new FileInfo(sFile);
if (sfsc == true && firstFileInfo.Length < size * )
{
firstFileInfo.CopyTo(dFile);
return true;
} int dHeight = iSource.Height / ;
int dWidth = iSource.Width / ;
int sW = , sH = ;
//按比例缩放
Size tem_size = new Size(iSource.Width, iSource.Height);
if (tem_size.Width > dHeight || tem_size.Width > dWidth)
{
if ((tem_size.Width * dHeight) > (tem_size.Width * dWidth))
{
sW = dWidth;
sH = (dWidth * tem_size.Height) / tem_size.Width;
}
else
{
sH = dHeight;
sW = (tem_size.Width * dHeight) / tem_size.Height;
}
}
else
{
sW = tem_size.Width;
sH = tem_size.Height;
} Bitmap ob = new Bitmap(dWidth, dHeight);
Graphics g = Graphics.FromImage(ob); g.Clear(Color.WhiteSmoke);
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.DrawImage(iSource, new Rectangle((dWidth - sW) / , (dHeight - sH) / , sW, sH), , , iSource.Width, iSource.Height, GraphicsUnit.Pixel); g.Dispose(); //以下代码为保存图片时,设置压缩质量
EncoderParameters ep = new EncoderParameters();
long[] qy = new long[];
qy[] = flag;//设置压缩的比例1-100
EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy);
ep.Param[] = eParam; try
{
ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo jpegICIinfo = null;
for (int x = ; x < arrayICI.Length; x++)
{
if (arrayICI[x].FormatDescription.Equals("JPEG"))
{
jpegICIinfo = arrayICI[x];
break;
}
}
if (jpegICIinfo != null)
{
ob.Save(dFile, jpegICIinfo, ep);//dFile是压缩后的新路径
FileInfo fi = new FileInfo(dFile);
if (fi.Length > * size)
{
flag = flag - ;
CompressImage(sFile, dFile, flag, size, false);
}
}
else
{
ob.Save(dFile, tFormat);
}
return true;
}
catch
{
return false;
}
finally
{ iSource.Dispose();
ob.Dispose();
// System.IO.File.Delete(sFile);
}
}
2.js调用系统相机(推荐Firefox)
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<script src="~/Js/photo/jquery-1.11.3.js"></script>
<script src="~/Js/photo/cropper.min.js"></script>
<link href="~/Js/photo/cropper.min.css" rel="stylesheet" />
<link href="~/Js/photo/ImgCropping.css" rel="stylesheet" />
<script src="~/layer/layer.js"></script>
<style>
/*.cropper-crop-box {
width: 400px !important;
height: 400px !important;
}*/
</style> </head>
<body>
@*<div style="margin-left:50%;margin-top:25%">
<button id="replaceImg" class="l-btn" style="width:400px;height:100px">更换图片</button>
</div>*@ <!--图片裁剪框 start-->
<div style="" class="tailoring-container">
@*<div class="black-cloth" onclick="closeTailor(this)"></div>*@
<div class="tailoring-content">
<div class="tailoring-content-one">
<label title="上传图片" for="chooseImg" class="l2-btn choose-btn">
<input type="file" accept="image/jpg,image/jpeg,image/png" name="file" id="chooseImg" class="hidden" onchange="selectImg(this)">
本地上传
</label>
@*<label title="拍照" class="l2-btn choose-btn" id='capture' style="margin-left: 2%;">拍照</label>
<label title="重拍" class="l2-btn choose-btn" id='takeAgain' style="margin-left: 2%;">重拍</label>*@
@*<div class="close-tailoring" onclick="closeTailor(this)">×</div>*@
</div>
<div class="tailoring-content-two">
<div class="tailoring-box-parcel">
<video id="video" width="80%" height="80%" controls style="float: left;"></video>
<canvas id="canvas" width="482px" height="448px" style="float: left;" hidden="hidden"></canvas>
<div id="showImg" hidden="hidden" style="width: 80%;height:80%;">
<img id="tailoringImg">
</div>
</div>
<div class="preview-box-parcel">
<p>图片预览:</p>
<div class="square previewImg"></div>
@*<div class="circular previewImg"></div>*@
</div>
</div>
<div class="tailoring-content-three">
@*<button class="l2-btn cropper-reset-btn">复位</button>
<button class="l2-btn cropper-rotate-btn">旋转</button>
<button class="l2-btn cropper-scaleX-btn">换向</button>*@
<button class="l2-btn sureCut" id="sureCut">确定</button>
</div>
</div>
</div>
<!--图片裁剪框 end-->
<script> //弹出框水平垂直居中
(window.onresize = function () {
var win_height = $(window).height();
var win_width = $(window).width();
if (win_width <= 768) {
$(".tailoring-content").css({
"top": (win_height - $(".tailoring-content").outerHeight()) / 2,
"left": 0
});
} else {
$(".tailoring-content").css({
"top": (win_height - $(".tailoring-content").outerHeight()) / 2,
"left": (win_width - $(".tailoring-content").outerWidth()) / 2
});
}
})(); //图像上传
function selectImg(file) {
if (!file.files || !file.files[0]) {
return;
}
var reader = new FileReader();
reader.onload = function (evt) {
var replaceSrc = evt.target.result;
//更换cropper的图片
$('#tailoringImg').cropper('replace', replaceSrc, false);//默认false,适应高度,不失真 }
reader.readAsDataURL(file.files[0]);
mediaStreamTrack && mediaStreamTrack.stop();
$("#video").hide();
$("#showImg").show(); }
//cropper图片裁剪
$('#tailoringImg').cropper({
aspectRatio: 1 / 1,//默认比例
preview: '.previewImg',//预览视图
guides: false, //裁剪框的虚线(九宫格)
autoCropArea: 0.5, //0-1之间的数值,定义自动剪裁区域的大小,默认0.8
movable: false, //是否允许移动图片
dragCrop: true, //是否允许移除当前的剪裁框,并通过拖动来新建一个剪裁框区域
movable: true, //是否允许移动剪裁框
resizable: true, //是否允许改变裁剪框的大小
zoomable: false, //是否允许缩放图片大小
mouseWheelZoom: false, //是否允许通过鼠标滚轮来缩放图片
touchDragZoom: true, //是否允许通过触摸移动来缩放图片
rotatable: true, //是否允许旋转图片
crop: function (e) {
// 输出结果数据裁剪图像。
}
}); //弹框
$("#replaceImg").on("click", function () {
takeImg()
}); //旋转
$(".cropper-rotate-btn").on("click", function () {
$('#tailoringImg').cropper("rotate", 45);
});
//复位
$(".cropper-reset-btn").on("click", function () {
$('#tailoringImg').cropper("reset");
});
//换向
var flagX = true;
$(".cropper-scaleX-btn").on("click", function () {
if (flagX) {
$('#tailoringImg').cropper("scaleX", -1);
flagX = false;
} else {
$('#tailoringImg').cropper("scaleX", 1);
flagX = true;
}
flagX != flagX;
}); //裁剪后的处理
var shadIndex = 0;
$("#sureCut").on("click", function () {
var cas = $('#tailoringImg').cropper('getCroppedCanvas');//获取被裁剪后的canvas
var base64url = cas.toDataURL('image/png'); //转换为base64地址形式
base64url = base64url.replace("\r", "")
$.ajax({ url: "/SysPadBindMobile/UploadPhoto",
data: { op: "takePhoto", base64url: base64url },
type: "POST",
dataType: "json",
beforeSend: function () {
shadeIndex = layer.load(2, {
time: 0,
content: '解析中...',
success: function (layero) {
layero.find('.layui-layer-content').css({
'padding-left': '40px',//图标与样式会重合,这样设置可以错开
'width': '200px'//文字显示的宽度
});
}
});
},
success: function (data) {
layer.close(shadeIndex);
//var result = parseInt($.trim(data.result));
if (data.code == -1) {
//未找到绑定列表
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
window.parent.showAccounts(data.msg); } else if (data.code > 0) {
//存在绑定列表,调用父窗体方法显示
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
window.parent.showAccounts(data.msg);
//$('#attendance_info').css('color','green').text("已提交");
} else {
// $.messager.alert("失败提示", "头像更新失败,请稍后重试...", 'error')
}
}
});
//关闭裁剪框
closeTailor();
});
//关闭裁剪框
function closeTailor() {
$(".tailoring-container").toggle();
mediaStreamTrack && mediaStreamTrack.stop();
} //访问用户媒体设备的兼容方法
function getUserMedia(constraints, success, error) {
if (navigator.mediaDevices.getUserMedia) {
//最新的标准API
navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
} else if (navigator.webkitGetUserMedia) {
//webkit核心浏览器
navigator.webkitGetUserMedia(constraints, success, error)
} else if (navigator.mozGetUserMedia) {
//firfox浏览器
navigator.mozGetUserMedia(constraints, success, error);
} else if (navigator.getUserMedia) {
//旧版API
navigator.getUserMedia(constraints, success, error);
}
} let video = document.getElementById('video');
let canvas = document.getElementById('canvas');
let context = canvas.getContext('2d');
var mediaStreamTrack
function success(stream) {
//兼容webkit核心浏览器
let CompatibleURL = window.URL || window.webkitURL;
//将视频流设置为video元素的源
mediaStreamTrack = stream.getTracks()[0];
//video.src = CompatibleURL.createObjectURL(stream);
video.srcObject = stream;
video.play();
} function error(error) {
alert('访问用户媒体设备失败,请尝试更换浏览器')
} document.getElementById('capture').addEventListener('click', function () {
context.drawImage(video, 0, 0, 480, 320);
mediaStreamTrack && mediaStreamTrack.stop();
$('#tailoringImg').cropper('replace', canvas.toDataURL("image/png"), false);//默认false,适应高度,不失真
$("#video").hide();//隐藏拍照框
$("#showImg").show()//将拍照结果显示
}) //请求拍照
$("#takeAgain").bind("click", function () {
takePhoto();
}); //开始拍照
function takeImg() {
$(".tailoring-container").toggle();
takePhoto();
} //请求摄像头
function takePhoto() {
if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {
//调用用户媒体设备, 访问摄像头
getUserMedia({ video: { width: 100, height: 100 } }, success, error);
$("#showImg").hide();//隐藏拍照结果显示框
//$('#showImg').html('<img id="tailoringImg" hidden="hidden">')
$("#video").show();//开启拍照框
} else {
alert('不支持访问用户媒体');
}
}
</script> </body> </html>
3.C# 后台接收二维码图片,进行压缩解析的相关代码
[HttpPost]
public ActionResult UploadPhoto(/*[System.Web.Http.FromBody] photoinfo photo*/)
{
try
{
string base64Str = Request.Form["base64url"];
base64Str = base64Str.Substring(base64Str.IndexOf("base64,") + ); byte[] imgBytes = Convert.FromBase64String(base64Str);
Stream stream = new MemoryStream(imgBytes);
var bit = new Bitmap(stream);
string photoBase = $"{AppDomain.CurrentDomain.BaseDirectory}\\photoimg";
if (!Directory.Exists(photoBase))
{
Directory.CreateDirectory(photoBase);
}
string guid = Guid.NewGuid().ToString();
string oldPath = $"{photoBase}\\{guid}.png";
string newPath = $"{photoBase}\\{guid}_1.png";
bit.Save(oldPath); var compress = CompressImage(oldPath, newPath);
var bindInfo = new QrCodeInfo(); if (!compress)
{
var result = new ZXing.BarcodeReader().Decode(bit);
bit.Dispose();
if (result != null && !string.IsNullOrEmpty(result.Text))
{
bindInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<QrCodeInfo>(result.Text);
}
}
else
{ var f = System.IO.File.Open(newPath, System.IO.FileMode.Open);
var newbit = new Bitmap(f);
var result = new BarcodeReader().Decode(newbit);
f.Dispose();
newbit.Dispose();
if (result != null && !string.IsNullOrEmpty(result.Text))
{
bindInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<QrCodeInfo>(result.Text);
}
}
// bit.Save($"{photoBase}\\{Guid.NewGuid()}.png"); if (bindInfo != null)
{
//传参,调用方法,显示查询查询到的数据
var accountList = _iSysPadBindMobileCore.GetAccountsByMachineId(bindInfo.MachineId, bindInfo.Model);
if (accountList.Any())
{
return Json(new { code = , msg = Newtonsoft.Json.JsonConvert.SerializeObject(accountList) }, JsonRequestBehavior.AllowGet);
}
}
}
catch (Exception ex)
{
return Json(new { code = -, msg = ex.ToString().Substring(, ) }, JsonRequestBehavior.AllowGet);
} return Json(new { code = -, msg = "" }, JsonRequestBehavior.AllowGet);
}
PS:另外在上传图片的过程中,可能会因为图片文件过大导致报500错误,只需要在web.config的<system.web></system.web>节点中配置大小即可: <httpRuntime maxRequestLength="102400" executionTimeout="200" enable="true" />