Skip to content

Commit 2313636

Browse files
committed
Add auto config for camera
1 parent a7a35a0 commit 2313636

File tree

5 files changed

+132
-63
lines changed

5 files changed

+132
-63
lines changed

libraries/Camera/extras/WebSerialCamera/app.js

+17-28
Original file line numberDiff line numberDiff line change
@@ -11,63 +11,52 @@ const ctx = canvas.getContext('2d');
1111
// SEE: https://developer.chrome.com/articles/serial/#transforming-streams
1212
// SEE: https://developer.chrome.com/articles/serial/#signals
1313

14-
config = {
15-
"RGB565": {
16-
"bytesPerPixel": 2
17-
},
18-
"GRAYSCALE": {
19-
"bytesPerPixel": 1
20-
},
21-
"RGB888": {
22-
"bytesPerPixel": 3
23-
}
24-
};
25-
26-
const imageWidth = 320; // Adjust this value based on your bitmap width
27-
const imageHeight = 240; // Adjust this value based on your bitmap height
28-
const mode = 'RGB565'; // Adjust this value based on your bitmap format
29-
// const mode = 'GRAYSCALE'; // Adjust this value based on your bitmap format
30-
const totalBytes = imageWidth * imageHeight * config[mode].bytesPerPixel;
3114

3215
// Set the buffer size to the total bytes. This allows to read the entire bitmap in one go.
33-
const bufferSize = Math.min(totalBytes, 16 * 1024 * 1024); // Max buffer size is 16MB
16+
const bufferSize = 1024 * 1024;//Math.min(totalBytes, 16 * 1024 * 1024); // Max buffer size is 16MB
3417
const flowControl = 'hardware';
3518
const baudRate = 115200; // Adjust this value based on your device's baud rate
3619
const dataBits = 8; // Adjust this value based on your device's data bits
3720
const stopBits = 2; // Adjust this value based on your device's stop bits
3821

39-
const imageDataProcessor = new ImageDataProcessor(ctx, mode, imageWidth, imageHeight);
22+
const imageDataProcessor = new ImageDataProcessor(ctx);
4023
const connectionHandler = new SerialConnectionHandler(baudRate, dataBits, stopBits, "even", "hardware", bufferSize);
4124

