This is the algorithm I am using to flip an image in-place. Though it works perfectly, I would like if any one can spot any potential problems or offer any points on improvement and optimizations.
/**
* pixels_buffer - Pixels buffer to be operated
* width - Image width
* height - Image height
* bytes_per_pixel - Number of image components, ie: 3 for rgb, 4 rgba, etc...
**/
void flipVertically(unsigned char *pixels_buffer, const unsigned int width, const unsigned int height, const int bytes_per_pixel)
{
const unsigned int rows = height / 2; // Iterate only half the buffer to get a full flip
const unsigned int row_stride = width * bytes_per_pixel;
unsigned char* temp_row = (unsigned char*)malloc(row_stride);
int source_offset, target_offset;
for (int rowIndex = 0; rowIndex < rows; rowIndex++)
{
source_offset = rowIndex * row_stride;
target_offset = (height - rowIndex - 1) * row_stride;
memcpy(temp_row, pixels_buffer + source_offset, row_stride);
memcpy(pixels_buffer + source_offset, pixels_buffer + target_offset, row_stride);
memcpy(pixels_buffer + target_offset, temp_row, row_stride);
}
free(temp_row);
temp_row = NULL;
}
4 Answers 4
source_offset = rowIndex * row_stride;
target_offset = (height - rowIndex - 1) * row_stride;
memcpy(temp_row, pixels_buffer + source_offset, row_stride);
memcpy(pixels_buffer + source_offset, pixels_buffer + target_offset, row_stride);
memcpy(pixels_buffer + target_offset, temp_row, row_stride);
could be written:
- with
const
variables - without any duplicated evaluation of
pixels_buffer + [source/target]_offset
doing something like:
unsigned char* source = pixels_buffer + rowIndex * row_stride;
unsigned char* target = pixels_buffer + (height - rowIndex - 1) * row_stride;
memcpy(temp_row, source, row_stride);
memcpy(source, target, row_stride);
memcpy(target, temp_row, row_stride);
Also, a different way to do would be to initialize
source = pixels_buffer
and
target = pixels_buffer + (height - 1) * row_stride
and to increment/decrement them by row_stride
at each iteration.
Edit: if you go for this option, you might be able to use this for the stopping condition of your loop.
A few comments:
you use signed/unsigned types inconsistently. Your
width
andheight
are unsigned, while all other integers are signed. If you are using unsigned types because the values can never be negative, then the same applies to all integers in the function and, by that reasoning, they should all be unsigned (size_t
is a common type used for sizes). There are arithmetic errors that can occur with mixed signed and unsigned arithmetic, so you should turn on conversion warnings (eg-Wconversion
forgcc
).your naming is inconsistent. The function and the variable
rowIndex
use "camel-case", while the other variables don't.the return from
malloc
should not be cast in C (C++ requires casting).source_offset
andtarget_offset
should be defined at the point of first use (ie. within the loop). There is no performance penalty for defining a variable within a loop in C.there is no check for success from
malloc
Here is a slightly simplified version of the function
void flip_vertically(unsigned char *pixels, const size_t width, const size_t height, const size_t bytes_per_pixel)
{
const size_t stride = width * bytes_per_pixel;
unsigned char *row = malloc(stride);
unsigned char *low = pixels;
unsigned char *high = &pixels[(height - 1) * stride];
for (; low < high; low += stride, high -= stride) {
memcpy(row, low, stride);
memcpy(low, high, stride);
memcpy(high, row, stride);
}
free(row);
}
You can see that I have changed some types, shortened variable names (it is a short function so short names are appropriate) and simplified the way buffer offsets are calculated to just addition/subtraction.
Just a quick addition to something these already great answers stated but somewhat glossed over. Originally this question had the C++ tag in it. I'm assuming because of this line:
unsigned char* temp_row = (unsigned char*) malloc(row_stride);
that you are compiling this C code as C++ code. Don't do that. While the languages seem very similar and the compiler you are using will probably allow it, it is a bad habit to form. One of the main reasons not to do this is that you won't get certain warnings that you would normally get by compiling this as C code, meaning right now there could be certain vulnerabilities and bugs in your application.
I speak from personal experience.
Just some quick things:
unsigned int width
doesn't need to beconst
since it's passed-by-value. But that's not really significant as the compiler will optimize away theconst
if needed.Prefer
size_t
for types representing sizes. It's also useful for loop counters as you'll be able to loop through any size of data supported by your computer. However, you'll receive type-matching warnings if your counter and size types are not both signed or unsigned. In this case, usingint
(which is a signed type) will cause such warnings if the size type is unsigned.
malloc
in C. \$\endgroup\$