Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my algorithm to convert between index and x,y with bitmap buffers result in the image being flipped vertically?

When working with bitmap buffers like:

[50, 50, 50, 255, 50, 50, 50, 255, ...]
[r,  g,  b,  a,   r,  g,  b,  a, ...]

I often use math like this:

let bufferWidth = width * 4;
buffer.forEach((channel, index) => {
    let y = Math.floor(index / bufferWidth);
    let x = Math.floor((index % bufferWidth) / 4);
    let remainder = index % 4;

in order to calculate x, y, or vice versa to work with flat buffers of bitmap data. Almost always I end up with flipped results and some way or another end up flipping them back, but clearly there's something wrong with my thinking on this.

What's wrong with this math that would cause the bitmap to be flipped?

Full code, a function to crop a bitmap:

function crop(
  buffer,
  width,
  height,
  leftLimit,
  rightLimit,
  lowerLimit,
  upperLimit
) {
  let croppedWidth = rightLimit - leftLimit;
  let croppedHeight = upperLimit - lowerLimit;
  let length = croppedHeight * croppedWidth * 4;
  let bufferWidth = width * 4;
  let croppedBuffer = new Uint8Array(length);
  buffer.forEach((channel, index) => {
    let y = Math.floor(index / bufferWidth);
    let x = Math.floor((index % bufferWidth) / 4);
    let remainder = index % 4;
    let yCropped = y - lowerLimit;
    let xCropped = x - leftLimit;
    let indexCropped = yCropped * croppedWidth * 4 + xCropped * 4 + remainder;
    if (
      xCropped >= 0 &&
      xCropped <= croppedWidth &&
      yCropped >= 0 &&
      yCropped <= croppedHeight
    ) {
      croppedBuffer[indexCropped] = buffer[index];
    }
  });
  return croppedBuffer;
}
like image 695
J.Todd Avatar asked May 06 '19 02:05

J.Todd


2 Answers

Bitmap usually starts from bottom-left corner and proceeds to top-right corner. But not always.

There is a value biHeight in bitmap header file, if this value is negative, then bitmap is upside down, it starts from bottom-left. And if this value is positive then the bitmap starts from top-left.

If you have access to biHeight then simply flip its value to show the bitmap right side up.

To make the calculations easier, pick any valid X/Y point, then find the corresponding source_index in the buffer, as shown below. Copy that point in to your destination buffer.

Note that you need an additional loop to copy 4 bytes from source to destination (you don't have that in your code, so I am not sure how your code works at all)

for(let i = 0; i < bytes_per_pixel; i++)
    buffer_cropped[dst_index + i] = buffer[source_index + i];

The code below should work for 32-bit images (4 bytes per pixel). Note that 24-bit images will need padding.

if (height < 0) 
    height = -height;
let bytes_per_pixel = 4;
let cropped_x = 10;
let cropped_y = 10;
let cropped_width = width / 2;
let cropped_height = height / 2;

if (new_x < 0 || new_x >= new_width || 
    new_y < 0 || new_y >= new_height) { error... }
if (cropped_width < 1 || cropped_width > width || 
    cropped_height < 1 || cropped_height > height) { error... }

let dst_index = 0;
for(let y = cropped_y; y < cropped_y + cropped_height; y++)
{
    for(let x = cropped_x; x < cropped_x + cropped_width; x++)
    {
        //use for right-side up bitmap:
        //int source_index = (y * width + x) * bytes_per_pixel;
        ////

        //use for upside-down bitmap:
        let source_index = ((height - y - 1)* width + x) * bytes_per_pixel;
        ////

        for(let i = 0; i < bytes_per_pixel; i++)
            buffer_cropped[dst_index + i] = buffer[source_index + i];
        dst_index += bits_per_pixel;
    }
}
like image 191
Barmak Shemirani Avatar answered Oct 30 '22 20:10

Barmak Shemirani


Check the header of your bitmap. In the case (top to bottom), height has to be negative. The documentation.

biHeight The height of the bitmap, in pixels. If biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower-left corner. If biHeight is negative, the bitmap is a top-down DIB and its origin is the upper-left corner.

If biHeight is negative, indicating a top-down DIB, biCompression must be either BI_RGB or BI_BITFIELDS. Top-down DIBs cannot be compressed.

like image 22
Andrey Chistyakov Avatar answered Oct 30 '22 20:10

Andrey Chistyakov