One of the image processing procedures is scaling — to resize the original picture to be bigger or smaller. if this is done by a hardware block, usually the software needs tell the hardware about the resizing details:
- provide the original picture size and the scaled size only. This is easy for the software, while the hardware needs figure it out by itself which filter (if there are a few) it needs to use in order to achieve the goal.
- provide more information, other than just the original size and scaled size. For instance, provide the scaling ratios for each filter which might be involved. When doing this, the software usually need provide a fixed-point scaling ratio value which will be stored in one register.
The background of this scenario
- The hardware performs only down-scaling, up to the scaling ratio 8.
- there is a decimal filter available, which can do the scaling ratio 2,4,8.
- a bilinear filter can handle a maximum scaling ratio 2.
The hardware provides 2 register fields per direction (horizontal, vertical) for the software to set the scaling ratio.
1) DEC_RATIO 2-bit field, it controls the pre-decimation filter.
00b – Pre-decimation filter is disabled.
01b – Decimate by 2
10b – Decimate by 4
11b – Decimate by 8
2) SCALE_FACTOR 14 bit fixed-point, with 12 fractional bits. This scale factor will be provided to bilinear filter.
How does it work
Assume the original size is 1280 and we need scale it down to 256 or 284.
(1) 256
The scaling ratio is 1280/256 = 5.
We need set 4 to DEC_RATIO.
We need set 5/*4 = 1.25 to SCALE_FACTOR.
(2) for 284
The scaling ratio is 1280/284 = 4.50704…
We need set 4 to DEC_RATIO.
WE NEED SET 4.5/4 = 1.12676 to SCALE_FACTOR.
How do we program it
uint32_t width;
uint32_t scaling_width;
(1) DEC_RATIO
uint32_t dec_ratio;
dec_ratio= width / scaling_width; // note we need validate if dec_ratio < 16.0
Then we need find a way to map dec_ratio the DEC_RATIO field. There could be done by using some math, or just an array for mapping since there are no many numbers involved.
static const uint8_t dec_array[] = {0, 0, 1, 1, 2, 2, 2, 2, 3};
if (dec_ratio > 8) {
dec_ratio = 8;
}
write dec_array[dec_ratio] to DEC_RATIO field.
(2) SCALE_FACTOR
(2.1)If the hardware doesn’t have specific requirement about rounding, we simply calculate scale_factor as below.
uint32_t scale_factor;
scale_factor = (width * (1 << 12) )/( scaling_width * (2 << dec_array[dec_ratio]));
// note that the calculation above avoids the floating-point math, so it is faster than calculation below and the result is same.
scale_factor = (uint32_t)((float) width/( scaling_width * (2 << dec_array[dec_ratio])) * (1 << 12);
(2.2) If the hardware wants to round the number to a nearest higher integer, a bit complicated math is needed here, we can either simply use ceil() function, like
float scale_factor;
scale_factor = ((float) width/( scaling_width * (2 << dec_array[dec_ratio])) * (1 << 12);
scale_factor = ceil(scale_factor);
then set (uint32_t)scale_factor to SCALE_FACTOR field.
or we can use fixed-point math to achieve the same result, with floating-point math avoided, as blow.
#define NUM_EXTRA_BITS 4
uint32_t scale_factor;
scale_factor = width * (1 << (12 + NUM_EXTRA_BITS) / (scaled_width * (2 << dec_array[dec_ratio]));
scale_factor = (scale_factor + (1 << NUM_EXTRA_BITS) – 1) >> NUM_EXTRA_BITS;