diff --git a/.gitignore b/.gitignore index 68bc17f..4e8244a 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +/test/output +/test/output diff --git a/imagestamp.bat b/imagestamp.bat new file mode 100644 index 0000000..d58e2fe --- /dev/null +++ b/imagestamp.bat @@ -0,0 +1 @@ +python %~dp0/src/main.py %* diff --git a/ImageStamp.code-workspace b/requirements.code-workspace similarity index 100% rename from ImageStamp.code-workspace rename to requirements.code-workspace diff --git a/src/classes/BlobDetector.py b/src/classes/BlobDetector.py new file mode 100644 index 0000000..8aafb36 --- /dev/null +++ b/src/classes/BlobDetector.py @@ -0,0 +1,215 @@ +import cv2 +import numpy as np +from PIL import Image +import matplotlib.pyplot as plt +import math + +class BlobDetector: + + def __init__(self) -> None: + + pass + + def _get_contrast_points(self,_image_path)->list: + # read image + img = cv2.imread(_image_path) + + # convert img to grayscale + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + # do adaptive threshold on gray image + thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3) + + # apply morphology open then close + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) + blob = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9)) + blob = cv2.morphologyEx(blob, cv2.MORPH_CLOSE, kernel) + + + cv2.imwrite("doco3_contour.jpg", blob) + cv2.imshow("IMAGE", blob) + cv2.waitKey(0) + + + image = Image.open("doco3_contour.jpg") + + def _unsgined(self,_int:int)->int: + if _int < 0: + return _int*-1 + return _int + + + def _square_color_variation_is_bellow_thres(self,_image:cv2.typing.MatLike,_top_x:int,_top_y:int,_width:int,_thres:int): + + + + def _pixels_color_variation_is_bellow_thres(self,_image:cv2.typing.MatLike,_pixels:list[list],_start_value:int=0,_thres:int=100)->bool: + last_value = _start_value + for pix in _pixels: + value = _image[pix["y"],pix["x"]] + value_distance = self._unsgined(last_value -value ) + if value_distance > _thres: + return False + last_value = value + return True + + + def _detect_contrast_points(self,_image_path:str,_width)->list: + + image = cv2.imread(_image_path) + + # grab the image dimensions + h = image.shape[0] + w = image.shape[1] + + # convert img to grayscale + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + + # do adaptive threshold on gray image + thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3) + + #cv2.imshow("IMAGE", thresh) + #cv2.waitKey(0) + + last_h_value = 0 + thres = 200 + points = [] + aproximation = 1 + last_x_value = 0 + last_y_value = 0 + x_distance = 0 + + max_points = 300 + + # loop over the image, pixel by pixel + + padding = 5 + + final_with = _width+padding + + for y in range(0, h): + if y % final_with != 0: + continue + for x in range(0, w): + if x % aproximation != 0: + continue + value = thresh[y, x] + value_distance = self._unsgined(last_h_value -value ) + last_h_value = value + + ''' + distance_sum +=distance + distance_count+=1 + + average_distance = int(distance_sum/distance_count) + print(average_distance) + ''' + # does the gray value changed ? + if value_distance > thres: + # strat mesuring from this point + last_x_value = x + last_y_value = y + + # mesure the distance where the gray value of pixels are the same + x_distance = self._unsgined(last_x_value -x) + y_distance = self._unsgined(last_y_value -y) + + # is there room for the qr code in this distance ? + if x == 0: + last_x_value = x + last_y_value = y + + if x_distance >= final_with : + #does the qr code will be cut by the image border ? + if last_x_value+final_with > w: + continue + points.append({"x":last_x_value,"y":last_y_value}) + last_x_value = x + last_y_value = y + + return points + + + def _get_pixel_value(self,_image:Image,_x,_y): + im = Image.open('image.gif') + rgb_im = im.convert('RGB') + r, g, b = rgb_im.getpixel((_x, _y)) + return r + print(r, g, b) + (65, 100, 137) + + + def get_blobs(self,_image_path:str,_min=200,_max=8000)->list: + + return self._detect_contrast_points(_image_path,_min) + + # read image + img = cv2.imread(_image_path) + + # convert img to grayscale + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + # do adaptive threshold on gray image + thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3) + + # apply morphology open then close + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) + blob = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9)) + blob = cv2.morphologyEx(blob, cv2.MORPH_CLOSE, kernel) + + # invert blob + blob = (255 - blob) + + # Get contours + cnts = cv2.findContours(blob, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + cnts = cnts[0] if len(cnts) == 2 else cnts[1] + #big_contour = max(cnts, key=cv2.contourArea) + + filtered_contours = [] + bounding_boxes = [] + + min = 100 + max = 300000 + for contour in cnts: + blob_area = cv2.contourArea(contour) + if blob_area < min: + continue + if blob_area > max: + continue + filtered_contours.append(contour) + rect = cv2.minAreaRect(contour) + box = cv2.boxPoints(rect) + box = np.int0(box) + bounding_boxes.append(box) + print(box) + ... + + result = img.copy() + + cv2.drawContours(result, filtered_contours, -1, (0,0,255), 1) + for box in bounding_boxes: + cv2.drawContours(result,[box],0,(0,255,0),2) + + cv2.imwrite("doco3_contour.jpg", result) + cv2.imshow("RESULT", result) + cv2.waitKey(0) + return bounding_boxes + + # test blob size + blob_area = cv2.contourArea(big_contour) + if blob_area < blob_area_thresh: + print("Blob Is Too Small") + + # draw contour + result = img.copy() + cv2.drawContours(result, [big_contour], -1, (0,0,255), 1) + + # write results to disk + cv2.imwrite("doco3_threshold.jpg", thresh) + + # display it + return + cv2.imshow("IMAGE", img) + cv2.imshow("THRESHOLD", thresh) \ No newline at end of file diff --git a/src/classes/Combiner.py b/src/classes/Combiner.py new file mode 100644 index 0000000..27982eb --- /dev/null +++ b/src/classes/Combiner.py @@ -0,0 +1,147 @@ +from PIL import Image +import os +import shutil +import uuid +from classes.ImageChecker import ImageChecker +from classes.ImageEditor import ImageEditor + +class Combiner(): + + _IC = ImageChecker() + _IE:ImageEditor = ImageEditor() + + def __init__(self): + self._axe= "H" + self._crop = True + ... + + def combine(self,_paths:list)->str: + print(_paths) + images = _paths + if self._crop: + images = [] + for path in _paths: + if self._IC.check(path)==None: + return + images.append(self._crop_to_bbox(path)) + temp = self._generate_tmp_path()+".png" + if self._axe == "H": + self._combine_images_horizontaly(images,temp) + if self._axe == "V": + self._combine_images_verticaly(images,temp) + return temp + + def set_axe(self,_axe:str): + self._axe = _axe + return self + + def set_crop(self,_c:bool): + self._crop = _c + return self + + def _combine_images_verticaly(self,_paths,_output,_padding=100): + for path in _paths: + if self._IC.check(path) is None: + return + images = [Image.open(x) for x in _paths] + widths, heights = zip(*(i.size for i in images)) + + total_height = sum(heights)+(_padding*(len(_paths)+1)) + max_width = max(widths) + + new_im = Image.new('RGBA', (max_width,total_height)) + path = self._IE.resize_by_width(self._IC.get_max_width()) + + x_offset = 0 + y_offset = _padding + + for im in images: + new_im.paste(im, (x_offset,y_offset)) + x_offset += im.size[1]+_padding + + new_im.save(_output) + return new_im + + def _conform_images_width(self,_paths,_total_width)->list: + # check if the combined image is not too hudge (will cause errors on PIL side) + max_auhorised_width = self._IC.get_max_width() + conformed_paths = _paths + if _total_width >= max_auhorised_width: + print("[Combiner] WARNING ! final width is too high , resizing images ! ") + conformed_paths = [] + forced_equal_width = int((max_auhorised_width/len(_paths))*0.95) + for path in _paths: + resized_image = self._IE.resize_by_width(path,forced_equal_width) + conformed_paths.append(resized_image) + return conformed_paths + + def _combine_images_horizontaly(self,_paths,_output,_padding=100): + for path in _paths: + if self._IC.check(path)==None: + return + + # original width sum and max heigth + images = [Image.open(x) for x in _paths] + widths, heights = zip(*(i.size for i in images)) + total_width = sum(widths)+(_padding*(len(_paths)+1)) + max_height = max(heights) + + #width check + conformed_paths = self._conform_images_width(_paths,total_width) + conformed_images = [Image.open(x) for x in conformed_paths] + + # conformed width sum and max heigth + widths, heights = zip(*(i.size for i in conformed_images)) + total_width = sum(widths)+(_padding*(len(_paths)+1)) + max_height = max(heights) + + new_im = Image.new('RGBA', (total_width, max_height)) + max_height = max(heights) + + x_offset =_padding + y_offset = 0 + + for im in conformed_images: + new_im.paste(im, (x_offset,y_offset)) + x_offset += im.size[0]+_padding + + new_im.save(_output) + return new_im + + def _crop_to_bbox(self,_path:str)->str: + + # Opens a image in RGB mode + im = Image.open(_path) + bbox = im.getbbox() + + # Size of the image in pixels (size of original image) + # (This is not mandatory) + width, height = im.size + + # Cropped image of above dimension + # (It will not change original image) + im1 = im.crop(bbox) + + # Shows the image in image viewer + temp = self._generate_tmp_path() + ext = "."+_path.split(".")[-1] + im1.save(temp+ext) + return temp+ext + + def _generate_tmp_path(self)->str: + image_folder= os.getenv("TEMP") + image_name = str(uuid.uuid4())[-15:] + path = image_folder+"/"+image_name + return path + + def _copy_image_to_temp(self,_path): + image_temp = os.getenv("TEMP") + shutil.copy(_path, os.getenv("TEMP")) + return image_temp+"/"+os.path.basename(_path) + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/classes/Harmoniser.py b/src/classes/Harmoniser.py new file mode 100644 index 0000000..26f4e56 --- /dev/null +++ b/src/classes/Harmoniser.py @@ -0,0 +1,44 @@ +from PIL import Image +import os +import shutil +import uuid + +class Harmoniser(): + + def __init__(self): + self._output_folder = None + ... + + def use_output_folder(self,_of:str): + self._output_folder = _of + + def maximise(self,_paths:list)->str: + print(_paths) + images = [Image.open(x) for x in _paths] + widths, heights = zip(*(i.size for i in images)) + max_width = max(widths) + max_heigth = max(heights) + + new_images = [] + + for path in _paths: + img = Image.open(path) + new_blank_img = Image.new('RGBA', (max_width,max_heigth)) + new_blank_img.paste(img, (0,0)) + new_path = path + if self._output_folder is not None: + new_folder = self._output_folder + new_path = new_folder+"/"+os.path.basename(path) + if os.path.exists(new_folder)==False: + os.mkdir(new_folder) + new_blank_img.save(new_path) + new_images.append(new_path) + + return new_images + + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -maximise -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/classes/ImageChecker.py b/src/classes/ImageChecker.py new file mode 100644 index 0000000..fe6823a --- /dev/null +++ b/src/classes/ImageChecker.py @@ -0,0 +1,121 @@ +import os +import re +import subprocess + +class ImageChecker(): + + _max_nb_of_pixels:int = 178956970 + _max_heigth:int = 15000 + _max_width:int = 15000 + _image_magick_path = "P:/pipeline/extra_soft/ImageMagick-7.0.10-Q16/magick.exe" + + def __init__(self): + ... + + def _get_image_infos(self, _path: str) -> dict: + args = [self._image_magick_path, "identify", _path] + result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if result.returncode != 0: + raise RuntimeError(f"ImageMagick identify failed for '{_path}': {result.stderr.decode()}") + + string = result.stdout.decode('utf-8') + width, height = self._get_resolution(string) + + # ---- EARLY VALIDATION ---- + if width is None or height is None: + raise ValueError(f"Failed to parse resolution from `identify` output: {string!r}") + + bitdepth = self._get_bitdepth(string) + if bitdepth is None: + raise ValueError(f"Failed to parse bit depth from: {string!r}") + + colorspace = self._get_colorspace(string) + image_format = self._get_image_format(string) + + return { + "width": int(width), + "height": int(height), + "bitdepth": int(bitdepth), + "colorspace": colorspace, + "image_format": image_format, + "nb_pixels": int(width) * int(height) + } + + def get_max_width(self)->int: + return self._max_width + + def _get_colorspace(self,_magic_string:str)->str: + keys = ["sRGB","Yuv","Rec740"] + for colorspace in keys: + if colorspace not in _magic_string: + continue + return colorspace + return "" + + def _get_bitdepth(self,_magic_string:str)->str: + result = re.findall('(\d{1,2})-bit', _magic_string) + if len(result)==0: + return "" + return result[0] + + def _get_resolution(self, _magic_string: str) -> tuple[int, int]: + result = re.search(r'(\d+)x(\d+)', _magic_string) + if not result: + raise ValueError( + f"Could not parse resolution from ImageMagick output: { _magic_string!r }" + ) + return result.groups() + + def _get_image_format(self,_magic_string:str)->str: + return _magic_string.split(" ")[1].lower() + + def validate(self,_path:str): + if isinstance(_path,str)==True: + return self._validate(_path) + if isinstance(_path,list)==False: + return False + for path in _path: + if self._validate(path)==False: + return False + return True + + def _validate(self,_path:str): + if os.path.exists(_path)==False: + return False + infos = self._get_image_infos(_path) + print(f"[ImageChecker] {_path} ") + print(f"[ImageChecker] {infos} ") + nb_pixels = infos["nb_pixels"] + width = infos["nb_pixels"] + heigth = infos["nb_pixels"] + if nb_pixels>self._max_nb_of_pixels: + print(f"[ImageChecker] ERROR max nb of pixel {self._max_nb_of_pixels} reached ({nb_pixels})") + return False + if infos["width"]>self._max_width: + print(f"[ImageChecker] ERROR max width {self._max_width} reached {width}") + return False + if infos["heigth"]>self._max_heigth: + print(f"[ImageChecker] ERROR max heigth {self._max_heigth} reached ({heigth})") + return False + return True + + def check(self,_path_or_paths:str=None): + if _path_or_paths is None: + return None + if self.validate(_path_or_paths)==False: + return None + return _path_or_paths + + def check_svg(self,_path_or_paths:str=None): + # wip + return _path_or_paths + + + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -apply_filter "BW" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -add_text "this is my text" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/classes/ImageEditor.py b/src/classes/ImageEditor.py new file mode 100644 index 0000000..4bdaabb --- /dev/null +++ b/src/classes/ImageEditor.py @@ -0,0 +1,73 @@ +''' + +''' + +from PIL import Image +import os +from classes.PathManager import PathManager + +class ImageEditor(): + + _PM:PathManager = PathManager() + + def __init__(self): + self._current_filter = "BW" + ... + + def crop(self,_path:str,_x1:int=0,_x2:int=0,_y1:int=0,_y2:int=0)->Image: + # Opens a image in RGB mode + im = Image.open(_path) + + # Cropped image of above dimension + # (It will not change original image) + cropped = im.crop((_x1, _y1, _x2, _y2)) + + # Shows the image in image viewer + return cropped + + def resize_by_width(self,_path:str,_width:int=0)->str: + img = Image.open(_path) + wpercent = (_width / float(img.size[0])) + hsize = int((float(img.size[1]) * float(wpercent))) + resized = img.resize((_width, hsize), Image.Resampling.LANCZOS) + path = self._PM.get_temp_image_path() + resized.save(path) + return path + + def vectorise(self,_path:str,_width:int=0)->str: + img = Image.open(_path) + wpercent = (_width / float(img.size[0])) + hsize = int((float(img.size[1]) * float(wpercent))) + resized = img.resize((_width, hsize), Image.Resampling.LANCZOS) + path = self._PM.get_temp_image_path() + resized.save(path) + return path + + def split(self,_path:str,_value:int=2)->list: + + im = Image.open(_path) + width, height = im.size + chunk_x = int(width/_value) + chunk_y = int(height/_value) + split = [] + + for i in range(0,_value): + x1 = chunk_x*i + x2 = chunk_x*(i+1) + for j in range(0,_value): + y1 = chunk_y*j + y2 = chunk_y*(j+1) + croped = self.crop(_path,x1,x2,y1,y2) + path = self._PM.get_temp_image_path() + croped.save(path) + split.append(path) + + # Shows the image in image viewer + return split + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -apply_filter "BW" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -add_text "this is my text" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/classes/ImageFilter.py b/src/classes/ImageFilter.py new file mode 100644 index 0000000..7847b26 --- /dev/null +++ b/src/classes/ImageFilter.py @@ -0,0 +1,105 @@ +''' + +''' + +from PIL import Image,ImageOps,ImageChops +import os +import uuid +from classes.PathManager import PathManager +from PIL import Image, ImageEnhance +from PIL import ImageChops + +class ImageFilter(): + + _PM:PathManager = PathManager() + + def __init__(self): + self._current_filter = "BW" + ... + + def set_filter(self,_filter_name:str): + self._current_filter = _filter_name + return self + ... + + def apply_filter(self,_path:str)->str: + + # Read the image + image = self._open_rgb_image(_path) + if image is None: + return _path + if self._current_filter == "BW": + image = image.convert('L') # convert image to black and white + temp = self._generate_tmp_path()+".png" + image.save(temp) + return temp + ... + + def create_diff_map(self,_path_A:str,_path_B:str)->str: + + # Read the images + image_A = self._open_rgb_image(_path_A) + image_B = self._open_rgb_image(_path_B) + + diff_img = ImageChops.difference(image_A, image_B) + temp = self._generate_tmp_path()+".png" + diff_img.save(temp) + return temp + ... + + def increase_contrast(self,_path:str,_value:1.5)->str: + + # Read the image + image = self._open_rgb_image(_path) + if image is None: + return _path + # Image brightness enhancer + enhancer = ImageEnhance.Contrast(image) + path = self._PM.get_temp_image_path() + + factor = _value #increase contrast + im_output = enhancer.enhance(factor) + im_output.save(path) + return path + + def _open_rgb_image(self,_path:str)->Image: + try: + image = Image.open(_path).convert("RGB") + return image + except(): + print("ERROR can't parse image path "+_path) + return None + + def grayscale(self,_path:str)->str: + image = self._open_rgb_image(_path) + if image is None: + return _path + grayscale = ImageOps.grayscale(image) + path = self._PM.get_temp_image_path() + grayscale.save(path) + return path + + def invert(self,_path:str)->str: + image = self._open_rgb_image(_path) + if image is None: + return _path + inverted_image =ImageChops.invert(image) + path = self._PM.get_temp_image_path() + inverted_image.save(path) + return path + + + def _generate_tmp_path(self)->str: + image_folder= os.getenv("TEMP") + image_name = str(uuid.uuid4())[-15:] + path = image_folder+"/"+image_name + return path + + + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -apply_filter "BW" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -add_text "this is my text" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/classes/ImageStamp.py b/src/classes/ImageStamp.py index 5f26ac3..f70c254 100644 --- a/src/classes/ImageStamp.py +++ b/src/classes/ImageStamp.py @@ -1,18 +1,214 @@ from classes.QRReader import QRReader -from classes.QRWriter import QRWriter +from classes.QRIntegrator import QRIntegrator +from classes.Combiner import Combiner +from classes.TextWriter import TextWriter +from classes.ImageChecker import ImageChecker +from classes.ImageFilter import ImageFilter +from classes.ImageEditor import ImageEditor +from classes.Harmoniser import Harmoniser +from classes.PathManager import PathManager +from classes.VideoEditor import VideoEditor +from classes.Vectorisator import Vectorisator +from PIL import Image,ImageOps,ImageChops +import json +import uuid +import os class ImageStamp : _R:QRReader = QRReader() - _W:QRWriter = QRWriter() + _PM:PathManager = PathManager() + _I:QRIntegrator = QRIntegrator() + _C:Combiner = Combiner() + _H:Harmoniser = Harmoniser() + _T:TextWriter = TextWriter() + _F:ImageFilter = ImageFilter() + _E:ImageEditor = ImageEditor() + _V:VideoEditor = VideoEditor() + _Ve:Vectorisator = Vectorisator() + _IC:ImageChecker = ImageChecker() + + def add_qrcode(self,_source:str,_code:str,_integration_mode:str="grid",_strategy:str="optimaly_hidden"): + self._I.set_strategy(_strategy) + image = self._I.add_qrcode(_source,_code,_integration_mode) + if image == "": + return _source + return image - def __init__(self): + def set_qrcode_contrast(self,_v): + self._I.set_contrast(_v) + + def set_qrcode_scale(self,_v): + self._I.set_scale_factor(_v) + + def set_grid_division(self,_v): + self._I.set_grid_division(_v) + + def set_qrcode_transparency(self,_v): + self._I.set_transparency(_v) + + def grayscale(self,_path:str)->str: + return self._F.grayscale(_path) + + def read(self,_paths:list)->str: + return self._R.read(_paths[0]) + + + def _find_qrcodes_in_image(self,_path:str)->dict: + found = self._R.find(_path) + if len(found)==0: + return {} + table = { + "files":[_path], + "qrcodes":[item.data.decode('utf-8') for item in found] + } + return table + ... + def _find_qrcodes_in_video(self,_path:str)->dict: + + frames = self._V.extract_frames(_path) + skip_rate = 1 + index = 0 + frame_table = {} + code_table = {} + search_list = [] + qrcodes = [] + + for frame_path in frames: + index+=1 + if index % skip_rate !=0: + continue + print(frame_path) + found = self._R.find(frame_path) + if len(found)==0: + continue + data = [item.data.decode('utf-8') for item in found] + #unique codes + data = list(set(data)) + first_code = data[0] + if first_code not in qrcodes: + qrcodes.append(first_code) + frame_table[str(index)] = data + if first_code not in code_table.keys(): + code_table[first_code] = [] + search_list.append(first_code) + code_table[first_code].append(index) + print(f" FRAME {index} === {data}") + + result = { + "input_path":_path, + "frame_table":frame_table, + "code_table":code_table, + "qrcodes":qrcodes, + "search":search_list, # will be used to cominucate with sgrequest + "result":{} + } + return result + + + + def _is_video(self,_path:str)->bool: + ext = os.path.basename(_path).split(".")[-1] + return ext in ["mov","mp4","avi"] ... + def _is_image(self,_path:str)->bool: + ext = os.path.basename(_path).split(".")[-1] + return ext in ["jpg","png","dpx","tga","tif","exr"] + + def find_qrcodes(self,_path:str,_output:str="")->dict: + path = _path + if type(_path)==list: + path = _path[0] + if self._is_video(path): + result = self._find_qrcodes_in_video(path) + if self._is_image(path): + result = self._find_qrcodes_in_image(path) - def generate(self,_source:str,_code:str,_output:str): - return self._W.generate(_source,_code,_output) + with open(_output,"w") as file: + file.write(json.dumps(result)) + return result + + def evaluate(self,_path:str,_minimum_qrcodes:int=2)->dict: + if _path is None: + return "" + return self._R.evaluate(_path,_minimum_qrcodes) + + def invert(self,_path:str)->str: + if _path is None: + return "" + return self._F.invert(_path) + + def combine(self,_paths:list,_axe:str="H")->str: + if _paths is None: + return "" + self._C.set_axe(_axe) + return self._C.combine(_paths) + + def convert_to_svg(self,_path:str)->str: + if _path is None: + return "" + return self._Ve.vectorise(_path) + + def maximise(self,_paths:list,_axe:str="H")->str: + if _paths is None: + return "" + self._C.set_axe(_axe) + return self._H.maximise(_paths) + + def add_text(self,_path:str,_text:str)->str: + if _path is None: + return "" + self._T.set_background_color("white") + self._T.set_text_color("black") + return self._T.add_text(_path,_text) + + def add_watermark(self,_path:str,_text:str)->str: + if _path is None: + return "" + if self._IC.check(_path) is None: + return "" + self._T.set_text_color("gray") + return self._T.add_watermark(_path,_text) + + def apply_filter(self,_path:str,_f:str)->str: + if _path is None: + return "" + self._F.set_filter(_f) + return self._F.apply_filter(_path) + + def create_diff_map(self,_image_A:str,_image_B:str)->str: + if _image_A is None: + return "" + if _image_B is None: + return "" + if os.path.exists(_image_A)==False: + return "" + if os.path.exists(_image_B)==False: + return "" + return self._F.create_diff_map(_image_A,_image_B) + + def crop(self,_path:str,_x1:int=0,_x2:int=0,_y1:int=0,_y2:int=0)->Image: + if _path is None: + return "" + return self._E.crop(_path,_x1,_x2,_y1,_y2) + + def _get_temp_image_path(self)->str: + return self._PM.get_temp_image_path() + + def split(self,_path:str,_value:int=2)->list: + if _path is None: + return "" + return self._E.split(_path,_value) + + def clean_temp(self): + return self._PM.clean_temp() + + def add_overlay(self,_path:str,_overlay_json:str,_output:str): + # read a json and add text and images on each frames + ... + + - def read(self,_path:str): - return self._R.read(_path) \ No newline at end of file + \ No newline at end of file diff --git a/src/classes/MonochromaticSquareDetector.py b/src/classes/MonochromaticSquareDetector.py new file mode 100644 index 0000000..de0671c --- /dev/null +++ b/src/classes/MonochromaticSquareDetector.py @@ -0,0 +1,276 @@ +import cv2 +import numpy as np +from PIL import Image +import matplotlib.pyplot as plt +import math + +class MonochromaticSquareDetector: + + def __init__(self,_image_path:str="") -> None: + self._image:cv2.typing.MatLike = None + self._image_width = None + self._image_heigth = None + self._graycale= None + self._high_contrast_image= None + self._value_threshold = 100 + self._pixel_approximation=1 + self._square_padding = 1 + self._max_squares = 300 + self._square_table = [] + self._square_min_width = 20 + self._square_max_width = 30 + if _image_path!="": + self.load(_image_path) + pass + + def load(self,_image_path:str): + self._image:cv2.typing.MatLike = cv2.imread(_image_path) + self._image_heigth = self._image.shape[0] + self._image_width = self._image.shape[1] + self._graycale= cv2.cvtColor(self._image, cv2.COLOR_BGR2GRAY) + self._high_contrast_image= self._get_high_contrast_image(self._image) + return self + + def _get_high_contrast_image(self,_image:cv2.typing.MatLike)->cv2.typing.MatLike: + # do adaptive threshold on gray image + gray = cv2.cvtColor(_image, cv2.COLOR_BGR2GRAY) + return cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3) + + def set_value_threshold(self,_value:int): + self._value_threshold = _value + + def set_pixel_approximation(self,_value:int): + # ignore pixels to gain speed (a value of 5 means : ignore 4 pixels out of 5 . ect... ) + self._pixel_approximation= _value + + def set_max_squares(self,_value:int): + # WIP : ingore some squares + self._max_squares =_value + + def set_square_padding(self,_value:int): + # space between square , it's like an extra width on the square to detect + self._square_padding= _value + + def set_square_min_width(self,_value:int): + # WIP , scale the square but not bellow this _image_width + self._square_min_image_width= _value + + def _validate_square(self,_square:dict)->bool: + if _square["width"] == 0: + return False + if self._overlap_detected_square(_square)==True: + return False + return True + + def _overlap_detected_square(self,_square:dict)->bool: + return False + + def get_squares_positions(self,_width:int)->list: + return self._get_monochromatic_squares(_width) + + def _unsigned(self,_int:int)->int: + if int(_int) < 0: + return int(_int)*-1 + return int(_int) + + + def _get_monochromatic_squares(self,_width): + + thresh = self._high_contrast_image + + h = self._image_heigth + w = self._image_width + + last_v_value = 0 + points = [] + thres = self._value_threshold + aproximation = self._pixel_approximation + last_x_value = 0 + last_y_value = 0 + x_distance = 0 + padding = self._square_padding + + final_with = _width+padding + + for y in range(0, h): + + if y % final_with != 0: + continue + + for x in range(0, w): + + if x % aproximation != 0: + continue + + value = thresh[y, x] + value_distance = self._unsigned(last_v_value -value ) + last_v_value = value + + ''' + distance_sum +=distance + distance_count+=1 + + average_distance = int(distance_sum/distance_count) + print(average_distance) + ''' + + # does the gray value changed on x axis ? + if value_distance > thres: + # strat again mesuring from this point + last_x_value = x + last_y_value = y + + + # does the gray value changed on y axis below the x ? + y_distance_to_contrast =self._get_y_distance_to_contrast(thresh,x,y) + if y_distance_to_contrast < final_with: + #there is no room under this point to fit the square + # strat again mesuring from this point + last_x_value = x + last_y_value = y + continue + + if x == 0: + last_x_value = x + last_y_value = y + + # mesure the distance where the gray value of pixels are the same + x_distance = self._unsigned(last_x_value-x) + + # is there room for the qr code in this distance ? + if x_distance < final_with : + continue + + #does the qr code will be cut by the image border ? + if last_x_value+final_with > w: + continue + if last_y_value+final_with > h: + continue + points.append({"x":last_x_value,"y":last_y_value}) + last_x_value = x + last_y_value = y + + + return points + + def _get_y_distance_to_contrast(self,_image:cv2.typing.MatLike,_from_x:int,_from_y:int): + + last_h_value = _image[_from_y, _from_x] + thres = self._value_threshold + last_y = 0 + for y in range(_from_y, self._image_heigth): + + if y % self._pixel_approximation != 0: + continue + + absolute_y = _from_y+y + if absolute_y >= self._image_heigth: + absolute_y = self._image_heigth-1 + value = _image[absolute_y, _from_x] + value_distance = self._unsigned(last_h_value - value ) + last_h_value = value + last_y = y + if value_distance > thres: + break + return last_y + + def _get_monochromic_square(self,_image:cv2.typing.MatLike,_top_x:int,_top_y:int,_width:int=0)->dict: + + width = _width + if width==0: + width=self._square_min_width + + y_distance_to_contrast = 0 + x_distance_to_contrast = 0 + + last_h_value = _image[_top_y,_top_x] + last_v_value = _image[_top_y,_top_x] + + square = { + "x":_top_x, + "y":_top_y, + "width":0 + } + + # y axis first + for y in range(_top_y,self._image_heigth): + + h_value = _image[y,_top_x] + + if y % self._pixel_approximation != 0: + continue + + h_distance = self._unsigned(last_h_value -h_value ) + y_distance = self._unsigned(y-_top_y) + print(y_distance) + if h_distance>self._value_threshold: + #contrast detected + y_distance_to_contrast = y_distance + # we got the max heigth + break + if y_distance > self._square_max_width: + y_distance_to_contrast = y_distance + break + + + # x axis + for y in range(_top_y,y_distance_to_contrast): + + if y % self._pixel_approximation != 0: + continue + + for x in range(_top_x, self._image_width): + + if x % self._pixel_approximation != 0: + continue + + v_value = _image[y,x] + + v_distance = self._unsigned(last_v_value -v_value ) + x_distance = self._unsigned(x-_top_x) + if h_distance>self._value_threshold: + #contrast detected + x_distance_to_contrast = x_distance + # we got the max heigth + break + if x_distance > self._square_max_width: + #contrast detected + x_distance_to_contrast = x_distance + # we got the max width + break + + #choosing a maximum square in the low contrast rectangular area + max_width = 0 + if x_distance_to_contrast == y_distance_to_contrast: + # by chance it's actualy a square + max_width = x_distance_to_contrast + if x_distance_to_contrast < y_distance_to_contrast: + # the rectangle is more high than large + max_width = x_distance_to_contrast + if y_distance_to_contrast < x_distance_to_contrast: + # the rectangle is more large than high + max_width = y_distance_to_contrast + + print(max_width) + + square = { + "x":_top_x, + "y":_top_y, + "width":max_width + } + + # every pixel value in the square is bellow threshold + return square + + ... + + def _pixels_color_variation_is_bellow_thres(self,_image:cv2.typing.MatLike,_pixels:list[list],_start_value:int=0,_thres:int=100)->bool: + last_value = _start_value + for pix in _pixels: + value = _image[pix["y"],pix["x"]] + value_distance = self._unsigned(last_value -value ) + if value_distance > _thres: + return False + last_value = value + return True + diff --git a/src/classes/PathManager.py b/src/classes/PathManager.py new file mode 100644 index 0000000..3d7aee0 --- /dev/null +++ b/src/classes/PathManager.py @@ -0,0 +1,58 @@ +import os +import uuid + +class PathManager(): + + _index:int = 0 + _session_id = str(uuid.uuid4())[-8:] + _temp_paths = [] + + def __init__(self): + ... + + def get_temp_image_path(self): + serial = self._get_serial() + name = f"{self._session_id}_{serial}_{self._index}" + path = f"{os.getenv('TEMP')}\{name}.png" + self._index+=1 + self._temp_paths.append(path) + return path + + def get_temp_txt_path(self): + serial = self._get_serial() + name = f"{self._session_id}_{serial}_{self._index}" + path = f"{os.getenv('TEMP')}\{name}.txt" + self._index+=1 + self._temp_paths.append(path) + return path + + def create_temp_folder(self)->str: + serial = self._get_serial() + name = f"{self._session_id}_{serial}_{self._index}" + path = f"{os.getenv('TEMP')}\{name}" + self._index+=1 + os.mkdir(path) + return path + + def _get_serial(self): + return str(uuid.uuid4())[-8:] + + def add_as_temp_path(self,_path:str): + self._temp_paths.append(_path) + + def clean_temp(self): + print("Temp Cleanup") + for path in self._temp_paths: + if os.path.exists(path)==False: + continue + print("Deleting temp file "+path) + os.unlink(path) + self._temp_paths = [] + + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/classes/QRIntegrator.py b/src/classes/QRIntegrator.py new file mode 100644 index 0000000..e15ad3c --- /dev/null +++ b/src/classes/QRIntegrator.py @@ -0,0 +1,92 @@ +from PIL import Image +from classes.QRWriter import QRWriter +from classes.ImageFilter import ImageFilter +from classes.QRReader import QRReader + +class QRIntegrator (): + + _W:QRWriter = QRWriter() + _F:ImageFilter = ImageFilter() + _R:QRReader = QRReader() + _max_trials = 30 + + def __init__(self): + self._strategy = "optimaly_hidden" # visible + ... + + def set_strategy(self,_s:str): + self._strategy = str(_s) + + def set_integration_mode(self,_im:str): + self._W.set_integration_mode(_im) + + def set_scale_factor(self,_v:int): + self._W.set_scale_factor(_v) + + def set_contrast(self,_v:int): + self._W.set_contrast(_v) + + def set_grid_division(self,_v:int): + self._W.set_grid_division(_v) + + def set_transparency(self,_v:int): + self._W.set_transparency(_v) + + def add_qrcode(self,_source_image:str,_code:str,_integration_mode:str="grid")->str: + if self._strategy == "optimaly_hidden": + return self._add_qrcode_optimaly_hidden(_source_image,_code,_integration_mode) + if self._strategy == "visible": + return self._add_qrcode_visible(_source_image,_code,_integration_mode) + return self._W.add_qrcode(_source_image,_code,_integration_mode) + + def _add_qrcode_visible(self,_source_image:str,_code:str,_integration_mode:str="grid")->str: + self._W.set_grid_division(1) + self._W.set_blend_mode("over") + return self._W.add_qrcode(_source_image,_code,_integration_mode) + + def _add_qrcode_optimaly_hidden(self,_source_image:str,_code:str,_integration_mode:str="grid"): + + self._W.reset() + contrast = 5 + scale = 20 + code = _code + transparency = 1 + grid_division = 4 + integration_mode = _integration_mode + + max_trial = self._max_trials + + increment_table = { + "contrast":int(100/max_trial), + "grid_division":0.3 + } + + image_stream =self._F.grayscale(_source_image) + original_image = image_stream + + self._W.set_grid_division(grid_division) + self._W.set_scale_factor(scale) + self._W.set_transparency(transparency) + self._W.set_contrast(contrast) + + match_target = 3 + + for trial in range(0,max_trial): + + if self._R.evaluate(image_stream,match_target)==True: + print(f"REQUIRED MINIMUM OF VISIBLE {match_target} QRCODE REACHED ") + break + print("QR CODE NOT VISIBLE -- TRIAL "+str(trial)) + contrast+=increment_table["contrast"] + grid_division+=round(increment_table["grid_division"]) + self._W.set_grid_division(grid_division) + self._W.set_contrast(contrast) + image_stream =self._W.add_qrcode(original_image,code,integration_mode) + + if match_target < 3: + #for safety we had realy visible qrcodes in the corners + return self._add_qrcode_visible(image_stream,_code,_integration_mode) + + return image_stream + + \ No newline at end of file diff --git a/src/classes/QRReader.py b/src/classes/QRReader.py index e2e9d5f..0ab9c81 100644 --- a/src/classes/QRReader.py +++ b/src/classes/QRReader.py @@ -1,27 +1,88 @@ import pyqrcode from PIL import Image from pyzbar.pyzbar import decode - +from classes.ImageEditor import ImageEditor +from classes.ImageFilter import ImageFilter class QRReader(): + + + _IE:ImageEditor = ImageEditor() + _IF:ImageFilter = ImageFilter() + _minimum_detected_qrcode = 1 def __init__(self): + + ... + + def find(self,_path:str)->list: + + #split the image in sub parts and look fo qrcodes + split_division = 2 + split = [] + split.append(_path) + split.extend(self._IE.split(_path,split_division)) + split.extend(self._IE.split(_path,split_division+1)) + for image_part in split: + split_data = self.read(image_part) + if len(split_data)>0: + return split_data + # test on contrasted image + contrasted_image = self._IF.increase_contrast(image_part,5) + split_data = self.read(contrasted_image) + if len(split_data)>0: + return split_data + # test on inverted image + inverted_image= self._IF.invert(image_part) + split_data=self.read(inverted_image) + if len(split_data)>0: + return split_data + return [] + - def read(self,_path): - return self._decode_image(_path) - - def _decode_image(self,_path): - data = decode(Image.open(_path)) - return data + def read(self,_path)->list: + # can you see a qrcode ? + qrcodes = self._decode_image(_path) + return qrcodes - def _decode_video(self,_path): + def evaluate(self,_path:str,_min_qrcodes:int=3)->bool: + found = self.find(_path) + print(len(found)) + return len(found) >= _min_qrcodes + + # check if the image have some minimum detectable qrcodes even if it's croped + + #split the image in sub parts and test the qrcodes + split_division = 2 + split = self._IE.split(_path,split_division) + split.append(_path) + split.extend(self._IE.split(_path,split_division+2)) + + match = 0 + + for image_part in split: + image_data = self.read(image_part) + if len(image_data)>=self._minimum_detected_qrcode: + print(f"--------- MATCH {len(image_data)}") + match+=1 + #testing on inveted image + inverted_image= self._IF.invert(image_part) + image_data=self.read(inverted_image) + if len(image_data)>=self._minimum_detected_qrcode: + print(f"---------INVERTED MATCH {len(image_data)}") + match+=1 + + if match < _min_qrcodes: + return False + + return True + + def _decode_image(self,_path)->list: data = decode(Image.open(_path)) return data - - diff --git a/src/classes/QRWriter.py b/src/classes/QRWriter.py index 6290e19..e7dcfcb 100644 --- a/src/classes/QRWriter.py +++ b/src/classes/QRWriter.py @@ -4,66 +4,307 @@ import uuid import os import shutil +import random +import math +from PIL import Image, ImageDraw, ImageEnhance +import PIL.ImageOps + class QRWriter (): def __init__(self): - + self._integration_mode = "grid" # all_corners , top_left ect... + self._blend_mode = "hide" # hide , over + self._under_path = "" + self._over_path = "" + self._scale_factor = 0.0025 + self._contrast = 5 + self._transparency = 1 + self._rgb_over:Image = None + self._rgb_under:Image = None + self._grid_division:int = 2 ... - def generate(self,_source_image,_code,_output_folder)->bool: - new_image = self.add_qrcode_to_image(_source_image,_code) - final_path = self.copy_image_to_output(new_image,_output_folder) - data = self._decode_image(final_path) - for qr in list(data): - print(qr) - if data == _code: - return True - return False + def reset(self): + self._integration_mode = "grid" # all_corners , top_left ect... + self._under_path = "" + self._over_path = "" + self._scale_factor = 0.0025 + self._contrast = 5 + self._transparency = 1 + self._rgb_over:Image = None + self._rgb_under:Image = None + self._grid_division:int = 2 + + def set_blend_mode(self,_bm:str): + self._blend_mode = _bm + + def set_integration_mode(self,_im:str): + self._integration_mode = str(_im) + + def set_scale_factor(self,_v:int): + value = int(_v) + self._scale_factor = value/10000 + + def set_contrast(self,_v:int): + value = int(_v) + self._contrast= value + + def set_grid_division(self,_v:int): + value = int(_v) + self._grid_division= value + + def set_transparency(self,_v:int): + v = int(_v) + if v == 0: + v=1 + value = float(100/int(v)) + self._transparency= value + + def add_qrcode(self,_source_image:str,_code:str,_integration_mode:str="")->str: + if os.path.exists(_source_image)==False: + return False + new_image = self._add_qrcode_to_image(_source_image,_code,_integration_mode) + if new_image == "": + return "" + temp = self._generate_tmp_path() + new_image.save(temp) + return temp + + def _generate_tmp_path(self)->str: + image_folder= os.getenv("TEMP") + image_name = str(uuid.uuid4())[-15:] + path = image_folder+"/"+image_name+".png" + return path def _decode_image(self,_path): data = decode(Image.open(_path)) return data + + def _calculate_qr_scale(self,_source_image)->float: + source = Image.open(_source_image) + factor = self._scale_factor + scale = source.width*factor + print("SCALE") + print(scale) + if scale <= 1: + print("SCALE CANNOT BE INFERIOR TO 1 ") + scale = 1 + return scale + ... - def _add_qrcode_to_image(self,_source_image:str,_code:str,_scale:float=2)->str: - qrcode = self._generate_image_with_qr_code(str(_code),_scale) - new_image = self._paste_image_in_corners(_source_image,qrcode) + def _add_qrcode_to_image(self,_source_image:str,_code:str,_integration_mode:str="")->Image: + self.set_integration_mode(_integration_mode) + qrcode_scale = self._calculate_qr_scale(_source_image) + qrcode_image = self._generate_image_with_qr_code(str(_code),qrcode_scale) + if qrcode_image == "": + return "" + new_image = self._paste_image(_source_image,qrcode_image) return new_image - def _generate_image_with_qr_code(self,_code,_scale:float=2): + def _generate_image_with_qr_code(self,_code,_scale:float=8)->Image: + print("[QRWriter] code = "+_code) + print("[QRWriter] scale "+str(_scale)) + print("[QRWriter] contrast "+str(self._contrast)) + print("[QRWriter] transparency "+str(self._transparency)) qr = pyqrcode.create(_code) path = self._generate_qrcode_image_path(_code) + print("[QRWriter] temp file = "+path) + print(f"[QRWriter] {qr}") qr.png(path, scale=_scale) return path - def _paste_image_in_corners(self,_image_under,_image_over): - copy = self.copy_image_to_temp(_image_under) - under = Image.open(copy) + def _paste_image(self,_image_under:str,_image_over:str)->Image: + self._under_path = _image_under + self._over_path = _image_over + under = Image.open(_image_under) under_rgb = under.convert('RGB') - uW = under.width - uH = under.height - + over = Image.open(_image_over) over_rgb = over.convert('RGB') - oW = over.width - oH = over.height + + with_qr_codes = self._paste_qrcodes(under_rgb,over_rgb,self._integration_mode) + return with_qr_codes + + def _combine_images(self,_under:Image,_over:Image,_x:int=0,_y:int=0,_mask=None)->Image: + if _mask is None: + _under.paste(_over,(_x,_y)) + return _under + _under.paste(_over,(_x,_y),_mask) + return _under + + def _blend(self,_under:Image,_over:Image,_x:int=0,_y:int=0): + if self._blend_mode=="hide": + return self._hide_in(_under,_over,_x,_y) + if self._blend_mode=="over": + return self._put_over(_under,_over,_x,_y) + return _under + + + def _paste_qrcodes(self,_under:Image,_over:Image,_integration_mode:str)->Image: + + uW = _under.width + uH = _under.height + oW = _over.width + oH = _over.height x_pixel_padding = uW - oW y_pixel_padding = uH - oH + + result = None - print(x_pixel_padding) - print(y_pixel_padding) - print(uW,uH,oW,oH) - - for i in range(2): - for j in range(2): - y = j * y_pixel_padding - x = i * x_pixel_padding - under_rgb.paste(over_rgb, (x,y)) - under_rgb.save(copy, quality=95) - return copy + if _integration_mode == "top_left": + y = 0 * y_pixel_padding + x = 0 * x_pixel_padding + self._blend(_under,_over,x,y) + + if _integration_mode == "top_right": + y = 0 * y_pixel_padding + x = 1 * x_pixel_padding + self._blend(_under,_over,x,y) + + if _integration_mode== "bottom_right": + y = 1 * y_pixel_padding + x = 1 * x_pixel_padding + self._blend(_under,_over,x,y) + + if _integration_mode == "bottom_left": + y = 1 * y_pixel_padding + x = 0 * x_pixel_padding + + self._blend(_under,_over,x,y) + + if _integration_mode == "all_corners": + margin = 20 + for i in range(2): + for j in range(2): + y = (j * y_pixel_padding-margin)+margin + x = (i * x_pixel_padding-margin)+margin + result =self._blend(_under,_over,x,y) + + if _integration_mode == "grid": + + grid_division = self._grid_division + margin = oW + final_width = x_pixel_padding-(margin*2) + final_height = y_pixel_padding-(margin*2) + column_width = int(final_width/grid_division) + row_width = int(final_height/grid_division) + x=0 + y=0 + for i in range(grid_division+1): + y =(row_width*i)+margin + for j in range(grid_division+1): + x = (column_width*j)+margin + result =self._blend(_under,_over,x,y) + + if _integration_mode == "random": + + for i in range(30): + random_position = self._random_position_in_rect(x_pixel_padding,x_pixel_padding) + result = self._blend(_under,_over,random_position["x"],random_position["y"]) + + if _integration_mode == "fill": + '''' + _under = PIL.ImageOps.grayscale(_under) + + MCSD = MonochromaticSquareDetector(self._under_path) + MCSD.set_square_padding(0) + MCSD.set_pixel_approximation(3) + MCSD.set_value_threshold(30) + points = MCSD.get_squares_positions(oW) + + for pt in points: + x = int(pt["x"]) + y = int(pt["y"]) + result = self._hide_in(_under,_over,x,y) + + + ''' + + + return result + + def _put_over(self,_under:Image,_over:Image,_x:int=0,_y:int=0)->Image: + result = self._combine_images(_under,_over,_x,_y) + return result + + def _hide_in(self,_under:Image,_over:Image,_x:int=0,_y:int=0)->Image: + + if self._rgb_under == None: + self._rgb_under = _under.convert('RGB') + + if self._rgb_over == None: + rgb_over = _over.convert('RGB') + # Image brightness enhancer + inverted_over = PIL.ImageOps.invert(rgb_over) + enhancer = ImageEnhance.Brightness(inverted_over) + + factor = self._transparency #gives original image + tranparent_over = enhancer.enhance(factor) + a_channel = tranparent_over.convert('L') + rgb_over.putalpha(a_channel) + self._rgb_over = rgb_over + + over_width= _over.width + average_color = self._get_average_color_behind(self._rgb_under,_x,_y,over_width) + displayed_color = self._display_color(average_color) + hex = '#%02x%02x%02x' % (displayed_color, displayed_color, displayed_color) + background = Image.new('RGB',(over_width,over_width),hex) + result = self._combine_images(_under,background,_x,_y,self._rgb_over) + return result + + + def _get_average_color_behind(self,_image,_x,_y,_width): + count = 1 + sum = 0 + average = 0 + w,h = _image.size + for y in range(_y, _y+_width): + if y > h : + continue + for x in range(_x,_x+_width): + if x > w : + continue + r, g, b = _image.getpixel((x,y)) + count+=1 + sum+=r + average = round(sum/count) + return average + + def _display_color(self,_value): + gap = self._contrast + half_tone = 200 + new_color = _value + if _value == 255: + new_color = _value-gap + if _value == 0: + new_color = _value+gap + if _value >= half_tone and _value < 255-gap: + new_color = _value-gap + if _value <= half_tone: + new_color = _value+gap + if new_color > 255: + return 255 + if new_color < 0: + return 0 + return new_color + + def _get_hex_value_of_pixel(_image:Image,_x,_y)->str: + rgb_im = _image.convert('RGB') + r, g, b = rgb_im.getpixel((_x, _y)) + return '#%02x%02x%02x' % (r, g, b) + + + def _random_position_in_rect(self,_W:int,_H:int)->dict: + pos = { + "x": int(random.uniform(0, _W)), + "y": int(random.uniform(0, _H)) + } + return pos + def _copy_image_to_temp(self,_path): image_temp = os.getenv("TEMP") shutil.copy(_path, os.getenv("TEMP")) @@ -82,7 +323,7 @@ def _copy_image_to_output(self,_path:str,_folder:str): def _generate_qrcode_image_path(self,_code): serial = str(uuid.uuid4())[-8:] name = f"ImageStamp_{serial}_{_code}" - return f"{os.getenv("TEMP")}/{name}.png" + return f"{os.getenv('TEMP')}/{name}.png" \ No newline at end of file diff --git a/src/classes/TextWriter.py b/src/classes/TextWriter.py new file mode 100644 index 0000000..e48d6a2 --- /dev/null +++ b/src/classes/TextWriter.py @@ -0,0 +1,107 @@ +from PIL import Image,ImageDraw,ImageFont +import os +import shutil +import uuid +from classes.ImageChecker import ImageChecker + +class TextWriter(): + + _IC = ImageChecker() + + def __init__(self): + self._text_color = self._get_color("white") + self._background_color = self._get_color("gray") + self._use_background = False + ... + + def set_text_color(self,_color_name): + self._text_color = self._get_color(_color_name) + ... + + def set_background_color(self,_color_name): + self._background_color = self._get_color(_color_name) + self._use_background = True + ... + + def _get_color(self,_color_name): + table ={ + "white":(255, 255, 255), + "black":(0, 0, 0), + "gray":(100, 100, 100), + "red":(255, 0, 0) + } + if _color_name in table.keys(): + return table[_color_name] + return table["white"] + + def _filter_text(self,_text:str)->str: + return _text + + + def add_watermark(self,_path:str,_text:str)->str: + + im = Image.open(_path) + width, height = im.size + draw = ImageDraw.Draw(im) + font_size = round(height/40) + font = ImageFont.truetype("arial.ttf",font_size) + padding = round(font_size/3) + position = (0,0) + + #bounding box of the texte + left, top, right, bottom = draw.textbbox(position, _text, font=font) + draw = ImageDraw.Draw(im) + + try: + # draw rectancle behind the text + draw.rectangle((left-padding, top-padding, right+padding, bottom+round(padding*0.8)), fill="white") + draw.text(position, _text, self._text_color, font=font) + except: + print(f"[TextWriter] Exeption occured , problem with adding text to {_path}") + + temp = self._generate_tmp_path()+".png" + im.save(temp) + return temp + + def add_text(self,_path:str,_text:str)->str: + + if self._IC.check(_path)==None: + return "" + + im = Image.open(_path) + width, height = im.size + draw = ImageDraw.Draw(im) + font_size = round(height/20) + font = ImageFont.truetype("arial.ttf",font_size) + padding = round(font_size/3) + position = (padding+5,padding+5) + left, top, right, bottom = draw.textbbox(position, _text, font=font) + text_h = ((bottom-top)+padding*2)*2 + image_with_text = Image.new("RGBA", (width, height+text_h)) + draw = ImageDraw.Draw(image_with_text) + image_with_text.paste(im, (0,text_h)) + + if self._use_background == True: + draw.rectangle((left-padding, top-padding, right+padding, bottom+round(padding*0.8)), fill=self._background_color) + + draw.text(position, _text, self._text_color, font=font) + temp = self._generate_tmp_path()+".png" + image_with_text.save(temp) + return temp + ... + + + def _generate_tmp_path(self)->str: + image_folder= os.getenv("TEMP") + image_name = str(uuid.uuid4())[-15:] + path = image_folder+"/"+image_name + return path + + + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -add_text "this is my text" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/classes/Vectorisator.py b/src/classes/Vectorisator.py new file mode 100644 index 0000000..da35d95 --- /dev/null +++ b/src/classes/Vectorisator.py @@ -0,0 +1,84 @@ +from PIL import Image +import cv2 +import numpy as np +import os +import uuid + + +class Vectorisator(): + + def __init__(self): + ... + + def vectorise(self,_path:str)->str: + print(_path) + temp_path = self._generate_tmp_path()+".svg" + self.detailed_gray_svg(_path,temp_path) + return temp_path + + def _generate_tmp_path(self)->str: + image_folder= os.getenv("TEMP") + image_name = str(uuid.uuid4())[-15:] + path = image_folder+"/"+image_name + return path + + def detailed_gray_svg(self,image_path, svg_path="output.svg", + num_shades=4, blur=1, edge_boost=True, simplify=0.0001): + """ + Convert a grayscale or black-and-white image to a detailed gray-tone SVG. + + Args: + image_path (str): Input image path. + svg_path (str): Output SVG path. + num_shades (int): Number of gray levels (4–8 recommended). + blur (int): Gaussian blur radius to reduce noise (0 = none). + edge_boost (bool): Apply edge sharpening to preserve lines. + simplify (float): Simplification factor for contours (lower = more detail). + """ + # Load grayscale + img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) + if img is None: + raise ValueError(f"Could not load image: {image_path}") + + # Optional blur to smooth noise + if blur > 0: + img = cv2.GaussianBlur(img, (blur*2+1, blur*2+1), 0) + + # Optional edge enhancement + if edge_boost: + edges = cv2.Laplacian(img, cv2.CV_8U) + img = cv2.addWeighted(img, 1.2, edges, -0.3, 0) + + # Normalize and quantize + img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX) + levels = np.linspace(0, 255, num_shades+1, dtype=np.uint8) + quantized = np.digitize(img, levels) - 1 + + h, w = img.shape + svg_lines = [ + f'' + ] + + # Iterate through tones (dark → light) + for i in range(num_shades): + mask = np.uint8(quantized == i) * 255 + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) + gray_val = int(255 * (i / (num_shades - 1))) + hex_gray = f"#{gray_val:02x}{gray_val:02x}{gray_val:02x}" + + for cnt in contours: + if len(cnt) < 5: + continue + # Lower epsilon = more detail, higher = smoother + epsilon = simplify * cv2.arcLength(cnt, True) + cnt = cv2.approxPolyDP(cnt, epsilon, True) + path_data = "M " + " L ".join(f"{x},{y}" for [[x, y]] in cnt) + " Z" + svg_lines.append(f'') + + svg_lines.append("") + + with open(svg_path, "w") as f: + f.write("\n".join(svg_lines)) + + print(f"✅ Saved detailed SVG to: {svg_path}") diff --git a/src/classes/VideoEditor.py b/src/classes/VideoEditor.py new file mode 100644 index 0000000..aa71f2d --- /dev/null +++ b/src/classes/VideoEditor.py @@ -0,0 +1,89 @@ +import os +from classes.PathManager import PathManager +import subprocess +from subprocess import PIPE +from pathlib import Path + +class VideoEditor(): + + _PM:PathManager = PathManager() + _ffmpeg_path:str = "P:/pipeline/extra_soft/ffmpeg/bin/ffmpeg.exe" + _extraction_rate:int = 1 + + def __init__(self): + + ... + + def extract_frames(self,_video_path:str,_select:str="non_similar")->list: + if _select =='non_similar': + return self._extract_non_similar_frames(_video_path) + return self._extract_all_frames(_video_path) + + def _extract_all_frames(self,_video_path:str)->list: + frames = [] + temp_folder_path = self._PM.create_temp_folder() + cmd = self._format_get_all_frames_cmd_line(_video_path,temp_folder_path) + result = subprocess.run(cmd, stdout=PIPE) + print(result) + frames = self._get_folder_frames(temp_folder_path) + self._delete_folder(temp_folder_path) + return frames + + def _extract_non_similar_frames(self,_video_path:str)->list: + # don't extract similar frames + frames = [] + temp_folder_path = self._PM.create_temp_folder() + temp_txt_path = self._PM.get_temp_txt_path() + cmd = self._format_get_non_similar_frames_cmd_line(_video_path,temp_txt_path,temp_folder_path) + result = subprocess.run(cmd, stdout=PIPE) + print(result) + frames = self._get_folder_frames(temp_folder_path) + self._delete_folder(temp_folder_path) + return frames + # ffmpeg -i inputvideo.mp4 -filter_complex "select='gt(scene,0.3)' -frame_pts true ,metadata=print:file=time.txt" -vsync vfr img%03d.png + ... + + def _get_folder_frames(self,_folder_path:str)->list: + frame_paths = [] + image_formats = ["png","tga","jpg"] + for file in os.listdir(_folder_path): + if file.split(".")[-1] not in image_formats: + continue + full_path = _folder_path+"\\"+file + frame_paths.append(full_path) + #self._PM.add_as_temp_path(full_path) + return frame_paths + + def _delete_folder(self,_folder:str): + ... + + def _format_get_all_frames_cmd_line(self,_video_path:str,_temp_folder:str="")->list: + # ffmpeg -i video.mp4 -r 1 frame%d.png + cmd = [] + cmd.append(self._ffmpeg_path) + cmd.append("-i") + cmd.append(_video_path) + cmd.append(_temp_folder+"/frame_%4d.png") + return cmd + + def _format_get_non_similar_frames_cmd_line(self,_video_path:str,_temp_txt:str="",_temp_folder:str="")->list: + # ffmpeg -i inputvideo.mp4 -filter_complex "select='gt(scene,0.3)' -frame_pts true ,metadata=print:file=time.txt" -vsync vfr img%03d.png + cmd = [] + cmd.append(self._ffmpeg_path) + cmd.append("-i") + cmd.append(_video_path) + cmd.append("-filter:v") + #cmd.append('"'+"select='gt(scene,0.3)' -frame_pts true ,metadata=print:file="+_temp_txt+'"') + cmd.append("select='gt(scene,0.2)'") + cmd.append("-vsync") + cmd.append("vfr") + cmd.append(_temp_folder+"/frame_%4d.png") + return cmd + + + +''' +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp" +python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -apply_filter "BW" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -add_text "this is my text" -o "P:/projects/riv/temp_no_backup/image_stamp" + +''' \ No newline at end of file diff --git a/src/main.py b/src/main.py index a0dbe53..3e9c202 100644 --- a/src/main.py +++ b/src/main.py @@ -1,36 +1,141 @@ +import sys +sys.path.insert(0,'P:/pipeline/extra_scripts/python_include') from classes.ImageStamp import ImageStamp +from classes.ImageChecker import ImageChecker +from PIL import Image +import shutil import argparse def main(): - print("ImageStamp") parser = argparse.ArgumentParser(prog='ImageStamp',description='add qrcode to image') parser.add_argument('-read','--read',action='store_true') + parser.add_argument('-find_qrcodes','--find_qrcodes',action='store_true') parser.add_argument('-generate','--generate',action='store_true') - parser.add_argument("-i","--image_source",required=True) + parser.add_argument('-combine','--combine',action='store_true') + parser.add_argument('-maximise','--maximise',action='store_true') + parser.add_argument('-check_image','--check_image',action='store_true') + parser.add_argument('-create_diff_map','--create_diff_map',action='store_true') + parser.add_argument('-convert_to_svg','--convert_to_svg',action='store_true') + parser.add_argument('-add_text','--add_text') + parser.add_argument('-add_watermark','--add_watermark') + parser.add_argument('-add_overlay','--add_overlay') + parser.add_argument('-add_qrcode','--add_qrcode') + parser.add_argument('-apply_filter','--apply_filter') + parser.add_argument("-i","--input",action='append' ,required=True) parser.add_argument("-c","--code") - parser.add_argument("-o","--output_folder") + parser.add_argument("-ct","--contrast") + parser.add_argument("-tr","--transparency") + parser.add_argument("-sf","--scale_factor") + parser.add_argument("-st","--strategy") + parser.add_argument("-gd","--grid_division") + parser.add_argument("-o","--output_path") + parser.add_argument("-oi","--output_image") + parser.add_argument("-oj","--overlay_json") parser.add_argument("-s","--scale") + parser.add_argument("-im","--integration_mode",default="all_corners") + args = parser.parse_args() IS = ImageStamp() - - if args.generate: - IS.generate(args.image_source,args.code,args.output_folder) - + IC = ImageChecker() + + input_stream =args.input + if args.read: - IS.read(args.image_source) - - -if __name__=="__main__": - main() + data = IS.read(input_stream) + return data + if args.find_qrcodes and args.input and args.output_path: + data = IS.find_qrcodes(input_stream,args.output_path) + return data + if args.add_overlay and args.input and args.overlay_json and args.output_path: + data = IS.add_overlay(input_stream,args.overlay_json,args.output_path) + return data + + if args.check_image: + data = IC.check(input_stream) + return data + + if args.combine: + input_stream =IC.check(IS.combine(input_stream)) + + if args.maximise: + input_stream =IC.check(IS.maximise(input_stream)) + + if args.create_diff_map: + input_stream =IS.create_diff_map(input_stream[0],input_stream[1]) + + # process on single images : + + if isinstance(input_stream,list): + input_stream = input_stream[0] + + if args.convert_to_svg: + input_stream =IC.check_svg(IS.convert_to_svg(input_stream)) + if args.add_text: + input_stream = IC.check(IS.add_text(input_stream,args.add_text)) + + if args.add_watermark: + input_stream = IC.check(IS.add_watermark(input_stream,args.add_watermark)) + + if args.add_qrcode: + code = args.add_qrcode + integration_mode = "grid" + strategy = "optimaly_hidden" + if args.integration_mode: + integration_mode = args.integration_mode + if args.strategy: + strategy = args.strategy + # put hidden qrcodes + input_stream =IS.add_qrcode(input_stream,code,integration_mode,strategy) + + if args.apply_filter: + input_stream =IS.apply_filter(input_stream,args.apply_filter) + + if input_stream is None or input_stream == "": + print("Image stream is None") + sys.exit(0) + if args.output_image: + if input_stream.split(".")[-1] in ["svg",'tvg']: + copy_and_rename(input_stream,args.output_image) + IS.clean_temp() + return input_stream + im = Image.open(input_stream) + im.save(args.output_image) + + IS.clean_temp() + + return input_stream + +def copy_and_rename(input_stream, output_image): + """ + Copy a file from input_stream to output_image. + + Args: + input_stream (str): Source file path. + output_image (str): Destination file path (new name or location). + """ + shutil.copyfile(input_stream, output_image) + print(f"Copied file from '{input_stream}' to '{output_image}'") + +if __name__=="__main__": + result = main() + print(result) ''' -python D:/1_TRAVAIL/WIP/CODING/repos/ImageStamp/main.py -generate -i D:/1_TRAVAIL/WIP/CODING/resources/images/png/dog.png -c TEST -o D:/1_TRAVAIL/WIP/CODING/repos/ImageStamp/output -python D:/1_TRAVAIL/WIP/CODING/repos/ImageStamp/main.py -generate -i D:/1_TRAVAIL/WIP/CODING/resources/images/png/dog.png -c 1897 -o D:/1_TRAVAIL/WIP/CODING/repos/ImageStamp/output + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -generate -i "P:/projects/billy/library/boxanim/assets/Character/ch_biff_le_borgne/png/ch_biff_le_borgne.png" -c 1897 -o "P:/projects/riv/temp_no_backup/image_stamp" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -generate -i "P:/projects/billy/library/boxanim/assets/Character/ch_billy/png/ch_billy.png" -c 1897 -o "P:/projects/riv/temp_no_backup/image_stamp" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -add_text "this is my text" -o "P:/projects/riv/temp_no_backup/image_stamp" -generate -c "mycode" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -add_text "test" -i "P:/projects/testa/temp_no_backup/render/packboard/download/bg_bil_ext_m_a1_multipl.png" -oi "P:/projects/testa/temp_no_backup/render/packboard/test/add_text.png" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -add_text "test" -add_qrcode "my_code" -i "P:/projects/testa/temp_no_backup/render/packboard/download/bg_bil_ext_m_a1_multipl.png" -oi "P:/projects/testa/temp_no_backup/render/packboard/test/add_text.png" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -apply_filter "BW" -i "P:/projects/testa/temp_no_backup/render/packboard/download/bg_bil_ext_m_a1_multipl.png" -oi "P:/projects/testa/temp_no_backup/render/packboard/test/add_text.png" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -generate -i "P:/projects/billy/library/boxanim/assets/Character/ch_billy/png/ch_billy.png" -c 1897 -o "P:/projects/riv/temp_no_backup/image_stamp" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -i P:/projects/testa/users/a.cormier/test/image_stamp/my_image.png -add_watermark "this is a watemark" + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_22/download/attachment_ch_suzie_606866.png -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_22/download/attachment_ch_suzie_606867.png -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_22/download/attachment_ch_suzie_606868.png -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_22/download/attachment_ch_suzie_606869.png -add_text "CH_SUZIE_CHARCATER-1" -apply_filter BW -oi P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_22/pack_board/fe3d7/ep297_testboard_packboard_design_fe3d6/CH_SUZIE_CHARCATER-1.png + python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_25/download/attachment_ch_suzie_606866.png -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_25/download/attachment_ch_suzie_606867.png -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_25/download/attachment_ch_suzie_606868.png -i P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_25/download/attachment_ch_suzie_606869.png -add_text "CH_SUZIE_CHARCATER-1" -apply_filter BW -oi P:/projects/billy/temp_no_backup/Sherif_temp/2024_11_25/pack_board/3fab7/ep297_testboard_packboard_design_3fab6/CH_SUZIE_CHARCATER-1.png ''' \ No newline at end of file diff --git a/test/add_overlay.bat b/test/add_overlay.bat new file mode 100644 index 0000000..71615db --- /dev/null +++ b/test/add_overlay.bat @@ -0,0 +1,12 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\sequence\image_0001.png +set overlay_json=%root%test\input\json\sequence\image_0001.png +set output_path=%root%test\output\add_overlay\billy_decor_1_watermark_%serial%.png +echo.%script_path% +python %script_path% -add_overlay %serial% -i %input_image% -oi %output_path% -oj %overlay_json% +%output_path% \ No newline at end of file diff --git a/test/add_qrcode_grid.bat b/test/add_qrcode_grid.bat new file mode 100644 index 0000000..f62a9f8 --- /dev/null +++ b/test/add_qrcode_grid.bat @@ -0,0 +1,35 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor_1.png +set asset_id=8946 +set output_path=%root%test\output\add_qrcodes\billy_decor_1_qrcode_%serial%.png +echo.%script_path% +python %script_path% -add_qrcode %asset_id% -i %input_image% -oi %output_path% -im grid +echo.NEXT +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor_2.png +set asset_id=2075 +set output_path=%root%test\output\add_qrcodes\billy_decor_2_qrcode_%serial%.png +echo.%script_path% +python %script_path% -add_qrcode %asset_id% -i %input_image% -oi %output_path% -im grid +echo.NEXT +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor_3095.png +set asset_id=3095 +set output_path=%root%test\output\add_qrcodes\billy_decor_3095_%serial%.png +echo.%script_path% +python %script_path% -add_qrcode %asset_id% -i %input_image% -oi %output_path% -im grid +echo.NEXT +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor_6039.png +set asset_id=6039 +set output_path=%root%test\output\add_qrcodes\billy_decor_6039_%serial%.png +echo.%script_path% +python %script_path% -add_qrcode %asset_id% -i %input_image% -oi %output_path% -im grid \ No newline at end of file diff --git a/test/add_watermark.bat b/test/add_watermark.bat new file mode 100644 index 0000000..b4e2499 --- /dev/null +++ b/test/add_watermark.bat @@ -0,0 +1,12 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor_1.png +%input_image% +set output_path=%root%test\output\add_watermark\billy_decor_1_watermark_%serial%.png +echo.%script_path% +python %script_path% -add_watermark %serial% -i %input_image% -oi %output_path% -im grid +%output_path% \ No newline at end of file diff --git a/test/check_image.bat b/test/check_image.bat new file mode 100644 index 0000000..a2ec570 --- /dev/null +++ b/test/check_image.bat @@ -0,0 +1,10 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor_1.png +set output_path=%root%test\output\check_image\billy_decor_1_watermark_%serial%.png +echo.%script_path% +python %script_path% -check_image -i %input_image% -oi %output_path% \ No newline at end of file diff --git a/test/code/pytybe.py b/test/code/pytybe.py deleted file mode 100644 index adb98ca..0000000 --- a/test/code/pytybe.py +++ /dev/null @@ -1,10 +0,0 @@ -from pytube import YouTube -YouTube('https://youtu.be/2lAe1cqCOXo').streams.first().download() -yt = YouTube('http://youtube.com/watch?v=2lAe1cqCOXo') -video = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').desc().first().download() -print(video) - -''' -python D:/1_TRAVAIL/WIP/CODING/repos/ImageStamp/test/code/pytybe.py - -''' \ No newline at end of file diff --git a/test/code/test.py b/test/code/test.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/convert_to_svg.bat b/test/convert_to_svg.bat new file mode 100644 index 0000000..b3c40bc --- /dev/null +++ b/test/convert_to_svg.bat @@ -0,0 +1,12 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\single_images\convert_me_1.png +set output_path=%root%test\output\convert_to_svg\10591_image_%serial%.tvg +echo.%script_path% +python %script_path% -convert_to_svg -i %input_image% -oi %output_path% + +%output_path% diff --git a/test/convert_to_svg_billy.bat b/test/convert_to_svg_billy.bat new file mode 100644 index 0000000..e07796f --- /dev/null +++ b/test/convert_to_svg_billy.bat @@ -0,0 +1,12 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\single_images\billy_decor_1.png +set output_path=%root%test\output\convert_to_svg\10591_image_%serial%.tvg +echo.%script_path% +python %script_path% -convert_to_svg -i %input_image% -oi %output_path% + +%output_path% diff --git a/test/create_diff_map.bat b/test/create_diff_map.bat new file mode 100644 index 0000000..f315d11 --- /dev/null +++ b/test/create_diff_map.bat @@ -0,0 +1,12 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=%RANDOM% +set script_path=%root%src\main.py +set input_A=%root%test\input\png\single_images\image_A.png +set input_B=%root%test\input\png\single_images\image_B.png +set output_path=%root%test\output\create_diff_map\diff_map_%serial%.png +echo.%script_path% +python %script_path% -create_diff_map -i %input_A% -i %input_B% -oi %output_path% +%output_path% \ No newline at end of file diff --git a/test/find_qrcodes_in_image.bat b/test/find_qrcodes_in_image.bat new file mode 100644 index 0000000..cf8eadc --- /dev/null +++ b/test/find_qrcodes_in_image.bat @@ -0,0 +1,10 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input=%root%test\input\png\single_images\detect_me.png +set output_path=%root%test\output\find_qrcodes\detect_me_%serial%.json +echo.%script_path% +python %script_path% -find_qrcodes -i %input% -o %output_path% diff --git a/test/find_qrcodes_in_video.bat b/test/find_qrcodes_in_video.bat new file mode 100644 index 0000000..eec9554 --- /dev/null +++ b/test/find_qrcodes_in_video.bat @@ -0,0 +1,10 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input=%root%test\input\video\scene_with_qrcodes.mov +set output_path=%root%test\output\find_qrcodes\video_qrcodes_%serial%.json +echo.%script_path% +python %script_path% -find_qrcodes -i %input% -o %output_path% diff --git a/test/generate_grid.bat b/test/generate_grid.bat new file mode 100644 index 0000000..6a3f739 --- /dev/null +++ b/test/generate_grid.bat @@ -0,0 +1,11 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor.png +set asset_id=8946 +set output_path=%root%test\output\generate\billy_decor_qrcode_%serial%.png +echo.%script_path% +python %script_path% -generate -i %input_image% -c %asset_id% -oi %output_path% -im grid diff --git a/test/generate_grid_simple.bat b/test/generate_grid_simple.bat new file mode 100644 index 0000000..60856ae --- /dev/null +++ b/test/generate_grid_simple.bat @@ -0,0 +1,11 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=blobs_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\simple_shape.png +set asset_id=8946 +set output_path=%root%test\output\generate\simple_%serial%.png +echo.%script_path% +python %script_path% -generate -i %input_image% -c %asset_id% -oi %output_path% -im grid diff --git a/test/generate_random.bat b/test/generate_random.bat new file mode 100644 index 0000000..19d6242 --- /dev/null +++ b/test/generate_random.bat @@ -0,0 +1,11 @@ +@echo off +set _path=%~dp0 +for %%a in ("%_path%") do set "p_dir=%%~dpa" +for %%a in (%p_dir:~0,-2%) do set "root=%%~dpa" +set serial=random_%RANDOM% +set script_path=%root%src\main.py +set input_image=%root%test\input\png\billy_decor.png +set asset_id=8946 +set output_path=%root%test\output\generate\billy_decor_qrcode_%serial%.png +echo.%script_path% +python %script_path% -generate -i %input_image% -c %asset_id% -oi %output_path% -im random diff --git a/test/input/json/test_overlay.json b/test/input/json/test_overlay.json new file mode 100644 index 0000000..6852f87 --- /dev/null +++ b/test/input/json/test_overlay.json @@ -0,0 +1,35 @@ +{ + "elements":[ + { + "type":"text", + "content":"persistant_text", + "color":"white", + "bg_color":"black", + "placement":"top_left" + }, + { + "type":"text", + "content":"timed_text_10-20", + "placement":"bottom_left", + "color":"black", + "bg_color":"white", + "start_frame":10, + "end_frame":20 + }, + { + "type":"image", + "path":"image/path" + }, + { + "type":"image", + "path":"image/path", + "start_frame":20, + "end_frame":30 + }, + { + "type":"time_code", + "frame_rate":25, + "placement":"bottom_rigth" + } + ] +} \ No newline at end of file diff --git a/test/input/mp4/scene_with_qrcodes.mov b/test/input/mp4/scene_with_qrcodes.mov new file mode 100644 index 0000000..96d5282 Binary files /dev/null and b/test/input/mp4/scene_with_qrcodes.mov differ diff --git a/test/input/png/Thumbs.db b/test/input/png/Thumbs.db new file mode 100644 index 0000000..f3b1d54 Binary files /dev/null and b/test/input/png/Thumbs.db differ diff --git a/test/input/png/apple.png.png b/test/input/png/apple.png.png deleted file mode 100644 index 0b23317..0000000 Binary files a/test/input/png/apple.png.png and /dev/null differ diff --git a/test/input/png/banana.png.png b/test/input/png/banana.png.png deleted file mode 100644 index a8eda53..0000000 Binary files a/test/input/png/banana.png.png and /dev/null differ diff --git a/test/input/png/billy_decor_1.png b/test/input/png/billy_decor_1.png new file mode 100644 index 0000000..c64ef2c Binary files /dev/null and b/test/input/png/billy_decor_1.png differ diff --git a/test/input/png/billy_decor_2.png b/test/input/png/billy_decor_2.png new file mode 100644 index 0000000..a793032 Binary files /dev/null and b/test/input/png/billy_decor_2.png differ diff --git a/test/input/png/billy_decor_3.png b/test/input/png/billy_decor_3.png new file mode 100644 index 0000000..8645f25 Binary files /dev/null and b/test/input/png/billy_decor_3.png differ diff --git a/test/input/png/billy_decor_3095.png b/test/input/png/billy_decor_3095.png new file mode 100644 index 0000000..1a1da1a Binary files /dev/null and b/test/input/png/billy_decor_3095.png differ diff --git a/test/input/png/billy_decor_6039.png b/test/input/png/billy_decor_6039.png new file mode 100644 index 0000000..58e6f1c Binary files /dev/null and b/test/input/png/billy_decor_6039.png differ diff --git a/test/input/png/dog.png.png b/test/input/png/dog.png.png deleted file mode 100644 index 819c344..0000000 Binary files a/test/input/png/dog.png.png and /dev/null differ diff --git a/test/input/png/grappes.png.png b/test/input/png/grappes.png.png deleted file mode 100644 index 8f25bf9..0000000 Binary files a/test/input/png/grappes.png.png and /dev/null differ diff --git a/test/input/png/simple_shape.png b/test/input/png/simple_shape.png new file mode 100644 index 0000000..6a392b8 Binary files /dev/null and b/test/input/png/simple_shape.png differ diff --git a/test/input/png/single_images/Thumbs.db b/test/input/png/single_images/Thumbs.db new file mode 100644 index 0000000..3f98d68 Binary files /dev/null and b/test/input/png/single_images/Thumbs.db differ diff --git a/test/input/png/single_images/billy_decor_1.png b/test/input/png/single_images/billy_decor_1.png new file mode 100644 index 0000000..c64ef2c Binary files /dev/null and b/test/input/png/single_images/billy_decor_1.png differ diff --git a/test/input/png/single_images/convert_me_1.png b/test/input/png/single_images/convert_me_1.png new file mode 100644 index 0000000..821ede9 Binary files /dev/null and b/test/input/png/single_images/convert_me_1.png differ diff --git a/test/input/png/single_images/detect_me.png b/test/input/png/single_images/detect_me.png new file mode 100644 index 0000000..9ed8d1d Binary files /dev/null and b/test/input/png/single_images/detect_me.png differ diff --git a/test/input/png/single_images/image_A.png b/test/input/png/single_images/image_A.png new file mode 100644 index 0000000..ddb0b3a Binary files /dev/null and b/test/input/png/single_images/image_A.png differ diff --git a/test/input/png/single_images/image_B.png b/test/input/png/single_images/image_B.png new file mode 100644 index 0000000..002b366 Binary files /dev/null and b/test/input/png/single_images/image_B.png differ diff --git a/test/input/video/scene_with_qrcodes.mov b/test/input/video/scene_with_qrcodes.mov new file mode 100644 index 0000000..ef4cc2b Binary files /dev/null and b/test/input/video/scene_with_qrcodes.mov differ diff --git a/test/output/generate/Thumbs.db b/test/output/generate/Thumbs.db new file mode 100644 index 0000000..6b6c260 Binary files /dev/null and b/test/output/generate/Thumbs.db differ