Skip to main content
Fixed last line identation (copy-paste mistake), improved tips for running it
Source Link

Example with 3 images: When this URL is provided, one of possible outcomes: http://localhost:8080/mozaika?losowo=1&rozdzielczosc=512x512&zdjecia=https://www.humanesociety.org/sites/default/files/styles/768x326/public/2018/08/kitten-440379.jpg?h=f6a7b1af&itok=vU0J0uZR,https://cdn.britannica.com/67/197567-131-1645A26E.jpg,https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80 enter image description here

  • Required libraries: http.server, numpy, opencv-python

  • Github: https://github.com/Krzysztof-Wojtczak/Allegro-Task

  • Run server.py

  • In your browser type: http://localhost:8080/mozaika?losowo=Z&rozdzielczosc=XxY&zdjecia=URL1,URL2,URL3...

    where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses, 1 to 9.(or copy the link above).

import cv2
import numpy as np
import random
from math import ceil

class Mozaika:
    """Class Mozaika takes 1 required attribute: list of images in cv2 format,
    3 optional attributes: random image positioning, width of output image, height of output image.
    Output image is stored in variable 'output_image'.
    Class is looking for the least proportional image and returns it in (0,0) - top left corner if no random positioning"""
    def __init__(self, image_list, losowo, w=2048, h=2048):
        self.losowo = losowo # defines whether image position is random
        self.w = int(w) # width of output image
        self.h = int(h) # height of output image
        self.output_image = 0

        # variables are stored in 3 lists: image_names for sorted name strings, image_list for image in cv2 format,
        # image_dict for height and width for every image
        self.image_names = [] # Names of images

        self.image_list = image_list # list of files (images)
        if self.losowo == 1:
            random.shuffle(self.image_list)

        for i in range(len(self.image_list)):
            self.image_names.append(f"img{i}")

        self.image_dict = {}
        for image in range(len(self.image_list)):
            key = self.image_names[image]
            h, w = self.image_list[image].shape[:2] # height, width of each image
            self.image_dict[key] = [h, w]

        self.how_many_images()

    def how_many_images(self):
        number_of_images = len(self.image_dict) # checks how many images is given
        if number_of_images == 1:
            self.make_square()
            self.resize_huge_image()
        elif number_of_images == 2:
            self.rectangle_image(2)
        elif number_of_images == 3 or number_of_images == 4:
            self.grid2x2()
        elif number_of_images > 4:
            self.grid3x3()

    def rectangle_image(self, images=1): # the least proportional image will become a rectangle
        ratios = []
        self.check_ratio() 
        ratios = [e[2] for e in list(self.image_dict.values())] # getting image ratio(s)
        max_ratio = max(ratios)

        for name, value in self.image_dict.items(): # finding highest/longest image
            if value[2] == max_ratio:
                name_max = name
                list_index_max = self.image_names.index(name)
        
        if images == 1: # method is called for 1 image
            if self.image_dict[name_max][1] > self.image_dict[name_max][0]: # checks if width or height of the image is greater
                return self.make_horizontal_rectangle(name_max, list_index_max, max_ratio), 0, name_max # return image, horizontal/vertical, name of image 
                
            elif self.image_dict[name_max][1] < self.image_dict[name_max][0]:
                return self.make_vertical_rectangle(name_max, list_index_max, max_ratio), 1, name_max
                

        elif images == 2: #it will only work if there are 2 images, creates mosaic of 2 images
            i = 0
            if self.image_dict[name_max][1] > self.image_dict[name_max][0]:
                for name, value in self.image_dict.items(): # checks ratio the least proportional image and decides
                    self.make_horizontal_rectangle(name, i, value[2]) # whether images should be vertical or horizontal
                    i += 1
                self.merge_two_images_horizontally() # merge 2 images with minimum quality loss
            elif self.image_dict[name_max][1] < self.image_dict[name_max][0]:
                for name, value in self.image_dict.items():
                    self.make_vertical_rectangle(name, i, value[2])
                    i += 1
                self.merge_two_images_vertically()

    def check_ratio(self):
        # appends to dictionary height to width (or width to height) ratio
        i = 0
        for image in self.image_dict:
            if self.image_dict[image][0] > self.image_dict[image][1]:
                ratio = self.image_dict[image][0]/self.image_dict[image][1]
            else:
                ratio = self.image_dict[image][1]/self.image_dict[image][0]
            self.image_dict[image].append(ratio)
        

    def make_square(self):
        # centralizes picture and cuts it so it becomes a square
        i = 0
        for image in self.image_dict.values(): # check in dictionary for width/height
            if image[0] > image[1]:
                cut = int((image[0] - image[1])/2)
                self.image_list[i] = self.image_list[i][cut : -cut, :image[1]] # numpy operation on image
            elif image[0] < image[1]:
                cut = int((image[1] - image[0])/2)
                self.image_list[i] = self.image_list[i][:image[0], cut : -cut]
            i += 1

    def make_horizontal_rectangle(self, name, list_index, ratio):
        # if ratio == 2, it's perfect rectangle. Otherwise it is cut to this ratio
        if ratio < 2:
            cut = int(  (self.image_dict[name][0] - (self.image_dict[name][0] / (2/ratio)))/2  )
            return self.image_list[list_index][cut : -cut, : self.image_dict[name][1]]  
        elif ratio > 2:
            if self.image_dict[name][1] > self.image_dict[name][0]:
                cut = int(  (self.image_dict[name][0] - (self.image_dict[name][0] / (ratio/2)))/2  )
                return self.image_list[list_index][: self.image_dict[name][0], cut : -cut]
                            
    def make_vertical_rectangle(self, name, list_index, ratio):
        if ratio < 2:
            cut = int(  (self.image_dict[name][1] - (self.image_dict[name][1] / (2/ratio)))/2  )
            return self.image_list[list_index][: self.image_dict[name][0], cut : -cut]
        elif ratio > 2:
            cut = int(  (self.image_dict[name][1] - (self.image_dict[name][1] / (ratio/2)))/2  )
            return self.image_list[list_index][cut : -cut, : self.image_dict[name][1]]

    def merge_two_images_horizontally(self):
        # method takes 2 horizontal images and merges them
        self.image_list[0] = cv2.resize(self.image_list[0], (self.w, int(self.h/2)))
        self.image_list[1] = cv2.resize(self.image_list[1], (self.w, int(self.h/2)))
        self.output_image = np.concatenate((self.image_list[0], self.image_list[1]), axis=0)        

    def merge_two_images_vertically(self):
        # method takes 2 vertical images and merges them
        self.image_list[0] = cv2.resize(self.image_list[0], (int(self.w/2), self.h))
        self.image_list[1] = cv2.resize(self.image_list[1], (int(self.w/2), self.h))
        self.output_image = np.concatenate((self.image_list[0], self.image_list[1]), axis=1)

    def resize_huge_image(self):
        # returns one image of the size of the output image
        self.output_image = cv2.resize(self.image_list[0], (self.w, self.h))

    def resize_big_image(self, index):
        # returns one image of 2/3 width/height of the output image
        name = self.image_names[index]
        return cv2.resize(self.image_list[index], (int(self.w/(3/2)), int(self.h/(3/2)))), name

    def resize_medium_image(self, index):
        # returns one image of 1/2 width/height of the output image
        return cv2.resize(self.image_list[index], (int(self.w/2), int(self.h/2)))
        
    def resize_small_image(self, index):
        # returns one image of 1/3 width/height of the output image
        return cv2.resize(self.image_list[index], (int(self.w/3), int(self.h/3)))

    def grid2x2(self):
        placement = self.put_image2x2() # defines where to put images
        decrease_h = ceil(2*(self.h/2 - int(self.h/2))) # decrease size of output image due to roundings, so there are no black spaces
        decrease_w = ceil(2*(self.w/2 - int(self.w/2)))
        vis = np.zeros((self.h - decrease_h, self.w - decrease_w, 3), np.uint8) # smaller image due to roundings
        num = 0
        for i in range(0,2): # grid 2x2, so 4 squares to fill
            for k in range(0,2):
                vis[i*int(self.h/2) : (i+1)*int(self.h/2), k*int(self.w/2) : (k+1)*int(self.w/2)] = placement[num]
                num += 1
        self.output_image = cv2.resize(vis, (self.w, self.h)) # optional, scales image to match requirements accurately

    def grid3x3(self):
        placement = self.put_image3x3() # defines where to put images
        decrease_h = ceil(3*(self.h/3 - int(self.h/3))) # decrease size of output image due to roundings, so there are no black spaces
        decrease_w = ceil(3*(self.w/3 - int(self.w/3)))
        vis = np.zeros((self.h - decrease_h, self.w - decrease_w, 3), np.uint8) # smaller image due to roundings
        num = 0
        for i in range(0,3): # grid 3x3, so nine squares to fill
            for k in range(0,3):
                vis[i*int(self.h/3) : (i+1)*int(self.h/3), k*int(self.w/3) : (k+1)*int(self.w/3)] = placement[num]
                num += 1
        self.output_image = cv2.resize(vis, (self.w, self.h)) # optional, scales image to match requirements accurately

    def put_image2x2(self):
        placement = [0]*4 # it'll store images
        if len(self.image_names) == 3: # to do if there are 3 images
            rect_image, vertical, name = self.rectangle_image()
            index = self.image_names.index(name)
            self.image_list.pop(index) # deleting rectangle image from image_list, so there will be no duplicates
            other_position = [e for e in range(4)] # 4 possibilities to put 1 image
            if vertical: # 1 vertical image
                rect_image = cv2.resize(rect_image, (int(self.w/2), self.h))
                if self.losowo == 1:
                    position = random.randrange(0,2) # choose random position for image
                else:
                    position = 0                    # or fixed position
                other_position.remove(position) # rectangle image takes 2 places
                other_position.remove(position + 2)
                placement[position] = rect_image[:int(self.h/2), :int(self.w/2)]
                placement[position + 2] = rect_image[int(self.h/2):self.h, :int(self.w/2)]
            else: # 1 horizontal image
                rect_image = cv2.resize(rect_image, (self.w, int(self.h/2)))
                if self.losowo == 1:
                    position = random.randrange(0,3,2) # possible positions are top left and bottom left
                else:
                    position = 0
                other_position.remove(position)
                other_position.remove(position + 1)
                placement[position] = rect_image[:int(self.h/2), :int(self.w/2)]
                placement[position + 1] = rect_image[:int(self.h/2), int(self.w/2):self.w]

            num = 0
            for i in other_position: # after puting bigger image fill other places with smalles images
                placement[i] = self.resize_medium_image(num)
                num += 1
        else: # 4 images
            for i in range(len(self.image_list)): 
                placement[i] = self.resize_medium_image(i) # fill 4 places with medium images

        return placement

    def put_image3x3(self):
        placement = [0]*9
        img2x = [] # list of rectangle images
        img4x = [] # list of big square images
        num_img = len(self.image_names)
        var = 0
        var1 = 0
        while num_img < 9:
            if 9 - num_img < 3: # big image can't fit, increase number of takes space by making rectangles
                img2x.append(self.rectangle_image())
                remove_image = img2x[var][2] # get image name
                self.image_dict.pop(remove_image) # delete image to avoid duplicates (there are 3 places where it is)
                index = self.image_names.index(remove_image)
                self.image_names.remove(remove_image)
                self.image_list.pop(index)
                num_img += 1
                var += 1
            else:
                img4x.append(self.resize_big_image(0))
                remove_image = img4x[var1][1] # get image name
                self.image_dict.pop(remove_image) # delete image to avoid duplicates
                index = self.image_names.index(remove_image)
                self.image_names.remove(remove_image)
                self.image_list.pop(index)
                var1 += 1
                num_img += 3
        
        biash = ceil(self.h*(2/3) - int(self.h*(2/3))) # image can be to big to fit in square, need to decrease it
        biasw = ceil(self.w*(2/3) - int(self.w*(2/3)))
        other_position = set([e for e in range(9)]) # 9 possible places for one image
        
        for img in img4x: # takes big image and tries to fit it
            square_img = img[0]
            other_position, position = self.find_big_position(other_position) # find possible position
            placement[position] = square_img[:int(self.h/3), :int(self.w/3)] # top left corner of the image
            placement[position + 1] = square_img[:int(self.h/3), int(self.w/3):int(self.w*(2/3)) - biasw] # top right corner
            placement[position + 3] = square_img[int(self.h/3):int(self.h*(2/3)) - biash, :int(self.w/3)] # bottom left corner
            placement[position + 4] = square_img[int(self.h/3):int(self.h*(2/3)) - biash, int(self.w/3):int(self.w*(2/3)) - biasw] # bottom right corner

        for img in img2x: # takes rectangles and tries to fit them
            rect_image, vertical = img[:2] # check if rectangle is vertical
            if vertical:
                rect_image = cv2.resize(rect_image, (int(self.w/3), int(self.h*(2/3))))
                other_position, position = self.find_vertical_position(other_position) # checks for vertical possibilities
                placement[position] = rect_image[:int(self.h/3), :int(self.w/3)]
                placement[position + 3] = rect_image[int(self.h/3):int(self.h*(2/3)) - biash, :int(self.w/3)]
            else:
                rect_image = cv2.resize(rect_image, (int(self.w*(2/3)), int(self.h/3)))
                other_position, position = self.find_horizontal_position(other_position) # checks for horizontal possibilities
                placement[position] = rect_image[:int(self.h/3), :int(self.w/3)]
                placement[position + 1] = rect_image[:int(self.h/3), int(self.w/3):int(self.w*(2/3)) - biasw]

        num = 0
        for i in other_position: # after puting bigger image fill other places with smaller images
            placement[i] = self.resize_small_image(num)
            num += 1

        return placement

    def find_big_position(self, avaiable_pos):
        # find position for 2/3 width/height image
        myList = avaiable_pos
        mylistshifted=[x-1 for x in myList]
        possible_position = [0,1,3,4] # only possible possisions for big image
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 1)
        myList.remove(position + 3)
        myList.remove(position + 4)
        return myList, position

    def find_horizontal_position(self, avaiable_pos):
        # find position for horizontal rectangle image
        myList = avaiable_pos
        mylistshifted=[x-1 for x in myList]
        possible_position = [0,1,3,4,6,7] # positions where image is not cut in half
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 1)
        return myList, position

    def find_vertical_position(self, avaiable_pos):
        # find position vertical rectangle image
        myList = avaiable_pos
        mylistshifted=[x-3 for x in myList]
        possible_position = [e for e in range(6)] # positions where image is not cut in half
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 3)
        return myList, position

