以鼠标位置进行图像非中心zoom

#coding=utf-8
# Reference

# tkinter的Label控件以及三种布局管理方法 
# https://www.cnblogs.com/jackie-lee/p/16191662.html 

# python 对话框图形界面显示图片
# https://blog.csdn.net/SAPmatinal/article/details/131818285  

# 菜单设置
# https://blog.csdn.net/weixin_42272768/article/details/100808828  

# tk布局设置
# https://www.cnblogs.com/peachh/p/16776734.html 

#opencv PIL格式互相转换
# https://blog.csdn.net/qq_19707521/article/details/78367617

#Pyinstaller 打包exe后,打开出现can‘t find package tkdnd 报错
#https://blog.csdn.net/qq_43079386/article/details/139725149

#十六进制颜色取色  google
#https://www.sioe.cn/yingyong/yanse-rgb-16/ color convert

#1 BUILD
# pyinstaller -F --windowed oplus_imageview.py --paths "C:\Program Files\Python311\lib\site-packages" --add-data "C:\Program Files\Python311\lib\site-packages\tkinterdnd2;tkinterdnd2"  --hidden-import=tkinterdnd2 --hidden-import=tkdnd --clean
# 2 DEBUG
# 如果在打包过程中遇到问题,可以将输出重定向到一个日志文件中,以便详细检查:
# pyinstaller -F ccc.py --paths C:\Program Files\Python311\lib\site-packages --add-data "C:\Program Files\Python311\lib\site-packages\tkinterdnd2;tkinterdnd2" --hidden-import=tkinterdnd2 --hidden-import=tkdnd --clean > build_log.txt 2>&1
# 这样所有输出(包括错误信息)都会保存到 build_log.txt 文件中,你可以查看该文件以获取详细的调试信息。

import tkinter as tk  
import tkinter.filedialog
from tkinter import messagebox
from tkinter import font
from tkinterdnd2 import DND_FILES, TkinterDnD  
from PIL import Image, ImageTk  
import cv2
import numpy as np
import os
import re  
import time
import shutil

def timebegin():
    # 记录开始时间
    # global start_time
    start_time = time.time()
    return start_time

def timeend(code_blokc, start_time):
    # 记录结束时间
    # global end_time
    end_time = time.time()
    # 计算耗时(单位:秒)
    elapsed_time = end_time - start_time
    # print(code_blokc + f" 代码块耗时: {elapsed_time} 秒")

class Rect:
    def __init__(self, x = 0, y = 0, width = 0, height = 0):
       self.x = x
       self.y = y
       self.width = width
       self.height = height

class Point:
    def __init__(self, x = 0, y = 0):
       self.x = x
       self.y = y

SDK_VERSION = "1.0.0"

image_show_width = 1024
image_show_height = 768
window_width = image_show_width
window_height = image_show_height+130
check10bit_width_thresh = 5800
suport_image_format = ('.yuv', '.gray16', 'gray','.png', '.jpg', '.jpeg', '.gif', '.bmp')

#打开二进制buffer gray8 gray16, 需要输入buffer 宽度高度
def cv_imread_gray(file_path, image_bit, width8, height8):
    if(image_bit == "16bit"):
        databuf16 = np.fromfile(file_path, dtype=np.uint16)
        databuf8 = (databuf16 / 4).astype(np.uint8)#取低8bit数据
        cv_img = databuf8.reshape(height8, width8)
    elif(image_bit == "8bit"):
        databuf8 = np.fromfile(file_path, dtype=np.uint8)
        cv_img = databuf8.reshape((height8, width8))
    else:
        cv_img = None
    return cv_img

#打开普通图像,如png jpeg 自动解码
def cv_imread(file_path, image_bit):
    if(image_bit == "8bit"):
        cv_img = cv2.imdecode(np.fromfile(file_path,dtype=np.uint8), -1)
    else:
        cv_img = None
    return cv_img

#NV21 8bit转为BGR
def NV21_P8_CONVERT_BGR(yuv_path,width,height):
    with open(yuv_path, 'rb') as f:
        yuvdata = np.fromfile(f, dtype=np.uint8)
    cv_format=cv2.COLOR_YUV2BGR_NV21
    bgr_img = cv2.cvtColor(yuvdata.reshape(((int)(height*3/2), width)), cv_format)                    
    return bgr_img

#NV12 10bit转为BGR
def NV12_P010_CONVERT_BGR(file_path, width10, height10):
    width8 = int(width10 / 2)
    height8 = height10
    databuf10 = np.fromfile(file_path, dtype=np.uint16)
    databuf8 = (databuf10 / 4).astype(np.uint8)#取低8bit数据
    cv_format=cv2.COLOR_YUV2BGR_NV12
    bgr_img = cv2.cvtColor(databuf8.reshape(((int)(height8*3/2), width8)), cv_format)                    
    return bgr_img

