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;
}
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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With