Beginner’s Guide to Creating an OCR( Image To Text) App with Angular

06/09/2023 @ 6:51 am
00:00

1. Installing Node.js and npm:

Before you can install Angular and its CLI, you need to have Node.js and npm (Node package manager) on your system.

Download and install Node.js from Node.js official website. The npm is included in the installation.

To check if you have Node.js and npm correctly installed, run the following commands:

node -v npm -v

2. Installing the Angular CLI:

Once you have Node.js and npm installed, you can install the Angular CLI globally on your machine:

npm install -g @angular/cli

To verify that the Angular CLI was installed correctly:

ng --version

3. Creating a New Angular Project:

After installing the Angular CLI, you can create a new Angular project using:

ng new your-project-name

Replace your-project-name with your desired project name. This command will prompt you for some configuration choices. For most beginners, the default options will suffice.

4. Navigating into Your Project:

cd your-project-name

5. Serving Your Application:

To see your new Angular app in action:

ng serve

By default, this will start a development server, and you can view your app by navigating to http://localhost:4200/ in your browser. The app will automatically reload if you make any changes to the source code.

6. Generating Component & Services

ng generate component image-upload

ng generate component result-display

ng generate service Image

ng generate service Ocr

Create new file pdfjs.d.ts under app folder and write this code in the file

declare module 'pdfjs-dist/build/pdf';

7. Required Libraries

Tesseract.js:

Tesseract.js is a pure JavaScript port of the popular Tesseract OCR engine. To install it, run:

npm install tesseract.js

pdfjs-dist:

PDF.js is a Portable Document Format (PDF) library that is built with HTML5. To install it, run:

npm install pdfjs-dist

ImageUploadComponent

Image-Upload.Component.html

<div class="image-upload-area" (click)="triggerInputClick()">

  <input #fileInput type="file" (change)="onFileChange($event)" />

  <div class="upload-button">Upload | Drag & Drop | Screenshot & Paste Image | Then Hit The Center Button</div>

</div>

<img *ngIf="imageSrc" [src]="imageSrc" alt="Preview" />

<div class="screenshot-instructions" *ngIf="showScreenshotInstructions">

  <p>Press Ctrl + PrtSc to capture a screenshot, then paste it here.</p>

</div>

Image-Upload.Component.ts

import { Component, OnInit, OnDestroy, ViewChild, ElementRef, HostListener } from '@angular/core';

import { ImageService } from '../image.service';

import { Subscription } from 'rxjs';

@Component({

  selector: 'app-image-upload',

  templateUrl: './image-upload.component.html',

  styleUrls: ['./image-upload.component.css']

})

export class ImageUploadComponent implements OnInit, OnDestroy {

  imageSrc: string | ArrayBuffer = '';

  private imageSubscription!: Subscription;

  @ViewChild('fileInput') fileInput!: ElementRef;

  showScreenshotInstructions: boolean = true;

  constructor(private imageService: ImageService) { }

  ngOnInit(): void {

    this.imageSubscription = this.imageService.imageSource$.subscribe(imageSrc => {

      this.imageSrc = imageSrc;

    });

  }

  triggerInputClick(): void {

    this.fileInput.nativeElement.click();

  }

  onFileChange(event: Event) {

    const file = (event.target as HTMLInputElement).files?.[0];

    console.log('Selected file:', file);

    const reader = new FileReader();

    reader.onload = e => {

      if (reader.result) {

        this.imageService.setImageSource(reader.result);

      }

    };

    if (file) {

      reader.readAsDataURL(file);

    }

  }

  @HostListener('document:paste', ['$event'])

  onPaste(event: ClipboardEvent) {

    if (event.clipboardData) {

      const item = event.clipboardData.items[0]; // Assuming the first item is an image

      if (item && item.type.indexOf('image') !== -1) {

        const blob = item.getAsFile();

        if (blob) {

          const reader = new FileReader();

          reader.onload = e => {

            if (reader.result) {

              this.imageService.setImageSource(reader.result);

            }

          };

          reader.readAsDataURL(blob);

        }

      }

    }

    this.showScreenshotInstructions = false;

  }

  ngOnDestroy(): void {

    // Unsubscribe to prevent memory leaks

    this.imageSubscription.unsubscribe();

  }

}

Result-DiplayComponent

Result-display.Component.html

<div>

    <textarea readonly [value]="resultText"></textarea>

  </div>

Result-display.Component.ts

import { Component, Input } from '@angular/core'; // Importing Input here

@Component({

  selector: 'app-result-display',

  templateUrl: './result-display.component.html',

  styleUrls: ['./result-display.component.css']

})

export class ResultDisplayComponent {

  @Input() resultText: string = '';

}

App.component.html

<div class="bg_set">

    <div class="main-container cont">

    <div class="logo"><img src="../assets/logo.png"></div>

</div></div>

