- Timestamp:
- 2021-11-24T10:57:13+13:00 (2 years ago)
- Location:
- main/trunk/model-interfaces-dev/atea/ocr
- Files:
-
- 1 added
- 2 deleted
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/model-interfaces-dev/atea/ocr
- Property svn:ignore
-
old new 1 1 dist 2 node_modules
-
- Property svn:ignore
-
main/trunk/model-interfaces-dev/atea/ocr/src/App.vue
r35734 r35743 1 1 <template> 2 2 <div class="root"> 3 < div class="app-bar theme-primary">3 <!-- <div class="app-bar theme-primary"> 4 4 <div class="app-bar-content"> 5 5 <span class="heading1">{{ translations.get("Title") }}</span> 6 6 </div> 7 </div> 7 </div> --> 8 8 9 <div class="content"> 10 <main-page /> 11 </div> 9 <main-page class="content" /> 12 10 </div> 13 11 </template> -
main/trunk/model-interfaces-dev/atea/ocr/src/components/EditPage.vue
r35734 r35743 1 1 <template> 2 <div class=" root">2 <div class="edit-page-root"> 3 3 <div class="image-panel"> 4 <table v-if="rotateMode" class="rotate-guide"> 5 <tr><hr /></tr> 6 <tr><hr /></tr> 7 <tr><hr /></tr> 8 <tr><hr /></tr> 9 <tr><hr /></tr> 10 <tr><hr /></tr> 11 <tr><hr /></tr> 12 <tr><hr /></tr> 13 <tr><hr /></tr> 14 <tr><hr /></tr> 15 <tr><hr /></tr> 16 <tr><hr /></tr> 17 </table> 18 19 <!-- <img :src="imageUrl" class="image" :style="{ filter: filterString, transform: transformString }" /> --> 20 <div class="image-container"> 21 <img v-if="rotateMode" src="images/rotate_arrow_64.png" class="rotate-handle rotate-tl" /> 22 <img v-if="rotateMode" src="images/rotate_arrow_64.png" class="rotate-handle rotate-tr" /> 23 <img v-if="rotateMode" src="images/rotate_arrow_64.png" class="rotate-handle rotate-bl" /> 24 <img v-if="rotateMode" src="images/rotate_arrow_64.png" class="rotate-handle rotate-br" /> 25 26 <img :src="imageUrl" class="image" :style="{ filter: filterString, transform: transformString }" /> 4 <div class="image-container" :style="{ transform: transformString }"> 5 <img v-if="rotateMode" src="images/rotate-arrow.svg" class="rotate-handle rotate-tl" @mousedown="allowRotation = true" draggable="false" /> 6 <img v-if="rotateMode" src="images/rotate-arrow.svg" class="rotate-handle rotate-tr" @mousedown="allowRotation = true" draggable="false" /> 7 <img v-if="rotateMode" src="images/rotate-arrow.svg" class="rotate-handle rotate-bl" @mousedown="allowRotation = true" draggable="false" /> 8 <img v-if="rotateMode" src="images/rotate-arrow.svg" class="rotate-handle rotate-br" @mousedown="allowRotation = true" draggable="false" /> 9 10 <table v-if="rotateMode" class="rotate-guide" :style="{ transform: transformStringInverted }"> 11 <tr><td><hr /></td></tr> 12 <tr><td><hr /></td></tr> 13 <tr><td><hr /></td></tr> 14 <tr><td><hr /></td></tr> 15 <tr><td><hr /></td></tr> 16 <tr><td><hr /></td></tr> 17 <tr><td><hr /></td></tr> 18 <tr><td><hr /></td></tr> 19 <tr><td><hr /></td></tr> 20 <tr><td><hr /></td></tr> 21 <tr><td><hr /></td></tr> 22 <tr><td><hr /></td></tr> 23 </table> 24 25 <img ref="image" :src="imageUrl" class="image" :style="{ filter: filterString }" /> 27 26 </div> 28 27 </div> … … 45 44 46 45 <style lang="scss" scoped> 47 . root {46 .edit-page-root { 48 47 display: grid; 49 48 grid-template-columns: 1fr auto; 50 grid-template-rows: 100%;51 49 align-items: center; 52 50 justify-items: center; … … 65 63 66 64 .image { 67 max-width: 100%;68 height: auto;65 max-width: 70vw; 66 max-height: 70vh; 69 67 } 70 68 71 69 .image-container { 72 max-width: 70%;73 max-height: 70%;74 70 position: relative; 75 71 margin: auto; 76 padding: 1em; 72 padding: 1.5em; 73 user-select: none; 77 74 78 75 .rotate-handle { 79 76 height: 2em; 80 77 position: absolute; 78 z-index: 5; 81 79 82 80 // Generated with https://codepen.io/sosuke/pen/Pjoqqp using var(--primary-bg-color) 83 filter: invert(16%) sepia(100%) saturate(3091%) hue-rotate(347deg) brightness(75%) contrast(90%);81 // filter: invert(16%) sepia(100%) saturate(3091%) hue-rotate(347deg) brightness(75%) contrast(90%); 84 82 } 85 83 … … 116 114 z-index: 3; 117 115 118 tr hr { 119 background-color: rgba(var(--bg-color-raw), 0.5); 116 tr { 117 td { 118 vertical-align: center; 119 } 120 121 hr { 122 background-color: rgba(var(--bg-color-raw), 0.5); 123 height: 2px; 124 } 120 125 } 121 126 } … … 137 142 <script> 138 143 import { mapState } from "vuex"; 144 import Jimp from "jimp/es"; 139 145 import ToggleButton from "./ToggleButton.vue" 140 146 … … 151 157 imageUrl: URL.createObjectURL(this.image), 152 158 invert: false, 153 rotateMode: false 159 rotation: 0, 160 rotateMode: false, 161 allowRotation: false 154 162 } 155 163 }, … … 167 175 }, 168 176 transformString() { 169 return `rotate(${this.rotation}deg)` 177 return `rotate(${this.rotation}deg)`; 178 }, 179 transformStringInverted() { 180 return `rotate(-${this.rotation}deg)`; 170 181 } 171 182 }) 172 183 }, 173 184 methods: { 185 async getImageBlobAsync() { 186 const buffer = Buffer.from(this.image.arrayBuffer()); 187 const that = this; 188 189 const modifiedBuffer = await Jimp.read(buffer) 190 .then(async image => { 191 if (that.invert) { 192 image.invert(); 193 } 194 195 // if (that.rotation !== 0) { 196 // image.rotate(that.rotation); 197 // } 198 199 return await image.getBufferAsync(Jimp.MIME_PNG); 200 }); 201 202 return new Blob([ modifiedBuffer ], { type: Jimp.MIME_PNG }); 203 }, 204 205 onRotateHandleMouseDown(point) { 206 console.log(point); 207 this.rotatePoint = point; 208 }, 209 /** 210 * @param {MouseEvent} e The mouse movement event. 211 */ 212 onDocumentMouseMove(e) { 213 if (!this.allowRotation) { 214 return; 215 } 216 217 const imageRectangle = this.$refs.image.getBoundingClientRect(); 218 const imageCenterX = (imageRectangle.width / 2) + imageRectangle.left; 219 const imageCenterY = (imageRectangle.height / 2) + imageRectangle.top; 220 221 if (e.clientX > imageCenterX) { 222 this.rotation += e.movementY / 8; 223 } 224 else { 225 this.rotation -= e.movementY / 8; 226 } 227 228 if (e.clientY > imageCenterY) { 229 this.rotation -= e.movementX / 8; 230 } 231 else { 232 this.rotation += e.movementX / 8; 233 } 234 235 if (this.rotation < 0) { 236 this.rotation = 360 + this.rotation; 237 } 238 else if (this.rotation > 360) { 239 this.rotation -= 360; 240 } 241 }, 242 onDocumentMouseUp() { 243 this.allowRotation = false; 244 } 245 }, 246 beforeMount() { 247 document.addEventListener("mousemove", this.onDocumentMouseMove); 248 document.addEventListener("mouseup", this.onDocumentMouseUp); 249 }, 250 beforeUnmount() { 251 document.removeEventListener("mousemove", this.onDocumentMouseMove); 252 document.removeEventListener("mouseup", this.onDocumentMouseUp); 174 253 } 175 254 } -
main/trunk/model-interfaces-dev/atea/ocr/src/components/MainPage.vue
r35734 r35743 1 1 <template> 2 <div class="root"> 3 <edit-page v-if="imageToEdit != null" class="image-editor" :image="imageToEdit" @closeRequested="onEditorCloseRequested" /> 4 5 <button class="btn-primary spacing-bottom" @click="uploadFile"> 6 <span class="material-icons">upload</span> 7 <span>{{ translations.get("Main_UploadImages") }}</span> 8 </button> 9 10 <div v-for="[id, imageInfo] in images" :key="id" class="image-list card" 11 :style="{ 'max-height': imageInfo.isExpanded ? 'none' : '12em' }"> 12 <image-display :imageBuffer="imageInfo.buffer" :type="imageInfo.image.type" :ref="id" /> 13 14 <div class="controls"> 15 <button class="btn-primary" @click="doOcr(id)" :disabled="imageInfo.ocr"> 16 <span class="material-icons">play_arrow</span> 17 <span>{{ translations.get("Main_PerformOCR") }}</span> 18 </button> 19 20 <!-- <div v-if="imageInfo.ocr" class="progress-bar-container"> 21 <div class="progress-bar-value progress-bar-indeterminate" /> 22 </div> --> 23 <button class="btn-primary" @click="imageToEdit = imageInfo.image">Edit</button> 2 <div class="main-page-root"> 3 <edit-page v-if="showEditor" class="image-editor" :image="image" @closeRequested="onEditorCloseRequested" /> 4 5 <div class="paper root-container" :class="{ 'root-container-image-state': image !== null }"> 6 <div v-if="image === null" class="upload-area" @click="uploadFile"> 7 <span class="heading1">{{ translations.get("Title") }}</span> 8 <span class="material-icons mdi-xl">upload_file</span> 9 <span>Upload an image/PDF</span> 10 </div> 11 12 <div v-if="image !== null" class="image-area"> 13 <div class="controls"> 14 <button class="btn-primary" @click="doOcr" :disabled="ocrInProgress"> 15 <span class="material-icons">play_arrow</span> 16 <span>{{ translations.get("Main_PerformOCR") }}</span> 17 </button> 18 19 <button class="btn-primary" @click="showEditor = true"> 20 <span class="material-icons">edit</span> 21 <span>Edit Image</span> 22 </button> 23 24 <button class="btn-primary" @click="reset" :disabled="ocrInProgress"> 25 <span class="material-icons">restart_alt</span> 26 <span>New</span> 27 </button> 28 29 <div v-if="ocrInProgress" class="progress-bar-container"> 30 <div class="progress-bar-value progress-bar-indeterminate" /> 31 </div> 32 </div> 33 34 <img class="image-display" :src="imageUrl" /> 24 35 25 36 <div class="text-container"> 26 <pre>{{ imageInfo.text }}</pre>37 <pre>{{ ocrResult }}</pre> 27 38 </div> 28 39 </div> 29 30 <div class="expander-spacer" />31 <div v-if="!imageInfo.isExpanded" class="washout" />32 33 <button class="btn-primary theme-flat expander" @click="imageInfo.isExpanded = !imageInfo.isExpanded">34 v Expand35 </button>36 40 </div> 37 41 38 <input ref="fileInput" type="file" @input="onFilesChanged" class="hidden" multiple42 <input ref="fileInput" type="file" @input="onFilesChanged" class="hidden" 39 43 accept="image/png,image/jpeg,image/gif,image/bmp,image/tiff,image/webp,application/pdf" /> 40 44 </div> … … 42 46 </template> 43 47 44 <style scoped lang="scss"> 48 <style lang="scss" scoped> 49 .main-page-root { 50 display: flex; 51 align-items: center; 52 justify-content: center; 53 } 54 55 .root-container { 56 padding: 1em; 57 transition-duration: var(--transition-duration); 58 } 59 60 .root-container-image-state { 61 width: calc(100% - 3em); 62 height: calc(100% - 3em); 63 } 64 65 .upload-area { 66 display: grid; 67 grid-template-columns: auto auto; 68 grid-template-rows: auto 1fr; 69 align-items: center; 70 justify-items: center; 71 72 padding: 2em; 73 gap: 1em; 74 75 font-size: 2rem; 76 cursor: pointer; 77 border: 3px dashed var(--bg-color); 78 border-radius: var(--border-radius); 79 80 .heading1 { 81 grid-column-start: span 2; 82 color: #666; 83 } 84 } 85 45 86 .hidden { 46 87 display: none; 47 }48 49 .spacing-bottom {50 margin-bottom: 1em;51 88 } 52 89 … … 60 97 } 61 98 62 .image- list{99 .image-area { 63 100 display: grid; 64 101 grid-template-columns: 1fr 1fr; 102 grid-template-rows: auto 1fr; 103 height: 100%; 104 65 105 gap: 1rem; 106 align-items: center; 107 justify-items: center; 66 108 67 109 position: relative; 68 110 overflow: hidden; 69 111 70 margin-bottom: 1rem; 71 72 .expander { 73 position: absolute; 74 bottom: 0; 112 .text-container { 113 height: 100%; 75 114 width: 100%; 76 z-index: 2;77 115 } 78 79 .expander-spacer { 80 height: 1em; 81 } 82 83 .washout { 84 position: absolute; 85 bottom: 0; 86 height: 60%; 87 width: 100%; 88 background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgb(247, 247, 247) 95%); 89 } 116 } 117 118 .image-display { 119 border: 1px solid #444; 120 max-width: 100%; 121 max-height: 80vh; 122 object-fit: contain; 90 123 } 91 124 92 125 .controls { 93 126 display: flex; 94 flex-direction: column;95 127 gap: 1em; 128 grid-column-start: span 2; 96 129 } 97 130 </style> … … 100 133 import { mapState } from "vuex"; 101 134 import EditPage from "./EditPage.vue"; 102 import ImageDisplay from "./ImageDisplay.vue"103 135 import OcrService, { OcrOptions } from "../js/OcrService" 104 import Utilfrom "../js/Util";136 import { log } from "../js/Util"; 105 137 106 138 const ocrService = new OcrService(); … … 109 141 name: "MainPage", 110 142 components: { 111 ImageDisplay,112 143 EditPage 113 144 }, 114 145 data() { 115 146 return { 116 /** @type {Map<String, {image: File, buffer: ArrayBuffer, text: String, ocr: Boolean, isExpanded: Boolean}>} */ 117 images: new Map(), 118 imageToEdit: null 147 /** @type {File} */ 148 image: null, 149 imageUrl: null, 150 ocrInProgress: false, 151 ocrResult: null, 152 showEditor: false 119 153 } 120 154 }, … … 131 165 /** @type {File[]} */ 132 166 const files = this.$refs.fileInput.files; 133 if (files === null || files === undefined || files.length <1) {167 if (files === null || files === undefined || files.length !== 1) { 134 168 return; 135 169 } 136 170 137 for (const file of files) { 138 const buffer = await file.arrayBuffer(); 139 140 this.images.set( 141 Util.generateUuid(), 171 this.image = files[0]; 172 this.imageUrl = URL.createObjectURL(this.image); 173 }, 174 async doOcr() { 175 try { 176 this.ocrInProgress = true; 177 178 const imageBlob = await this.$refs.imageEditor.getImageBlobAsync(); 179 180 const result = await ocrService.run([ 142 181 { 143 image: file,144 buffer: buffer182 image: imageBlob, 183 options: new OcrOptions(false) 145 184 } 146 ) 147 } 148 }, 149 async doOcr(id) { 150 // TODO: Catch and log error 151 152 const imageInfo = this.images.get(id); 153 imageInfo.ocr = true; 154 155 const imageBlob = await this.$refs[id].getImageBlobAsync(); 156 157 const result = await ocrService.run([ 158 { 159 image: imageBlob, 160 options: new OcrOptions(false) 161 } 162 ]); 163 164 imageInfo.ocr = false; 165 imageInfo.text = result[0].text; 185 ]); 186 187 this.ocrResult = result[0].text; 188 } 189 catch (ex) { 190 // TODO: Display error 191 log("Failed to perform OCR", "error"); 192 log(ex, "error"); 193 } 194 finally { 195 this.ocrInProgress = false; 196 } 166 197 }, 167 198 onEditorCloseRequested() { 168 this.imageToEdit = null; 199 this.showEditor = false; 200 }, 201 reset() { 202 this.image = null; 203 URL.revokeObjectURL(this.imageUrl); 204 this.imageUrl = null; 205 this.ocrInProgress = false; 206 this.ocrResult = null; 207 this.showEditor = false; 208 this.$refs.fileInput.value = ""; 169 209 } 170 210 } -
main/trunk/model-interfaces-dev/atea/ocr/src/main.js
r35734 r35743 1 1 import { createApp } from "vue"; 2 2 import { createStore } from "vuex" 3 import Util from "./js/Util"4 3 import App from "./App.vue"; 5 4 import ToggleButton from "./components/ToggleButton.vue" … … 9 8 return { 10 9 /** @type {Map<String, String>} */ 11 translations: new Map(), 12 macronisedFileInfos: [], 13 directInput: null, 14 directOutput: [] 10 translations: new Map() 15 11 } 16 12 }, … … 18 14 setTranslations(state, translations) { 19 15 state.translations = translations; 20 },21 pushFileInfo(state, fileInfo) {22 state.macronisedFileInfos.push({23 id: Util.generateUuid(),24 ...fileInfo25 });26 },27 setDirectInput(state, input) {28 state.directInput = input;29 },30 setDirectOutput(state, output) {31 state.directOutput = output;32 16 } 33 17 } … … 48 32 /* eslint-disable no-undef */ 49 33 if (typeof gs !== "undefined" && gs.text && gs.text.atea && gs.text.atea.ocr) { 50 for (const key in gs.text.atea ) {51 translations.set(key, gs.text.atea [key]);34 for (const key in gs.text.atea.ocr) { 35 translations.set(key, gs.text.atea.ocr[key]); 52 36 } 53 37
Note:
See TracChangeset
for help on using the changeset viewer.