Combine several PNGs onto one canvas so that they use a minimum of space

23 Views Asked by At

I tried to connect several images with an alpha channel on 1 canvas, but the images are connected one after another (as I wrote in the code), but I am interested in how to make sure that all available transparent space is used, because I insert many different transparent images, and some have such a transparent space that in you can add a smaller image to them. Even before inserting the image, I crop them to the minimum transparency limits (i.e. up to 1 shaded pixel). In general, how to make sure that you use the transparent space of the image for insertion, because if you analyze every pixel (left, down, right, up) for transparency, then for a picture of 10k+ pixels it can take hours of work! (How to do this without using neural networks)[enter image description here] How does it work out

As it should turn out, only for a larger number of images

My code:


from PIL import Image
import pandas as pd
import time
import os



DESIGN_FOLDER = 'desings'
SM_SIZE = 100

def find_empty_transparent_regions(im, min_width, min_height):
    alpha = im.split()[3]

    width, height = im.size

    transparent_regions = []

    for y in range(0, height, int(SM_SIZE/2)):
        for x in range(0, width, int(SM_SIZE/2)):
            if transparent_regions:
                for req in transparent_regions:
                    if x in range(req[0], req[2]) or y in range(req[1], req[3]):
                        continue

            try:
                if alpha.getpixel((x, y)) == 0:

                    x1 = x
                    while x < width and alpha.getpixel((x, y)) == 0:
                        x += 1
                    y1 = y
                    while y < height and alpha.getpixel((x1, y)) == 0:
                        y += 1
                    if x - x1 >= min_width and y - y1 >= min_height:
                        transparent_regions.append((x1, y1, x, y))
                        print(transparent_regions)

            except IndexError:
                continue

    return transparent_regions

def crop_transparent(image_path=None, image=None):
    if image_path:
        image = Image.open(image_path)
        image = image.convert("RGBA")

    width, height = image.size

    left, top, right, bottom = width, height, 0, 0
    for x in range(width):
        for y in range(height):
            r, g, b, a = image.getpixel((x, y))
            if a != 0:

                left = min(left, x)
                top = min(top, y)
                right = max(right, x)
                bottom = max(bottom, y)

    image = image.crop((left, top, right + 1, bottom + 1))

    return image


def rotate_image(img):

    width, height = img.size

    if height > width:
        img = img.transpose(Image.ROTATE_90)

    return img

def resize_image(image, target_size):

    width, height = image.size

    if width > height:
        ratio = target_size / width
        new_width = target_size
        new_height = int(height * ratio)
    else:
        ratio = target_size / height
        new_height = target_size
        new_width = int(width * ratio)

    resized_image = image.resize((new_width, new_height))

    return resized_image


def merge_images(images_list):
    total_height = sum([image['img'].size[1] for image in images_list])

    max_width = 45*SM_SIZE

    merged_image = Image.new('RGBA', (max_width, total_height))

    y_offset = 0
    width_line = 0
    min_width = images_list[-1]['img'].width
    count_set = int(len(images_list)//2)
    heidth = 0
    crop_cheack = 0
    i = 0
    while images_list:
        i += 1
        print(len(images_list))

        image = images_list[0]['img']
        min_width = image.width
        min_height = image.height
        if image.size[0] + width_line < max_width:
            merged_image.paste(image, (width_line, y_offset))
            width_line += image.size[0] + int(SM_SIZE*0.5)

            if heidth < image.size[1]:
                heidth = image.size[1]

            del images_list[0]
            continue
        else:
            d = 1
            while True:
                try:
                    img = images_list[d]['img']
                    if img.size[0] + width_line < max_width:
                        merged_image.paste(img, (width_line, y_offset))
                        width_line += img.size[0] + int(SM_SIZE*0.5)

                        if heidth < img.size[1]:
                            heidth = img.size[1]

                        del images_list[d]
                        continue
                    else:
                        d += 1
                except IndexError:

                    if count_set <= i:
                        if crop_cheack:
                            merged = merged_image.crop((0, crop_cheack, max_width, total_height))
                            image_check = crop_transparent(image=merged)

                        else:
                            image_check = crop_transparent(image=merged_image)
                        empties = find_empty_transparent_regions(image_check, min_width+int(SM_SIZE*0.5), min_height+int(SM_SIZE*0.5))
                        # empties = extract_regions(image_check, min_height+int(SM_SIZE*0.5), min_width+int(SM_SIZE*0.5))

                        if empties:
                            k = 0
                            d = 1
                            while empties:
                                if d > len(images_list) - 1:
                                    break
                                img = images_list[d]['img']
                                img_width, img_height = img.size
                                x, y, width, height = empties[k]
                                width = width - x
                                height = height - y

                                if img_width <= width and img_height <= height:
                                    merged_image.paste(img, (x, y))
                                    del images_list[d]
                                    del empties[k]
                                d += 1

                    y_offset += heidth + int(SM_SIZE*0.5)
                    width_line = 0
                    heidth = 0
                    break



    merged_image = crop_transparent(image=merged_image)

    merged_image.save('merged_image.png')



def main():
    start_time = time.time()
    df = pd.read_excel('table.xlsx')
    imgs = []

    for i, design in enumerate(df['Name']):
        size_sm = df['Size'][i]
        size = size_sm * SM_SIZE
        count = df['Count'][i]
        path = f'{DESIGN_FOLDER}/{design}'
        if os.path.exists(path + '.png'):
            path += '.png'
        elif os.path.exists(path + '.jpg'):
            path += '.jpg'
        else:
            print(f'File {path}  dont find!')

        crop_img = crop_transparent(path)
        crop_img = resize_image(crop_img, size)
        crop_img = rotate_image(crop_img)

        for c in range(count):
            imgs.append({'size': sum(crop_img.size), 'img': crop_img})

    imgs = sorted(imgs,  key=lambda x: x['size'], reverse=True)
    merge_images(imgs)
    print("--- %s seconds ---" % (time.time() - start_time))

I tried to use opencv, but it takes longer to process the image than pillow. I will be glad if you can give me an idea of how best to organize this, you can do it without changes in the code, just an idea. You may use another language, i need an idea.

0

There are 0 best solutions below