The problem is given image in 32 Bit Floating Point Format (float
) how to convert it to UINT8 (char
) or UNIT16 (unsigned short
) in the most efficient way.
Assumptions:
- Image is very long, namely don't be troubled with edge cases.
- One can not assume the image is in any given range.
- Output data must be clipped into the proper range ([0, 255], [0, 2 ^ 16 - 1]). I think the term is to be saturated.
- Data should be rounded to nearest integer.
- One could use SSE4 operations (Namely, everything but AVX).
I'd rather have a code in C style.
But C++ is also OK (I will translate it on my own).
I tried this simple code for the UINT8 case:
void ConvertToUint8(char* mO, float* mF, float* mB, int numRows, int numCols, int numColsPad, float scalingFctr)
{
// mB - Background, mF - Foreground
int ii, jj;
__m128 floatPx;
__m128 scalingFactor;
__m128i uint8Px;
scalingFactor = _mm_set1_ps(scalingFctr);
#pragma omp parallel for private(jj, floatPx, scalingFactor, uint8Px)
for (ii = 0; ii < numRows; ii++) {
for (jj = 0; jj < numCols; jj += SSE_STRIDE) {
floatPx = _mm_loadu_ps(&mB[(ii * numColsPad) + jj]);
uint8Px = _mm_cvtps_epi32(_mm_mul_ps(floatPx, scalingFactor));
uint8Px = _mm_packus_epi32(uint8Px, uint8Px);
uint8Px = _mm_packus_epi16(uint8Px, uint8Px);
*(int *) (mO[(ii * numColsPad) + jj]) = _mm_cvtsi128_si32(uint8Px);
}
}
}
The code above is based on StackOverflow Q29856006 - SSE intrinsics: Convert 32-bit floats to UNSIGNED 8-bit integers.
But I'm sure there are better ways (For instance, why not load 4 packed __m128
and then store 32 Pixels at once)?
_mm_packus_epi32
first probably. \$\endgroup\$_mm_packus_epi16
still treats its input as signed, so the first step in a 2-step pack should bepackss
to create input forpackus
. And yes, of course you should be using eachpack
with 2 different inputs. \$\endgroup\$mF
andmB
. It doesn't change the validity of any answer. It was done because it made user1118321 confused. \$\endgroup\$