I am trying to find a method to shear/skew an image horizontally or vertically by n degrees (n ranges from -90 to 90 excluding the terminals) so that the result would meet the following conditions:
The result would keep the size of the dimension that is not sheared, it means if you shear the image horizontally, the result would have the same height as the input and vice-versa.
The result would keep the relationship between the width and height of the original image.
The above means, if you name the width and height of the original image w and h, and shear the image horizontally by n degrees, the result would have be h tall, and the width w1 would be:
w1 = w / math.cos(math.radians(n)) + h * math.tan(math.radians(n))
So that the skewed width w2 and skewed height h2 would satisfy the following:
h2 = h / math.cos(math.radians(n))
w2 = w1 - h * math.tan(math.radians(n))
assert h2/w2 == h/w
I have written a custom function that does exactly this, except that it is inefficient and inelegant
Code
import math
import numpy as np
from itertools import product
from PIL import Image
def rhombize(arr, angle, dimension='vertical', reverse=False):
assert 0 < angle < 90
assert dimension in ('horizontal', 'vertical')
height, width, dimensions = arr.shape
ratio1 = math.cos(math.radians(angle))
ratio2 = math.tan(math.radians(angle))
if reverse:
arr = np.flipud(arr) if dimension == 'vertical' else np.fliplr(arr)
h, w = height, width
if dimension == 'vertical':
h = round(height / ratio1 + width * ratio2 )
else:
w = round(width / ratio1 + height * ratio2 )
rhomb = np.zeros((h, w, dimensions), dtype=np.uint8)
for y in range(height):
for x in range(width):
a, b = x, y
a1, b1 = a+1, b+1
if dimension == 'vertical':
b = round(y / ratio1 + x * ratio2)
b1 = round((y + 1) / ratio1 + (x + 1) * ratio2)
else:
a = round(x / ratio1 + y * ratio2)
a1 = round((x + 1) / ratio1 + (y + 1) * ratio2)
rhomb[b:b1, a:a1] = arr[y, x]
if reverse:
rhomb = np.flipud(rhomb) if dimension == 'vertical' else np.fliplr(rhomb)
return rhomb
if __name__ == '__main__':
arr = np.zeros((1024, 768, 3), dtype=np.uint8)
for y, x in product(range(256), repeat=2):
arr[y*4:y*4+4, x*3:x*3+3] = (x, 0, y)
img1 = Image.fromarray(arr)
img1.show()
img1.save('D:/img1.jpg')
rhombized = rhombize(arr, 30)
img2 = Image.fromarray(rhombized)
img2.show()
img2.save('D:/img2.jpg')
First image:
Second image:
I am wondering what is the proper way to do this, I know about affine transformations and that seems indeed to be the right way to do this, but my math is not that good and none of the online tutorials I can find covers it.
So how can I properly implement the exactly same logic using affine transformations?
Edit
Maybe my description does not succinctly describe what I am really trying to achieve here, so here are some pictures that explains what I am really trying to do better:
Then I am trying to upscale that rectangle, so that the dimension that is not skewed equals to its original value.
My code did exactly that, shearing while keeping aspect ratio, I only wanted the shearing to happen in one dimension, either horizontal or vertical, but not both.
I have already discovered that for vertical shearing, the relationship between the new y coordinate y1 and the original y0 coordinate should satisfy the following in order to achieve intended result:
y1 = y0 / cos(a) + x0 * tan(a)
x1 = x0
For horizontal shearing:
x1 = x0 / cos(a) + y0 * tan(a)
y1 = y0
And the above is exactly what my code implements, and I have measured the results and they do indeed keep the aspect ratio.
I just can't write my idea into a transformation matrix. None of the online tutorials I can find covered how to convert math formulas into transformation matrixes, Google search is useless as usual.