Image rotation - doesn't always work
I spent 2 hours last night trying to figure out why my rotation function isn't behaving properly with zoom values a multiple of 8. The generation of the image works properly, even with 64 used, but the rotation isn't. Can you find what's causing this? I've uploaded some screenshots to help explain.
ZoomFactor is 3, no rotation - displays correctly
ZoomFactor is 3, rotation used - displays correctly
ZoomFactor is 8, no rotation - displays correctly
ZoomFactor is 8, rotation used - very distorted when displayed
ZoomFactor is 60, no rotation - displays correctly
ZoomFactor is 60, rotation used - displays correctly
ZoomFactor is 48, no rotation - displays correctly
ZoomFactor is 48, rotation used - very distorted when displayed
This is the code that generates the image. The one with the rotation is the one in focus:
Code:
// related globals
unsigned char TerrainData[144000];
unsigned char TerrainPreviewData[15360000];
unsigned char TerrainRotatedImageData[15360000];
FILE *FileHandle;
void RotateTerrainImage(unsigned int Width, unsigned int Height) // width and height are the number of blocks
{
unsigned char ImageRotateBlock[8]; // a block of 8x8 pixels
unsigned char ImageRotateBits[64];
unsigned int ArrayIndex = 0;
unsigned int SourceImageArrayIndex = 0;
unsigned int TargetImageArrayIndex = 0;
unsigned int Row = 0; // current row for the block
unsigned int Column = 0;
unsigned int DebugTest[16];
unsigned int LoopCount = 0;
unsigned int StartLoopCount = 9;
unsigned int StartArrayIndex = 0;
Width /= 8; // for easier use of blocks - number of blocks wide
printf("Width %d\nHeight %d\n", Width, Height);
while (Row < Height)
{
ArrayIndex = 0; // reset everything as needed
// first, read a block, going up - each row is an 8 wide by 1 high bunch of pixels
while (ArrayIndex < 8)
{
ImageRotateBlock[ArrayIndex] = TerrainPreviewData[(Row+ArrayIndex)*Width+Column];
ArrayIndex++;
}
// next, copy the bits of the block
ArrayIndex = 0;
while (ArrayIndex < 8)
{
ImageRotateBits[ArrayIndex*8] = (ImageRotateBlock[ArrayIndex] & 128) / 128; // copies the bits of every block as a 0 or 1
ImageRotateBits[ArrayIndex*8+1] = (ImageRotateBlock[ArrayIndex] & 64) / 64;
ImageRotateBits[ArrayIndex*8+2] = (ImageRotateBlock[ArrayIndex] & 32) / 32;
ImageRotateBits[ArrayIndex*8+3] = (ImageRotateBlock[ArrayIndex] & 16) / 16;
ImageRotateBits[ArrayIndex*8+4] = (ImageRotateBlock[ArrayIndex] & 8) / 8;
ImageRotateBits[ArrayIndex*8+5] = (ImageRotateBlock[ArrayIndex] & 4) / 4;
ImageRotateBits[ArrayIndex*8+6] = (ImageRotateBlock[ArrayIndex] & 2) / 2;
ImageRotateBits[ArrayIndex*8+7] = (ImageRotateBlock[ArrayIndex] & 1) / 1;
ArrayIndex++;
}
// after that, write the output as bottom up as left to right
ArrayIndex = 0;
while (ArrayIndex < 8)
{
// join the bits, reading what was left to right as bottom to top (which rotates it 90 degrees counterclockwise)
TerrainRotatedImageData[(ArrayIndex*(Height/8))+(Column*Height)+Row/8] = ImageRotateBits[0+ArrayIndex]*128 + ImageRotateBits[8+ArrayIndex]*64 + ImageRotateBits[16+ArrayIndex]*32 + ImageRotateBits[24+ArrayIndex]*16 + ImageRotateBits[32+ArrayIndex]*8 + ImageRotateBits[40+ArrayIndex]*4 + ImageRotateBits[48+ArrayIndex]*2 + ImageRotateBits[56+ArrayIndex]*1;
ArrayIndex++;
}
// lastly, the column (and row if at the end) should be incremented
Column++; // go to the next block column
if (Column >= Width) // if at the end
{
Column = 0; // return to the far left
Row += 8; // but go up a row (8 because a block is 8x8, not 8x1)
}
LoopCount++; // this is for debugging
}
}
void CheckTerrainHeights()
{
unsigned int ArrayIndex = 0;
int CurrentHeight = 52; // you start here
int MaxHeightPx = CurrentHeight;
double MaxHeightFt = 0.0;
unsigned int MaxHeightLocation = 0;
int MinHeightPx = CurrentHeight;
double MinHeightFt = 0.0;
unsigned int MinHeightLocation = 0;
int Errors = 0;
double ImageTargetHeight = 0.0;
int ImageBaseTargetHeight = 0;
double ImageTerrainHeight = 0.0;
unsigned int BitPos = 0;
unsigned int ByteBlock = 0;
unsigned int ImageBaseArrayIndex = 0;
unsigned char Colors[8] = {48, 24, 16, 0, 0, 192, 96, 0}; // dark blue sky and green landscape
long Temp;
int ZoomFactor = 60; // sets the zoom factor value, or how much to shrink each side of the image by
unsigned int DebugStart;
BITMAPFILEHEADER TempFileHead;
BITMAPINFOHEADER TempInfoHead;
FileHandle = fopen("C:\\My Documents\\My programs\\Terrain height test.bmp", "rb"); // first, read the data
fseek(FileHandle, 1078, SEEK_SET); // skip the header straight to the main data
fread(&TerrainData, 1, 144000, FileHandle);
fclose(FileHandle);
/*
slope Y change - slope angle
0 - 0.000
5 - 4.764
11 - 10.389
16 - 14.931
22 - 20.136
28 - 25.017
35 - 30.256
42 - 34.992
50 - 39.806
60 - 45.000
72 - 50.194
86 - 55.098
104 - 60.018
129 - 65.056
165 - 70.017
224 - 75.005
340 - 79.992
*/
TempInfoHead.biSize = 40;
TempInfoHead.biWidth = 7680/(long)ZoomFactor; // 7680 pixels wide (base image, high once rotated) at 1x zoom
TempInfoHead.biHeight = 144000/(long)ZoomFactor; // 144000 pixels high (base image, wide once rotated) at 1x zoom
TempInfoHead.biPlanes = 1; // must always be 1
TempInfoHead.biBitCount = 1; // monochrome bitmap
TempInfoHead.biCompression = BI_RGB; // no compression
TempInfoHead.biSizeImage = (DWORD)TempInfoHead.biWidth*(DWORD)TempInfoHead.biHeight/8; // the array size
TempInfoHead.biXPelsPerMeter = 2835; // this doesn't really matter what it is
TempInfoHead.biYPelsPerMeter = 2835;
TempInfoHead.biClrUsed = 2; // black and white only
TempInfoHead.biClrImportant = 2; // both possible are important
TempFileHead.bfType = 19778; // fills out the structures // 19778 supposedly is "BM"
TempFileHead.bfSize = TempInfoHead.biSizeImage+62; // array length plus off bits (or 62)
TempFileHead.bfReserved1 = 0;
TempFileHead.bfReserved2 = 0;
TempFileHead.bfOffBits = 62;
while ((ArrayIndex < 144000) && (TerrainData[ArrayIndex] != 0)) // loop until the end or unfinished areas are reached
{
if (TerrainData[ArrayIndex] == 203) { CurrentHeight -= 340; } // steepest downward slope
else if (TerrainData[ArrayIndex] == 251) { CurrentHeight -= 224; }
else if (TerrainData[ArrayIndex] == 48) { CurrentHeight -= 165; }
else if (TerrainData[ArrayIndex] == 72) { CurrentHeight -= 129; }
else if (TerrainData[ArrayIndex] == 96) { CurrentHeight -= 104; }
else if (TerrainData[ArrayIndex] == 120) { CurrentHeight -= 86; }
else if (TerrainData[ArrayIndex] == 143) { CurrentHeight -= 72; }
else if (TerrainData[ArrayIndex] == 167) { CurrentHeight -= 60; }
else if (TerrainData[ArrayIndex] == 191) { CurrentHeight -= 50; }
else if (TerrainData[ArrayIndex] == 215) { CurrentHeight -= 42; }
else if (TerrainData[ArrayIndex] == 239) { CurrentHeight -= 35; }
else if (TerrainData[ArrayIndex] == 32) { CurrentHeight -= 28; }
else if (TerrainData[ArrayIndex] == 56) { CurrentHeight -= 22; }
else if (TerrainData[ArrayIndex] == 80) { CurrentHeight -= 16; }
else if (TerrainData[ArrayIndex] == 104) { CurrentHeight -= 11; }
else if (TerrainData[ArrayIndex] == 128) { CurrentHeight -= 5; }
else if (TerrainData[ArrayIndex] == 151) { CurrentHeight += 0; }
else if (TerrainData[ArrayIndex] == 175) { CurrentHeight += 5; }
else if (TerrainData[ArrayIndex] == 199) { CurrentHeight += 11; }
else if (TerrainData[ArrayIndex] == 223) { CurrentHeight += 16; }
else if (TerrainData[ArrayIndex] == 247) { CurrentHeight += 22; }
else if (TerrainData[ArrayIndex] == 40) { CurrentHeight += 28; }
else if (TerrainData[ArrayIndex] == 64) { CurrentHeight += 35; }
else if (TerrainData[ArrayIndex] == 88) { CurrentHeight += 42; }
else if (TerrainData[ArrayIndex] == 112) { CurrentHeight += 50; }
else if (TerrainData[ArrayIndex] == 135) { CurrentHeight += 60; }
else if (TerrainData[ArrayIndex] == 159) { CurrentHeight += 72; }
else if (TerrainData[ArrayIndex] == 183) { CurrentHeight += 86; }
else if (TerrainData[ArrayIndex] == 207) { CurrentHeight += 104; }
else if (TerrainData[ArrayIndex] == 231) { CurrentHeight += 129; }
else if (TerrainData[ArrayIndex] == 255) { CurrentHeight += 165; }
else if (TerrainData[ArrayIndex] == 36) { CurrentHeight += 224; }
else if (TerrainData[ArrayIndex] == 84) { CurrentHeight += 340; } // steepest upward slope
else { Errors++; printf("Invalid color at location %d\n", ArrayIndex); } // error occurred!
if (CurrentHeight > MaxHeightPx) { MaxHeightPx = CurrentHeight; MaxHeightLocation = ArrayIndex;} // logs minimums and maximums
if (CurrentHeight < MinHeightPx) { MinHeightPx = CurrentHeight; MinHeightLocation = ArrayIndex;}
ImageTerrainHeight = CurrentHeight/(60.0*(double)ZoomFactor); // gives the height of the terrain in pixels
ImageBaseTargetHeight = (int)ImageTerrainHeight;
BitPos = (unsigned int)(ImageBaseTargetHeight) % 8; // gives offset for terrain data
ByteBlock = 0;
if (ArrayIndex % ZoomFactor == 0) // do only if the array index is a multiple of the zoom factor
{
while (ByteBlock < 960/ZoomFactor) // 256 bytes, 2048 pixels // each byte block is 26.4 feet
{
ImageTargetHeight = ((double)ByteBlock-1.0)*8.0; // /(double)ZoomFactor;
ImageBaseArrayIndex = ByteBlock+((960/ZoomFactor)*(ArrayIndex/ZoomFactor));
if (ImageBaseTargetHeight < ImageTargetHeight) // if below, it should be all black
{
TerrainPreviewData[ImageBaseArrayIndex] = 0; // all 8 pixels are black
}
else if (ImageBaseTargetHeight >= ImageTargetHeight+8.0) // if above the next step, it should be all white
{
TerrainPreviewData[ImageBaseArrayIndex] = 255; // all 8 pixels are white
}
else
{
if (BitPos == 0) { TerrainPreviewData[ImageBaseArrayIndex] = 128; } // more white added for higher BitPos values
else if (BitPos == 1) { TerrainPreviewData[ImageBaseArrayIndex] = 192; }
else if (BitPos == 2) { TerrainPreviewData[ImageBaseArrayIndex] = 224; }
else if (BitPos == 3) { TerrainPreviewData[ImageBaseArrayIndex] = 240; }
else if (BitPos == 4) { TerrainPreviewData[ImageBaseArrayIndex] = 248; }
else if (BitPos == 5) { TerrainPreviewData[ImageBaseArrayIndex] = 252; }
else if (BitPos == 6) { TerrainPreviewData[ImageBaseArrayIndex] = 254; }
else if (BitPos == 7) { TerrainPreviewData[ImageBaseArrayIndex] = 255; }
}
ByteBlock++;
}
}
ArrayIndex++;
}
while (ImageBaseArrayIndex < 15360000) // to fill the rest of the image as something odd to indicate incomplete areas
{
TerrainPreviewData[ImageBaseArrayIndex] = 9;
ImageBaseArrayIndex++;
}
MaxHeightFt = (double)MaxHeightPx*0.055;
MinHeightFt = (double)MinHeightPx*0.055;
RotateTerrainImage(TempInfoHead.biWidth, TempInfoHead.biHeight); // actual image dimensions passed to the function
Temp = TempInfoHead.biWidth; // swap the width and height around
TempInfoHead.biWidth = TempInfoHead.biHeight;
TempInfoHead.biHeight = Temp;
FileHandle = fopen("C:\\My Documents\\My programs\\Terrain height test heights.bmp", "wb"); // prepare to write a BMP file
fwrite(&TempFileHead.bfType, 2, 1, FileHandle); // it's only intended to run on my computer
fwrite(&TempFileHead.bfSize, 4, 1, FileHandle);
fwrite(&TempFileHead.bfReserved1, 2, 1, FileHandle);
fwrite(&TempFileHead.bfReserved2, 2, 1, FileHandle);
fwrite(&TempFileHead.bfOffBits, 4, 1, FileHandle);
fwrite(&TempInfoHead.biSize, 4, 1, FileHandle);
fwrite(&TempInfoHead.biWidth, 4, 1, FileHandle);
fwrite(&TempInfoHead.biHeight, 4, 1, FileHandle);
fwrite(&TempInfoHead.biPlanes, 2, 1, FileHandle);
fwrite(&TempInfoHead.biBitCount, 2, 1, FileHandle);
fwrite(&TempInfoHead.biCompression, 4, 1, FileHandle);
fwrite(&TempInfoHead.biSizeImage, 4, 1, FileHandle);
fwrite(&TempInfoHead.biXPelsPerMeter, 4, 1, FileHandle);
fwrite(&TempInfoHead.biYPelsPerMeter, 4, 1, FileHandle);
fwrite(&TempInfoHead.biClrUsed, 4, 1, FileHandle);
fwrite(&TempInfoHead.biClrImportant, 4, 1, FileHandle);
fwrite(&Colors, 1, 8, FileHandle); // the colors black then white
fwrite(&TerrainRotatedImageData, 1, TempInfoHead.biSizeImage, FileHandle); // writes the entire output image data (rotated)
// fwrite(&TerrainPreviewData, 1, TempInfoHead.biSizeImage, FileHandle); // writes the entire output image data (nonrotated)
fclose(FileHandle);
printf("\n\nThe max height seen was %d (%6.3lf ft) at %d (%3.4lf mi).\nThe min height was %d (%6.3lf ft) at %d (%3.4lf mi).\nYou left off at %d (%3.4lf mi) and a height of %d (%6.3lf ft).\n\n", MaxHeightPx, MaxHeightFt, MaxHeightLocation, (double)MaxHeightLocation/1600.0, MinHeightPx, MinHeightFt, MinHeightLocation, (double)MinHeightLocation/1600.0, ArrayIndex, (double)ArrayIndex/1600.0, CurrentHeight, (double)CurrentHeight*0.055);
}
It's the rotation function in focus, not the bottom one that generates the terrain itself (the bottom one works just fine). Although integer division is involved, it's based on the width and height. In fact, the largest common multiple of the width and height is 1920* so anything in the ZoomFactor variable that is a multiple of 1920 would not cause rounding errors. 1920/8 is exactly 240. 1920/48 is exactly 40. I don't see why it's being shifted to the left 16 pixels when a multiple of 8 is used and otherwise random if a multiple of 16 and beyond are used.
* These are the prime factors, those in common with both are multiplied (7 2's, a 3, and a 5 are common in both):
7680
2 2 2 2 2 2 2 2 2 3 5
144000
2 2 2 2 2 2 2 3 3 5 5 5