C#/.net 通过js调用系统相机进行拍照,图片无损压缩后进行二维码识别
这两天撸了一个需求,通过 JS 调用手机后置相机,进行拍照扫码。前台实现调用手机相机,然后截取图片并上传到后台的功能。后台接收传过来的图片后,通过调用开源二维码识别库 ZXing 进行二维码数据解析。刚开始在电脑上截图,上传到后台进行识别,测试了几个没有问题。但是发布外网后,一直解析失败。我把手机拍的照片,放到电脑上也是识别不了。后来通过对比图片,发现手机拍的照片有15M大。怀疑是图片过大导致解析二维码失败,才想着把图片无损压缩后再进行二维码识别,压缩后果然见效。
1.图片无损压缩方法
1 /// <summary> 2 /// 无损压缩图片 3 /// </summary> 4 /// <param name="sFile">原图片地址</param> 5 /// <param name="dFile">压缩后保存图片地址</param> 6 /// <param name="flag">压缩质量(数字越小压缩率越高)1-100</param> 7 /// <param name="size">压缩后图片的最大大小</param> 8 /// <param name="sfsc">是否是第一次调用</param> 9 /// <returns></returns> 10 public static bool CompressImage(string sFile, string dFile, int flag = 90, int size = 300, bool sfsc = true) 11 { 12 Image iSource = Image.FromFile(sFile); 13 ImageFormat tFormat = iSource.RawFormat; 14 //如果是第一次调用,原始图像的大小小于要压缩的大小,则直接复制文件,并且返回true 15 FileInfo firstFileInfo = new FileInfo(sFile); 16 if (sfsc == true && firstFileInfo.Length < size * 1024) 17 { 18 firstFileInfo.CopyTo(dFile); 19 return true; 20 } 21 22 int dHeight = iSource.Height / 2; 23 int dWidth = iSource.Width / 2; 24 int sW = 0, sH = 0; 25 //按比例缩放 26 Size tem_size = new Size(iSource.Width, iSource.Height); 27 if (tem_size.Width > dHeight || tem_size.Width > dWidth) 28 { 29 if ((tem_size.Width * dHeight) > (tem_size.Width * dWidth)) 30 { 31 sW = dWidth; 32 sH = (dWidth * tem_size.Height) / tem_size.Width; 33 } 34 else 35 { 36 sH = dHeight; 37 sW = (tem_size.Width * dHeight) / tem_size.Height; 38 } 39 } 40 else 41 { 42 sW = tem_size.Width; 43 sH = tem_size.Height; 44 } 45 46 Bitmap ob = new Bitmap(dWidth, dHeight); 47 Graphics g = Graphics.FromImage(ob); 48 49 g.Clear(Color.WhiteSmoke); 50 g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; 51 g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 52 g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 53 54 g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel); 55 56 g.Dispose(); 57 58 //以下代码为保存图片时,设置压缩质量 59 EncoderParameters ep = new EncoderParameters(); 60 long[] qy = new long[1]; 61 qy[0] = flag;//设置压缩的比例1-100 62 EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy); 63 ep.Param[0] = eParam; 64 65 try 66 { 67 ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); 68 ImageCodecInfo jpegICIinfo = null; 69 for (int x = 0; x < arrayICI.Length; x++) 70 { 71 if (arrayICI[x].FormatDescription.Equals("JPEG")) 72 { 73 jpegICIinfo = arrayICI[x]; 74 break; 75 } 76 } 77 if (jpegICIinfo != null) 78 { 79 ob.Save(dFile, jpegICIinfo, ep);//dFile是压缩后的新路径 80 FileInfo fi = new FileInfo(dFile); 81 if (fi.Length > 1024 * size) 82 { 83 flag = flag - 10; 84 CompressImage(sFile, dFile, flag, size, false); 85 } 86 } 87 else 88 { 89 ob.Save(dFile, tFormat); 90 } 91 return true; 92 } 93 catch 94 { 95 return false; 96 } 97 finally 98 { 99 100 iSource.Dispose(); 101 ob.Dispose(); 102 // System.IO.File.Delete(sFile); 103 } 104 }
2.js调用系统相机(推荐Firefox)
1 @{ 2 Layout = null; 3 } 4 <!DOCTYPE html> 5 <html> 6 <head> 7 <script src="~/Js/photo/jquery-1.11.3.js"></script> 8 <script src="~/Js/photo/cropper.min.js"></script> 9 <link href="~/Js/photo/cropper.min.css" rel="stylesheet" /> 10 <link href="~/Js/photo/ImgCropping.css" rel="stylesheet" /> 11 <script src="~/layer/layer.js"></script> 12 <style> 13 /*.cropper-crop-box { 14 width: 400px !important; 15 height: 400px !important; 16 }*/ 17 </style> 18 19 </head> 20 <body> 21 @*<div style="margin-left:50%;margin-top:25%"> 22 <button id="replaceImg" class="l-btn" style="width:400px;height:100px">更换图片</button> 23 </div>*@ 24 25 <!--图片裁剪框 start--> 26 <div style="" class="tailoring-container"> 27 @*<div class="black-cloth" onclick="closeTailor(this)"></div>*@ 28 <div class="tailoring-content"> 29 <div class="tailoring-content-one"> 30 <label title="上传图片" for="chooseImg" class="l2-btn choose-btn"> 31 <input type="file" accept="image/jpg,image/jpeg,image/png" name="file" id="chooseImg" class="hidden" onchange="selectImg(this)"> 32 本地上传 33 </label> 34 @*<label title="拍照" class="l2-btn choose-btn" id='capture' style="margin-left: 2%;">拍照</label> 35 <label title="重拍" class="l2-btn choose-btn" id='takeAgain' style="margin-left: 2%;">重拍</label>*@ 36 @*<div class="close-tailoring" onclick="closeTailor(this)">×</div>*@ 37 </div> 38 <div class="tailoring-content-two"> 39 <div class="tailoring-box-parcel"> 40 <video id="video" width="80%" height="80%" controls style="float: left;"></video> 41 <canvas id="canvas" width="482px" height="448px" style="float: left;" hidden="hidden"></canvas> 42 <div id="showImg" hidden="hidden" style="width: 80%;height:80%;"> 43 <img id="tailoringImg"> 44 </div> 45 </div> 46 <div class="preview-box-parcel"> 47 <p>图片预览:</p> 48 <div class="square previewImg"></div> 49 @*<div class="circular previewImg"></div>*@ 50 </div> 51 </div> 52 <div class="tailoring-content-three"> 53 @*<button class="l2-btn cropper-reset-btn">复位</button> 54 <button class="l2-btn cropper-rotate-btn">旋转</button> 55 <button class="l2-btn cropper-scaleX-btn">换向</button>*@ 56 <button class="l2-btn sureCut" id="sureCut">确定</button> 57 </div> 58 </div> 59 </div> 60 <!--图片裁剪框 end--> 61 <script> 62 63 //弹出框水平垂直居中 64 (window.onresize = function () { 65 var win_height = $(window).height(); 66 var win_width = $(window).width(); 67 if (win_width <= 768) { 68 $(".tailoring-content").css({ 69 "top": (win_height - $(".tailoring-content").outerHeight()) / 2, 70 "left": 0 71 }); 72 } else { 73 $(".tailoring-content").css({ 74 "top": (win_height - $(".tailoring-content").outerHeight()) / 2, 75 "left": (win_width - $(".tailoring-content").outerWidth()) / 2 76 }); 77 } 78 })(); 79 80 81 //图像上传 82 function selectImg(file) { 83 if (!file.files || !file.files[0]) { 84 return; 85 } 86 var reader = new FileReader(); 87 reader.onload = function (evt) { 88 var replaceSrc = evt.target.result; 89 //更换cropper的图片 90 $('#tailoringImg').cropper('replace', replaceSrc, false);//默认false,适应高度,不失真 91 92 } 93 reader.readAsDataURL(file.files[0]); 94 mediaStreamTrack && mediaStreamTrack.stop(); 95 $("#video").hide(); 96 $("#showImg").show(); 97 98 } 99 //cropper图片裁剪 100 $('#tailoringImg').cropper({ 101 aspectRatio: 1 / 1,//默认比例 102 preview: '.previewImg',//预览视图 103 guides: false, //裁剪框的虚线(九宫格) 104 autoCropArea: 0.5, //0-1之间的数值,定义自动剪裁区域的大小,默认0.8 105 movable: false, //是否允许移动图片 106 dragCrop: true, //是否允许移除当前的剪裁框,并通过拖动来新建一个剪裁框区域 107 movable: true, //是否允许移动剪裁框 108 resizable: true, //是否允许改变裁剪框的大小 109 zoomable: false, //是否允许缩放图片大小 110 mouseWheelZoom: false, //是否允许通过鼠标滚轮来缩放图片 111 touchDragZoom: true, //是否允许通过触摸移动来缩放图片 112 rotatable: true, //是否允许旋转图片 113 crop: function (e) { 114 // 输出结果数据裁剪图像。 115 } 116 }); 117 118 //弹框 119 $("#replaceImg").on("click", function () { 120 takeImg() 121 }); 122 123 //旋转 124 $(".cropper-rotate-btn").on("click", function () { 125 $('#tailoringImg').cropper("rotate", 45); 126 }); 127 //复位 128 $(".cropper-reset-btn").on("click", function () { 129 $('#tailoringImg').cropper("reset"); 130 }); 131 //换向 132 var flagX = true; 133 $(".cropper-scaleX-btn").on("click", function () { 134 if (flagX) { 135 $('#tailoringImg').cropper("scaleX", -1); 136 flagX = false; 137 } else { 138 $('#tailoringImg').cropper("scaleX", 1); 139 flagX = true; 140 } 141 flagX != flagX; 142 }); 143 144 //裁剪后的处理 145 var shadIndex = 0; 146 $("#sureCut").on("click", function () { 147 var cas = $('#tailoringImg').cropper('getCroppedCanvas');//获取被裁剪后的canvas 148 var base64url = cas.toDataURL('image/png'); //转换为base64地址形式 149 base64url = base64url.replace("\r", "") 150 $.ajax({ 151 152 url: "/SysPadBindMobile/UploadPhoto", 153 data: { op: "takePhoto", base64url: base64url }, 154 type: "POST", 155 dataType: "json", 156 beforeSend: function () { 157 shadeIndex = layer.load(2, { 158 time: 0, 159 content: '解析中...', 160 success: function (layero) { 161 layero.find('.layui-layer-content').css({ 162 'padding-left': '40px',//图标与样式会重合,这样设置可以错开 163 'width': '200px'//文字显示的宽度 164 }); 165 } 166 }); 167 }, 168 success: function (data) { 169 layer.close(shadeIndex); 170 //var result = parseInt($.trim(data.result)); 171 if (data.code == -1) { 172 //未找到绑定列表 173 var index = parent.layer.getFrameIndex(window.name); 174 parent.layer.close(index); 175 window.parent.showAccounts(data.msg); 176 177 } else if (data.code > 0) { 178 //存在绑定列表,调用父窗体方法显示 179 var index = parent.layer.getFrameIndex(window.name); 180 parent.layer.close(index); 181 window.parent.showAccounts(data.msg); 182 //$('#attendance_info').css('color','green').text("已提交"); 183 } else { 184 // $.messager.alert("失败提示", "头像更新失败,请稍后重试...", 'error') 185 } 186 } 187 }); 188 //关闭裁剪框 189 closeTailor(); 190 }); 191 //关闭裁剪框 192 function closeTailor() { 193 $(".tailoring-container").toggle(); 194 mediaStreamTrack && mediaStreamTrack.stop(); 195 } 196 197 //访问用户媒体设备的兼容方法 198 function getUserMedia(constraints, success, error) { 199 if (navigator.mediaDevices.getUserMedia) { 200 //最新的标准API 201 navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error); 202 } else if (navigator.webkitGetUserMedia) { 203 //webkit核心浏览器 204 navigator.webkitGetUserMedia(constraints, success, error) 205 } else if (navigator.mozGetUserMedia) { 206 //firfox浏览器 207 navigator.mozGetUserMedia(constraints, success, error); 208 } else if (navigator.getUserMedia) { 209 //旧版API 210 navigator.getUserMedia(constraints, success, error); 211 } 212 } 213 214 let video = document.getElementById('video'); 215 let canvas = document.getElementById('canvas'); 216 let context = canvas.getContext('2d'); 217 var mediaStreamTrack 218 function success(stream) { 219 //兼容webkit核心浏览器 220 let CompatibleURL = window.URL || window.webkitURL; 221 //将视频流设置为video元素的源 222 mediaStreamTrack = stream.getTracks()[0]; 223 //video.src = CompatibleURL.createObjectURL(stream); 224 video.srcObject = stream; 225 video.play(); 226 } 227 228 function error(error) { 229 alert('访问用户媒体设备失败,请尝试更换浏览器') 230 } 231 232 233 234 document.getElementById('capture').addEventListener('click', function () { 235 context.drawImage(video, 0, 0, 480, 320); 236 mediaStreamTrack && mediaStreamTrack.stop(); 237 $('#tailoringImg').cropper('replace', canvas.toDataURL("image/png"), false);//默认false,适应高度,不失真 238 $("#video").hide();//隐藏拍照框 239 $("#showImg").show()//将拍照结果显示 240 }) 241 242 //请求拍照 243 $("#takeAgain").bind("click", function () { 244 takePhoto(); 245 }); 246 247 //开始拍照 248 function takeImg() { 249 $(".tailoring-container").toggle(); 250 takePhoto(); 251 } 252 253 //请求摄像头 254 function takePhoto() { 255 if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) { 256 //调用用户媒体设备, 访问摄像头 257 getUserMedia({ video: { width: 100, height: 100 } }, success, error); 258 $("#showImg").hide();//隐藏拍照结果显示框 259 //$('#showImg').html('<img id="tailoringImg" hidden="hidden">') 260 $("#video").show();//开启拍照框 261 } else { 262 alert('不支持访问用户媒体'); 263 } 264 } 265 </script> 266 267 </body> 268 269 </html>
3.C# 后台接收二维码图片,进行压缩解析的相关代码
1 [HttpPost] 2 public ActionResult UploadPhoto(/*[System.Web.Http.FromBody] photoinfo photo*/) 3 { 4 try 5 { 6 string base64Str = Request.Form["base64url"]; 7 base64Str = base64Str.Substring(base64Str.IndexOf("base64,") + 7); 8 9 byte[] imgBytes = Convert.FromBase64String(base64Str); 10 Stream stream = new MemoryStream(imgBytes); 11 var bit = new Bitmap(stream); 12 string photoBase = $"{AppDomain.CurrentDomain.BaseDirectory}\\photoimg"; 13 if (!Directory.Exists(photoBase)) 14 { 15 Directory.CreateDirectory(photoBase); 16 } 17 string guid = Guid.NewGuid().ToString(); 18 string oldPath = $"{photoBase}\\{guid}.png"; 19 string newPath = $"{photoBase}\\{guid}_1.png"; 20 bit.Save(oldPath); 21 22 var compress = CompressImage(oldPath, newPath); 23 var bindInfo = new QrCodeInfo(); 24 25 if (!compress) 26 { 27 var result = new ZXing.BarcodeReader().Decode(bit); 28 bit.Dispose(); 29 if (result != null && !string.IsNullOrEmpty(result.Text)) 30 { 31 bindInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<QrCodeInfo>(result.Text); 32 } 33 } 34 else 35 { 36 37 var f = System.IO.File.Open(newPath, System.IO.FileMode.Open); 38 var newbit = new Bitmap(f); 39 var result = new BarcodeReader().Decode(newbit); 40 f.Dispose(); 41 newbit.Dispose(); 42 if (result != null && !string.IsNullOrEmpty(result.Text)) 43 { 44 bindInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<QrCodeInfo>(result.Text); 45 } 46 } 47 // bit.Save($"{photoBase}\\{Guid.NewGuid()}.png"); 48 49 if (bindInfo != null) 50 { 51 //传参,调用方法,显示查询查询到的数据 52 var accountList = _iSysPadBindMobileCore.GetAccountsByMachineId(bindInfo.MachineId, bindInfo.Model); 53 if (accountList.Any()) 54 { 55 return Json(new { code = 1, msg = Newtonsoft.Json.JsonConvert.SerializeObject(accountList) }, JsonRequestBehavior.AllowGet); 56 } 57 } 58 } 59 catch (Exception ex) 60 { 61 return Json(new { code = -1, msg = ex.ToString().Substring(0, 20) }, JsonRequestBehavior.AllowGet); 62 } 63 64 return Json(new { code = -1, msg = "" }, JsonRequestBehavior.AllowGet); 65 }
PS:另外在上传图片的过程中,可能会因为图片文件过大导致报500错误,只需要在web.config的<system.web></system.web>节点中配置大小即可: <httpRuntime maxRequestLength="102400" executionTimeout="200" enable="true" />