简介
前面介绍了很多稍微大型点的工具,剩下还有一些工具我觉得也是对我帮助挺多的,下面简单介绍下。
正则表达式
正则表达式来说,熟悉的人很熟悉,不熟悉人的简直太痛苦了,我日常工作中正则表达式算用的中规中矩吧,不算多也不算少,当然网上有很多校验正则表达式的工具。我做这个工具的目的,也是需要有个可以调试和记录正则表达式的地方。
帮助说明
表达式校验
JSON格式化
这个工具用的也是比较多,经常调试网页的时候,需要对一个字符串进行格式化处理或者查看,就会把字符串复制到这里,然后格式化。功能很简单,但是很实用,当然互联网上这种也很多。
Vscode代码片段
程序员开发或者写文档的过程中,代码片段是个很好用的东西,可以把你常用的一些片段封装起来,后续用到的时候输入指定关键字,可以把片段输出来,可以节省大量的时间。我代码片段用的非常多。
代码片段是好用,但是vscode中配置复杂代码片段的时候挺麻烦的,它是一个数组,需要你一行一行的去组织,如果碰到页面级的代码片段,那就要花很多时间去处理。所以我就做了一个转换工具,把你需要的代码片段内容复制进去,自动输出vscode需要的代码片段格式,一键复制到vscode就行了。
OCR识别
OCR识别的需求,也是源于很多人发错误信息什么的,会拍照或者截图过来,然后复制不出来,要自己手动一个个敲,太难受了,所以就想办法做了一个OCR识别的小功能。
本身没有OCR识别的能力,是借助了百度云的OCR功能,每个账户有免费的限额,我自己用用基本上足够了。
识别后的结果
主要逻辑就是通过百度云SDK识别图片,获取识别的文字内容和文字坐标,然后前端通过这些信息,把识别后的图绘制出来
后端
import com.baidu.aip.ocr.AipOcr;
//百度云官网有详细的ocr识别说明
public List<RecognizeResult> getOcrText(MultipartFile multipartFile) throws IOException, JSONException {
String md5 = FileUtils.getMd5(multipartFile);
OcrEntity ocrEntity = ocrDao.getOcr(md5);
if(ocrEntity!=null){
String content = ocrEntity.getResult();
CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, RecognizeResult.class);
return objectMapper.readValue(content, listType);
}
// 初始化一个AipOcr
AipOcr client = new AipOcr(APP_ID, API_KEY, SECRET_KEY);
// 可选:设置网络连接参数
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
System.setProperty("aip.log4j.conf", "log4j.properties");
HashMap<String, String> options = new HashMap<>(10);
options.put("language_type", "CHN_ENG");
options.put("detect_direction", BOOLEAN_FALSE.toString());
options.put("detect_language", BOOLEAN_FALSE.toString());
options.put("vertexes_location", BOOLEAN_TRUE.toString());
options.put("probability", BOOLEAN_FALSE.toString());
JSONObject jsonObject = client.accurateGeneral(multipartFile.getBytes(), options);
JSONArray jsonArray = jsonObject.getJSONArray("words_result");
List<RecognizeResult> recognizeResultList = new LinkedList<>();
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObjectBase = jsonArray.getJSONObject(i);
String words = jsonObjectBase.getString("words");
RecognizeResult recognizeResult = new RecognizeResult();
recognizeResult.setWords(words);
JSONObject jsonLocation = jsonObjectBase.getJSONObject("location");
RecognizeLocationResult recognizeLocationResult=new RecognizeLocationResult();
//获取识别结果的坐标位置,便于前端绘制
recognizeLocationResult.setHeight(jsonLocation.getInt("height"));
recognizeLocationResult.setWidth(jsonLocation.getInt("width"));
recognizeLocationResult.setTop(jsonLocation.getInt("top"));
recognizeLocationResult.setLeft(jsonLocation.getInt("left"));
recognizeResult.setLocation(recognizeLocationResult);
recognizeResultList.add(recognizeResult);
}
//写入数据库,识别一次,下次就不识别了
OcrEntity ocrEntityInsert = new OcrEntity();
ocrEntityInsert.setId(UUID.randomUUID().toString());
ocrEntityInsert.setMd5(md5);
ocrEntityInsert.setResult(objectMapper.writeValueAsString(recognizeResultList));
ocrEntityInsert.setCreateTime(new Date());
ocrDao.insertOcr(ocrEntityInsert);
return recognizeResultList;
}
前端
<!--原理是原图片作为背景图片,然后把识别的文字叠加到图片上,看起来就是被识别的样式-->
<template>
<a-modal
:title="title"
:dialog-style="{ top: '5px' }"
:visible="visible"
:width="1200"
:destroyOnClose="true"
@ok="handleOk"
:footer="null"
@cancel="handleCancel">
<div style="height: 650px;overflow: hidden;display: flex">
<div :style="{width: width+'px',height: height+'px'}">
<canvas id="canvas" ref="canvas" @click="getText">
</canvas>
</div>
<div style="border-left: 1px solid lightgrey">
<div style="font-size: 16px;font-weight: bold;margin-left: 5px">
识别结果
</div>
<div id="textList" style="height: 630px;width: 250px;overflow-y: auto;">
<div class="textClass" @click="copyText(item.words)" :style="{backgroundColor: currentId===item.id?'rgb(232, 240, 254)':'white' }" v-for="(item) in data" style="" :id="item.id">
{{item.words}}
</div>
</div>
</div>
</div>
</a-modal>
</template>
<script>
import {v4 as UuidV4} from "uuid";
import Tiff from "tiff.js"
// noinspection JSUnusedLocalSymbols
export default {
name: "DataSourceEdit",
data(){
return{
title: "",
visible: false,
canvasId: UuidV4(),
data: [],
file: null,
width: 930,
height: 660,
positionList: [],
currentText: "",
currentId: ""
}
},mounted() {
Tiff.initialize({TOTAL_MEMORY :150 * 1024 * 1024});
},methods:{
show(data, file){
this.canvasId = "";
this.data = data;
this.file = file;
this.visible = true;
this.$nextTick(()=>{
this.createCanvas(data,file);
})
},
handleOk(){
this.visible = false;
},
handleCancel(){
this.visible = false;
},
loadImageToViewer(buffer){
let tiff;
try {
tiff = new Tiff({buffer: buffer})
} catch (e) {
this.$message.info("当前tiff无法查看!");
return;
}
for (let i = 0, len = tiff.countDirectory(); i < len; ++i) {
tiff.setDirectory(i)
const imgs = tiff.toDataURL(); // 转化成base64
if (imgs) {
this.canvasList.push(imgs)
}
}
this.$nextTick(() => {
const viewer = this.$refs['viewer'].$viewer
viewer.show()
})
},
//创建canvas文件,其中tiff文件的背景图片加载要单独处理
createCanvas(data, file){
const that = this;
//tiff文件特殊处理
if(file.name.toLowerCase().indexOf(".tif")>-1 || file.name.toLowerCase().indexOf(".tiff")>-1 ){
const bufferReader = new FileReader()
bufferReader.readAsArrayBuffer(file)
bufferReader.onload = () => {
if (bufferReader && bufferReader.result) {
const image = new Tiff({ buffer: bufferReader.result })
that.loadCanvas(image.toDataURL('image/jpeg'), data, file)
} else {
cosole.error('上传失败, 请重试')
}
}
return;
}
const URL = window.URL || window.webkitURL;
// 通过 file 生成目标 url
const imgURL = URL.createObjectURL(file);
this.loadCanvas(imgURL, data, file);
},
loadCanvas(imgURL, data, file){
let image = new Image()
image.src = imgURL;
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
image.onload =()=>{
//因为有些图片很大,有些图片很小,所以这里在显示图片的时候,都是按照屏幕自适应
//自适应以后,图片的位置就发生了变化,原来后台识别的文字坐标都是按照图片决定定位的,所以在绘制文字框的时候,也需要随着图片的缩放,自动调整位置和大小
canvas.width = image.width;
canvas.height = image.height;
let scaleX = Math.floor((this.width/image.width) * 100) /100;
if(this.width>image.width){
scaleX = 1;
}
let scaleY = Math.floor((this.height/image.height) * 100) / 100
if(this.height>image.height){
scaleY=1;
}
ctx.scale(scaleX,scaleY)
ctx.textBaseline = "top";
ctx.drawImage(image, 0, 0, image.width, image.height);
ctx.fillStyle = "rgba(24,144,255,0.3)"
data.forEach(p=>{
ctx.lineWidth = 1;
ctx.strokeStyle = "red";
ctx.fillRect(p.location.left, p.location.top, p.location.width, p.location.height);
ctx.strokeRect(p.location.left, p.location.top, p.location.width, p.location.height);
//记录坐标列表,文字所在的框,方便后续和文字对应
this.positionList.push({
x: p.location.left * scaleX,
y: p.location.top * scaleY,
xEnd: (p.location.left + p.location.width) * scaleX,
yEnd: (p.location.top + p.location.height) * scaleY,
text: p.words,
id: p.id
})
})
console.log(data)
}
},
/**
* 这里做了个小效果,点击图片上的文字的时候,右边的列表会自动定位,便于复制
*/
getText(e){
console.log(e)
const canvas = this.$refs.canvas;
const clickX = e.pageX - canvas.offsetLeft;
const clickY = e.pageY - canvas.offsetTop;
const filterData = this.positionList.filter(p =>
clickX >= p.x && clickY >= p.y && clickX <= p.xEnd && clickY <= p.yEnd
);
if(filterData.length > 0){
const divList = document.getElementById("textList");
const divText = document.getElementById(filterData[0].id);
divList.scrollTop = divText.offsetTop -50;
this.currentId = filterData[0].id
}
},
copyText(words){
this.$copyText(words).then(
() => {
this.$message.info("复制成功!");
},
() => {
this.$message.error("复制失败了");
}
);
}
}
}
</script>
<style scoped>
.textClass{
border-bottom: 1px solid lightgrey;cursor: pointer;padding:5px
}
.textClass:hover{
background-color: rgb(232, 240, 254);
}
</style>