diff --git a/ImageMerger.py b/ImageMerger.py index e933766..c0362d4 100644 --- a/ImageMerger.py +++ b/ImageMerger.py @@ -24,6 +24,7 @@ def __get_image_from_url(self): img = Image.open(u) return img except Exception as e: + # TODO raise exception print(str(e)) print(traceback.format_exc()) return None @@ -34,14 +35,6 @@ def __post_init__(self): else: self.content = Image.open(self.path) - def resize(self, basewidth=800): - if self.content is None: - return None - wpercent = (basewidth / float(self.content.size[0])) - hsize = int((float(self.content.size[1]) * float(wpercent))) - img = self.content.resize((basewidth, hsize), Image.Resampling.LANCZOS) - self.content = img - @dataclass class Merger: @@ -55,11 +48,12 @@ class Merger: preserve_aspect_ratio: bool = False def __nearest_square(self, limit): + # TODO: rework sq = int((limit ** 0.5)) return sq def __post_init__(self): - # self.list_images = [im.content for im in list_images] + self.list_images = [im.content for im in self.list_images] warning_str = (f"Warning: limit_horizontal({self.limit_horizontal})*limit_vertical({self.limit_vertical}) is smaller than image set size({len(self.list_images)}). Output will not contain all images") if self.shuffle: @@ -69,9 +63,9 @@ def __post_init__(self): self.limit_horizontal = len(self.list_images) / self.limit_vertical elif self.limit_vertical and self.limit_horizontal: - print(warning_str) m = self.limit_vertical * self.limit_horizontal if m < len(self.list_images): + print(warning_str) self.list_images = self.list_images[0:m] self.limit_horizontal = self.limit_horizontal @@ -89,12 +83,12 @@ def generate_merge_list(self): elif self.merge_strategy == MERGE_GRID: merge, h = [], [] - limit_h = self.limit_horizontal if self.limit_horizontal is None: - limit_h = self.__nearest_square(len(self.list_images)) + self.limit_horizontal = self.__nearest_square(len(self.list_images)) + limit_h = self.limit_horizontal for idx, im in enumerate(self.list_images): - if idx < limit_h or idx > len(self.list_images) - self.limit_horizontal / 2: + if idx < limit_h or (idx > round(len(self.list_images) - self.limit_horizontal / 2) and len(merge) > 1): h.append(im) else: merge.append(h) @@ -106,7 +100,6 @@ def generate_merge_list(self): def __generate_merged_image(self): t = self.generate_merge_list() - print(t) if self.merge_strategy in (MERGE_HORIZONTALLY, MERGE_VERTICALLY): _im = merge_images(list_images_tmp=t, direction=self.merge_strategy, preserve_aspect_ratio=self.preserve_aspect_ratio) else: @@ -134,6 +127,7 @@ def generate_filename(suffix, extension): def concat_two_images(im1, im2, direction): + # TODO: Factorize if im1 is None: return im2 if direction == MERGE_HORIZONTALLY: @@ -147,10 +141,19 @@ def concat_two_images(im1, im2, direction): return dst +def resize(im, basewidth=800): + if im is None: + return None + wpercent = (basewidth / float(im.size[0])) + hsize = int((float(im.size[1]) * float(wpercent))) + img = im.resize((basewidth, hsize), Image.Resampling.LANCZOS) + return img + + def merge_images(list_images_tmp: List[ImageToMerge], direction: int, preserve_aspect_ratio: bool): _im = None for im in list_images_tmp: if not preserve_aspect_ratio: - im = im.resize() + im = resize(im) _im = concat_two_images(im1=_im, im2=im, direction=direction) return _im diff --git a/README.md b/README.md index 1b8b207..5662f23 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,109 @@ -# WIP # image-merger -A small Python library to merge images +A small Python library to merge images easily + +# Installation +``` +pip install image-merger +``` + +# Usage + +## Simple use case +``` +from ImageMerger import Merger, ImageToMerge, MERGE_GRID + +# Initialize list of images +list_images = [ + ImageToMerge(path='images/samples/1.png'), + ImageToMerge(path='images/samples/2.png'), + ImageToMerge(path='images/samples/3.png'), + ImageToMerge(path='images/samples/4.png') +] + +# Load merger with different settings +m = Merger(list_images=list_images) + +# Save merged image +m.save_image(filename="images/results/output_4_grid.png") +``` +![output_4_grid.png](https://raw.githubusercontent.com/ggouzi/image-merger/main/images/results/output_4_grid.png) + +## Parameters + + - `limit_horizontal: int`: Optional int to define the maximum number of images to append horizontally +- `limit_vertical: int`: Optional int to define the maximum number of images to append vertically +- `suffle: bool`: Optional boolean to decide if `list_images` must be shuffled prior to process. Default `False` +- `merge_strategy: int`: Optional int to set the merging strategy. Either `MERGE_HORIZONTALLY`, - `MERGE_VERTICALLY` or `MERGE_GRID`(default) +- `preserve_aspect_ratio: bool`: Optional boolean that defines if proportion of each image should be kept or if image can be squeezed/extended to fit. Default `False` + - Setting it to `True` can lead to images being distored to fit layout but there will be no gaps. + - Setting it to `False` will kept each image aspect ratio but can lead to black gaps in output image. + +## Configuration examples +- `ImageToMerge.path` can either be a path(local or relative) or HTTP(S) URL +- `limit_horizontal` and `limit_vertical` are optional and can also be used at the same time +- If `limit_horizontal` and `limit_vertical` are not set, and `merge_strategy` is set to `MERGE_GRID` The process will try to fit as best as possible all images in a square grid. +- List of images can be shuffled randomly using `shuffle=True` parameter + + +## More examples +``` +from ImageMerger import Merger, ImageToMerge + +# Initialize list of images +list_images = [ + ImageToMerge(path='images/samples/1.png'), + ImageToMerge(path='images/samples/2.png'), + ImageToMerge(path='https://raw.githubusercontent.com/ggouzi/image-merger/main/images/samples/3.png'), + ImageToMerge(path='https://raw.githubusercontent.com/ggouzi/image-merger/main/images/samples/4.png'), + ImageToMerge(path='https://raw.githubusercontent.com/ggouzi/image-merger/main/images/samples/5.png') +] + +# Load merger with different settings +m = Merger( + list_images=list_images, + preserve_aspect_ratio=True, + limit_vertical=2 + ) + +# Save merged image +m.save_image(filename="images/results/output_5_grid_keep_aspect_ratio.png") +``` +![output_5_grid_keep_aspect_ratio.png](https://raw.githubusercontent.com/ggouzi/image-merger/main/images/results/output_5_grid_keep_aspect_ratio.png) + +``` +from ImageMerger import Merger, ImageToMerge + +# Initialize list of images +list_images = [ImageToMerge(path=f"images/samples/{i}.png") for i in range(1, 11)] + +# Load merger with different settings +m = Merger( + list_images=list_images, + limit_horizontal=2 +) + +# Save merged image +m.save_image(filename="images/results/output_10_2rows.png") +``` +![output_10_2rows.png](https://raw.githubusercontent.com/ggouzi/image-merger/main/images/results/output_10_2rows.png) + +``` +from ImageMerger import Merger, ImageToMerge + +# Initialize list of images +list_images = [ImageToMerge(path=f"images/samples/{i}.png") for i in range(1, 11)] + +# Load merger with different settings +m = Merger( + list_images=list_images, + shuffle=True +) + +# Save merged image +m.save_image(filename="images/results/output_10_grid_shuffled.png") +``` +![output_10_grid_shuffled.png](https://raw.githubusercontent.com/ggouzi/image-merger/main/images/results/output_10_grid_shuffled.png) + +# References +- [Pillow](https://github.com/python-pillow/Pillow/) diff --git a/images/results/output_10_2rows.png b/images/results/output_10_2rows.png new file mode 100644 index 0000000..c5116cd Binary files /dev/null and b/images/results/output_10_2rows.png differ diff --git a/images/results/output_10_grid_shuffled.png b/images/results/output_10_grid_shuffled.png new file mode 100644 index 0000000..178b928 Binary files /dev/null and b/images/results/output_10_grid_shuffled.png differ diff --git a/images/results/output_4_grid.png b/images/results/output_4_grid.png new file mode 100644 index 0000000..e73a209 Binary files /dev/null and b/images/results/output_4_grid.png differ diff --git a/images/results/output_5_grid_keep_aspect_ratio.png b/images/results/output_5_grid_keep_aspect_ratio.png new file mode 100644 index 0000000..6fd05cb Binary files /dev/null and b/images/results/output_5_grid_keep_aspect_ratio.png differ diff --git a/samples/1.png b/images/samples/1.png similarity index 100% rename from samples/1.png rename to images/samples/1.png diff --git a/samples/10.png b/images/samples/10.png similarity index 100% rename from samples/10.png rename to images/samples/10.png diff --git a/samples/2.png b/images/samples/2.png similarity index 100% rename from samples/2.png rename to images/samples/2.png diff --git a/samples/3.png b/images/samples/3.png similarity index 100% rename from samples/3.png rename to images/samples/3.png diff --git a/samples/4.png b/images/samples/4.png similarity index 100% rename from samples/4.png rename to images/samples/4.png diff --git a/samples/5.png b/images/samples/5.png similarity index 100% rename from samples/5.png rename to images/samples/5.png diff --git a/samples/6.png b/images/samples/6.png similarity index 100% rename from samples/6.png rename to images/samples/6.png diff --git a/samples/7.png b/images/samples/7.png similarity index 100% rename from samples/7.png rename to images/samples/7.png diff --git a/samples/8.png b/images/samples/8.png similarity index 100% rename from samples/8.png rename to images/samples/8.png diff --git a/samples/9.png b/images/samples/9.png similarity index 100% rename from samples/9.png rename to images/samples/9.png diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a6d4d60 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Pillow==9.2.0