电子签章图片采集
印章图片的采集两种互补方式:
方式1:在线生成印章图片方式,但是这种方式有个弊端,对印章中公司名称字数有限制,字数越多可能就完蛋了。
方式2:上传印章扫描件,系统来对扫描图片进行处理,提取扫描件中的印章图片。
方式1:本来想用java实现印章图片生成,虽然网上有很多现成代码,但需要调整印章图片大小达到规范,印章大小:圆形印章尺寸43mm*43mm(误差允许范围2-3mm),椭圆印章43mm*26mm(误差允许范围2-3mm)比较接近真实印章。想到java调试起来比较费劲,所以改用html5实现。
方式2:考虑移植性,方式2也采用html5来实现,支持照片背景一个颜色的印章图片,其余复杂图片不考虑了。
开始
方式1-----系统生成印章图片
先上效果图:
圆形中英文圆形印章 中文圆形印章 椭圆中英文印章 椭圆中文印章
<!DOCTYPE HTML>
<html>
<head>
<style>
body > div > div{
border: 1px solid #9e9e9e;
padding: 20px;
}
</style>
</head>
<body>
<div>
<div style="height: 100%;width:100%">
<h1>印章参数</h1>
<h3>印章长宽</h3>
长(毫米):<input type="text" id="sealHiegth" value="50"/><br>
宽(毫米):<input type="text" id="sealWidth" value="50"/><br>
<h3>印章颜色</h3>
<input type="radio" value="red" name="sealColor" checked="checked"/>红色
<input type="radio" value="blue" name="sealColor"/>蓝色<br>
<h3>印章形状</h3>
<input type="radio" value="1" name="sealShape" checked="checked"/>圆形
<input type="radio" value="2" name="sealShape" />椭圆
<input type="radio" value="3" name="sealShape"/>长方形<br>
<span style="font-size: 12px;color: red;">圆形、椭圆:长宽推荐50*50,内外间距:1</span><br>
<h3>印章内容</h3>
<input type="radio" value="1" name="sealdanshuang" checked="checked"/>单内容
<input type="radio" value="2" name="sealdanshuang"/>双内容<br><br>
印章填充:<input type="text" id="sealCompany" value="菜鸟先飞公司"/>字体大小:<input type="text" id="sealFontSize" value="15"/><br><br>
<div id="shuang" style="display:none;">印章填充(外圈):<input type="text" id="sealECompany" value="EFEFFEEFFDDFEFEFE"/>字体大小:<input type="text" id="sealFontSize2" value="12"/></div><br>
印章标记:<input type="text" id="sealType" value="公司章"/><input type="checkbox" value="1" name="sealTypeMark" checked="checked"/>是否显示标记<br>
<h3>印章微调</h3>
内外间距(毫米):<input type="text" id="sealSpacing" value="1"/><br>
</div>
<div style="height: 100%;width:100%">
<button onclick="createSeal();" >预览</button><button onclick="toImage();" >转换成png图片</button><br>
展示图:<br><div id="sealPicMake"></div>
<br>png图片(右键另存):<br><img id="sealPic">
</div>
</div>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("input[name='sealShape']").on('click',function(){
var value = $(this).val();
if('3'==value){
$("#sealHiegth").val(20);
$("#sealWidth").val(50);
$("#sealSpacing").val(5);
}else{
$("#sealHiegth").val(50);
$("#sealWidth").val(50);
$("#sealSpacing").val(1);
}
});
$("input[name='sealdanshuang']").on('click',function(){
var value = $(this).val();
if('2'==value){
$("#shuang").show();
}else{
$("#shuang").hide();
}
});
});
function toImage(){
var img = document.getElementById('sealPic');
var data = createSeal().toDataURL( 'image/png', 1 ); //1表示质量(无损压缩)
img.src = data;
}
function createSeal(){
var sealHiegth = $("#sealHiegth").val();
var sealWidth = $("#sealWidth").val();
var sealFontSize = $("#sealFontSize").val()+"px Arial";
var sealFontSize2 = $("#sealFontSize2").val()+"px Arial";
var sealType = $("#sealType").val();
var sealTypeMark = $("input[name='sealTypeMark']:checked").val();
var sealCompany = $("#sealCompany").val();
var sealECompany = $("#sealECompany").val();
var sealColor = $("input[name='sealColor']:checked").val();
var sealShape = $("input[name='sealShape']:checked").val();
var sealSpacing = $("#sealSpacing").val();
var sealdanshuang = $("input[name='sealdanshuang']:checked").val();
//长宽 毫米转px
var height = parseFloat(sealHiegth)*96/25.4;
var width = parseFloat(sealWidth)*96/25.4;
var spacing = parseFloat(sealSpacing)*96/25.4;
$("#sealPicMake").html("<canvas id='canvas' width='"+width+"px' height='"+height+"px'></canvas>");
//印章数据
var sealData={
sealHiegth:height,
sealWidth:width,
sealColor:sealColor,
sealType:sealType,
sealTypeMark:sealTypeMark,
sealCompany:sealCompany,
sealECompany:sealECompany,
sealShape:sealShape,
sealSpacing:spacing,
sealdanshuang:sealdanshuang,
sealFontSize:sealFontSize,
sealFontSize2:sealFontSize2,
};
//html5对象
var canvas = document.getElementById("canvas");;
var context = canvas.getContext('2d');
if(sealData.sealShape=="1"){
//圆形印章
createCircularSeal(context,sealData);
}else if(sealData.sealShape=="2"){
//椭圆印章
createEllipseSeal(context,sealData);
}else if(sealData.sealShape=="3"){
//方形印章
createRectangleSeal(context,sealData);
}else{
alert('刷新页面重试');
}
return canvas;
};
//长方形章
function createRectangleSeal(context,sealData){
//设置文本的垂直对齐方式
context.textBaseline = 'middle';
//设置文本的水平对对齐方式
context.textAlign = 'center';
//设置文本颜色
context.lineWidth = 2;
context.strokeStyle=sealData.sealColor;
context.strokeRect(1,1,canvas.width-5,canvas.height-5);
context.lineWidth = 1;
context.strokeRect(4,4,canvas.width-11,canvas.height-11);
//中文
context.save();
context.font = sealData.sealFontSize;
context.lineWidth=2;
context.fillStyle = sealData.sealColor;
var hideshhh = 1.4;
if(sealData.sealdanshuang=="2"){
hideshhh = 1.6;
}
context.fillText(sealData.sealCompany,canvas.width/2,canvas.height/hideshhh-sealData.sealSpacing);
context.restore();
if(sealData.sealdanshuang=="2"){
//英文
context.save();
context.font = sealData.sealFontSize2;
context.lineWidth=2;
context.fillStyle = sealData.sealColor;
context.fillText(sealData.sealECompany,canvas.width/2,canvas.height-sealData.sealSpacing);
context.restore();
}
if(sealData.sealTypeMark){
// 绘制印章类型
context.save();
context.lineWidth=2;
context.fillStyle = sealData.sealColor;
context.fillText(sealData.sealType,canvas.width/2,canvas.height/2+sealData.sealSpacing);
context.restore();
}
}
//椭圆印章
function createEllipseSeal(context,sealData){
var width=canvas.width/2;
var height=canvas.height/2;
//设置文本颜色
context.strokeStyle=sealData.sealColor;
//设置文本的垂直对齐方式
context.textBaseline = 'middle';
//设置文本的水平对对齐方式
context.textAlign = 'center';
//3个参数:左边距 上边据 宽度 椭圆扁度
//椭圆1
context.lineWidth = 2;
BezierEllipse4(context, width+2, height-1, width-4, height-24);
//椭圆2
context.lineWidth = 1;
BezierEllipse4(context, width+2, height-1, width-6, height-26);
if(sealData.sealdanshuang=="1"){
//绘制英文
var circle={
x:width,
y:height,
radius:width-21
};
//控制字符起始位置度数
var startAngle=190;
//首位字符相隔度数
var endAngle =-10;
//圆的半径
var radius=circle.radius-sealData.sealSpacing
//每个字母占的弧度
var angleDecrement=(startAngle-endAngle)/(sealData.sealCompany.length-1)
context.font=sealData.sealFontSize;
//横轴缩放比率
var ratioX = (width-17) / circle.radius;
//纵轴缩放比率
var ratioY = (height-34) / circle.radius;
//进行缩放(均匀压缩)
context.scale(ratioX, ratioY);
var index=0;
context.lineWidth=1;
for(var index=0;index<sealData.sealCompany.length;index++){
context.save()
context.beginPath()
//绘制点
context.translate(circle.x+Math.cos((Math.PI/180)*startAngle)*radius-3,circle.y-Math.sin((Math.PI/180)*startAngle)*radius+24)
context.rotate((Math.PI/2)-(Math.PI/180)*startAngle) //Math.PI/2为旋转90度 Math.PI/180*X为旋转多少度
context.fillText(sealData.sealCompany.charAt(index),0,0)
context.strokeText(sealData.sealCompany.charAt(index),0,0)
startAngle-=angleDecrement
context.restore()
}
if(sealData.sealTypeMark){
// 绘制印章类型
context.font = 'border 18px SimSun';
context.lineWidth=2;
context.fillStyle = sealData.sealColor;
context.fillText(sealData.sealType,width-6,height+45);
context.save();
}
}else if(sealData.sealdanshuang=="2"){
//椭圆3
context.lineWidth = 2;
BezierEllipse4(context, width+2, height-1, width-20-sealData.sealSpacing, height-40-sealData.sealSpacing);
//绘制英文
var circle={
x:width,
y:height,
radius:width-22
};
//控制字符起始位置度数
var startAngle=230;
//首位字符相隔度数
var endAngle =-40;
//圆的半径
var radius=circle.radius
//每个字母占的弧度
var angleDecrement=(startAngle-endAngle)/(sealData.sealECompany.length-1)
context.font=sealData.sealFontSize2;
//横轴缩放比率
var ratioX = (width-12.5) / circle.radius;
//纵轴缩放比率
var ratioY = (height-34.5) / circle.radius;
//进行缩放(均匀压缩)
context.scale(ratioX, ratioY);
var index=0;
context.lineWidth=1;
for(var index=0;index<sealData.sealECompany.length;index++){
context.save()
context.beginPath()
//绘制点
context.translate(circle.x+Math.cos((Math.PI/180)*startAngle)*radius-10,circle.y-Math.sin((Math.PI/180)*startAngle)*radius+19)
context.rotate((Math.PI/2)-(Math.PI/180)*startAngle) //Math.PI/2为旋转90度 Math.PI/180*X为旋转多少度
context.fillText(sealData.sealECompany.charAt(index),0,0)
context.strokeText(sealData.sealECompany.charAt(index),0,0)
startAngle-=angleDecrement
context.restore()
}
// 绘制印章公司
context.font = '14px SimSun';
context.lineWidth=1;
context.fillStyle = sealData.sealColor;
context.fillText(sealData.sealCompany.substring(0,6),width-11,height+6);
context.save();
context.fillText(sealData.sealCompany.substring(6,12),width-12,height+25);
context.save();
context.fillText(sealData.sealCompany.substring(12,sealData.sealCompany.length),width-12,height+40);
context.save();
if(sealData.sealTypeMark){
// 绘制印章类型
context.font = '10px SimSun';
context.lineWidth=1;
context.fillStyle = sealData.sealColor;
context.fillText(sealData.sealType,width-10,height+55);
context.save();
}
}
}
//圆形印章
function createCircularSeal(context,sealData){
//绘制印章边框1
var width=canvas.width/2;
var height=canvas.height/2;
//-------------------最外圈两个圆 开始------------------------------------------------------
context.lineWidth=2;
context.strokeStyle=sealData.sealColor;
context.beginPath();
//arc(x, y, radius, startRad, endRad, anticlockwise)
//在canvas画布上绘制以坐标点(x,y)为圆心、
//半径为radius的圆上的一段弧线。
//起始弧度是startRad.
//结束弧度是endRad。
//anticlockwise表示是以逆时针方向还是顺时针方向开始绘制,如果为true则表示逆时针,如果为false则表示顺时针。anticlockwise参数是可选的,默认为false,即顺时针。
//以方形中心为圆心 画半径为边长一半减2px的园
context.arc(width,height,width-2,0,Math.PI*2, false);
context.stroke();
context.save();
//绘制印章边框2
context.lineWidth=1;
context.strokeStyle=sealData.sealColor;
context.beginPath();
//圆的半径在边长一半基础上减去5px
context.arc(width,height,width-5,0,Math.PI*2, false);
context.stroke();
context.save();
//-------------------最外圈两个圆 结束-------------------------------------------------------
if(sealData.sealdanshuang=='1'){
//只含有中文
//-------------------------画中文环绕 开始---------------------------------------------------
//控制字符起始位置度数
var startAngle=240;
//首位字符相隔度数
var endAngle =-40;
//圆的半径在边长一半基础上减去23px
var radius=width-25-sealData.sealSpacing ;
//每个字母占的弧度
var angleDecrement=(startAngle-endAngle)/(sealData.sealCompany.length-1);
context.font=sealData.sealFontSize;
var index=0;
for(var index=0;index<sealData.sealECompany.length;index++){
context.save();
context.beginPath();
//绘制点
context.translate(width+Math.cos((Math.PI/180)*startAngle)*radius,
height-Math.sin((Math.PI/180)*startAngle)*radius);
//Math.PI/2为旋转90度 Math.PI/180*X为旋转多少度
context.rotate((Math.PI/2)-(Math.PI/180)*startAngle);
context.fillText(sealData.sealCompany.charAt(index),0,0);
context.strokeText(sealData.sealCompany.charAt(index),0,0);
startAngle-=angleDecrement;
context.restore();
}
//-------------------------画中文环绕 结束----------------------------------------------------
}else if(sealData.sealdanshuang=='2'){
//含中英文
//-------------------------画英文环绕 开始---------------------------------------------------
//控制字符起始位置度数
var startAngle=240;
//首位字符相隔度数
var endAngle =-40;
//圆的半径在边长一半基础上减去16px
var radius=width-16 ;
//每个字母占的弧度
var angleDecrement=(startAngle-endAngle)/(sealData.sealECompany.length-1);
context.font=sealData.sealFontSize2;
var index=0;
for(var index=0;index<sealData.sealECompany.length;index++){
context.save();
context.beginPath();
//绘制点
context.translate(width+Math.cos((Math.PI/180)*startAngle)*radius,
height-Math.sin((Math.PI/180)*startAngle)*radius);
//Math.PI/2为旋转90度 Math.PI/180*X为旋转多少度
context.rotate((Math.PI/2)-(Math.PI/180)*startAngle);
context.fillText(sealData.sealECompany.charAt(index),0,0);
context.strokeText(sealData.sealECompany.charAt(index),0,0);
startAngle-=angleDecrement;
context.restore();
}
//-------------------------画英文环绕 结束----------------------------------------------------
//-------------------------画内圆 开始--------------------------------------------------------
//绘制印章边框3
context.lineWidth=1;
context.strokeStyle=sealData.sealColor;
context.beginPath();
//圆的半径在边长一半基础上减去20px再减去用户设置的内间距
context.arc(width,height,width-20-sealData.sealSpacing,0,Math.PI*2, false);
context.stroke();
context.save();
//-------------------------画内圆 结束---------------------------------------------------------
//-------------------------画中文环绕 开始---------------------------------------------------
startAngle=240;
//圆的半径在边长一半基础上减去37px 再减去用户设置的间隔
radius=width-37-sealData.sealSpacing;
//每个字母占的弧度
var angleDecrement=(startAngle-endAngle)/(sealData.sealCompany.length-1);
context.font="15px SimSun";
var index=0;
for(var index=0;index<sealData.sealECompany.length;index++){
context.save();
context.beginPath();
//绘制点
context.translate(width+Math.cos((Math.PI/180)*startAngle)*radius,
height-Math.sin((Math.PI/180)*startAngle)*radius);
//Math.PI/2为旋转90度 Math.PI/180*X为旋转多少度
context.rotate((Math.PI/2)-(Math.PI/180)*startAngle);
context.fillText(sealData.sealCompany.charAt(index),0,0);
context.strokeText(sealData.sealCompany.charAt(index),0,0);
startAngle-=angleDecrement;
context.restore();
}
//-------------------------画中文环绕 结束----------------------------------------------------
}
//-----------------画五角星 开始----------------------------------------------------------------
context.save();
context.fillStyle=sealData.sealColor;
//移动坐标圆心原点
context.translate(width,height);
//旋转
context.rotate(Math.PI);
//创建路径
context.beginPath();
//画五角星的五条边
for(var i = 0;i< 5;i++){
context.lineTo(Math.sin(i*(Math.PI/5 *4))*20,Math.cos(i*(Math.PI/5 *4))*20);
}
context.closePath();
context.stroke();
context.fill();
context.restore();
//-----------------画五角星 结束-------------------------------------------------------------------
if(sealData.sealTypeMark){
//-----------------绘制印章类型 开始----------------------------------------------------------------
context.font = '12px SimSun';
context.textBaseline = 'middle';//设置文本的垂直对齐方式
context.textAlign = 'center'; //设置文本的水平对对齐方式
context.lineWidth=1;
context.fillStyle = sealData.sealColor;
context.fillText(sealData.sealType,width,height+30);
//-----------------绘制印章类型 结束----------------------------------------------------------------
}
};
function BezierEllipse4(ctx, x, y, a, b){
var k = .5522848,
ox = a * k, // 水平控制点偏移量
oy = b * k; // 垂直控制点偏移量</p> <p>
ctx.beginPath();
//从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线
ctx.moveTo(x - a, y);
ctx.bezierCurveTo(x - a, y - oy, x - ox, y - b, x, y - b);
ctx.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y);
ctx.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b);
ctx.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y);
ctx.closePath();
ctx.stroke();
};
</script>
</body>
</html>
方式2---上传印章图片提取印章
效果:
要提取的印章图片
提取后:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>裁剪图片</title>
<link href="https://cdn.bootcss.com/cropper/3.1.3/cropper.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<style>
.row{
margin-bottom: 5px;
}
#photo {
max-width: 100%;
}
.img-preview {
width: 100px;
height: 100px;
overflow: hidden;
}
button {
margin-top:10px;
}
#result {
width: 150px;
height: 150px;
}
</style>
</head>
<body>
<div class="container" style="
margin-top: 40px;
border: 1px solid;
padding-top: 10px;
">
<div class="row">
<div class="col-sm-12">
<label for="input" class="btn btn-danger" id="">
<span>选择图片</span>
<input type="file" id="input" class="sr-only">
</label>
<button class="btn btn-primary" onclick="crop()" style="margin-top: 0px;">提取</button>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<img src="" id="photo">
</div>
<div>
<div class="col-sm-12">
<h3>
裁剪预览(100*100):
</h3>
<div class="img-preview"></div>
</div>
<div class="col-sm-6">
<h3>
裁剪结果:
</h3>
<div id="result" style="text-align: center;"></div>
</div>
<div class="col-sm-6">
<h3>
提取印章结果(右键另存为):<span id="tiqun" style="color: red;font-size: small;"></span>
</h3>
<div style="background:gray;text-align: center;" id="resultSeal"></div>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/cropper/3.1.3/cropper.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script>
$(function(){
initCropper($('#photo'),$('#input'));
});
// 修改自官方demo的js
var initCropper = function (img, input){
var $image = img;
var options = {
aspectRatio: 1, // 纵横比
viewMode: 2,
preview: '.img-preview' // 预览图的class名
};
$image.cropper(options);
var $inputImage = input;
var uploadedImageURL;
if (URL) {
// 给input添加监听
$inputImage.change(function () {
var files = this.files;
var file;
if (!$image.data('cropper')) {
return;
}
if (files && files.length) {
file = files[0];
// 判断是否是图像文件
if (/^image\/\w+$/.test(file.type)) {
// 如果URL已存在就先释放
if (uploadedImageURL) {
URL.revokeObjectURL(uploadedImageURL);
}
uploadedImageURL = URL.createObjectURL(file);
// 销毁cropper后更改src属性再重新创建cropper
$image.cropper('destroy').attr('src', uploadedImageURL).cropper(options);
$inputImage.val('');
} else {
window.alert('请选择一个图像文件!');
}
}
});
} else {
$inputImage.prop('disabled', true).addClass('disabled');
}
}
var crop = function(){
var $image = $('#photo');
var $target = $('#result');
$image.cropper('getCroppedCanvas',{
width:200, // 裁剪后的长宽
height:200
}).toBlob(function(blob){
// 裁剪后将图片放到指定标签
var imageblob = URL.createObjectURL(blob);
$target.html('<img src="'+imageblob+'" alt="裁剪结果" >');
//提取印章
var newimg = new Image();
newimg.src=imageblob;
$("#resultSeal").html("");
$("#tiqun").html("正在努力提取印章....");
setTimeout(function(){
$("#tiqun").html("完成");
seal_image.removeImgBg(newimg);
$("#resultSeal").html(newimg);
},1000);
});
}
var seal_image={
removeImgBg:function(img){
// 容差大小
const tolerance = 70;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const w = img.width;
const h = img.height;
canvas.width = w;
canvas.height = h;
context.drawImage(img, 0, 0);
// 取图片四个脚边的像素点rgba
let tl = Array.prototype.slice.call(context.getImageData(0, 0, 1, 1).data).join(',')
let tr = Array.prototype.slice.call(context.getImageData(w - 1, 0, 1, 1).data).join(',')
let br = Array.prototype.slice.call(context.getImageData(w - 1, h - 1, 1, 1).data).join(',')
let bl = Array.prototype.slice.call(context.getImageData(0, h - 1, 1, 1).data).join(',')
let img4P = [tl, tr, bl, br];
img4P.sort();
// 目前只支持纯色背景抠图,简单的判断是否为纯色
let deferNum = seal_image.unique(img4P,tolerance).length;
if (deferNum <= 1){
//背景颜色
const [r0, g0, b0, a0] = img4P[1].split(",");
var imgData = context.getImageData(0, 0, w, h);
//当前点的值
var r, g, b, a;
var PixelsX = new Array();
var PixelsY = new Array();
for (let i = 0; i < imgData.data.length; i += 4) {
r = imgData.data[i];
g = imgData.data[i + 1];
b = imgData.data[i + 2];
a = imgData.data[i + 3];
const t = Math.sqrt((r - r0) ** 2 + (g - g0) ** 2 + (b - b0) ** 2 + (a - a0) ** 2);
if (t <= tolerance) {
imgData.data[i] = 0;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 0;
}else{
//获取当前像素点位置
var x=i/4%w,
y=(i/4-x)/w;
PixelsX.push(x);
PixelsY.push(y);
}
}
context.putImageData(imgData, 0, 0);
const newBase64 = canvas.toDataURL('image/png');
img.src = newBase64;
}else{
alert("只支持统一背景颜色图片");
}
},
unique:function(str,tolerance){
const [r0, g0, b0, a0] = str[0].split(",");
var newArr = [str[0]];
for(i=1; i < str.length; i++) {
const [r, g, b, a] = str[i].split(",");
const t = Math.sqrt((r - r0) ** 2 + (g - g0) ** 2 + (b - b0) ** 2 + (a - a0) ** 2);
if (t <= tolerance){
continue;
}else {
newArr[newArr.length] = str[i];
}
}
return newArr;
}
};
</script>
</body>
</html>
关注公众号