<div class="main-container">

  <div class="overall_ocr">

    <div class="left_data">

        <div class="uploader">

    <app-image-upload></app-image-upload>

  </div>

      <!-- Content for the left_data section goes here -->

    </div>

    <div class="center_btn">

      <img src="../assets/convert.svg" (click)="onConvertClick()">

      <div *ngIf="isProcessing" class="processing">

        Processing...

      </div>

    </div>

    <div class="right_data">

      <app-result-display [resultText]="resultText"></app-result-display>

    </div>

  </div>

  <div class="warning">

    *For Better And Accurate Result Upload The High Resolution Image

</div>

</div>

<div class="footer">

    &copy; 2023 Your imagetotextfree. All rights reserved.

</div>

App.component.ts

import { Component } from '@angular/core';

import { ImageService } from './image.service';

import { OcrService } from './ocr.service';

import { HostListener } from '@angular/core';

@Component({

  selector: 'app-root',

  templateUrl: './app.component.html',

  styleUrls: ['./app.component.css']

})

export class AppComponent {

  imageSrc: string | ArrayBuffer = '';

  resultText: string = '';

  isProcessing: boolean = false; // Corrected property name

  constructor(private ocrService: OcrService, private imageService: ImageService) {

    this.imageService.imageSource$.subscribe(imageSrc => {

      this.imageSrc = imageSrc;

    });

  }

  onConvertClick() {

    if (typeof this.imageSrc === 'string') {

      this.isProcessing = true; // Corrected property name

      this.ocrService.recognizeImage(this.imageSrc).then((result: string) => {

        this.resultText = result;

        this.isProcessing = false; // Corrected property name

      });

    }

  }

  @HostListener('window:keydown', ['$event'])

  handleKeyboardEvent(event: KeyboardEvent) {

    const key = event.key || String.fromCharCode(event.keyCode);

    if ((event.ctrlKey || event.metaKey) && (key === 'u' || key === 'U')) {

      event.preventDefault();

      console.log('CTRL+U is disabled on this site.');

    }

  }

}

App.module.ts

import { NgModule } from '@angular/core';

import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

import { ImageUploadComponent } from './image-upload/image-upload.component';

import { ResultDisplayComponent } from './result-display/result-display.component';

import { FormsModule } from '@angular/forms';

@NgModule({

  declarations: [

    AppComponent,

    ImageUploadComponent,

    ResultDisplayComponent

  ],

  imports: [

    BrowserModule,

    FormsModule

  ],

  providers: [],

  bootstrap: [AppComponent]

})

export class AppModule { }

image.service.ts

import { Injectable } from '@angular/core';

import { BehaviorSubject } from 'rxjs';

@Injectable({

  providedIn: 'root',

})

export class ImageService {

  private imageSourceSubject = new BehaviorSubject<string | ArrayBuffer>('');

  imageSource$ = this.imageSourceSubject.asObservable();

  setImageSource(imageSource: string | ArrayBuffer) {

    console.log('Setting image source:', imageSource); // Log the new value of imageSource

    this.imageSourceSubject.next(imageSource);

  }

}

ocr.service.ts

import * as Tesseract from 'tesseract.js';

import * as pdfjsLib from 'pdfjs-dist/build/pdf';

import { Injectable } from '@angular/core';

@Injectable({

  providedIn: 'root',

})

export class OcrService {

  constructor() {}

  private removeSpecialCharacters(text: string): string {

    return text.replace(/[^\w\s]/gi, ''); // Replace special characters with empty strings

  }

  async recognizeImage(imageSrc: string | ArrayBuffer): Promise<string> {

    console.log('Starting OCR recognition for:', imageSrc); // Log the starting of recognition

    if (typeof imageSrc !== 'string') {

      throw new Error('Invalid image source type');

    }

    // Load the image as an Image object

    const image = new Image();

    image.src = imageSrc;

    console.log('Image object created:', image); // Log the image object

    // Ensure the image is loaded before proceeding

    await new Promise<HTMLImageElement>((resolve) => {

      image.onload = () => resolve(image);

    });

    // Use Tesseract to recognize the text in the image

    const result = await Tesseract.recognize(image, 'eng', {

      logger: (progress) => console.log(progress),

    });

    return result.data.text;

  }

  async recognizePdf(pdfSrc: string | ArrayBuffer): Promise<string[]> {

    console.log('Recognizing PDF with source:', pdfSrc); // Log the starting of PDF recognition

    const loadingTask = pdfjsLib.getDocument(pdfSrc);

    const pdf = await loadingTask.promise;

    const numPages = pdf.numPages;

    const pagesText = [];

    for (let i = 1; i <= numPages; i++) {

      console.log('Processing page:', i); // Log the processing of each page

      const page = await pdf.getPage(i);

      const canvas = document.createElement('canvas');

      const viewport = page.getViewport({ scale: 1 });

      canvas.width = viewport.width;

      canvas.height = viewport.height;

      const renderContext = {

        canvasContext: canvas.getContext('2d')!,

        viewport: viewport,

      };

      await page.render(renderContext).promise;

      const text = await Tesseract.recognize(canvas, 'eng', {

        logger: (progress) => console.log(progress),

      });

      pagesText.push(text.data.text);

    }

    return pagesText;

  }

}