def find_number_x_number(s):  
    # 定义正则表达式模式  
    # pattern = r'\b(\d+)x(\d+)\b'  
    # input_string = "IMG20240926193220_wh=4096x3072_ss=4096x3072_idx0_beforebeauty_doBokehEffect"
    pattern = r'ss\=(\d+)x(\d+)'   #find _ss=4096x3072
    # 使用 re.findall() 查找所有匹配的内容  
    matches = re.findall(pattern, s) 
    if len(matches) == 0:
        # input_string = "IMG20240926193220_4096x3072_idx0_beforebeauty_doBokehEffect"
        pattern = r'\_(\d+)x(\d+)'  #find _4096x3072
        # 使用 re.findall() 查找所有匹配的内容  
        matches = re.findall(pattern, s) 
        print(matches)
    return matches  

def find_yuvimage_size(input_string):
    results = find_number_x_number(input_string)  
    width = -1
    height = -1
    for match in results:  
        # print(f"{match[0]}x{match[1]}") 
        width = int(match[0])
        height = int(match[1])
    return width, height

def findAllfile_slow(path, allfile):
    filelist =  os.listdir(path)  
    for filename in filelist:  
        filepath = os.path.join(path, filename) 
        # print(filepath) 
        if os.path.isdir(filepath):
            # print(filepath)  
            findAllfile(filepath, allfile)  
        else:  
            # allfile.append(filepath) 
            # allfile = allfile + "/" + filepath
            allfile.append(filename)  
    return allfile  

def findAllfile_signlefolder(path, allfile):
    filelist =  os.listdir(path)  
    for filename in filelist:  
        allfile.append(filename)  
    return allfile  

def GetStringFileFormat(image_path):
    format = "." + image_path.split('.')[-1]
    return format

def GetStringFileImageId(image_path):
    ImageId = image_path.split('_')[0]
    return ImageId

def close_main_if_no_child(root, child):
    def on_child_close():
        if not child.winfo_exists():
            root.destroy()
    return on_child_close