Example: When this URL is provided, one of possible outcomes: http://localhost:8080/mozaika?losowo=1&rozdzielczosc=512x512&zdjecia=https://www.humanesociety.org/sites/default/files/styles/768x326/public/2018/08/kitten-440379.jpg?h=f6a7b1af&itok=vU0J0uZR,https://cdn.britannica.com/67/197567-131-1645A26E.jpg,https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80 enter image description here

  • Required libraries: http.server, numpy, opencv-python

  • Github: https://github.com/Krzysztof-Wojtczak/Allegro-Task

  • Run server.py

  • In your browser type: http://localhost:8080/mozaika?losowo=Z&rozdzielczosc=XxY&zdjecia=URL1,URL2,URL3...

    where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

import cv2
import numpy as np
import random
from math import ceil

class Mozaika:
    """Class Mozaika takes 1 required attribute: list of images in cv2 format,
    3 optional attributes: random image positioning, width of output image, height of output image.
    Output image is stored in variable 'output_image'.
    Class is looking for the least proportional image and returns it in (0,0) - top left corner if no random positioning"""
    def __init__(self, image_list, losowo, w=2048, h=2048):
        self.losowo = losowo # defines whether image position is random
        self.w = int(w) # width of output image
        self.h = int(h) # height of output image
        self.output_image = 0

        # variables are stored in 3 lists: image_names for sorted name strings, image_list for image in cv2 format,
        # image_dict for height and width for every image
        self.image_names = [] # Names of images

        self.image_list = image_list # list of files (images)
        if self.losowo == 1:
            random.shuffle(self.image_list)

        for i in range(len(self.image_list)):
            self.image_names.append(f"img{i}")

        self.image_dict = {}
        for image in range(len(self.image_list)):
            key = self.image_names[image]
            h, w = self.image_list[image].shape[:2] # height, width of each image
            self.image_dict[key] = [h, w]

        self.how_many_images()

    def how_many_images(self):
        number_of_images = len(self.image_dict) # checks how many images is given
        if number_of_images == 1:
            self.make_square()
            self.resize_huge_image()
        elif number_of_images == 2:
            self.rectangle_image(2)
        elif number_of_images == 3 or number_of_images == 4:
            self.grid2x2()
        elif number_of_images > 4:
            self.grid3x3()

    def rectangle_image(self, images=1): # the least proportional image will become a rectangle
        ratios = []
        self.check_ratio() 
        ratios = [e[2] for e in list(self.image_dict.values())] # getting image ratio(s)
        max_ratio = max(ratios)

        for name, value in self.image_dict.items(): # finding highest/longest image
            if value[2] == max_ratio:
                name_max = name
                list_index_max = self.image_names.index(name)
        
        if images == 1: # method is called for 1 image
            if self.image_dict[name_max][1] > self.image_dict[name_max][0]: # checks if width or height of the image is greater
                return self.make_horizontal_rectangle(name_max, list_index_max, max_ratio), 0, name_max # return image, horizontal/vertical, name of image 
                
            elif self.image_dict[name_max][1] < self.image_dict[name_max][0]:
                return self.make_vertical_rectangle(name_max, list_index_max, max_ratio), 1, name_max
                

        elif images == 2: #it will only work if there are 2 images, creates mosaic of 2 images
            i = 0
            if self.image_dict[name_max][1] > self.image_dict[name_max][0]:
                for name, value in self.image_dict.items(): # checks ratio the least proportional image and decides
                    self.make_horizontal_rectangle(name, i, value[2]) # whether images should be vertical or horizontal
                    i += 1
                self.merge_two_images_horizontally() # merge 2 images with minimum quality loss
            elif self.image_dict[name_max][1] < self.image_dict[name_max][0]:
                for name, value in self.image_dict.items():
                    self.make_vertical_rectangle(name, i, value[2])
                    i += 1
                self.merge_two_images_vertically()

    def check_ratio(self):
        # appends to dictionary height to width (or width to height) ratio
        i = 0
        for image in self.image_dict:
            if self.image_dict[image][0] > self.image_dict[image][1]:
                ratio = self.image_dict[image][0]/self.image_dict[image][1]
            else:
                ratio = self.image_dict[image][1]/self.image_dict[image][0]
            self.image_dict[image].append(ratio)
        

    def make_square(self):
        # centralizes picture and cuts it so it becomes a square
        i = 0
        for image in self.image_dict.values(): # check in dictionary for width/height
            if image[0] > image[1]:
                cut = int((image[0] - image[1])/2)
                self.image_list[i] = self.image_list[i][cut : -cut, :image[1]] # numpy operation on image
            elif image[0] < image[1]:
                cut = int((image[1] - image[0])/2)
                self.image_list[i] = self.image_list[i][:image[0], cut : -cut]
            i += 1

    def make_horizontal_rectangle(self, name, list_index, ratio):
        # if ratio == 2, it's perfect rectangle. Otherwise it is cut to this ratio
        if ratio < 2:
            cut = int(  (self.image_dict[name][0] - (self.image_dict[name][0] / (2/ratio)))/2  )
            return self.image_list[list_index][cut : -cut, : self.image_dict[name][1]]  
        elif ratio > 2:
            if self.image_dict[name][1] > self.image_dict[name][0]:
                cut = int(  (self.image_dict[name][0] - (self.image_dict[name][0] / (ratio/2)))/2  )
                return self.image_list[list_index][: self.image_dict[name][0], cut : -cut]
                            
    def make_vertical_rectangle(self, name, list_index, ratio):
        if ratio < 2:
            cut = int(  (self.image_dict[name][1] - (self.image_dict[name][1] / (2/ratio)))/2  )
            return self.image_list[list_index][: self.image_dict[name][0], cut : -cut]
        elif ratio > 2:
            cut = int(  (self.image_dict[name][1] - (self.image_dict[name][1] / (ratio/2)))/2  )
            return self.image_list[list_index][cut : -cut, : self.image_dict[name][1]]

    def merge_two_images_horizontally(self):
        # method takes 2 horizontal images and merges them
        self.image_list[0] = cv2.resize(self.image_list[0], (self.w, int(self.h/2)))
        self.image_list[1] = cv2.resize(self.image_list[1], (self.w, int(self.h/2)))
        self.output_image = np.concatenate((self.image_list[0], self.image_list[1]), axis=0)        

    def merge_two_images_vertically(self):
        # method takes 2 vertical images and merges them
        self.image_list[0] = cv2.resize(self.image_list[0], (int(self.w/2), self.h))
        self.image_list[1] = cv2.resize(self.image_list[1], (int(self.w/2), self.h))
        self.output_image = np.concatenate((self.image_list[0], self.image_list[1]), axis=1)

    def resize_huge_image(self):
        # returns one image of the size of the output image
        self.output_image = cv2.resize(self.image_list[0], (self.w, self.h))

    def resize_big_image(self, index):
        # returns one image of 2/3 width/height of the output image
        name = self.image_names[index]
        return cv2.resize(self.image_list[index], (int(self.w/(3/2)), int(self.h/(3/2)))), name

    def resize_medium_image(self, index):
        # returns one image of 1/2 width/height of the output image
        return cv2.resize(self.image_list[index], (int(self.w/2), int(self.h/2)))
        
    def resize_small_image(self, index):
        # returns one image of 1/3 width/height of the output image
        return cv2.resize(self.image_list[index], (int(self.w/3), int(self.h/3)))

    def grid2x2(self):
        placement = self.put_image2x2() # defines where to put images
        decrease_h = ceil(2*(self.h/2 - int(self.h/2))) # decrease size of output image due to roundings, so there are no black spaces
        decrease_w = ceil(2*(self.w/2 - int(self.w/2)))
        vis = np.zeros((self.h - decrease_h, self.w - decrease_w, 3), np.uint8) # smaller image due to roundings
        num = 0
        for i in range(0,2): # grid 2x2, so 4 squares to fill
            for k in range(0,2):
                vis[i*int(self.h/2) : (i+1)*int(self.h/2), k*int(self.w/2) : (k+1)*int(self.w/2)] = placement[num]
                num += 1
        self.output_image = cv2.resize(vis, (self.w, self.h)) # optional, scales image to match requirements accurately

    def grid3x3(self):
        placement = self.put_image3x3() # defines where to put images
        decrease_h = ceil(3*(self.h/3 - int(self.h/3))) # decrease size of output image due to roundings, so there are no black spaces
        decrease_w = ceil(3*(self.w/3 - int(self.w/3)))
        vis = np.zeros((self.h - decrease_h, self.w - decrease_w, 3), np.uint8) # smaller image due to roundings
        num = 0
        for i in range(0,3): # grid 3x3, so nine squares to fill
            for k in range(0,3):
                vis[i*int(self.h/3) : (i+1)*int(self.h/3), k*int(self.w/3) : (k+1)*int(self.w/3)] = placement[num]
                num += 1
        self.output_image = cv2.resize(vis, (self.w, self.h)) # optional, scales image to match requirements accurately

    def put_image2x2(self):
        placement = [0]*4 # it'll store images
        if len(self.image_names) == 3: # to do if there are 3 images
            rect_image, vertical, name = self.rectangle_image()
            index = self.image_names.index(name)
            self.image_list.pop(index) # deleting rectangle image from image_list, so there will be no duplicates
            other_position = [e for e in range(4)] # 4 possibilities to put 1 image
            if vertical: # 1 vertical image
                rect_image = cv2.resize(rect_image, (int(self.w/2), self.h))
                if self.losowo == 1:
                    position = random.randrange(0,2) # choose random position for image
                else:
                    position = 0                    # or fixed position
                other_position.remove(position) # rectangle image takes 2 places
                other_position.remove(position + 2)
                placement[position] = rect_image[:int(self.h/2), :int(self.w/2)]
                placement[position + 2] = rect_image[int(self.h/2):self.h, :int(self.w/2)]
            else: # 1 horizontal image
                rect_image = cv2.resize(rect_image, (self.w, int(self.h/2)))
                if self.losowo == 1:
                    position = random.randrange(0,3,2) # possible positions are top left and bottom left
                else:
                    position = 0
                other_position.remove(position)
                other_position.remove(position + 1)
                placement[position] = rect_image[:int(self.h/2), :int(self.w/2)]
                placement[position + 1] = rect_image[:int(self.h/2), int(self.w/2):self.w]

            num = 0
            for i in other_position: # after puting bigger image fill other places with smalles images
                placement[i] = self.resize_medium_image(num)
                num += 1
        else: # 4 images
            for i in range(len(self.image_list)): 
                placement[i] = self.resize_medium_image(i) # fill 4 places with medium images

        return placement

    def put_image3x3(self):
        placement = [0]*9
        img2x = [] # list of rectangle images
        img4x = [] # list of big square images
        num_img = len(self.image_names)
        var = 0
        var1 = 0
        while num_img < 9:
            if 9 - num_img < 3: # big image can't fit, increase number of takes space by making rectangles
                img2x.append(self.rectangle_image())
                remove_image = img2x[var][2] # get image name
                self.image_dict.pop(remove_image) # delete image to avoid duplicates (there are 3 places where it is)
                index = self.image_names.index(remove_image)
                self.image_names.remove(remove_image)
                self.image_list.pop(index)
                num_img += 1
                var += 1
            else:
                img4x.append(self.resize_big_image(0))
                remove_image = img4x[var1][1] # get image name
                self.image_dict.pop(remove_image) # delete image to avoid duplicates
                index = self.image_names.index(remove_image)
                self.image_names.remove(remove_image)
                self.image_list.pop(index)
                var1 += 1
                num_img += 3
        
        biash = ceil(self.h*(2/3) - int(self.h*(2/3))) # image can be to big to fit in square, need to decrease it
        biasw = ceil(self.w*(2/3) - int(self.w*(2/3)))
        other_position = set([e for e in range(9)]) # 9 possible places for one image
        
        for img in img4x: # takes big image and tries to fit it
            square_img = img[0]
            other_position, position = self.find_big_position(other_position) # find possible position
            placement[position] = square_img[:int(self.h/3), :int(self.w/3)] # top left corner of the image
            placement[position + 1] = square_img[:int(self.h/3), int(self.w/3):int(self.w*(2/3)) - biasw] # top right corner
            placement[position + 3] = square_img[int(self.h/3):int(self.h*(2/3)) - biash, :int(self.w/3)] # bottom left corner
            placement[position + 4] = square_img[int(self.h/3):int(self.h*(2/3)) - biash, int(self.w/3):int(self.w*(2/3)) - biasw] # bottom right corner

        for img in img2x: # takes rectangles and tries to fit them
            rect_image, vertical = img[:2] # check if rectangle is vertical
            if vertical:
                rect_image = cv2.resize(rect_image, (int(self.w/3), int(self.h*(2/3))))
                other_position, position = self.find_vertical_position(other_position) # checks for vertical possibilities
                placement[position] = rect_image[:int(self.h/3), :int(self.w/3)]
                placement[position + 3] = rect_image[int(self.h/3):int(self.h*(2/3)) - biash, :int(self.w/3)]
            else:
                rect_image = cv2.resize(rect_image, (int(self.w*(2/3)), int(self.h/3)))
                other_position, position = self.find_horizontal_position(other_position) # checks for horizontal possibilities
                placement[position] = rect_image[:int(self.h/3), :int(self.w/3)]
                placement[position + 1] = rect_image[:int(self.h/3), int(self.w/3):int(self.w*(2/3)) - biasw]

        num = 0
        for i in other_position: # after puting bigger image fill other places with smaller images
            placement[i] = self.resize_small_image(num)
            num += 1

        return placement

    def find_big_position(self, avaiable_pos):
        # find position for 2/3 width/height image
        myList = avaiable_pos
        mylistshifted=[x-1 for x in myList]
        possible_position = [0,1,3,4] # only possible possisions for big image
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 1)
        myList.remove(position + 3)
        myList.remove(position + 4)
        return myList, position

    def find_horizontal_position(self, avaiable_pos):
        # find position for horizontal rectangle image
        myList = avaiable_pos
        mylistshifted=[x-1 for x in myList]
        possible_position = [0,1,3,4,6,7] # positions where image is not cut in half
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 1)
        return myList, position

    def find_vertical_position(self, avaiable_pos):
        # find position vertical rectangle image
        myList = avaiable_pos
        mylistshifted=[x-3 for x in myList]
        possible_position = [e for e in range(6)] # positions where image is not cut in half
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 3)
return myList, position