styles.css

/* You can add global styles to this file, and also import other style files */

@import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;600&display=swap');

*, ::after, ::before {

    box-sizing: border-box;

}

:focus-visible {

    outline: 0px !important;

}

body{  font-family: 'Jost', sans-serif; margin: 0px; padding: 0px;}

.container-fluid{    width: 100%;

    padding-right: 15px;

    padding-left: 15px;

    margin-right: auto;

    margin-left: auto;}

    .overall_ocr {

        display: flex;

        justify-content: space-between;

        align-items: center;

        background: #fff;

        padding: 40px;

        width: 90%;

        margin: 0 auto;

        border-radius: 10px;

        margin-top: -270px;

        box-shadow: rgba(35, 55, 80, 0.1) 0px 6px 12px;

        position: relative;

    }

    .left_data {

        width: 45%;

        border-radius: 8px;

        background-color: rgb(255, 255, 255);

        box-shadow: rgba(35, 55, 80, 0.3) 0px 6px 12px;

        padding: 20px;

        min-height: 500px;

    }

    .right_data{ width: 45%;

        border-radius: 8px;

        background-color: rgb(255, 255, 255);

        box-shadow: rgba(35, 55, 80, 0.3) 0px 6px 12px;

        padding: 20px;  min-height: 500px;}

    .center_btn{ width: 8%;}

    .right_data textarea {

        display: block;

        width: 100%;

        padding: 0.375rem 0.75rem;

        font-size: 1rem;

        font-weight: 400;

        line-height: 1.5;

        color: var(--bs-body-color);

        -webkit-appearance: none;

        appearance: none;

        background-color: var(--bs-body-bg);

        background-clip: padding-box;

        border: var(--bs-border-width) solid var(--bs-border-color);

        border-radius: var(--bs-border-radius);

        transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;

        resize: vertical;

        height: 500px;

    }

        .center_btn img{     background: #fff;

            padding: 20px;

            border-radius: 100px; border: 2px solid #0a146e; cursor: pointer;}

            .center_btn {

                width: 8%;

                text-align: center;

            }

            .processing {

                color: #212121;

            }

            /* Style for the image upload container */

.image-upload-container {

  display: flex;

  justify-content: center;

  align-items: center;

  height: 100vh; /* Adjust this to your preference */

}

/* Style for the image upload area */

.image-upload-area {

    text-align: center;

    border: 2px dashed #f56e23fc;

    padding: 20px;

    cursor: pointer;

}

/* Style for the custom file upload button */

.upload-button {

    background-color: #f56e23fc;

    color: white;

    padding: 17px 20px;

    border-radius: 4px;

    font-size: 18px;

}

/* Change background color on hover */

.upload-button:hover {

  background-color: #0a146e;

}

/* Hide the input file element */

input[type="file"] {

  display: none;

}

/* Style for the uploaded image */

.left_data img {

  display: block;

  margin: 0 auto;

  max-width: 100%;

  border: 1px solid #ccc;

  border-radius: 4px;

}

.bg_set {

    background: #0a146e;

    width: 100%;

    padding: 50px;

    padding-top: 180px;

    min-height: 450px;

}

.left_data {

    width: 45%;

    border-radius: 8px;

    background-color: rgb(255, 255, 255);

    box-shadow: rgba(35, 55, 80, 0.3) 0px 6px 12px;

    padding: 20px;

    min-height: 500px;

}

.left_data img{ max-height: 480px;}

.image-upload-area {

    position: absolute;

    top: -120px;

    left: 0;

}

.upload-button:hover {

    background-color: #fff;

    color: #212121;

}

.screenshot-instructions p {

    margin-top: -60px;

    text-align: center;

    position: absolute;

    top: 78px;

    width: 41%;

}

.footer {

    background: #0a146e;

    padding: 20px 0px;

    margin-top: 70px; color: #fff; text-align: center;

}

.warning {

    text-align: right;

    margin-top: 20px;

    margin-right: 90px;

}

.bg_set img {

    position: absolute;

    right: 38px;

    top: -141px;

    width: 100px;

}

.cont{ position: relative;}

/* responsive */

@media (min-width: 320px) and (max-width: 767px) {

    .overall_ocr {

        display: block;

    }

}

Result

Recent Posts

01/02/2024
11:37 am

Table of Contents Introduction to Fintech and Data Science Fintech, a

23/01/2024
5:32 am

ICE token is not just a digital currency; it’s the cornerstone o

07/01/2024
6:28 am

Table of Contents Introduction to Website Loading Speed In the digital

31/12/2023
8:22 am

Table of Contents Introduction The realm of artificial intelligence (A