最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Modifying image pixels using bitwise operators (JSFeat) - Stack Overflow

programmeradmin0浏览0评论

I am using the JSFeat Computer Vision Library and am trying to convert an image to greyscale. The function jsfeat.imgproc.grayscale outputs to a matrix (img_u8 below), where each element is an integer between 0 and 255. I was unsure how to apply this matrix to the original image so I went looking through their example at .htm.

Below is my code to convert an image to grey scale. I adopted their method to update the pixels in the original image but I do not understand how it works.

/**
 * I understand this stuff
 */
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

let img = document.getElementById('img-in');
ctx.drawImage(img, 0, 0, img.width, img.height);

let imageData = ctx.getImageData(0, 0, img.width, img.height);

let img_u8 = new jsfeat.matrix_t(img.width, img.height, jsfeat.U8C1_t);
jsfeat.imgproc.grayscale(imageData.data, img.width, img.height, img_u8);

let data_u32 = new Uint32Array(imageData.data.buffer);
let i = img_u8.cols*img_u8.rows, pix = 0;

/**
 * Their logic to update the pixel values of the original image
 * I need help understanding how the following works
 */
let alpha = (0xff << 24);
while(--i >= 0) {
    pix = img_u8.data[i];
    data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix;
}

/**
 * I understand this stuff
 */
context.putImageData(imageData, 0, 0);

Thanks in advance!

I am using the JSFeat Computer Vision Library and am trying to convert an image to greyscale. The function jsfeat.imgproc.grayscale outputs to a matrix (img_u8 below), where each element is an integer between 0 and 255. I was unsure how to apply this matrix to the original image so I went looking through their example at https://inspirit.github.io/jsfeat/sample_grayscale.htm.

Below is my code to convert an image to grey scale. I adopted their method to update the pixels in the original image but I do not understand how it works.

/**
 * I understand this stuff
 */
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

let img = document.getElementById('img-in');
ctx.drawImage(img, 0, 0, img.width, img.height);

let imageData = ctx.getImageData(0, 0, img.width, img.height);

let img_u8 = new jsfeat.matrix_t(img.width, img.height, jsfeat.U8C1_t);
jsfeat.imgproc.grayscale(imageData.data, img.width, img.height, img_u8);

let data_u32 = new Uint32Array(imageData.data.buffer);
let i = img_u8.cols*img_u8.rows, pix = 0;

/**
 * Their logic to update the pixel values of the original image
 * I need help understanding how the following works
 */
let alpha = (0xff << 24);
while(--i >= 0) {
    pix = img_u8.data[i];
    data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix;
}

/**
 * I understand this stuff
 */
context.putImageData(imageData, 0, 0);

Thanks in advance!

Share Improve this question asked Jun 2, 2016 at 6:08 Randy WRandy W 534 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 10

It's a wide topic, but I'll try to roughly cover the basics in order to understand what goes on here.

As we know, it's using 32-bit integer values which means you can operate on four bytes simultaneously using fewer CPU instructions and therefor in many cases can increase overall performance.

Crash course

A 32-bit value is often notated as hex like this:

0x00000000

and represents the equivalent of bits starting with the least significant bit 0 on the right to the most significant bit 31 on the left. A bit can of course only be either on/set/1 or off/unset/0. 4 bits is a nibble, 2 nibbles are one byte. The hex value has each nibble as one digit, so here you have 8 nibbles = 4 bytes or 32 bits. As in decimal notation, leading 0s have no effect on the value, i.e. 0xff is the same as 0x000000ff (The 0x prefix also has no effect on the value; it is just the traditional C notation for hexadecimal numbers which was then taken over by most other mon languages).

Operands

You can bit-shift and perform logic operations such as AND, OR, NOT, XOR on these values directly (in assembler language you would fetch the value from a pointer/address and load it into a registry, then perform these operations on that registry).

So what happens is this:

The << means bit-shift to the left. In this case the value is:

0xff

or in binary (bits) representation (a nibble 0xf = 1111):

0b11111111

This is the same as:

0x000000ff

or in binary (unfortunately we cannot denote bit representation natively in JavaScript actually, there is the 0b-prefix in ES6):

0b00000000 00000000 00000000 11111111

and is then bit-shifted to the left 24 bit positions, making the new value:

0b00000000 00000000 00000000 11111111
                         << 24 bit positions =
0b11111111 00000000 00000000 00000000

or

0xff000000

So why is this necessary here? Well, that's an excellent question!

The 32-bit value in relation to canvas represents RGBA and each of the ponents can have a value between 0 and 255, or in hex a value between 0x00 and 0xff. However, since most consumer CPUs today uses little-endian byte order each ponents for the colors is at memory level stored as ABGR instead of RGBA for 32-bit values.

We are normally abstracted away from this in a high-level language such as JavaScript of course, but since we now work directly with memory bytes through typed arrays we have to consider this aspect as well, and in relation to registry width (here 32-bits).

So here we try to set alpha channel to 255 (fully opaque) and then shift it 24 bits so it bees in the correct position:

0xff000000
0xAABBGGRR

(Though, this is an unnecessary step here as they could just as well have set it directly as 0xff000000 which would be faster, but anyhoo).

Next we use the OR (|) operator bined with bit-shift. We shift first to get the value in the correct bit position, then OR it onto the existing value.

OR will set a bit if either the existing or the new bit is set, otherwise it will remain 0. F.ex starting with an existing value, now holding the alpha channel value:

0xff000000

We then want the blue ponent of say value 0xcc (204 in decimal) bined which currently is represented in 32-bit as:

0x000000cc

so we need to first shift it 16 bits to the left in this case:

0x000000cc
     << 16 bits
0x00cc0000

When we now OR that value with the existing alpha value we get:

   0xff000000
OR 0x00cc0000
 = 0xffcc0000

Since the destination is all 0 bits only the value from source (0xcc) is set, which is what we want (we can use AND to remove unwanted bits but, that's for another day).

And so on for the green and red ponent (the order which in they are OR'ed doesn't matter so much).

So this line then does, lets say pix = 0xcc:

data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix;

which translates into:

alpha     = 0xff000000  Alpha
pix       = 0x000000cc  Red
pix <<  8 = 0x0000cc00  Green
pix << 16 = 0x00cc0000  Blue

and OR'ed together would bee:

value     = 0xffcccccc

and we have a grey value since all ponents has the same value. We have the correct byte-order and can write it back to the Uint32 buffer using a single operation (in JS anyways).

You can optimize this line though by using a hard-coded value for alpha instead of a reference now that we know what it does (if alpha channel vary then of course you would need to read the alpha ponent value the same way as the other values):

data_u32[i] = 0xff000000 | (pix << 16) | (pix << 8) | pix;

Working with integers, bits and bit operators is as said a wide topic and this just scratches the surface, but hopefully enough to make it more clear what goes on in this particular case.

发布评论

评论列表(0)

  1. 暂无评论