Skip to content

Commit

Permalink
chore: brought some things into the frame processor and wrote better …
Browse files Browse the repository at this point in the history
…docs for the reader
  • Loading branch information
cereallarceny committed Sep 11, 2024
1 parent a604c2d commit 2e50c46
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 139 deletions.
42 changes: 38 additions & 4 deletions packages/reader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ bun add @flipbookqr/reader

## Usage

```typescript
### Screenshare

The following will read a QR code via the `getDisplayMedia` (screenshare) API:

```ts
import { Reader } from '@flipbookqr/reader';

// Create a new instance of the Flipbook reader
Expand All @@ -42,20 +46,50 @@ const result = await reader.read();

The `result` is a is the original payload that was encoded into the series of QR codes.

**Please note:** The default configuration of the reader is to read the QR codes visible on the screen using WebRTC's `getUserMedia` API. This means that the reader will ask for permission to view the user's screen. You may want to use a different `frameProcessor` if you want to read the QR codes from a different source, such as a file. You can change the `frameProcessor` like this:
### Camera

The following will read a QR code via the `getUserMedia` (camera) API:

```ts
import { Reader, WebRTCProcessor } from '@flipbookqr/reader';

// Create a new instance of the Flipbook reader
const reader = new Reader({
frameProcessor: new WebRTCProcessor('camera')
});

// Get a list of all available camera sources
const sources = await reader.opts.frameProcessor.getStreamTracks();

// Select a camera source
reader.opts.frameProcessor.setStreamTrack(sources[0]);

// Note: If you don't do the above two commands, it will default to the first camera source

// Read the Flipbook visible on the screen
const result = await reader.read();
```

The `result` is a is the original payload that was encoded into the series of QR codes.

### File upload

The following will read a QR code from a file:

```ts
import { Reader, FileProcessor } from '@flipbookqr/reader';

const file = // some file
const file = new File(); // some file

const reader = new Reader({
frameProcessor: new FileProcessor(file)
});

const result = await reader.read()
const result = await reader.read();
```

The `result` is a is the original payload that was encoded into the series of QR codes.

## Configuration

The `Writer` class accepts an optional configuration object that can be used to customize the behavior of the writer. The following options are available:
Expand Down
65 changes: 0 additions & 65 deletions packages/reader/src/processors/file-processor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import jsQR, { type QRCode } from 'jsqr';
import { getLogger } from '@flipbookqr/shared';
import { type Logger } from 'loglevel';
import { parseGIF, decompressFrames } from 'gifuct-js';
import { sliceFrames, sortFrames } from '../helpers';
import { FrameProcessor } from './frame-processor';
Expand All @@ -11,11 +8,6 @@ import { FrameProcessor } from './frame-processor';
* @extends FrameProcessor
*/
export class FileProcessor extends FrameProcessor {
protected _ctx: CanvasRenderingContext2D | null;
protected _canvas: HTMLCanvasElement;
protected _width: number;
protected _height: number;
protected _log: Logger;
protected _file: File;

/**
Expand All @@ -27,65 +19,8 @@ export class FileProcessor extends FrameProcessor {
// Initialize the processor
super();

// Set up logger
this._log = getLogger();

// Set the file to process
this._file = file;

// Create canvas element
const canvas = document.createElement('canvas');
this._width = 1920;
this._height = 1080;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
}

/**
* Sets the specified frame on the canvas.
*
* @param {HTMLImageElement | ImageBitmap} frame - The frame to draw on the canvas.
* @throws Will throw an error if an unsupported frame type is provided.
*/
protected setFrame(frame: HTMLImageElement | ImageBitmap): void {
// Store the frame dimensions
let width, height;

// Get the frame dimensions
if (frame instanceof HTMLImageElement || frame instanceof ImageBitmap) {
width = frame.width;
height = frame.height;
} else {
throw new Error('Unsupported frame type');
}

// Update the canvas dimensions
this._width = width;
this._height = height;
this._canvas.width = width;
this._canvas.height = height;

// Draw the frame on the canvas
this._ctx?.drawImage(frame, 0, 0, this._width, this._height);
}

/**
* Retrieves QR code data from the current canvas frame.
*
* @returns {QRCode | null} The decoded QR code data, or null if no data is found.
*/
protected getFrameData(): QRCode | null {
// Get the image data from the canvas
const imageData = this._ctx?.getImageData(0, 0, this._width, this._height);

// Decode the image data to extract QR code data
let decodedData: null | QRCode = null;
if (imageData && 'data' in imageData) {
decodedData = jsQR(imageData.data, this._width, this._height);
}

// Return the decoded data
return decodedData;
}

/**
Expand Down
87 changes: 87 additions & 0 deletions packages/reader/src/processors/frame-processor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,90 @@
import { getLogger } from '@flipbookqr/shared';
import jsQR, { type QRCode } from 'jsqr';
import type { Logger } from 'loglevel';

export abstract class FrameProcessor {
protected _ctx: CanvasRenderingContext2D | null;
protected _canvas: HTMLCanvasElement;
protected _width: number;
protected _height: number;
protected _log: Logger;

constructor() {
// Set up logger
this._log = getLogger();

// Create canvas element
const canvas = document.createElement('canvas');
this._width = 1920;
this._height = 1080;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
}

/**
* Sets the specified frame on the canvas.
*
* @param {HTMLVideoElement | HTMLImageElement | ImageBitmap} source - The source to draw on the canvas.
* @throws Will throw an error if an unsupported frame type is provided.
*/
protected setFrame(
source: HTMLVideoElement | HTMLImageElement | ImageBitmap
): void {
// Store the source dimensions
let width, height;

// Get the source dimensions
if (source instanceof HTMLImageElement || source instanceof ImageBitmap) {
width = source.width;
height = source.height;
} else if (source instanceof HTMLVideoElement) {
width = source.videoWidth;
height = source.videoHeight;
} else {
throw new Error('Unsupported frame type');
}

// Update the canvas dimensions
this._width = width;
this._height = height;
this._canvas.width = width;
this._canvas.height = height;

// Draw the frame on the canvas
this._ctx?.drawImage(source, 0, 0, this._width, this._height);
}

/**
* Retrieves QR code data from the current frame.
*
* @returns {QRCode | null} The decoded QR code data, or null if no data was found.
*/
protected getFrameData(): QRCode | null {
// Get the frame data from the canvas
const results = this._ctx?.getImageData(
0,
0,
this._canvas.width,
this._canvas.height
);

// If no data is found, return null
if (!results) return null;

this._log.debug('Got frame data', results);

// Decode the frame data using a QR code reader
const decodedData = jsQR(
results.data,
this._canvas.width,
this._canvas.height
);

this._log.debug('Decoded frame data', decodedData);

// Return the decoded data
return decodedData;
}

abstract read(): Promise<string>;
}
72 changes: 2 additions & 70 deletions packages/reader/src/processors/webrtc-processor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import jsQR, { type QRCode } from 'jsqr';
import { getHeadLength, getLogger } from '@flipbookqr/shared';
import type { Logger } from 'loglevel';
import { getHeadLength } from '@flipbookqr/shared';
import { sliceFrames, sortFrames } from '../helpers';
import { FrameProcessor } from './frame-processor';