Example with 3 images: When this URL is provided, one of possible outcomes: http://localhost:8080/mozaika?losowo=1&rozdzielczosc=512x512&zdjecia=https://www.humanesociety.org/sites/default/files/styles/768x326/public/2018/08/kitten-440379.jpg?h=f6a7b1af&itok=vU0J0uZR,https://cdn.britannica.com/67/197567-131-1645A26E.jpg,https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80 enter image description here

  • Required libraries: http.server, numpy, opencv-python

  • Github: https://github.com/Krzysztof-Wojtczak/Allegro-Task

  • Run server.py

  • In your browser type: http://localhost:8080/mozaika?losowo=Z&rozdzielczosc=XxY&zdjecia=URL1,URL2,URL3...

    where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses, 1 to 9.(or copy the link above).

import cv2
import numpy as np
import random
from math import ceil

class Mozaika:
    """Class Mozaika takes 1 required attribute: list of images in cv2 format,
    3 optional attributes: random image positioning, width of output image, height of output image.
    Output image is stored in variable 'output_image'.
    Class is looking for the least proportional image and returns it in (0,0) - top left corner if no random positioning"""
    def __init__(self, image_list, losowo, w=2048, h=2048):
        self.losowo = losowo # defines whether image position is random
        self.w = int(w) # width of output image
        self.h = int(h) # height of output image
        self.output_image = 0

        # variables are stored in 3 lists: image_names for sorted name strings, image_list for image in cv2 format,
        # image_dict for height and width for every image
        self.image_names = [] # Names of images

        self.image_list = image_list # list of files (images)
        if self.losowo == 1:
            random.shuffle(self.image_list)

        for i in range(len(self.image_list)):
            self.image_names.append(f"img{i}")

        self.image_dict = {}
        for image in range(len(self.image_list)):
            key = self.image_names[image]
            h, w = self.image_list[image].shape[:2] # height, width of each image
            self.image_dict[key] = [h, w]

        self.how_many_images()

    def how_many_images(self):
        number_of_images = len(self.image_dict) # checks how many images is given
        if number_of_images == 1:
            self.make_square()
            self.resize_huge_image()
        elif number_of_images == 2:
            self.rectangle_image(2)
        elif number_of_images == 3 or number_of_images == 4:
            self.grid2x2()
        elif number_of_images > 4:
            self.grid3x3()

    def rectangle_image(self, images=1): # the least proportional image will become a rectangle
        ratios = []
        self.check_ratio() 
        ratios = [e[2] for e in list(self.image_dict.values())] # getting image ratio(s)
        max_ratio = max(ratios)

        for name, value in self.image_dict.items(): # finding highest/longest image
            if value[2] == max_ratio:
                name_max = name
                list_index_max = self.image_names.index(name)
        
        if images == 1: # method is called for 1 image
            if self.image_dict[name_max][1] > self.image_dict[name_max][0]: # checks if width or height of the image is greater
                return self.make_horizontal_rectangle(name_max, list_index_max, max_ratio), 0, name_max # return image, horizontal/vertical, name of image 
                
            elif self.image_dict[name_max][1] < self.image_dict[name_max][0]:
                return self.make_vertical_rectangle(name_max, list_index_max, max_ratio), 1, name_max
                

        elif images == 2: #it will only work if there are 2 images, creates mosaic of 2 images
            i = 0
            if self.image_dict[name_max][1] > self.image_dict[name_max][0]:
                for name, value in self.image_dict.items(): # checks ratio the least proportional image and decides
                    self.make_horizontal_rectangle(name, i, value[2]) # whether images should be vertical or horizontal
                    i += 1
                self.merge_two_images_horizontally() # merge 2 images with minimum quality loss
            elif self.image_dict[name_max][1] < self.image_dict[name_max][0]:
                for name, value in self.image_dict.items():
                    self.make_vertical_rectangle(name, i, value[2])
                    i += 1
                self.merge_two_images_vertically()

    def check_ratio(self):
        # appends to dictionary height to width (or width to height) ratio
        i = 0
        for image in self.image_dict:
            if self.image_dict[image][0] > self.image_dict[image][1]:
                ratio = self.image_dict[image][0]/self.image_dict[image][1]
            else:
                ratio = self.image_dict[image][1]/self.image_dict[image][0]
            self.image_dict[image].append(ratio)
        

    def make_square(self):
        # centralizes picture and cuts it so it becomes a square
        i = 0
        for image in self.image_dict.values(): # check in dictionary for width/height
            if image[0] > image[1]:
                cut = int((image[0] - image[1])/2)
                self.image_list[i] = self.image_list[i][cut : -cut, :image[1]] # numpy operation on image
            elif image[0] < image[1]:
                cut = int((image[1] - image[0])/2)
                self.image_list[i] = self.image_list[i][:image[0], cut : -cut]
            i += 1

    def make_horizontal_rectangle(self, name, list_index, ratio):
        # if ratio == 2, it's perfect rectangle. Otherwise it is cut to this ratio
        if ratio < 2:
            cut = int(  (self.image_dict[name][0] - (self.image_dict[name][0] / (2/ratio)))/2  )
            return self.image_list[list_index][cut : -cut, : self.image_dict[name][1]]  
        elif ratio > 2:
            if self.image_dict[name][1] > self.image_dict[name][0]:
                cut = int(  (self.image_dict[name][0] - (self.image_dict[name][0] / (ratio/2)))/2  )
                return self.image_list[list_index][: self.image_dict[name][0], cut : -cut]
                            
    def make_vertical_rectangle(self, name, list_index, ratio):
        if ratio < 2:
            cut = int(  (self.image_dict[name][1] - (self.image_dict[name][1] / (2/ratio)))/2  )
            return self.image_list[list_index][: self.image_dict[name][0], cut : -cut]
        elif ratio > 2:
            cut = int(  (self.image_dict[name][1] - (self.image_dict[name][1] / (ratio/2)))/2  )
            return self.image_list[list_index][cut : -cut, : self.image_dict[name][1]]

    def merge_two_images_horizontally(self):
        # method takes 2 horizontal images and merges them
        self.image_list[0] = cv2.resize(self.image_list[0], (self.w, int(self.h/2)))
        self.image_list[1] = cv2.resize(self.image_list[1], (self.w, int(self.h/2)))
        self.output_image = np.concatenate((self.image_list[0], self.image_list[1]), axis=0)        

    def merge_two_images_vertically(self):
        # method takes 2 vertical images and merges them
        self.image_list[0] = cv2.resize(self.image_list[0], (int(self.w/2), self.h))
        self.image_list[1] = cv2.resize(self.image_list[1], (int(self.w/2), self.h))
        self.output_image = np.concatenate((self.image_list[0], self.image_list[1]), axis=1)

    def resize_huge_image(self):
        # returns one image of the size of the output image
        self.output_image = cv2.resize(self.image_list[0], (self.w, self.h))

    def resize_big_image(self, index):
        # returns one image of 2/3 width/height of the output image
        name = self.image_names[index]
        return cv2.resize(self.image_list[index], (int(self.w/(3/2)), int(self.h/(3/2)))), name

    def resize_medium_image(self, index):
        # returns one image of 1/2 width/height of the output image
        return cv2.resize(self.image_list[index], (int(self.w/2), int(self.h/2)))
        
    def resize_small_image(self, index):
        # returns one image of 1/3 width/height of the output image
        return cv2.resize(self.image_list[index], (int(self.w/3), int(self.h/3)))

    def grid2x2(self):
        placement = self.put_image2x2() # defines where to put images
        decrease_h = ceil(2*(self.h/2 - int(self.h/2))) # decrease size of output image due to roundings, so there are no black spaces
        decrease_w = ceil(2*(self.w/2 - int(self.w/2)))
        vis = np.zeros((self.h - decrease_h, self.w - decrease_w, 3), np.uint8) # smaller image due to roundings
        num = 0
        for i in range(0,2): # grid 2x2, so 4 squares to fill
            for k in range(0,2):
                vis[i*int(self.h/2) : (i+1)*int(self.h/2), k*int(self.w/2) : (k+1)*int(self.w/2)] = placement[num]
                num += 1
        self.output_image = cv2.resize(vis, (self.w, self.h)) # optional, scales image to match requirements accurately

    def grid3x3(self):
        placement = self.put_image3x3() # defines where to put images
        decrease_h = ceil(3*(self.h/3 - int(self.h/3))) # decrease size of output image due to roundings, so there are no black spaces
        decrease_w = ceil(3*(self.w/3 - int(self.w/3)))
        vis = np.zeros((self.h - decrease_h, self.w - decrease_w, 3), np.uint8) # smaller image due to roundings
        num = 0
        for i in range(0,3): # grid 3x3, so nine squares to fill
            for k in range(0,3):
                vis[i*int(self.h/3) : (i+1)*int(self.h/3), k*int(self.w/3) : (k+1)*int(self.w/3)] = placement[num]
                num += 1
        self.output_image = cv2.resize(vis, (self.w, self.h)) # optional, scales image to match requirements accurately

    def put_image2x2(self):
        placement = [0]*4 # it'll store images
        if len(self.image_names) == 3: # to do if there are 3 images
            rect_image, vertical, name = self.rectangle_image()
            index = self.image_names.index(name)
            self.image_list.pop(index) # deleting rectangle image from image_list, so there will be no duplicates
            other_position = [e for e in range(4)] # 4 possibilities to put 1 image
            if vertical: # 1 vertical image
                rect_image = cv2.resize(rect_image, (int(self.w/2), self.h))
                if self.losowo == 1:
                    position = random.randrange(0,2) # choose random position for image
                else:
                    position = 0                    # or fixed position
                other_position.remove(position) # rectangle image takes 2 places
                other_position.remove(position + 2)
                placement[position] = rect_image[:int(self.h/2), :int(self.w/2)]
                placement[position + 2] = rect_image[int(self.h/2):self.h, :int(self.w/2)]
            else: # 1 horizontal image
                rect_image = cv2.resize(rect_image, (self.w, int(self.h/2)))
                if self.losowo == 1:
                    position = random.randrange(0,3,2) # possible positions are top left and bottom left
                else:
                    position = 0
                other_position.remove(position)
                other_position.remove(position + 1)
                placement[position] = rect_image[:int(self.h/2), :int(self.w/2)]
                placement[position + 1] = rect_image[:int(self.h/2), int(self.w/2):self.w]

            num = 0
            for i in other_position: # after puting bigger image fill other places with smalles images
                placement[i] = self.resize_medium_image(num)
                num += 1
        else: # 4 images
            for i in range(len(self.image_list)): 
                placement[i] = self.resize_medium_image(i) # fill 4 places with medium images

        return placement

    def put_image3x3(self):
        placement = [0]*9
        img2x = [] # list of rectangle images
        img4x = [] # list of big square images
        num_img = len(self.image_names)
        var = 0
        var1 = 0
        while num_img < 9:
            if 9 - num_img < 3: # big image can't fit, increase number of takes space by making rectangles
                img2x.append(self.rectangle_image())
                remove_image = img2x[var][2] # get image name
                self.image_dict.pop(remove_image) # delete image to avoid duplicates (there are 3 places where it is)
                index = self.image_names.index(remove_image)
                self.image_names.remove(remove_image)
                self.image_list.pop(index)
                num_img += 1
                var += 1
            else:
                img4x.append(self.resize_big_image(0))
                remove_image = img4x[var1][1] # get image name
                self.image_dict.pop(remove_image) # delete image to avoid duplicates
                index = self.image_names.index(remove_image)
                self.image_names.remove(remove_image)
                self.image_list.pop(index)
                var1 += 1
                num_img += 3
        
        biash = ceil(self.h*(2/3) - int(self.h*(2/3))) # image can be to big to fit in square, need to decrease it
        biasw = ceil(self.w*(2/3) - int(self.w*(2/3)))
        other_position = set([e for e in range(9)]) # 9 possible places for one image
        
        for img in img4x: # takes big image and tries to fit it
            square_img = img[0]
            other_position, position = self.find_big_position(other_position) # find possible position
            placement[position] = square_img[:int(self.h/3), :int(self.w/3)] # top left corner of the image
            placement[position + 1] = square_img[:int(self.h/3), int(self.w/3):int(self.w*(2/3)) - biasw] # top right corner
            placement[position + 3] = square_img[int(self.h/3):int(self.h*(2/3)) - biash, :int(self.w/3)] # bottom left corner
            placement[position + 4] = square_img[int(self.h/3):int(self.h*(2/3)) - biash, int(self.w/3):int(self.w*(2/3)) - biasw] # bottom right corner

        for img in img2x: # takes rectangles and tries to fit them
            rect_image, vertical = img[:2] # check if rectangle is vertical
            if vertical:
                rect_image = cv2.resize(rect_image, (int(self.w/3), int(self.h*(2/3))))
                other_position, position = self.find_vertical_position(other_position) # checks for vertical possibilities
                placement[position] = rect_image[:int(self.h/3), :int(self.w/3)]
                placement[position + 3] = rect_image[int(self.h/3):int(self.h*(2/3)) - biash, :int(self.w/3)]
            else:
                rect_image = cv2.resize(rect_image, (int(self.w*(2/3)), int(self.h/3)))
                other_position, position = self.find_horizontal_position(other_position) # checks for horizontal possibilities
                placement[position] = rect_image[:int(self.h/3), :int(self.w/3)]
                placement[position + 1] = rect_image[:int(self.h/3), int(self.w/3):int(self.w*(2/3)) - biasw]

        num = 0
        for i in other_position: # after puting bigger image fill other places with smaller images
            placement[i] = self.resize_small_image(num)
            num += 1

        return placement

    def find_big_position(self, avaiable_pos):
        # find position for 2/3 width/height image
        myList = avaiable_pos
        mylistshifted=[x-1 for x in myList]
        possible_position = [0,1,3,4] # only possible possisions for big image
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 1)
        myList.remove(position + 3)
        myList.remove(position + 4)
        return myList, position

    def find_horizontal_position(self, avaiable_pos):
        # find position for horizontal rectangle image
        myList = avaiable_pos
        mylistshifted=[x-1 for x in myList]
        possible_position = [0,1,3,4,6,7] # positions where image is not cut in half
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 1)
        return myList, position

    def find_vertical_position(self, avaiable_pos):
        # find position vertical rectangle image
        myList = avaiable_pos
        mylistshifted=[x-3 for x in myList]
        possible_position = [e for e in range(6)] # positions where image is not cut in half
        intersection_set = list(set(myList) & set(mylistshifted) & set(possible_position))
        if self.losowo == 1:
            position = random.choice(intersection_set)
        else:
            position = intersection_set[0]
        myList.remove(position) # removes places from other_position, so no other image can take these places
        myList.remove(position + 3)
        return myList, position
