import math
import random
import cv2
import numpy as np
import base64
def convex_concave():
if random.random() < 0.5:
return -1
return 1
def random_blueprint(row, col):
blueprint = []
# init
for i in range(0, row):
blueprint.append([])
for j in range(0, col):
blueprint[i].append({
'top': 0,
'bottom': 0,
'left': 0,
'right': 0
})
for i in range(0, row):
for j in range(0, col):
# top
if i != 0:
blueprint[i][j]['top'] = blueprint[i - 1][j]['bottom'] * -1
# bottom
if i != row - 1:
blueprint[i][j]['bottom'] = convex_concave()
# left
if j != 0:
blueprint[i][j]['left'] = blueprint[i][j - 1]['right'] * -1
# right
if j != col - 1:
blueprint[i][j]['right'] = convex_concave()
return blueprint
def resize_image(row: int, col: int, image):
size = image.shape
L = math.floor(size[0] / row)
if L % 2 == 0:
L = L + 1
return cv2.resize(image, (L * col, L * row))
def split_image_to_puzzle(image, row, col):
image = resize_image(row, col, image)
size = image.shape
L = size[0] // row
R = (L // 3 // 4) * 4
a = (R * 3 // 4)
blueprint = random_blueprint(row, col)
image = add_image_transparent(image, a)
cv2.imwrite(f"./image.png", image)
puzzle_box = []
for i in range(row):
layer = []
for j in range(col):
batch = add_image_transparent(image[a + i * L:a + (i + 1) * L, a + j * L:a + (j + 1) * L, 0:3], a)
convex_mask = build_circle_mask(image, blueprint, i, j, L, 1)
concave_mask = build_circle_mask(image, blueprint, i, j, L, -1)
puzzle = batch + convex_mask - concave_mask
# cv2.imwrite(f"./batch_{i}_{j}.png", batch)
# cv2.imwrite(f"./convex_mask_{i}_{j}.png", convex_mask)
# cv2.imwrite(f"./concave_mask_{i}_{j}.png", concave_mask)
cv2.imwrite(f"./puzzle_{i}_{j}.png", puzzle)
puzzle_str = cv2.imencode('.png', puzzle)[1].tobytes()
base64_code = base64.b64encode(puzzle_str)
layer.append(puzzle)
puzzle_box.append(layer)
do_the_puzzle(puzzle_box, row, col, L, True)
do_the_puzzle(puzzle_box, row, col, L, False)
return puzzle_box
def do_the_puzzle(puzzle_box, row, col, L, rand):
if len(puzzle_box) == 0 or len(puzzle_box[0]) == 0:
return
if not rand:
R = (L // 3 // 4) * 4
a = (R * 3 // 4)
d = 5
transparent = np.zeros((row * L + d * row + 2 * a, col * L + d * col + 2 * a, 4), dtype=np.uint8)
for i in range(row):
for j in range(col):
ret, mask = cv2.threshold(puzzle_box[i][j][:, :, 3:4].reshape(L + 2 * a, L + 2 * a), 10, 255,
cv2.THRESH_BINARY)
# transparent[(10 + L) * i:(10 + L) * i + L + 2 * a, (10 + L) * j:(10 + L) * j + L + 2 * a, :] = \
# puzzle_box[i][j]
batch = transparent[(d + L) * i:(d + L) * i + L + 2 * a, (d + L) * j:(d + L) * j + L + 2 * a, :]
batch = cv2.bitwise_and(batch, batch, mask=cv2.bitwise_not(mask))
transparent[(d + L) * i:(d + L) * i + L + 2 * a, (d + L) * j:(d + L) * j + L + 2 * a, :] = cv2.add(
batch, puzzle_box[i][j])
else:
size = puzzle_box[0][0].shape
h = size[0]
LL = math.floor(h * math.sqrt(3) / math.sqrt(2))
if LL % 2 == 0:
LL = LL + 1
ll = LL // 2
transparent = np.zeros((LL * row, LL * col, 4), dtype=np.uint8)
for i in range(row):
for j in range(col):
rotate = rotate_bound(puzzle_box[i][j], random.randint(-10, 10))
x, y = ll + j * LL, ll + i * LL
h, w = rotate.shape[0:2]
xx, yy = (col // 2 - j) * random.randint(ll // 2, ll), (row // 2 - i) * random.randint(ll // 2,
ll)
ret, mask = cv2.threshold(rotate[:, :, 3:4].reshape(h, w), 10, 255, cv2.THRESH_BINARY)
batch = transparent[y - h // 2 + yy:y - h // 2 + h + yy, x - w // 2 + xx:x - w // 2 + w + xx, :]
batch = cv2.bitwise_and(batch, batch, mask=cv2.bitwise_not(mask))
transparent[y - h // 2 + yy:y - h // 2 + h + yy, x - w // 2 + xx:x - w // 2 + w + xx, :] = cv2.add(
batch, rotate)
if rand:
cv2.imwrite(f"./puzzle_rand.png", transparent)
else:
cv2.imwrite(f"./puzzle_fixed.png", transparent)
def rotate_bound(image, angle):
# grab the dimensions of the image and then determine the
# center
(h, w) = image.shape[:2]
(cX, cY) = (w / 2, h / 2)
# grab the rotation matrix (applying the negative of the
# angle to rotate clockwise), then grab the sine and cosine
# (i.e., the rotation components of the matrix)
M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
# compute the new bounding dimensions of the image
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))
# adjust the rotation matrix to take into account translation
M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY
# perform the actual rotation and return the image
return cv2.warpAffine(image, M, (nW, nH))
def build_circle_mask(image, blueprint, i, j, L, type):
item = blueprint[i][j]
R = (L // 3 // 4) * 4
l = L // 2
a = (R * 3 // 4)
b = R // 2
transparent = np.zeros((L + 2 * a, L + 2 * a, 4), dtype=np.uint8)
if type == 1:
coordinate = [
[
[a + l + j * L, i * L + b],
[a + l, b],
[a + l + j * L - b, a + l + j * L + b],
[i * L, a + i * L],
item['top'],
],
[
[a + l + L * j, (i + 1) * L + R],
[a + l, L + R],
[a + l + j * L - b, a + l + j * L + b],
[a + L + i * L, 2 * a + L + i * L],
item['bottom'],
],
[
[j * L + b, a + l + i * L],
[b, a + l],
[j * L, a + j * L],
[a + l + i * L - b, a + l + i * L + b],
item['left']
],
[
[j * L + L + R, a + l + i * L],
[L + R, a + l],
[a + L + j * L, 2 * a + L + j * L],
[a + l + i * L - b, a + l + i * L + b],
item['right']
],
]
elif type == -1:
coordinate = [
[
[a + l + j * L, i * L + R],
[a + l, R],
[a + l + j * L - b, a + l + j * L + b],
[i * L + a, 2 * a + i * L],
item['top'],
],
[
[a + l + j * L, b + L + i * L],
[a + l, L + b],
[a + l + j * L - b, a + l + j * L + b],
[L + i * L, a + L + i * L],
item['bottom'],
],
[
[j * L + R, a + l + i * L],
[R, a + l],
[a + j * L, 2 * a + j * L],
[a + l + i * L - b, a + l + i * L + b],
item['left']
],
[
[j * L + L + b, a + l + i * L],
[L + b, a + l],
[L + j * L, a + L + j * L],
[a + l + i * L - b, a + l + i * L + b],
item['right']
],
]
for coord in coordinate:
x0, y0 = coord[0]
x1, y1 = coord[1]
if coord[4] == type:
for ii in range(coord[2][0], coord[2][1]):
for jj in range(coord[3][0], coord[3][1]):
if math.pow(ii - x0, 2) + math.pow(jj - y0, 2) < math.pow(b, 2):
transparent[jj - y0 + y1, ii - x0 + x1, :] = image[jj, ii, :]
# transparent[ii - x0 + x1, jj - y0 + y1, :] = transparent[ii - x0 + x1, jj - y0 + y1, :] * coord[2]
return transparent
def add_image_transparent(image, a):
size = image.shape
transparent = np.zeros((size[0] + 2 * a, size[1] + 2 * a, 4), dtype=np.uint8)
if size[2] == 3:
transparent[a:-a, a:-a, 0:3] = image
transparent[a:-a, a:-a, 3:4] = np.ones((size[0], size[1], 1), dtype=np.uint8) * 255
elif size[2] == 4:
transparent[a:-a, a:-a, :] = image
return transparent
if __name__ == "__main__":
image = cv2.imread('./jinx fan.png', cv2.IMREAD_UNCHANGED)
split_image_to_puzzle(image, 6, 10)