42-
connectionHandler.onConnect = () => {
25+
connectionHandler.onConnect = async () => {
4326
connectButton.textContent = 'Disconnect';
27+
cameraConfig = await connectionHandler.getConfig();
28+
const imageMode = CAMERA_MODES[cameraConfig[0]];
29+
const imageResolution = CAMERA_RESOLUTIONS[cameraConfig[1]];
30+
imageDataProcessor.setMode(imageMode);
31+
imageDataProcessor.setResolution(imageResolution.width, imageResolution.height);
4432
renderStream();
4533
};
4634

4735
connectionHandler.onDisconnect = () => {
4836
connectButton.textContent = 'Connect';
37+
imageDataProcessor.reset();
4938
};
5039

51-
function renderBitmap(bytes, width, height) {
52-
canvas.width = width;
53-
canvas.height = height;
54-
const imageData = imageDataProcessor.getImageDataBytes(bytes, width, height);
40+
function renderBitmap(imageData) {
41+
canvas.width = imageDataProcessor.width;
42+
canvas.height = imageDataProcessor.height;
5543
ctx.clearRect(0, 0, canvas.width, canvas.height);
5644
ctx.putImageData(imageData, 0, 0);
5745
}
5846

5947
async function renderStream(){
6048
while(connectionHandler.isConnected()){
61-
await renderFrame();
49+
if(imageDataProcessor.isConfigured()) await renderFrame();
6250
}
6351
}
6452

6553
async function renderFrame(){
6654
if(!connectionHandler.isConnected()) return;
67-
const bytes = await connectionHandler.getFrame(totalBytes);
55+
const bytes = await connectionHandler.getFrame(imageDataProcessor.getTotalBytes());
6856
if(!bytes || bytes.length == 0) return false; // Nothing to render
6957
// console.log(`Reading done ✅. Rendering image...`);
70-
renderBitmap(bytes, imageWidth, imageHeight);
58+
const imageData = imageDataProcessor.getImageData(bytes);
59+
renderBitmap(imageData);
7160
return true;
7261
}
7362

@@ -81,7 +70,7 @@ connectButton.addEventListener('click', async () => {
8170
}
8271
});
8372
refreshButton.addEventListener('click', () => {
84-
renderFrame();
73+
if(imageDataProcessor.isConfigured()) renderFrame();
8574
});
8675

8776
saveImageButton.addEventListener('click', () => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const CAMERA_MODES = {
2+
0: "GRAYSCALE",
3+
1: "BAYER",
4+
2: "RGB565"
5+
};
6+
7+
const CAMERA_RESOLUTIONS = {
8+
0: {
9+
"name": "QQVGA",
10+
"width": 160,
11+
"height": 120
12+
},
13+
1: {
14+
"name": "QVGA",
15+
"width": 320,
16+
"height": 240
17+
},
18+
2: {
19+
"name": "320x320",
20+
"width": 320,
21+
"height": 320
22+
},
23+
3: {
24+
"name": "VGA",
25+
"width": 640,
26+
"height": 480
27+
},
28+
5: {
29+
"name": "SVGA",
30+
"width": 800,
31+
"height": 600
32+
},
33+
6: {
34+
"name": "UXGA",
35+
"width": 1600,
36+
"height": 1200
37+
}
38+
};

libraries/Camera/extras/WebSerialCamera/imageDataProcessor.js

+52-27
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,54 @@
11
class ImageDataProcessor {
2+
pixelFormatInfo = {
3+
"RGB565": {
4+
"convert": this.convertRGB565ToRGB888,
5+
"bytesPerPixel": 2
6+
},
7+
"GRAYSCALE": {
8+
"convert": this.convertGrayScaleToRGB888,
9+
"bytesPerPixel": 1
10+
},
11+
"RGB888": {
12+
"convert": this.convertToRGB888,
13+
"bytesPerPixel": 3
14+
},
15+
"BAYER": {
16+
"convert": null, // TODO
17+
"bytesPerPixel": 1
18+
}
19+
};
220

3-
constructor(context, mode) {
4-
this.canvas = context.canvas;
5-
this.context = context;
6-
this.mode = mode;
7-
this.config = {
8-
"RGB565": {
9-
"convert": this.convertRGB565ToRGB888,
10-
"bytesPerPixel": 2
11-
},
12-
"GRAYSCALE": {
13-
"convert": this.convertGrayScaleToRGB888,
14-
"bytesPerPixel": 1
15-
},
16-
"RGB888": {
17-
"convert": this.convertToRGB888,
18-
"bytesPerPixel": 3
19-
}
20-
};
21-
this.setMode(mode);
21+
constructor(context, mode = null, width = null, height = null) {
22+
this.context = context;
23+
this.canvas = context.canvas;
24+
25+
if(mode) this.setMode(mode);
26+
if(width && height) this.setResolution(width, height);
2227
}
2328

2429
setMode(mode) {
2530
this.mode = mode;
26-
this.bytesPerPixel = this.config[mode].bytesPerPixel;
31+
this.bytesPerPixel = this.pixelFormatInfo[mode].bytesPerPixel;
32+
}
33+
34+
setResolution(width, height) {
35+
this.width = width;
36+
this.height = height;
37+
}
38+
39+
getTotalBytes() {
40+
return this.width * this.height * this.bytesPerPixel;
41+
}
42+
43+
isConfigured() {
44+
return this.mode && this.width && this.height;
45+
}
46+
47+
reset() {
48+
this.mode = null;
49+
this.bytesPerPixel = null;
50+
this.width = null;
51+
this.height = null;
2752
}
2853

2954
convertRGB565ToRGB888(pixelValue) {
@@ -62,19 +87,19 @@ class ImageDataProcessor {
6287
return 0;
6388
}
6489

65-
getImageDataBytes(bytes, width, height) {
66-
const BYTES_PER_ROW = width * this.bytesPerPixel;
90+
getImageData(bytes) {
91+
const BYTES_PER_ROW = this.width * this.bytesPerPixel;
6792

68-
const imageData = this.context.createImageData(width, height);
93+
const imageData = this.context.createImageData(this.width, this.height);
6994
const dataContainer = imageData.data;
7095

71-
for (let row = 0; row < height; row++) {
72-
for (let col = 0; col < width; col++) {
96+
for (let row = 0; row < this.height; row++) {
97+
for (let col = 0; col < this.width; col++) {
7398
const sourceDataIndex = (row * BYTES_PER_ROW) + (col * this.bytesPerPixel);
7499
const pixelValue = this.getPixelValue(bytes, sourceDataIndex, this.bytesPerPixel);
75-
const [r, g, b] = this.config[mode].convert(pixelValue);
100+
const [r, g, b] = this.pixelFormatInfo[this.mode].convert(pixelValue);
76101

77-
const pixelIndex = ((row * width) + col) * 4;
102+
const pixelIndex = ((row * this.width) + col) * 4; // 4 channels: R, G, B, A
78103
dataContainer[pixelIndex] = r; // Red channel
79104
dataContainer[pixelIndex + 1] = g; // Green channel
80105
dataContainer[pixelIndex + 2] = b; // Blue channel

libraries/Camera/extras/WebSerialCamera/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
</div>
1919
<script src="imageDataProcessor.js"></script>
2020
<script src="serialConnectionHandler.js"></script>
21+
<script src="cameraConfig.js"></script>
2122
<script src="app.js"></script>
2223
</body>
2324
</html>

libraries/Camera/extras/WebSerialCamera/serialConnectionHandler.js

+24-8
Original file line numberDiff line numberDiff line change
@@ -177,20 +177,36 @@ class SerialConnectionHandler {
177177
return bytesRead;
178178
}
179179

180+
async sendData(byteArray) {
181+
if (!this.currentPort?.writable) {
182+
console.log('🚫 Port is not writable. Ignoring request...');
183+
return;
184+
}
185+
const writer = this.currentPort.writable.getWriter();
186+
await writer.write(new Uint8Array(byteArray));
187+
await writer.close();
188+
}
189+
180190
/**
181191
* Reqests an image frame from the Arduino board by writing a 1 to the serial port.
182192
* @returns {Promise<void>} A promise that resolves when the frame has been requested and the write stream has been closed.
183193
*/
184194
async requestFrame() {
185-
if (!this.currentPort?.writable) {
186-
console.log('🚫 Port is not writable. Ignoring request...');
187-
return;
188-
}
189195
// console.log('Writing 1 to the serial port...');
190196
// Write a 1 to the serial port
191-
const writer = this.currentPort.writable.getWriter();
192-
await writer.write(new Uint8Array([1]));
193-
await writer.close();
197+
return this.sendData([1]);
198+
}
199+
200+
async requestConfig() {
201+
return this.sendData([2]);
202+
}
203+
204+
async getConfig() {
205+
if (!this.currentPort) return;
206+
207+
await this.requestConfig();
208+
// console.log(`Trying to read 2 bytes...`);
209+
return await this.readBytes(2, this.timeout);
194210
}
195211

196212
/**
@@ -201,7 +217,7 @@ class SerialConnectionHandler {
201217
async getFrame(totalBytes) {
202218
if (!this.currentPort) return;
203219

204-
await this.requestFrame(this.currentPort);
220+
await this.requestFrame();
205221
// console.log(`Trying to read ${totalBytes} bytes...`);
206222
// Read the given amount of bytes
207223
return await this.readBytes(totalBytes, this.timeout);

0 commit comments

Comments
 (0)