class ImageDropApp:  
    def __init__(self, root):  
        self.root = root  
        self.root.title("图片查看器--IMAGE_VIEW_" + SDK_VERSION)  
        self.root.geometry(f"{window_width}x{window_height}+800+50")  
        self.image_has_open = False
        self.show_image_PIL_SRC = None
        self.show_image_PIL_small = None
        self.last_save_imgpath = None
        # self.save_batchfile_choosefolder()

        # #创建一个frame窗体对象,用来包裹标签
        # self.frame = tk.Frame(self.root, relief=tk.SUNKEN, borderwidth=2, width=450, height=250)
        # self.frame.place(x=0, y=0, relheight=1.0, relwidth=1.0, bordermode = 'outside')

        # 创建标签用于显示图片像素坐标和像素值   
        self.label_imgpx = tk.Label(self.root, text="图像坐标与像素值:", width=image_show_width, height=20, bg="lightgrey", anchor = "nw") 
        self.label_imgpx.place(x=0, y=0, width=image_show_width, height=20, bordermode = 'outside')

        # 创建标签用于显示图片    
        self.label_imgshow = tk.Label(self.root, text="拖拽图片到这里", width=image_show_width, height=image_show_height, bg="lightgrey") 
        self.label_imgshow.place(relx = 0.0, rely=1.0, in_ = self.label_imgpx,\
                                 width=image_show_width, height=image_show_height, bordermode = 'outside')
       
        # 注册拖放功能  
        self.label_imgshow.drop_target_register(DND_FILES)  
        self.label_imgshow.dnd_bind('<<Drop>>', self.on_drop) 
        
        self.text_box = tk.Text(self.root, height=8, width=200)  # 设置文本框的高度和宽度   
        self.text_box.place(relx = 0.0, rely=1.0, in_ = self.label_imgshow, width=image_show_width, height=100, bordermode = 'outside')
        # 将 Text 控件设置为只读  
        # self.text_box.config(state=tk.DISABLED) 

        #image zoom 
        self.imgAdapting_display = self.show_image_PIL_SRC
        self.imgZoom = self.imgAdapting_display

        self.zoom_value = 1.0
        self.zoom_value_before = self.zoom_value
        self.showRect = Rect(0,0,image_show_width,image_show_height)
        self.showRect_xshift  = 0
        self.showRect_yshift  = 0
        self.Mouse_leftClik_pix = Point(0,0)
        self.Mouse_leftRelease_pix = Point(image_show_width-1,image_show_height-1)

        # 绑定鼠标左键单击事件到按钮控件上  
        self.label_imgshow.bind("<Button-1>", self.on_mouse_left_click)  
        self.label_imgshow.bind("<ButtonRelease-1>", self.on_mouse_left_Release)  
        self.label_imgshow.bind("<MouseWheel>", self.image_show_zoom)
        # self.label_imgshow.bind("<Motion>", self.show_coordinates_zoom) 
        self.label_imgshow.bind("<Motion>", self.show_coordinates)  

        #设置打开关闭菜单
        menubar = tk.Menu(root)
        filemenu=tk.Menu(menubar,activebackground='blue',tearoff=False)

        filemenu.add_command(label='打开文件',command=self.open_file)
        filemenu.add_command(label='另存文件',command=self.save_file_choosefolder)
        filemenu.add_command(label='批量另存YUV文件',command=self.save_batchfile_choosefolder)
        menubar.add_cascade(label='文件', menu=filemenu)
        menubar.add_command(label='退出',command=root.destroy)
        menubar.add_command(label='使用说明',command=self.show_guide)

        root.bind('<Control-o>',self.open_file)
        root.config(menu=menubar)

    def show_guide(self): 
        dialog = tk.Toplevel()
        dialog.geometry(f"{500}x{350}+810+55") 
        dialog.title("使用说明") 

        def open_link(event):
            import webbrowser
            webbrowser.open("https://odocs.myoas.com/docs/1lq7Mgb0XgS5O6Ae")

        texts = [
        "version " + SDK_VERSION, 
        "please reference  "
        ]
        text_box = tk.Text(dialog)
        text_box.place(x=0, y=0,  relwidth=1.0, relheight=0.12, bordermode = 'outside')

        for text in texts:
           text_box.insert(tk.END, text + '\n')
           text_box.see(tk.END)
           # dialog.update()

        text_widget = tk.Text(dialog)
        text_widget.place(relx = 0.0, rely=1.0, in_ = text_box, relwidth=1.0, relheight=1.0,bordermode = 'outside')

        # 设置超链接文本
        hyperlink_text = "https://odocs.myoas.com/docs/1lq7Mgb0XgS5O6Ae"
        text_widget.insert(tk.END, hyperlink_text)

        # 设置超链接的字体样式和颜色
        hyperlink_font = font.Font(text_widget, text_widget.cget("font"))
        hyperlink_font.configure(underline=True)
        text_widget.tag_configure("hyperlink", font=hyperlink_font, foreground="blue")
        text_widget.tag_add("hyperlink", "1.0", "1.end")

        # 绑定超链接的点击事件
        text_widget.tag_bind("hyperlink", "<Button-1>", open_link)

        # 设置鼠标停留显示手势
        def show_hand_cursor(event):
            text_widget.config(cursor="hand2")

        def hide_hand_cursor(event):
            text_widget.config(cursor="")

        text_widget.tag_bind("hyperlink", "<Enter>", show_hand_cursor)
        text_widget.tag_bind("hyperlink", "<Leave>", hide_hand_cursor)

        dialog.update()

    #image zoom  BEGIN..........................
    def on_mouse_left_click(self, event):  
        # print("on_mouse_left_click at:", event.x, event.y) 
        self.Mouse_leftClik_pix.x = event.x 
        self.Mouse_leftClik_pix.y = event.y
        # self.label_imgpx.config(text=f"on_mouse_left_click: ({self.zoom_value}, {self.Mouse_leftClik_pix.x}, {self.Mouse_leftClik_pix.y})")  
         
    def on_mouse_left_Release(self, event):  
        if self.imgZoom is not None:
            # print("on_mouse_left_Release at:", event.x, event.y)  
            self.Mouse_leftRelease_pix.x = event.x 
            self.Mouse_leftRelease_pix.y = event.y
            # self.label_imgpx.config(text=f"on_mouse_left_Release: ({self.zoom_value}, {self.Mouse_leftRelease_pix.x}, {self.Mouse_leftRelease_pix.y})")  

            #imgZoom的图像比原始图像大,从imgZoom的图像中crop一块区域进行显示,crop的大小为显示控件的size
            self.showRect_xshift  = self.Mouse_leftRelease_pix.x - self.Mouse_leftClik_pix.x 
            self.showRect_yshift  = self.Mouse_leftRelease_pix.y - self.Mouse_leftClik_pix.y 

            print("on_mouse_left_Release showRect before ", self.showRect.x, self.showRect.y)

            if(self.showRect.x - self.showRect_xshift < 0):
                self.showRect.x = 0
            else:
                self.showRect.x = self.showRect.x - self.showRect_xshift

            if(self.showRect.y - self.showRect_yshift < 0):
                self.showRect.y = 0
            else:
                self.showRect.y = self.showRect.y - self.showRect_yshift 

            if(self.showRect.x + self.showRect.width - 1 > self.imgZoom.size[0] - 1 ):
                self.showRect.x = self.imgZoom.size[0] - self.showRect.width

            if(self.showRect.y + self.showRect.height - 1 > self.imgZoom.size[1] - 1 ):
                self.showRect.y = self.imgZoom.size[1] - self.showRect.height

            print("on_mouse_left_Release showRect after ", self.showRect.x, self.showRect.y)

            left = self.showRect.x
            top = self.showRect.y
            width = self.showRect.width
            height = self.showRect.height
            crop_rect = (left, top, left + width, top + height)
            self.cropped_show_image = self.imgZoom.crop(crop_rect)

            self.show_lable_img()
      
    def image_show_zoom(self, event):
        # 1 imgAdapting_display用于控件显示的1x倍率图像,FOV为原始FOV, 大小与控件大小一致
        # 2 imgZoom是根据当前zoom_value,缩放的图像,FOV为原始FOV
        # 3 cropped_show_image 图像大小控件大小一致, FOV不确定
        start_time = timebegin()

        # 获取鼠标位置或其他指定的缩放触发点位置
        ptMouse = Point(event.x, event.y)
        print("ptMouse ", ptMouse.x, ptMouse.y)

        scalestep = 1.21

        self.zoom_value_before = self.zoom_value
        if (event.delta  > 0):#放大图片
            if (self.zoom_value > 10):
                return
            else:
                self.zoom_value = self.zoom_value * scalestep;
        elif (event.delta < 0): #缩小图片
            self.zoom_value /= scalestep
            if(self.zoom_value < 1.0):
                self.zoom_value = 1.0


        # print("zoom_value ", self.zoom_value)

        zoom_width = int(image_show_width * self.zoom_value)
        zoom_height = int(image_show_height * self.zoom_value)

        timeend("mgAdapting_display.resize pre", start_time)
        start_time = timebegin()

        self.imgZoom = self.imgAdapting_display.resize((zoom_width, zoom_height), Image.LANCZOS)

        timeend("mgAdapting_display.resize", start_time)

        start_time = timebegin()

        print("zoomfun showRect before ", (int)(self.showRect.x), (int)(self.showRect.y))
        print("zoomfun self.zoom_value_before ", self.zoom_value_before)
        print("zoomfun self.zoom_value ", self.zoom_value )

        #保持图像放大后,鼠标所指向的像素位置与原始像素位置一致
        self.showRect.x = (self.zoom_value / self.zoom_value_before)*(self.showRect.x + ptMouse.x) - ptMouse.x
        self.showRect.y = (self.zoom_value / self.zoom_value_before)*(self.showRect.y + ptMouse.y) - ptMouse.y

        print("zoomfun showRect after ", (int)(self.showRect.x), (int)(self.showRect.y))

        self.zoom_value_before = self.zoom_value

        self.showRect.width = image_show_width
        self.showRect.height = image_show_height
        
        if self.showRect.x < 0:
            self.showRect.x = 0
        if self.showRect.y < 0:
            self.showRect.y = 0

        if(self.showRect.x + self.showRect.width - 1 > self.imgZoom.size[0] - 1 ):
            self.showRect.x = self.imgZoom.size[0] - self.showRect.width

        if(self.showRect.y + self.showRect.height - 1 > self.imgZoom.size[1] - 1 ):
            self.showRect.y = self.imgZoom.size[1] - self.showRect.height

        self.showRect.x = int(self.showRect.x)
        self.showRect.y = int(self.showRect.y)

        left = self.showRect.x
        top = self.showRect.y
        width = self.showRect.width
        height = self.showRect.height
        crop_rect = (left, top, left + width, top + height)
        self.cropped_show_image = self.imgZoom.crop(crop_rect)
        timeend("cropped_show_image ", start_time)

        self.show_coordinates(event)

        self.show_lable_img()
     
    def show_lable_img(self):
        start_time = timebegin()
        # 更新Label中的图像
        self.photo_image = ImageTk.PhotoImage(self.cropped_show_image)
        self.label_imgshow.config(image = self.photo_image)
        self.label_imgshow.image = self.photo_image
        # self.label_imgshow.bind("<Motion>", self.show_coordinates_zoom)
        timeend("更新Label中的图像 ", start_time)

    def show_coordinates(self, event):  
        if self.show_image_PIL_SRC is not None:             
            #get mouse pix
            ptMouse = Point(event.x, event.y)

            #convert mouse pix To self.imgZoom pix
            ptZoom = Point(self.showRect.x + ptMouse.x, self.showRect.y + ptMouse.y)

            #convert self.imgZoom pix to SRC img
            scalex = self.show_image_PIL_SRC.width / self.imgZoom.width
            scaley = self.show_image_PIL_SRC.height / self.imgZoom.height

            x = int(ptZoom.x * scalex)
            y = int(ptZoom.y * scaley)
            
            zoomvalue = (int) (self.zoom_value * 100)  
            if x < self.show_image_PIL_SRC.width and y < self.show_image_PIL_SRC.height:  
                # 获取像素值  
                pixel_value = self.show_image_PIL_SRC.getpixel((x, y))  
                self.label_imgpx.config(text=f"Pixel zoom: ({zoomvalue}%)- Pixel Coordinates: ({x}, {y}) - Pixel RGB_value: {pixel_value}") 
    #image zoom  END.......................... 

    def winshow_image_PIL_SRC(self):
        self.show_image_PIL_small = self.show_image_PIL_SRC.resize((image_show_width, image_show_height))#, Image.ANTIALIAS) 

        # 将图片转换为Tkinter格式  
        photo = ImageTk.PhotoImage(self.show_image_PIL_small)  

        # 更新标签内容以显示图片  
        self.label_imgshow.config(image=photo)  
        self.label_imgshow.image = photo  # 保持引用以避免被垃圾回收  
        self.image_has_open = True

        self.imgAdapting_display = self.show_image_PIL_small 
        self.imgZoom = self.imgAdapting_display

    def winshow_image(self, image_path):
        self.current_image_path = image_path
        self.image_format = "." + image_path.split('.')[-1]
        if self.image_format == ".yuv":
            #fine image size
            self.image_srcwidth, self.image_srcheight = find_yuvimage_size(image_path)
            print("image src size " + str(self.image_srcwidth) + "x" + str(self.image_srcheight))
            self.image_channels = 3
            print("image src channels " + str(self.image_channels))

            if self.image_srcwidth == -1:
                messagebox.showinfo("prompt","not find yuv image size")
                return None

            #存储opencv原图
            if self.image_srcwidth > check10bit_width_thresh:
                self.image_bit = "10bit"
                print("input p010 yuv ")
                self.save_image_cv = NV12_P010_CONVERT_BGR(self.current_image_path, self.image_srcwidth, self.image_srcheight)
                self.image_srcwidth = int(self.image_srcwidth / 2)
                print("image src size p010_TO_p8 " + str(self.image_srcwidth) + "x" + str(self.image_srcheight))
            else:
                self.image_bit = "8bit"
                self.save_image_cv = NV21_P8_CONVERT_BGR(self.current_image_path, self.image_srcwidth, self.image_srcheight)        

            #opencv图像转换为PTL图像进行显示
            self.show_image_PIL_SRC = Image.fromarray(cv2.cvtColor(self.save_image_cv,cv2.COLOR_BGR2RGB))

            self.winshow_image_PIL_SRC()

        elif self.image_format == ".gray16":      
            self.image_bit = "16bit"
            #fine image size
            self.image_srcwidth, self.image_srcheight = find_yuvimage_size(image_path)
            if self.image_srcwidth == -1:
                messagebox.showinfo("prompt","not find yuv image size")
                return None
            self.image_srcwidth = int(self.image_srcwidth / 2)

            #存储opencv原图
            self.save_image_cv = cv_imread_gray(image_path, self.image_bit, self.image_srcwidth, self.image_srcheight ) 
            if self.save_image_cv is not None:
                shape_tuple = self.save_image_cv.shape
                self.image_channels = 1
                self.image_srcheight = shape_tuple[0]
                self.image_srcwidth = shape_tuple[1]
                print("image src size " + str(self.image_srcwidth) + "x" + str(self.image_srcheight))
                print("image src channels " + str(self.image_channels))
            else:
                messagebox.showinfo("prompt","open image failed")
                return

            #opencv图像转换为PTL图像进行显示
            self.show_image_PIL_SRC = Image.fromarray(self.save_image_cv)
            self.winshow_image_PIL_SRC()

        elif self.image_format == ".gray":      
            self.image_bit = "8bit"
            #fine image size
            self.image_srcwidth, self.image_srcheight = find_yuvimage_size(image_path)
            if self.image_srcwidth == -1:
                messagebox.showinfo("prompt","not find yuv image size")
                return None

            #存储opencv原图
            self.save_image_cv = cv_imread_gray(image_path, self.image_bit, self.image_srcwidth, self.image_srcheight ) 
            if self.save_image_cv is not None:
                shape_tuple = self.save_image_cv.shape
                self.image_channels = 1
                self.image_srcheight = shape_tuple[0]
                self.image_srcwidth = shape_tuple[1]
                print("image src size " + str(self.image_srcwidth) + "x" + str(self.image_srcheight))
                print("image src channels " + str(self.image_channels))
            else:
                messagebox.showinfo("prompt","open image failed")
                return

            #opencv图像转换为PTL图像进行显示
            self.show_image_PIL_SRC = Image.fromarray(self.save_image_cv)
            self.winshow_image_PIL_SRC()

        else:
            self.image_bit = "8bit"
            #存储opencv原图
            self.save_image_cv = cv_imread(image_path, self.image_bit) 
            if self.save_image_cv is not None:
                shape_tuple = self.save_image_cv.shape
                if(len(shape_tuple) == 2):
                    self.image_channels = 1
                else:
                    self.image_channels = 3
                self.image_srcheight = shape_tuple[0]
                self.image_srcwidth = shape_tuple[1]
                print("image src size " + str(self.image_srcwidth) + "x" + str(self.image_srcheight))
                print("image src channels " + str(self.image_channels))
            else:
                messagebox.showinfo("prompt","open image failed")
                return

            #opencv图像转换为PTL图像进行显示
            if self.image_channels == 3:
                print("image debug")
                self.show_image_PIL_SRC = Image.fromarray(cv2.cvtColor(self.save_image_cv,cv2.COLOR_BGR2RGB))
            else:
                self.show_image_PIL_SRC = Image.fromarray(self.save_image_cv)

            self.winshow_image_PIL_SRC()

        showinfo_text_box = "image_path: " + self.current_image_path + "\n" \
                            +"image_srcwidth: " + str(self.image_srcwidth) + "\n" \
                            +"image_srcheight: " + str(self.image_srcheight) + "\n" \
                            +"image_channels: " + str(self.image_channels) + "\n" \
                            +"image_format: " + self.image_format + "\n" \
                            +"image_bit: " + self.image_bit + "\n" \

        self.text_box.delete("1.0", tk.END)  # 从第一行第一列开始删除到最后 
        self.text_box.insert(tk.END, showinfo_text_box)  

    def on_drop(self, event):  
        # 获取文件路径  
        file_path = event.data
        #如果文件路径出现中文"新建文件夹",file_path会增加{file_path}
        if(len(file_path)> 2):
            if file_path[0] == "{" and file_path[-1] == "}":
                print("chinese path " + file_path)
                file_path = file_path[1:-1]
                print("remove {} in path " + file_path)
 
        if file_path.endswith(suport_image_format):  
            print("find " + file_path)
            self.winshow_image(file_path) 
        else:
            print(file_path + " format not support")
            messagebox.showinfo("prompt","this file format not support, please choose format like these---" \
                                + str(suport_image_format))

    def open_file(self):
        file_path = tkinter.filedialog.askopenfilename()
        if file_path.endswith(suport_image_format):  
            print("find " + file_path)
            self.winshow_image(file_path) 
        else:
            print(file_path + " format not support")
            messagebox.showinfo("prompt","this file format not support, please choose format like these---" \
                                + str(suport_image_format))

    def save_file_choosefolder(self):
        if self.image_has_open:
            defaultextension = ".png"
            init_current_image_name = self.current_image_path.replace(self.image_format, defaultextension)
            # print("src file " + init_current_image_name)

            if self.last_save_imgpath  == None:
                print("first save")
                initialdir  = os.path.dirname(self.current_image_path)
            else:
                print("not first save")
                print("last_save_imgpath " + self.last_save_imgpath)
                initialdir = os.path.dirname(self.last_save_imgpath)

            print("initialdir " + initialdir)
            
            savefolderpath = tkinter.filedialog.asksaveasfilename(
                                title="保存图像",  
                                defaultextension=".png",  
                                initialfile=os.path.basename(init_current_image_name),   
                                initialdir = initialdir,  
                                filetypes=(("PNG files", "*.png"), ("JPEG files", "*.jpg"), ("All files", "*.*")))

            image_save_format = "." + savefolderpath.split('.')[-1]

            if savefolderpath:   
                print("save file " + savefolderpath)
                self.last_save_imgpath = savefolderpath
                showinfo_text_box = "save image_path: " + savefolderpath + " begin\n" 
                self.text_box.insert(tk.END, showinfo_text_box) 
                # image = cv_imread(self.current_image_path)
                cv2.imencode(image_save_format, self.save_image_cv)[1].tofile(savefolderpath)
                showinfo_text_box = "save image_path: " + savefolderpath + " end\n" 
                self.text_box.insert(tk.END, showinfo_text_box) 
        else:
            print("please open image file") 
            messagebox.showinfo("prompt","please open image file")

    def save_batchfile_choosefolder(self):
        dialog = tk.Toplevel(self.root)
        dialog.geometry(f"{1000}x{800}+810+55") 
        dialog.title("批量转换yuv文件-支持文件格式: 1. 8bit NV21,  2.10bit  NV12") 

        filepath_textwidth = 800
        filepath_textheigth = 20

        imageid_textwidth = 200
        imageid_textheight = 20

        OBJECT_DIST_SCALE_X = 0.1
        OBJECT_DIST_SCALE_Y = 0.2

        #创建一个frame窗体对象,用来包裹标签
        frame = tk.Frame(dialog, relief=tk.SUNKEN, borderwidth=2, width=450, height=250)

        # 在水平、垂直方向上填充窗体
        # frame.pack(side=tk.TOP, fill=tk.BOTH, expand = True)
        frame.place(x=0, y=0, relheight=1.0, relwidth=1.0, bordermode = 'outside')

        # frame.bind("<Configure>",  lambda event: update_coordinates(frame, "frame"))

        label_inputpath = tk.Label(frame, text="input path:",bg='#a85632',fg='black', anchor = 'center')
        label_inputpath.place(x=0, y=0, width=100, height=20, bordermode = 'outside')

        entry_inputpath = tk.Entry(frame, width = 150)
        # entry_inputpath.insert(0, r"D:\Users\Desktop\bug\bokehdump")
        entry_inputpath.place(relx = 1.0, rely=0, in_ = label_inputpath, width=filepath_textwidth, height=filepath_textheigth, bordermode = 'outside')

        label_outputpath = tk.Label(frame, text="output path:",bg='#a8a632',fg='black', anchor = 'center')
        label_outputpath.place(in_ = label_inputpath, relx = 0.0, rely=1.0 + OBJECT_DIST_SCALE_Y, width=100, height=20, bordermode = 'outside')

        entry_outputpath = tk.Entry(frame, width = 150)
        entry_outputpath.place(in_ = label_outputpath, relx = 1, rely=0, width=filepath_textwidth, height=filepath_textheigth, bordermode = 'outside')
        # entry_outputpath.insert(0, r"D:\Users\Desktop\bug\bokehdump_png")

        label_imageid = tk.Label(frame, text="imageid:",bg='#6da832',fg='black', anchor = 'center')
        label_imageid.place(relx = 0, rely=1 + OBJECT_DIST_SCALE_Y, in_ = label_outputpath, width=100, height=20, bordermode = 'outside')

        entry_imageid = tk.Entry(frame, width = 150)
        entry_imageid.place(relx = 1, rely=0, in_ = label_imageid, width=imageid_textwidth, height=imageid_textheight, bordermode = 'outside')
        entry_imageid.insert(0, r"default_all_id")

        label_output_format = tk.Label(frame, text="outputformat:",bg='#326fa8',fg='black', anchor = 'center')
        label_output_format.place(relx = 0, rely= 1.0 + OBJECT_DIST_SCALE_Y, in_ = label_imageid, width=100, height=20, bordermode = 'outside')

        var_output_format = tk.StringVar()
        var_output_format.set(".png") # 默认选中选项png
        radio1 = tk.Radiobutton(frame, text = "png", variable = var_output_format, value = ".png")
        radio2 = tk.Radiobutton(frame, text = "jpg", variable = var_output_format, value = ".jpg")

        radio1.place(relx = 1, rely=0, in_ = label_output_format, bordermode = 'outside')
        radio2.place(relx = 1, rely=0, in_ = radio1, bordermode = 'outside')      

        def batch_yuv_convetimg(inputpath, outputpath, output_format):
            image_srcwidth, image_srcheight = find_yuvimage_size(inputpath)
            print(inputpath + "-image src size " + str(image_srcwidth) + "x" + str(image_srcheight))

            if image_srcwidth == -1:
                messagebox.showinfo("prompt","not find yuv image size")
                return None

            save_image_cv = None
            if image_srcwidth > check10bit_width_thresh:
                save_image_cv = NV12_P010_CONVERT_BGR(inputpath, image_srcwidth, image_srcheight)
                image_srcwidth = int(image_srcwidth / 2)
                print("image src size p010_TO_p8 " + str(image_srcwidth) + "x" + str(image_srcheight))
            else:
                save_image_cv = NV21_P8_CONVERT_BGR(inputpath, image_srcwidth, image_srcheight)  

            print(outputpath)
            cv2.imencode(output_format, save_image_cv)[1].tofile(outputpath)


        def convert_yuv_fun():
            inputpath = entry_inputpath.get()
            print(f"inputpath: {inputpath}")
            outputpath = entry_outputpath.get()
            print(f"outputpath: {outputpath}")
            imageid = entry_imageid.get()
            print(f"图像ID: {imageid}")
            output_format = var_output_format.get()
            print(f"图像format: {output_format}")

            if not os.path.exists(inputpath):
                messagebox.showinfo("prompt", "inputpath " + inputpath + " not exist!!!")
                return None

            if imageid != "default_all_id":
                outputpath = outputpath + '/' + imageid

            if not os.path.exists(outputpath):
                # messagebox.showinfo("prompt", "outputpath " + outputpath + " not exist will mkdir!!!") 
                showinfo_text_box = "outputpath " + outputpath + " not exist will mkdir!!!"
                text_box.insert(tk.END, showinfo_text_box)  
                text_box.see(tk.END)
                dialog.update()
                print(outputpath)
                os.makedirs(outputpath)
                # return None

            text_box.delete("1.0", tk.END)  # 从第一行第一列开始删除到最后 
            filelist = []
            filelist = findAllfile_signlefolder(inputpath, filelist)#find all file

            #find dist imageid
            filename_list=[]
            if imageid == "default_all_id":
                for filesrcname in filelist: 
                    file_format = GetStringFileFormat(filesrcname)    
                    if file_format.find(".yuv") != -1: 
                        filename_list.append(filesrcname)
            else:
                for filesrcname in filelist: 
                    file_format = GetStringFileFormat(filesrcname)    
                    if file_format.find(".yuv") != -1 and GetStringFileImageId(filesrcname) == imageid: 
                        filename_list.append(filesrcname)

            process_num = len(filename_list)
            index = 1
            for filename in filename_list:                  
                # time.sleep(0.1)
                showinfo_text_box = "process: " + str((int)(index / process_num * 100 ))+ "% " + filename + "  beign \n"
                # print(showinfo_text_box)                    
                text_box.insert(tk.END, showinfo_text_box)  
                text_box.see(tk.END)
                dialog.update()

                file_save = filename.replace(".yuv", output_format)

                batch_yuv_convetimg(inputpath + "/" + filename, outputpath + "/" +file_save, output_format)

                showinfo_text_box = "process: " + str((int)(index / process_num * 100 )) + "% " + file_save + " end \n"
                # print(showinfo_text_box)                    
                text_box.insert(tk.END, showinfo_text_box)  
                text_box.see(tk.END)
                dialog.update()
                index = index+1


        def copydumpfile_fun():
            inputpath = entry_inputpath.get()
            print(f"inputpath: {inputpath}")
            outputpath = entry_outputpath.get()
            print(f"outputpath: {outputpath}")
            imageid = entry_imageid.get()
            print(f"图像ID: {imageid}")
            output_format = var_output_format.get()
            print(f"图像format: {output_format}")

            if not os.path.exists(inputpath):
                messagebox.showinfo("prompt", "inputpath " + inputpath + " not exist!!!")
                return None

            if imageid != "default_all_id":
                outputpath = outputpath + '/' + imageid


            if not os.path.exists(outputpath):
                # messagebox.showinfo("prompt", "outputpath " + outputpath + " not exist will mkdir!!!")
                showinfo_text_box = "outputpath " + outputpath + " not exist will mkdir!!!"
                text_box.insert(tk.END, showinfo_text_box)  
                text_box.see(tk.END)
                dialog.update()
                print(outputpath)
                os.makedirs(outputpath)
                # return None

            text_box.delete("1.0", tk.END)  # 从第一行第一列开始删除到最后 
            filelist = []
            filelist = findAllfile_signlefolder(inputpath, filelist)#find all file

            #find dist imageid
            filename_list=[]
            if imageid == "default_all_id":
                for filesrcname in filelist:  
                    filename_list.append(filesrcname)
            else:
                for filesrcname in filelist:  
                    if GetStringFileImageId(filesrcname) == imageid: 
                        filename_list.append(filesrcname)

            process_num = len(filename_list)
            index = 1
            for filename in filename_list:                  
                # time.sleep(0.1)
                showinfo_text_box = "process: " + str((int)(index / process_num * 100 ))+ "% " + filename + "  beign \n"
                # print(showinfo_text_box)                    
                text_box.insert(tk.END, showinfo_text_box)  
                text_box.see(tk.END)
                dialog.update()

                # file_save = filename.replace(".yuv", output_format)
                file_save = filename

                # batch_yuv_convetimg(inputpath + "/" + filename, outputpath + "/" +file_save, output_format)
                shutil.copyfile(inputpath + "/" + filename, outputpath + "/" +file_save)

                showinfo_text_box = "process: " + str((int)(index / process_num * 100 )) + "% " + file_save + " end \n"
                # print(showinfo_text_box)                    
                text_box.insert(tk.END, showinfo_text_box)  
                text_box.see(tk.END)
                dialog.update()
                index = index+1


        # 创建一个 Text 控件  
        text_box = tk.Text(frame, height=50, width=150)  
        text_box.place(relx=0, rely=1.0 + OBJECT_DIST_SCALE_Y, in_ = label_output_format, bordermode = 'outside')
        text_box['fg'] = '#D8DEE9'
        text_box['bg'] = '#303841'

        convert_yuv_button = tk.Button(frame, text="批量另存yuv", bg='#326fa8',fg='black', command=convert_yuv_fun)
        convert_yuv_button.place(relx=0, rely=1.0+0.01, in_ = text_box, width=100, height=40,  bordermode = 'outside')

        copydumpfile_button = tk.Button(frame, text="批量拷贝文件", bg='#326fa8',fg='black', command=copydumpfile_fun)
        copydumpfile_button.place(relx=1, rely=0, in_ = convert_yuv_button, width=100, height=40,  bordermode = 'outside')

        cancel_button = tk.Button(frame, text="关闭",bg='#a8a632',fg='black', command=dialog.destroy)
        cancel_button.place(relx = 1, rely=0, in_ = copydumpfile_button, width=100, height=40, bordermode = 'outside')

if __name__ == "__main__":  
    root = TkinterDnD.Tk()  
    app = ImageDropApp(root)  
    root.mainloop() 

 

posted on 2024-10-03 18:53  Maddock  阅读(5)  评论(0编辑  收藏  举报

导航