deleted 62 characters in body
Source Link
Peilonrayz
  • 44.6k
  • 7
  • 80
  • 158

Start cmd where server.py is located. Mozaika.py should be in the same location. Run server.py with python. In your browser type: http://localhost:8080/mozaika?losowo=Z&rozdzielczosc=XxY&zdjecia=URL1,URL2,URL3...

where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

Required librariesTo run: http.server, numpy, opencv-python

Github: https://github.com/Krzysztof-Wojtczak/Allegro-Task

  • Required libraries: http.server, numpy, opencv-python

  • Github: https://github.com/Krzysztof-Wojtczak/Allegro-Task

  • Run server.py

  • In your browser type: http://localhost:8080/mozaika?losowo=Z&rozdzielczosc=XxY&zdjecia=URL1,URL2,URL3...

    where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

Start cmd where server.py is located. Mozaika.py should be in the same location. Run server.py with python. In your browser type: http://localhost:8080/mozaika?losowo=Z&rozdzielczosc=XxY&zdjecia=URL1,URL2,URL3...

where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

Required libraries: http.server, numpy, opencv-python

Github: https://github.com/Krzysztof-Wojtczak/Allegro-Task

To run:

  • Required libraries: http.server, numpy, opencv-python

  • Github: https://github.com/Krzysztof-Wojtczak/Allegro-Task

  • Run server.py

  • In your browser type: http://localhost:8080/mozaika?losowo=Z&rozdzielczosc=XxY&zdjecia=URL1,URL2,URL3...

    where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

added 1 character in body
Source Link

where: losowo - optional parameter, if Z = 1 images places are random. rozdielczoscrozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

where: losowo - optional parameter, if Z = 1 images places are random. rozdielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

where: losowo - optional parameter, if Z = 1 images places are random. rozdzielczosc - optional parameter, defines width and height. Default is 2048x2048 URL1,URL2,URL3... image addresses.(or copy the link above).

added 216 characters in body
Source Link
Loading
added 510 characters in body
Source Link
Loading
added more description from OPs comment, some grammar
Source Link
Graipher
  • 41.7k
  • 7
  • 70
  • 134
Loading
added 2920 characters in body; edited tags
Source Link
Loading
Improperly indented last line fixed, probably copy/paste mistake. Indentation is important in Python.
Source Link
Mast
  • 13.9k
  • 12
  • 57
  • 128
Loading
Source Link
Loading