Skip to content

Instantly share code, notes, and snippets.

@stengoes
Last active February 17, 2020 14:04
Show Gist options
  • Save stengoes/3076aea7ff24a183402764ec3984784b to your computer and use it in GitHub Desktop.
Save stengoes/3076aea7ff24a183402764ec3984784b to your computer and use it in GitHub Desktop.
Demosaic images with Uint8ClampedArray
function demosaic(raw) {
// Demosaics a Color Filter Array (CFA) into a RGBA color array.
// For implementation details see: http://research.microsoft.com/pubs/102068/demosaicing_icassp04.pdf
// Validate inputs
const raw_size = raw.height * raw.width * raw.depth;
if (raw.data.length != raw_size) {
console.log("Error: raw image dimensions do not match raw buffer size!");
return null;
}
if (raw.height <= 4 || raw.width <= 4) {
console.log("Error: raw image dimensions are too small!");
return null;
}
if (raw.depth != 1) {
console.log("Error: raw image should have a depth of 1!");
return null;
}
if (raw.data.constructor !== Uint8ClampedArray) {
console.log("Error: raw image data should be of type Uint8ClampedArray!");
return null;
}
// Init rgb image
const rgb = {
height: (raw.height - 4),
width: (raw.width - 4),
depth: 4,
data: new Uint8ClampedArray((raw.height - 4) * (raw.width - 4) * 4)
}
// Helper functions
function raw_idx(y, x) {
return 2 * raw.width + 2 + y * raw.width + x;
}
// 4 Different spatial filters
const F1 = function (y, x) {
let p1 = raw_idx(y - 2, x);
let p2 = raw_idx(y, x - 2);
let p3 = raw_idx(y, x + 2)
let p4 = raw_idx(y + 2, x);
let p5 = raw_idx(y - 1, x);
let p6 = raw_idx(y, x - 1);
let p7 = raw_idx(y, x + 1);
let p8 = raw_idx(y + 1, x);
let p9 = raw_idx(y, x);
return () => {
const val = (
-2 * (raw.data[p1] + raw.data[p2] + raw.data[p3] + raw.data[p4])
+ 4 * (raw.data[p5] + raw.data[p6] + raw.data[p7] + raw.data[p8])
+ 8 * raw.data[p9]
) / 16;
p1 += 2;
p2 += 2;
p3 += 2;
p4 += 2;
p5 += 2;
p6 += 2;
p7 += 2;
p8 += 2;
p9 += 2;
return val;
};
}
const F2 = function (y, x) {
let p1 = raw_idx(y - 2, x);
let p2 = raw_idx(y + 2, x);
let p3 = raw_idx(y - 1, x - 1);
let p4 = raw_idx(y - 1, x + 1);
let p5 = raw_idx(y, x - 2);
let p6 = raw_idx(y, x + 2);
let p7 = raw_idx(y + 1, x - 1);
let p8 = raw_idx(y + 1, x + 1);
let p9 = raw_idx(y, x - 1);
let p10 = raw_idx(y, x + 1);
let p11 = raw_idx(y, x);
return () => {
const val = (
1 * (raw.data[p1] + raw.data[p2])
- 2 * (raw.data[p3] + raw.data[p4] + raw.data[p5] + raw.data[p6] + raw.data[p7] + raw.data[p8])
+ 8 * (raw.data[p9] + raw.data[p10])
+ 10 * raw.data[p11]
) / 16;
p1 += 2;
p2 += 2;
p3 += 2;
p4 += 2;
p5 += 2;
p6 += 2;
p7 += 2;
p8 += 2;
p9 += 2;
p10 += 2;
p11 += 2;
return val;
};
}
const F3 = function (y, x) {
let p1 = raw_idx(y, x - 2);
let p2 = raw_idx(y, x + 2);
let p3 = raw_idx(y - 2, x);
let p4 = raw_idx(y - 1, x - 1);
let p5 = raw_idx(y - 1, x + 1);
let p6 = raw_idx(y + 1, x - 1);
let p7 = raw_idx(y + 1, x + 1);
let p8 = raw_idx(y + 2, x);
let p9 = raw_idx(y - 1, x);
let p10 = raw_idx(y + 1, x);
let p11 = raw_idx(y, x);
return () => {
const val = (
1 * (raw.data[p1] + raw.data[p2])
- 2 * (raw.data[p3] + raw.data[p4] + raw.data[p5] + raw.data[p6] + raw.data[p7] + raw.data[p8])
+ 8 * (raw.data[p9] + raw.data[p10])
+ 10 * raw.data[p11]
) / 16;
p1 += 2;
p2 += 2;
p3 += 2;
p4 += 2;
p5 += 2;
p6 += 2;
p7 += 2;
p8 += 2;
p9 += 2;
p10 += 2;
p11 += 2;
return val;
};
}
const F4 = function (y, x) {
let p1 = raw_idx(y - 2, x);
let p2 = raw_idx(y, x - 2);
let p3 = raw_idx(y, x + 2);
let p4 = raw_idx(y + 2, x);
let p5 = raw_idx(y - 1, x - 1);
let p6 = raw_idx(y - 1, x + 1);
let p7 = raw_idx(y + 1, x - 1)
let p8 = raw_idx(y + 1, x + 1);
let p9 = raw_idx(y, x);
return () => {
const val = (
-3 * (raw.data[p1] + raw.data[p2] + raw.data[p3] + raw.data[p4])
+ 4 * (raw.data[p5] + raw.data[p6] + raw.data[p7] + raw.data[p8])
+ 12 * raw.data[p9]
) / 16;
p1 += 2;
p2 += 2;
p3 += 2;
p4 += 2;
p5 += 2;
p6 += 2;
p7 += 2;
p8 += 2;
p9 += 2;
return val;
};
}
// Loop over the rgb image
let idx_rgb = 0;
let idx_raw = 2 * raw.width;
// Keep track of row location in color filter
let R_row = true;
for (let y = 0; y < rgb.height; y++) {
// Update raw index
idx_raw += 2;
// Keep track of col location in color filter
let B_col = false;
// Color filter
// [
// [R, G],
// [G, B]
// ]
// Handle each location in the color filter differently
if (R_row)
{
// Initilize filters at start of the row
const GatR = F1(y, 0);
const BatR = F4(y, 0);
const RatGinRrow = F2(y, 1);
const BatGinRrow = F3(y, 1);
for (let x = 0; x < rgb.width; x++)
{
if(!B_col)
{
// R
rgb.data[idx_rgb++] = raw.data[idx_raw++]; // R at R
rgb.data[idx_rgb++] = GatR(); // G at R
rgb.data[idx_rgb++] = BatR(); // B at R
rgb.data[idx_rgb++] = 255; // A
}
else
{
// G in R row
rgb.data[idx_rgb++] = RatGinRrow(); // R at G in R row
rgb.data[idx_rgb++] = raw.data[idx_raw++]; // G at G
rgb.data[idx_rgb++] = BatGinRrow(); // B at G in R row
rgb.data[idx_rgb++] = 255; // A
}
// Update col location
B_col = !B_col;
}
}
else
{
// Initilize filters at start of the row
const BatGinBrow = F2(y, 0);
const RatGinBrow = F3(y, 0);
const RatB = F4(y, 1);
const GatB = F1(y, 1);
for (let x = 0; x < rgb.width; x++)
{
if(!B_col)
{
// G in B row
rgb.data[idx_rgb++] = RatGinBrow(); // R at G in B row
rgb.data[idx_rgb++] = raw.data[idx_raw++]; // G at G
rgb.data[idx_rgb++] = BatGinBrow(); // B at G in B row
rgb.data[idx_rgb++] = 255; // A
}
else
{
// B
rgb.data[idx_rgb++] = RatB(); // R at B
rgb.data[idx_rgb++] = GatB(); // G at B
rgb.data[idx_rgb++] = raw.data[idx_raw++]; // B at B
rgb.data[idx_rgb++] = 255; // A
}
// Update col location
B_col = !B_col;
}
}
// Update row location
R_row = !R_row;
// Update raw index
idx_raw += 2;
}
return rgb;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment