Normally, we enumerate each pixel in the new image, map it back to the original image, and use the neighboring pixels to interpolate. However, for translating, mirroring and shearing, there is a one-to-one match between the pixels in two images, so interpolation is unnecessary.
voidtranslate(BITMAP *bmImg, BITMAP *bmTranslate, uint32_t disX, uint32_t disY) { BITMAPINFOHEADER *bmiHeader = &bmImg->bmInfo->bmiHeader; uint32_t height = bmiHeader->biHeight + disX; uint32_t width = bmiHeader->biWidth + disY; gen_color(bmImg, bmTranslate, height, width); for (uint32_t h = 0; h < height; ++h) { for (uint32_t w = 0; w < width; ++w) { int32_t x = h; int32_t y = w - disY; uint32_t pos = x * bmImg->bmBytesPerRow + y * bmImg->bmBytesPerPel; uint32_t _pos = h * bmTranslate->bmBytesPerRow + w * bmTranslate->bmBytesPerPel; if (x >= 0 && x < bmiHeader->biHeight && y >= 0 && y < bmiHeader->biWidth) { for (uint8_t k = 0; k < 3; ++k) { bmTranslate->bmData[_pos + k] = bmImg->bmData[pos + k]; } } } } }
voidmirror(BITMAP *bmImg, BITMAP *bmMirror) { BITMAPINFOHEADER *bmiHeader = &bmImg->bmInfo->bmiHeader; uint32_t height = bmiHeader->biHeight << 1; uint32_t width = bmiHeader->biWidth; gen_color(bmImg, bmMirror, height, width); for (uint32_t h = 0; h < height; ++h) { for (uint32_t w = 0; w < width; ++w) { int32_t x = bmiHeader->biHeight - 1 - h; int32_t y = w; uint32_t pos = x * bmImg->bmBytesPerRow + y * bmImg->bmBytesPerPel; uint32_t _pos = h * bmMirror->bmBytesPerRow + w * bmMirror->bmBytesPerPel; if (x >= 0 && x < bmiHeader->biHeight && y >= 0 && y < bmiHeader->biWidth) { for (uint8_t k = 0; k < 3; ++k) { bmMirror->bmData[_pos + k] = bmImg->bmData[pos + k]; } } } } }
voidshear(BITMAP *bmImg, BITMAP *bmShear, uint32_t disX) { BITMAPINFOHEADER *bmiHeader = &bmImg->bmInfo->bmiHeader; uint32_t height = bmiHeader->biHeight + disX; uint32_t width = bmiHeader->biWidth; gen_color(bmImg, bmShear, height, width); for (uint32_t h = 0; h < height; ++h) { for (uint32_t w = 0; w < width; ++w) { int32_t x = h + disX * w / (width - 1) - disX; int32_t y = w; uint32_t pos = x * bmImg->bmBytesPerRow + y * bmImg->bmBytesPerPel; uint32_t _pos = h * bmShear->bmBytesPerRow + w * bmShear->bmBytesPerPel; if (x >= 0 && x < bmiHeader->biHeight && y >= 0 && y < bmiHeader->biWidth) { for (uint8_t k = 0; k < 3; ++k) { bmShear->bmData[_pos + k] = bmImg->bmData[pos + k]; } } } } }
Step Three: Interpolate
There are several methods of interpolation, such as nearest-neighbor interpolation, bilinear interpolation, and bicubic interpolation. Among these, nearest-neighbor interpolation has the highest speed, bicubic interpolation has the highest quality.
The bicubic interpolation can be summarized as solving a system of linear equations with $16$ variables, i.e., find $a_{ij}$, s.t. $p(x, y)=\sum_{i=0}^3 \sum_{j=0}^3 a_{ij} x^i y^j$. An interpolator with similar properties can be obtained by applying a convolution with the following kernel in both dimensions,
for $t$ between $0$ and $1$ for one dimension. Note that for $1$-dimensional cubic convolution interpolation $4$ sample points are required. For each inquiry two samples are located on its left and two samples on the right. These points are indexed from $−1$ to $2$ in this text. The distance from the point indexed with $0$ to the inquiry point is denoted by $t$ here. For two dimensions first applied once in $y$ and again in $x$.
Similar to rotating, interpolation is vital for scaling. A slight difference is that, we can storage the results of all horizontal interpolations before performing the vertical, since they will be reused often.