# import scipy.ndimage as nd # import imageio.v3 as iio import numpy as np import math from PIL import Image, ImageFilter import pygame as pg IMAGE_PATH = 'sample-images/sunflower.jpg' BLUR_STRENGTH_1 = 1 BLUR_STRENGTH_2 = 2.5 DOG_THRESHOLD = 8 WHITE = (255,255,255) TEXT_WIDTH = 6 # on font size 12 monocraft medium TEXT_HEIGHT = 11 SCALE_FACTOR = 2 SCALED_WIDTH = math.floor(TEXT_WIDTH / SCALE_FACTOR) SCALED_HEIGHT = math.floor(TEXT_HEIGHT / SCALE_FACTOR) CHARS = list(reversed([ ' ', '.', '_', '+', '=', 'A', 'K', 'B', '&', '#' ])) def main(): image = Image.open(IMAGE_PATH) L_image = image.convert('L') blur_1 = image.filter(ImageFilter.GaussianBlur(BLUR_STRENGTH_1)) blur_2 = image.filter(ImageFilter.GaussianBlur(BLUR_STRENGTH_2)) print(image.size) dog: Image = difference_of_gaussians(blur_1, blur_2) dog.save('dog.png') # print(dog.size) # sb = sobel(image) # sb.save('sobel-pil.png') sb, gradient = sobel(dog) w, h = sb.size # print(w * h) # print(sb.size) # print(len(gradient)) # exit() sb.save('sobel.png') text_grid_width = w / SCALED_WIDTH text_grind_height = h / SCALED_HEIGHT pg.init() font = pg.font.SysFont("Monocraft Medium", 12) window = pg.display.set_mode((text_grid_width * TEXT_WIDTH, text_grind_height * TEXT_HEIGHT)) y_offset = 0 for y in range(math.floor(h / SCALED_HEIGHT)): x_offset = 0 for x in range(math.floor(w / SCALED_WIDTH)): histogram = {} # Collect the most common char for a group of pixels colors = [] for y2 in range(SCALED_HEIGHT): for x2 in range(SCALED_WIDTH): real_x = x2 + x_offset real_y = y2 + y_offset gradient_v = gradient[real_y * w + real_x] char = ' ' if gradient_v: char = match_gradient(gradient_v) else: char = CHARS[round(L_image.getpixel((real_x, real_y))/(255/10)) - 1] if char in histogram: histogram[char] += 1 else: histogram[char] = 1 colors.append(image.getpixel((real_x, real_y))) color_avg = average_colors(colors) # get most common most_common = None score = float('-inf') for char in histogram: if histogram[char] > score: score = histogram[char] most_common = char rendered_char = font.render(most_common, 1, color_avg) window.blit(rendered_char, (x * TEXT_WIDTH, y * TEXT_HEIGHT)) x_offset += SCALED_WIDTH y_offset += SCALED_HEIGHT # for y in range(0, text_grind_height): # for x in range(0, text_grid_width): # gradient_v = gradient[y * (w * TEXT_WIDTH) + x] # char = ' ' # if gradient_v: # char = match_gradient(gradient_v) # else: # char = CHARS[round(L_image.getpixel((x, y))/(255/10)) - 1] # rendered_char = font.render(char, 1, WHITE) # window.blit(rendered_char, (x * TEXT_WIDTH, y * TEXT_HEIGHT)) pg.display.update() pg.image.save(window, "render.png") pg.quit() sb.save('sobel-dog.png') # dog.save('dog.png') # sb_x = dog.filter(sobel_x_kernel) # sb_x.save('pil-sobel_x.png') # image_np = np.array(dog) # sb_x = nd.sobel(image_np, 1) # sb_y = nd.sobel(image_np, 0) # combined_sobel = np.sqrt(np.square(sb_x) + np.square(sb_y)) # i = Image.fromarray(combined_sobel, 'L') # i.save('sobel.png') # iio.imwrite('sobel_x.png', sb_x) # iio.imwrite('sobel_y.png', sb_y) def subtract_colors(t1: tuple, t2: tuple) -> tuple: if type(t1) != tuple: if (t2 - t1) >= DOG_THRESHOLD: return t2 - t1 else: return 0 if len(t1) != len(t2): print('Len of first tuple should equal second tuple probably') exit(1) ans = [] for i, n in enumerate(t1): ans.append(t2[i] - n) return tuple(ans) def difference_of_gaussians(blur_1: Image, blur_2: Image) -> Image: w, h = blur_1.size dog = Image.new(blur_1.mode, blur_1.size) for pixel_y in range(0, h): for pixel_x in range(0, w): coords = (pixel_x, pixel_y) dog.putpixel(coords, subtract_colors(blur_2.getpixel(coords), blur_1.getpixel(coords))) return dog # Taken from # https://enzoftware.github.io/posts/image-filter-python def sobel(img: Image) -> tuple[Image.Image, list]: if img.mode == 'L': # return sobel_L(img) pass img = img.convert('RGB') width, height = img.size newimg = Image.new("RGB", (width, height), "white") gradient = [None for x in range(0, width * height)] for x in range(1, width - 1): # ignore the edge pixels for simplicity (1 to width-1) for y in range(1, height - 1): # ignore edge pixels for simplicity (1 to height-1) # initialise Gx to 0 and Gy to 0 for every pixel Gx = 0 Gy = 0 # top left pixel r, g, b = img.getpixel((x - 1, y - 1)) # intensity ranges from 0 to 765 (255 * 3) intensity = r + g + b # accumulate the value into Gx, and Gy Gx += -intensity Gy += -intensity # remaining left column r, g, b = img.getpixel((x-1, y)) Gx += -2 * (r + g + b) r, g, b = img.getpixel((x-1, y+1)) Gx += -(r + g + b) Gy += (r + g + b) # middle pixels r, g, b = img.getpixel((x, y-1)) Gy += -2 * (r + g + b) r, g, b = img.getpixel((x, y+1)) Gy += 2 * (r + g + b) # right column r, g, b = img.getpixel((x+1, y-1)) Gx += (r + g + b) Gy += -(r + g + b) r, g, b = img.getpixel((x+1, y)) Gx += 2 * (r + g + b) r, g, b = img.getpixel((x+1, y+1)) Gx += (r + g + b) Gy += (r + g + b) # calculate the length of the gradient (Pythagorean theorem) length = math.sqrt((Gx * Gx) + (Gy * Gy)) # normalise the length of gradient to the range 0 to 255 length = length / 4328 * 255 length = int(length) gradient_v = math.atan2(Gy, Gx) # print(gradient_v) # draw the length in the edge image #newpixel = img.putpixel((length,length,length)) newimg.putpixel((x,y),(length,length,length)) if length < 20: gradient[y * width + x] = gradient_v return (newimg, gradient) def sobel_L(img: Image) -> tuple[Image.Image, list]: pass # TODO @0x01FE : refactor plz increment by 30 & 60 def match_gradient(n: float) -> str: n = abs(n) if n < math.radians(30): return '|' elif n < math.radians(60): return '/' elif n < math.radians(120): return '-' elif n < math.radians(150): return '\\' elif n < math.radians(210): return '|' elif n < math.radians(240): return '/' elif n < math.radians(300): return '-' elif n < math.radians(330): return '\\' else: return '|' def average_colors(colors: list): avg = [0 for x in range(len(colors[0]))] for color in colors: for i, num in enumerate(color): avg[i] += num for i, n in enumerate(avg): avg[i] /= len(colors) return avg if __name__ == '__main__': main() # image = iio.imread(IMAGE_PATH) # sb_x = nd.sobel(image, 1) # sb_y = nd.sobel(image, 0) # iio.imwrite('sobel_x.png', sb_x) # iio.imwrite('sobel_y.png', sb_y)