Expand All @@ -14,15 +12,10 @@ type MediaOptions = DisplayMediaStreamOptions | MediaStreamConstraints;
* @extends FrameProcessor
*/
export class WebRTCProcessor extends FrameProcessor {
protected _ctx: CanvasRenderingContext2D | null;
protected _canvas: HTMLCanvasElement;
protected _width: number;
protected _height: number;
protected _video: HTMLVideoElement | undefined;
protected _track: MediaStreamTrack | undefined;
protected _mediaType: MediaType;
protected _mediaOptions: MediaOptions;
protected _log: Logger;

/**
* Creates an instance of WebRTCProcessor.
Expand All @@ -34,16 +27,6 @@ export class WebRTCProcessor extends FrameProcessor {
// Initialize the processor
super();

// Set up logger
this._log = getLogger();

// Create canvas element
const canvas = document.createElement('canvas');
this._width = 1920;
this._height = 1080;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');

// Initialize video and track
this._video = undefined;
this._track = undefined;
Expand All @@ -56,57 +39,6 @@ export class WebRTCProcessor extends FrameProcessor {
};
}

/**
* Sets the current video frame onto the canvas.
* If the video element is present, it draws the video frame onto the canvas.
*/
protected setFrame(): void {
// If the video and context are available, draw the video frame onto the canvas
if (this._video && this._ctx) {
// Get the video dimensions
const { videoWidth, videoHeight } = this._video;

// Set the canvas dimensions
this._canvas.width = videoWidth;
this._canvas.height = videoHeight;

// Draw the video frame onto the canvas
this._ctx.drawImage(this._video, 0, 0, videoWidth, videoHeight);
}
}

/**
* Retrieves QR code data from the current frame.
*
* @returns {QRCode | null} The decoded QR code data, or null if no data was found.
*/
protected getFrameData(): QRCode | null {
// Get the frame data from the canvas
const results = this._ctx?.getImageData(
0,
0,
this._canvas.width,
this._canvas.height
);

// If no data is found, return null
if (!results) return null;

this._log.debug('Got frame data', results);

// Decode the frame data using a QR code reader
const decodedData = jsQR(
results.data,
this._canvas.width,
this._canvas.height
);

this._log.debug('Decoded frame data', decodedData);

// Return the decoded data
return decodedData;
}

/**
* Cleans up by removing the canvas and video elements.
*/
Expand Down Expand Up @@ -140,7 +72,7 @@ export class WebRTCProcessor extends FrameProcessor {
const processFrame = (): void => {
try {
// Set the current frame onto the canvas
this.setFrame();
this.setFrame(this._video!);

// Get the QR code data from the frame
const result = this.getFrameData();
Expand Down

0 comments on commit 2e50c46

Please sign in to comment.