如何在没有kv/id的情况下访问kivy图像(小部件)



这是我在stackoverflow的第一篇文章,请耐心:D

我想用python/kivy构建一个小的图片编辑器。这是我第一次使用kivy。我在python方面有一些经验。

我不知道如何更新/重新加载添加的图像(小部件(。例如,如果我想设置图片的对比度,则需要重新加载图片。我想避免将图片保存到磁盘,这就是我使用BytesIO->速度

我现在的解决方案是删除旧图像(小部件(并添加一个新图像。但如果我想添加其他小部件,比如按钮,我会遇到麻烦。所以现在代码只适用于一个小部件:-/

我也尝试过使用kv语言,但当我使用BytesIO处理图像时,我无法使用kv/id函数。源只接受字符串(路径/文件(。但我得到了ByteArray。

请看一下我的代码,你可以告诉我如何访问添加的小部件,并在稍后的运行应用程序中更新它吗。或者可能有一个解决方案与kv和BytesIO?

大部分代码都是从网络上收集并组合在一起的。。。


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.widget import Widget
from kivy.uix.gridlayout import GridLayout
import kivy.uix.button as btn
import time
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import NumericProperty, ObjectProperty

# used .raf (Fujifilm Raw) in this project
# https://filesamples.com/samples/image/raf/sample1.raf

class MyImageWidget(GridLayout):

def __init__(self,**kwargs):
super(MyImageWidget,self).__init__(**kwargs)
self.cols = 1 #1 column grid layout
self.contrast_counter = 0 # counter for contrast sweep
# Load Image (+standard features)
self.thumbnail_path = self.create_thumbnailpath() # set path to thumbnail (create thumbnail)
self.image = Image.open(self.thumbnail_path) # open thumbnail
self.image = self.set_size(self.image, 0.1) # minimize thumbail size for speed
self.image = self.whitebalance_auto(self.image) # do whitebalance on thumbnail
self.image = self.set_greyscale(self.image) # do black white conversion
# add Image to widget (kivy_image)
self.kivy_image = UixImage(source=self.thumbnail_path)
self.add_widget(self.kivy_image) # <--- how can i access this widget later?
# here a object is creatd for the widget, but how do I access it later on? 
print(self.children)
# TEST: reload Image in cycles (to see changing contrast effect on picture)
Clock.schedule_interval(self.update_pic,0.025) # refreshrate of app (25ms)

def update_pic(self, dt):
new_image = self.process_image(self.image)

self.remove_widget(self.kivy_image) # current widget need to be removed, so that contrast sweep is shown
self.kivy_image = new_image
self.add_widget(self.kivy_image) # new processed widget need to be added, so that contrast sweep is shown

#self.kivy_image.reload() # <--- how can i reload the widget (kivy_image), to not need to remove the old widget?
#print("reloaded")

def create_thumbnailpath(self):
# https://filesamples.com/samples/image/raf/sample1.raf
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 # cycle through contrast levels 0.0 - 2.0
if self.contrast_counter >= 2:
self.contrast_counter = 0 # reset contrast level cycle

#image = self.set_size(image, 0.2)
#image = self.whitebalance_auto(image)
#print_exif(image)
#image = self.set_greyscale(image)
#image = self.set_contrast(image,1.1)
image = self.set_contrast(image,self.contrast_counter)
#image = self.set_sharpness(image, 2.0)
image_processed = self.create_kivy_image(image, 'png')
return image_processed
def create_kivy_image(self,image, file_extension):
imgByteArr = io.BytesIO()
image.save(imgByteArr, file_extension)
# Return a Kivy image set from a bytes variable
imgByteArr.seek(0)
buf = io.BytesIO(imgByteArr.read())
buf.seek(0)
cim = CoreImage(buf, ext=file_extension)
image_to_show = UixImage(source='')
image_to_show.texture = cim.texture
return image_to_show
def whitebalance_auto(self,im):
image = to_pil(cca.retinex_with_adjust(from_pil(im)))
return image
def create_thumbnail(self,raw_image):
# Create JPG Thumbnail
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()

您不必删除self.kivy_image并重新创建它,但您必须替换.texture而不是完整的Image

self.kivy_image.texture = new_image.texture

这意味着您还可以减少create_kivy_image中的代码。您可以返回CoreImage而不是UixImage。您也可以使用一个io.BufferIO

def create_kivy_image(self, image, file_extension):
buf = io.BytesIO()
image.save(buf, file_extension)
buf.seek(0)
return CoreImage(buf, ext=file_extension)
def update_pic(self, dt):
new_image = self.process_image(self.image)

self.kivy_image.texture = new_image.texture

您也可以使用io.BytesIo()创建缩略图,而不必在文件中写入。我只能用JPG测试它,但我不知道它是否适用于BITMAP

def raw_to_pil(self, path):
"""Load RAW and convert to PIL.Image."""

with rawpy.imread(path) as 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:
print('thumbnail JPG')
return Image.open(io.BytesIO(thumb.data)) 
elif thumb.format == rawpy.ThumbFormat.BITMAP:                
print('thumbnail BITMAP')
buf = io.BytesIO()
imageio.imsave(buf, thumb.data, 'tiff')
buf.seek(0)
return Image.open(buf) 

我的测试完整代码:

import io
import time
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.app import App
from kivy.clock import Clock
from kivy.core.image import Image as CoreImage
from kivy.uix.image import Image as UixImage
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.properties import NumericProperty, ObjectProperty
# used .raf (Fujifilm Raw) in this project
# https://filesamples.com/samples/image/raf/sample1.raf
class MyImageWidget(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)

self.cols = 1              # 1 column grid layout
self.contrast_counter = 0  # counter for contrast sweep
# add Image to widget (kivy_image)
self.kivy_image = UixImage()       # empty image
self.add_widget(self.kivy_image)
self.image = self.raw_to_pil('lab2raw/sample1.raf')        

if self.image:  # can be `None`
self.image = self.set_size(self.image, 0.1) # minimize thumbail size for speed
#self.image = self.whitebalance_auto(self.image) # do whitebalance on thumbnail
self.image = self.set_greyscale(self.image) # do black white conversion
core_image = self.pil_to_kivy(self.image, 'png')
self.kivy_image.texture = core_image.texture
# TEST: reload Image in cycles (to see changing contrast effect on picture)
Clock.schedule_interval(self.update_pic, 0.025) # refreshrate of app (25ms)
def update_pic(self, dt):
if self.image:  # can be `None`
pil_image = self.process_image(self.image)
core_image = self.pil_to_kivy(pil_image, 'png')
self.kivy_image.texture = core_image.texture
def process_image(self, image):
"""Process PIL.Image without converting to other formats."""

self.contrast_counter += 0.02 # cycle through contrast levels 0.0 - 2.0
if self.contrast_counter >= 2:
self.contrast_counter = 0 # reset contrast level cycle

#image = self.set_size(image, 0.2)
#image = self.whitebalance_auto(image)
#image = self.set_greyscale(image)
#image = self.set_contrast(image, 1.1)
image = self.set_contrast(image, self.contrast_counter)
#image = self.set_sharpness(image, 2.0)
#self.print_exif(image)

return image
def pil_to_kivy(self, image, file_extension):
"""Convert PIL.Image to CoreImage (to get later .texture)."""

buf = io.BytesIO()
image.save(buf, file_extension)
buf.seek(0)

return CoreImage(buf, ext=file_extension)
def raw_to_pil(self, path):
"""Load RAW and convert to PIL.Image. Can return `None`."""
with rawpy.imread(path) as 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:
print('thumbnail JPG')
buf = io.BytesIO(thumb.data)
return Image.open(buf) 
elif thumb.format == rawpy.ThumbFormat.BITMAP:                
print('thumbnail BITMAP')
buf = io.BytesIO()
imageio.imsave(buf, thumb.data, 'tiff')
buf.seek(0)
return Image.open(buf) 

#return Image.new('RGB', (1500, 1500), 'white')   # create empty Image


def print_exif(self, im):
im_exif = im.getexif()

if im_exif is None:
print('Sorry, image has no exif data.')
else:
for key, val in im_exif.items():
if key in ExifTags.TAGS:
if ExifTags.TAGS[key] == "XMLPacket":
print('pass (XMLPacket)')
pass
elif ExifTags.TAGS[key] == "PrintImageMatching":
print('pass (PrintImageMatching)')
pass
else:
print(f'{ExifTags.TAGS[key]}:{val}')
# --- 

def whitebalance_auto(self, im):
return to_pil(cca.retinex_with_adjust(from_pil(im)))
def set_greyscale(self, im):
return im.convert("L")
def set_contrast(self, im, contrast_level):
return ImageEnhance.Contrast(im).enhance(contrast_level)
def set_sharpness(self, im, sharpness_level):
return ImageEnhance.Sharpness(im).enhance(sharpness_level)

def set_size(self, im, factor):
return im.resize((round(im.width*factor), round(im.height*factor)), Image.ANTIALIAS)

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()

最新更新