Rotate a Bitmap with Shearing

I am rotating a bitmap using the three shear algorithm documented in these articles [1][2].

From about 0-90°, the quality is acceptable, but beyond that it gets progressively more distorted until it's unintelligible.

Can anyone help me locate what is going wrong? Thank you!

This is my shear function:

def shear(angle,x,y):
'''
|1  -tan(𝜃/2) |  |1        0|  |1  -tan(𝜃/2) |
|0      1     |  |sin(𝜃)   1|  |0      1     |

'''
# shear 1
tangent=math.tan(angle/2)
new_x=round(x-y*tangent)
new_y=y

#shear 2
new_y=round(new_x*math.sin(angle)+new_y)      #since there is no change in new_x according to the shear matrix

#shear 3
new_x=round(new_x-new_y*tangent)              #since there is no change in new_y according to the shear matrix

return new_x,new_y


This is the code in the draw function:

cos = math.cos(self.rotation)
sin = math.sin(self.rotation)

# Define the width and height of the destination image
newWidth = round(abs(w*cos)+abs(h*sin))+1
newHeight = round(abs(h*cos)+abs(w*sin))+1

# Find the center of the source image for rotation
origCenterWidth  = round(((w+1)/2)-1)    #with respect to the source image
origCenterHeight = round(((h+1)/2)-1)    #with respect to the source image

# Find the center of the destination image
newCenterWidth  = round(((newWidth+1)/2)-1)  #with respect to the destination image
newCenterHeight = round(((newHeight)/2)-1)  #with respect to the destination image

for i in range(h):
for j in range(w):
#co-ordinates of pixel with respect to the center of source image
x = w-1-j-origCenterWidth
y = h-1-i-origCenterHeight

#Applying the Shear Transformation
new_x,new_y = shear(self.rotation,x,y)

#with rotation, the center will change so new_x and new_y will be the new center
new_y = newCenterHeight-new_y
new_x = newCenterWidth-new_x

alphaValue = sourceBmp.GetAlphaPixel(alphaChannel, j, i) #gets the source image pixel's alpha
col = sourceBmp.GetPixelDirect(j, i) #gets the source image pixel's color as a Color Vector
destBmp.SetAlphaPixel(nBmpAlpha, int(new_x), int(new_y), alphaValue) #sets the destination image pixel's alpha
destBmp.SetPixel(int(new_x), int(new_y), int(col.x), int(col.y), int(col.z)) #sets the destination image pixel's color

• One fundamental issue here is you're rotating in destination space rather than source space (e.g. apply the shear on source coordinates and output a rectangle). This way you can guarantee every pixel in the output is drawn once and only once and there are no gaps in between. Jan 28, 2021 at 7:24

as paulHK said:

One fundamental issue here is you're rotating in destination space rather than source space (e.g. apply the shear on source coordinates and output a rectangle). This way you can guarantee every pixel in the output is drawn once and only once and there are no gaps in between.

and the line that's causing the 180 problem is this line:

tangent=math.tan(angle/2)

If the angle is 180, the tan of 90 will be basically infinity.

This is a graph of 1 / (cos(rotation) ^ 2), one of the shearing calculations the narrator at datagenetics.com uses:

That means his method stalls at certain angles.

In all the code i wrote, I never needed to call tan(x). Use cos(x) and sin(x) instead, also use as few divisions as possible that could create infinity. Most rendering engines tackle this problem by limiting the angle of the camera to 89 degrees.

To get unaliased rotated images, i suggest a better drawing method, which computer graphics use today:

Use a 3x3 rotation matrix and a translation matrix , and loop over the pixels image that you want to draw on:

cos(angle) -sin(angle) + offset x
sin(angle) cos(angle) + offset y
0 0 1

to get this matrix, multiply first:

1 0 -[rotation centre x]
0 1 -[rotation centre y]
0 0 1

then:

cos(angle) -sin(angle) 0
sin(angle) cos(angle) 0
0 0 1

and then:
1 0 [rotation centre x]
0 1 [rotation centre y]
0 0 1

and then(if needed to translate):
1 0 [translation x]
0 1 [translation y]
0 0 1

Multiply the position on the target image by the INVERSE of the combined matrix to get the position on the source image.

If the position is in the bounds of the source image, fill the target pixel with the source pixel at that location.