this is my first post in stackoverflow, be patient :D
I want to build a little picture editor with python/kivy. It is my first time using kivy. I got some experience in python.
I don´t know how to update/reload an added image (widget). For example if I want to setup the contrast of a picture, the picture needs to be reloaded. I want to avoid saving pictures to disk, thats why I use BytesIO -> speed.
My solution right now is to delete the old image(widget) and add a new one. But I get in trouble if want to add other widgets, like buttons. So right now the code only works with one widget :-/
I tried also with kv language, but while I use BytesIO for processing my Image I cant use the kv/id function. Source only accepts string (path/files). But I got ByteArray.
Please take a look at my code, may you can show me how to access an added widget and update it later on in the running app. Or maybe there is a solution with kv and BytesIO?
Most of the code is gathered from web and put together...
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()