如何在不使用KV语言/ID的情况下访问并更新Kivy Image组件?
解决方案:更新Kivy Image组件的正确方式
你不需要删除并重新添加Image组件,直接更新现有组件的texture即可。同时也可以结合KV语言实现,下面分两种方式说明:
方式一:不使用KV,直接更新现有组件
你的核心问题是每次创建新的UixImage并替换旧组件,其实只需保留初始的self.kivy_image,每次处理完图片后更新它的texture属性。
修改关键代码:
- 重写
process_image方法,让它返回处理后的纹理对象 - 修改
update_pic方法,直接更新现有Image组件的texture
修改后的核心代码片段:
def update_pic(self, dt): # 获取处理后的纹理 new_texture = self.process_image(self.image) # 更新现有组件的texture,无需删除添加 self.kivy_image.texture = new_texture # 触发组件刷新 self.kivy_image.canvas.ask_update() def process_image(self, image): self.contrast_counter = self.contrast_counter + 0.02 if self.contrast_counter >= 2: self.contrast_counter = 0 image = self.set_contrast(image, self.contrast_counter) # 将处理后的PIL图片转为Kivy纹理 imgByteArr = io.BytesIO() image.save(imgByteArr, 'png') imgByteArr.seek(0) buf = io.BytesIO(imgByteArr.read()) buf.seek(0) cim = CoreImage(buf, ext='png') return cim.texture
初始化时的self.kivy_image保留原代码即可:
self.kivy_image = UixImage(source=self.thumbnail_path) self.add_widget(self.kivy_image)
这样就不用每次删加组件,后续添加按钮等其他控件也不会受影响。
方式二:结合KV语言使用
KV语言的id功能可以让你轻松引用组件,即使图片来自字节流也没问题——因为我们依然是更新texture,而非依赖source属性。
- 定义KV字符串(或单独的kv文件):
<MyImageWidget>: cols:1 UixImage: id: kivy_image source: '' Button: text: "增加对比度" size_hint_y: None height: 50 on_press: root.adjust_contrast(0.1) Button: text: "降低对比度" size_hint_y: None height: 50 on_press: root.adjust_contrast(-0.1)
- 修改
MyImageWidget类,通过id引用Image组件:
class MyImageWidget(GridLayout): def __init__(self,**kwargs): super(MyImageWidget,self).__init__(**kwargs) Builder.load_string(kv_string) # 加载KV定义,或用Builder.load_file self.kivy_image = self.ids.kivy_image # 初始化图片逻辑和之前一致,最后设置初始纹理 self.kivy_image.texture = self.get_initial_texture() # 测试用的定时更新保留 Clock.schedule_interval(self.update_pic,0.025) def get_initial_texture(self): # 把初始图片处理成纹理 imgByteArr = io.BytesIO() self.image.save(imgByteArr, 'png') imgByteArr.seek(0) buf = io.BytesIO(imgByteArr.read()) buf.seek(0) cim = CoreImage(buf, ext='png') return cim.texture # 手动调整对比度方法 def adjust_contrast(self, delta): self.contrast_counter += delta if self.contrast_counter > 2: self.contrast_counter = 2 elif self.contrast_counter < 0: self.contrast_counter = 0 new_texture = self.process_image(self.image) self.kivy_image.texture = new_texture
这样既利用了KV语言的布局优势,又能通过字节流生成的texture更新图片,完全避开source必须是文件路径的限制。
完整修改后的代码
from kivy.app import App from kivy.uix.image import Image as UixImage import rawpy import imageio from PIL import Image, ExifTags, ImageEnhance import colorcorrect.algorithm as cca from colorcorrect.util import from_pil, to_pil from kivy.core.image import Image as CoreImage import io from kivy.clock import Clock from kivy.uix.gridlayout import GridLayout from kivy.lang import Builder # KV布局定义 kv_string = """ <MyImageWidget>: cols:1 UixImage: id: kivy_image source: '' Button: text: "增加对比度" size_hint_y: None height: 50 on_press: root.adjust_contrast(0.1) Button: text: "降低对比度" size_hint_y: None height: 50 on_press: root.adjust_contrast(-0.1) """ class MyImageWidget(GridLayout): def __init__(self,**kwargs): super(MyImageWidget,self).__init__(**kwargs) Builder.load_string(kv_string) self.kivy_image = self.ids.kivy_image self.contrast_counter = 0 # 加载初始图片 self.thumbnail_path = self.create_thumbnailpath() self.image = Image.open(self.thumbnail_path) self.image = self.set_size(self.image, 0.1) self.image = self.whitebalance_auto(self.image) self.image = self.set_greyscale(self.image) # 设置初始纹理 self.kivy_image.texture = self.get_initial_texture() # 测试自动循环更新 Clock.schedule_interval(self.update_pic,0.025) def update_pic(self, dt): new_texture = self.process_image(self.image) self.kivy_image.texture = new_texture self.kivy_image.canvas.ask_update() def adjust_contrast(self, delta): self.contrast_counter += delta if self.contrast_counter > 2: self.contrast_counter = 2 elif self.contrast_counter < 0: self.contrast_counter = 0 new_texture = self.process_image(self.image) self.kivy_image.texture = new_texture def get_initial_texture(self): imgByteArr = io.BytesIO() self.image.save(imgByteArr, 'png') imgByteArr.seek(0) buf = io.BytesIO(imgByteArr.read()) buf.seek(0) cim = CoreImage(buf, ext='png') return cim.texture def create_thumbnailpath(self): path = 'lab2raw/sample1.raf' with rawpy.imread(path) as raw_image: thumbnail_path = self.create_thumbnail(raw_image) return thumbnail_path def process_image(self,image): # 自动循环用的逻辑,手动调整时会被覆盖 self.contrast_counter = self.contrast_counter + 0.02 if self.contrast_counter >= 2: self.contrast_counter = 0 image = self.set_contrast(image,self.contrast_counter) imgByteArr = io.BytesIO() image.save(imgByteArr, 'png') imgByteArr.seek(0) buf = io.BytesIO(imgByteArr.read()) buf.seek(0) cim = CoreImage(buf, ext='png') return cim.texture def whitebalance_auto(self,im): image = to_pil(cca.retinex_with_adjust(from_pil(im))) return image def create_thumbnail(self,raw_image): try: thumb = raw_image.extract_thumb() except rawpy.LibRawNoThumbnailError: print('no thumbnail found') except rawpy.LibRawUnsupportedThumbnailError: print('unsupported thumbnail') else: if thumb.format == rawpy.ThumbFormat.JPEG: with open('lab2raw/thumb.jpg', 'wb') as file: file.write(thumb.data) return 'lab2raw/thumb.jpg' elif thumb.format == rawpy.ThumbFormat.BITMAP: imageio.imsave('lab2raw/thumb.tiff', thumb.data) return 'lab2raw/thumb.tiff' def print_exif(self,image): img_exif = image.getexif() if img_exif is None: print('Sorry, image has no exif data.') else: for key, val in img_exif.items(): if key in ExifTags.TAGS: if ExifTags.TAGS[key]=="XMLPacket": pass elif ExifTags.TAGS[key]=="PrintImageMatching": pass else: print(f'{ExifTags.TAGS[key]}:{val}') def set_greyscale (self,im): image = im.convert("L") return image def set_contrast (self,im, contrast_level): enhancer = ImageEnhance.Contrast(im) return_image = enhancer.enhance(contrast_level) return return_image def set_sharpness (self,im, sharpness_level): enhancer = ImageEnhance.Sharpness(im) image = enhancer.enhance(sharpness_level) return image def set_size(self,im, size_factor): width, height = im.size im = im.resize((round(width*size_factor),round(height*size_factor)),Image.ANTIALIAS) return im def save_jpg(self,im,quality, optimize, path): im.save(path,optimize=optimize, quality=quality) class MyApp(App): def build(self): return MyImageWidget() if __name__ == "__main__": MyApp().run()
内容的提问来源于stack exchange,提问作者JEffnot
相关产品推荐
相关产品推荐

