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()
You don't have to delete self.kivy_image and create it again but you have to replace .texture instead of full Image
self.kivy_image.texture = new_image.texture
And this means you can also reduce code in create_kivy_image. You can return CoreImage instead of UixImage. You may also use one 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
You could also use io.BytesIo() to create thumbnail and you wouldn't have to write in file. I could test it only with JPG but I don't know if it is correct for 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)
My full code for tests:
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()
Related
I want to make an internet speed test application for Android with Python.
I have done the back-end side but I have a hard time with the front-end.
After research, I decided to use the Kivy framework, however, I need guidance on how to create a gauge like this.
There are two ways to create a gauge. The first one is by using code with mathematics and the second one is by using graphic files for cadran and needle. Below, you will find the implementation of the second way, with the help of the original code of the gauge widget of Kivy Garden project, in which you will be able to understand how the gauge works more easily.
import kivy
kivy.require('1.6.0')
from kivy.app import App
from kivy.clock import Clock
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
from kivy.properties import BoundedNumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
from os.path import join, dirname, abspath
class Gauge(Widget):
'''
Gauge class
'''
unit = NumericProperty(1.8)
value = BoundedNumericProperty(0, min=0, max=100, errorvalue=0)
path = dirname(abspath(__file__))
file_gauge = StringProperty(join(path, "cadran.png"))
file_needle = StringProperty(join(path, "needle.png"))
size_gauge = BoundedNumericProperty(128, min=128, max=256, errorvalue=128)
size_text = NumericProperty(10)
def __init__(self, **kwargs):
super(Gauge, self).__init__(**kwargs)
self._gauge = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_gauge = Image(
source=self.file_gauge,
size=(self.size_gauge, self.size_gauge)
)
self._needle = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_needle = Image(
source=self.file_needle,
size=(self.size_gauge, self.size_gauge)
)
self._glab = Label(font_size=self.size_text, markup=True)
self._progress = ProgressBar(max=100, height=20, value=self.value)
self._gauge.add_widget(_img_gauge)
self._needle.add_widget(_img_needle)
self.add_widget(self._gauge)
self.add_widget(self._needle)
self.add_widget(self._glab)
self.add_widget(self._progress)
self.bind(pos=self._update)
self.bind(size=self._update)
self.bind(value=self._turn)
def _update(self, *args):
'''
Update gauge and needle positions after sizing or positioning.
'''
self._gauge.pos = self.pos
self._needle.pos = (self.x, self.y)
self._needle.center = self._gauge.center
self._glab.center_x = self._gauge.center_x
self._glab.center_y = self._gauge.center_y + (self.size_gauge / 4)
self._progress.x = self._gauge.x
self._progress.y = self._gauge.y + (self.size_gauge / 4)
self._progress.width = self.size_gauge
def _turn(self, *args):
'''
Turn needle, 1 degree = 1 unit, 0 degree point start on 50 value.
'''
self._needle.center_x = self._gauge.center_x
self._needle.center_y = self._gauge.center_y
self._needle.rotation = (50 * self.unit) - (self.value * self.unit)
self._glab.text = "[b]{0:.0f}[/b]".format(self.value)
self._progress.value = self.value
if __name__ == '__main__':
from kivy.uix.slider import Slider
class GaugeApp(App):
increasing = NumericProperty(1)
begin = NumericProperty(50)
step = NumericProperty(1)
def build(self):
box = BoxLayout(orientation='horizontal', padding=5)
self.gauge = Gauge(value=50, size_gauge=256, size_text=25)
self.slider = Slider(orientation='vertical')
stepper = Slider(min=1, max=25)
stepper.bind(
value=lambda instance, value: setattr(self, 'step', value)
)
box.add_widget(self.gauge)
box.add_widget(stepper)
box.add_widget(self.slider)
Clock.schedule_interval(lambda *t: self.gauge_increment(), 0.03)
return box
def gauge_increment(self):
begin = self.begin
begin += self.step * self.increasing
if 0 < begin < 100:
self.gauge.value = self.slider.value = begin
else:
self.increasing *= -1
self.begin = begin
GaugeApp().run()
Of course, if you don't want to use the default cadran and needle, you will have to design your own, using a vector graphics editor.
So i'm making this arcade game and i have a state machine that takes an object(the enemy class) as a constructor parameter, and i have 1 folder for the enemy sprite.
So when i instantiate 1 state machine the game works fine, but when i instantiate 2 state machines(2 enemies) things get weird, when 1 enemy gets to the end of the screen and turn around with kivy's "flip_horizontal" the other enemy turns around too, and its because both state machines are controlling the same enemy folder(same images).
Now if i create an enemy folder for each state machine then it works fine but this is not good. so i thought about manipulating the images in memory, this way the effects of a state machine on the images wont be shared to other state machines.
I tried to do this using CoreImage but the furthest i got was to get a static on the screen and move the "x position", no animation, nothing.
So would anyone help me with this? or if you have a better solution i will be grateful.
heres a sample code:
statemachine.py:
from kivy.app import App
from kivy.properties import Clock, StringProperty, NumericProperty
from kivy.atlas import Atlas
from kivy.uix.image import Image
from itertools import cycle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.core.window import Window
class EnemyFSM(FloatLayout):
def __init__(self, enemy_object, **kwargs):
super().__init__(**kwargs)
self.enemy = enemy_object
self.states = {'ROAM': 'ROAM',
'CHASE': 'CHASE',
'ATTACK': 'ATTACK',
'DEAD': 'DEAD',
'IDLE': 'IDLE'}
self.state = self.states['ROAM']
self.init_state = True
def update(self, dt):
if self.state == 'ROAM':
self.roam_state()
def idle_state(self):
self.enemy.methods_caller("walk")
def roam_state(self):
self.enemy.methods_caller("walk")
main.py:
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.floatlayout import MDFloatLayout
from kivy.properties import Clock
from kivy.uix.image import Image
from kivy.properties import NumericProperty
from random import uniform as rdm
from random import randint as rdi
from random import choice
enemy_list = []
from baseenemy import Enemy
from statemachine import EnemyFSM
class LevelOne(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.setup, 1/60)
def setup(self, dt):
self.left_side_spawn = rdi(-500, -100)
self.right_side_spawn = rdi(500, 600)
self.spawn_side = [self.left_side_spawn, self.right_side_spawn]
self.speed = rdi(1, 5)
"""here we instatiate the enemies, 1 works fine, 2 or more then hell breaks loose unless we give each state machine its own image folder"""
for _ in range(1):
self.base_enemy = Enemy(choice(self.spawn_side), rdm(1, 5))
self.add_widget(self.base_enemy)
self.enemy_ai = EnemyFSM(self.base_enemy)
self.add_widget(self.enemy_ai)
enemy_list.append(self.enemy_ai)
Clock.schedule_interval(self.init_game, 1/60)
def init_game(self, dt):
for i in range(len(enemy_list)):
enemy_list[i].update(dt)
class MainApp(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Red"
return None
def on_start(self):
self.fps_monitor_start()
main.kv:
LevelOne:
<EnemyFSM>:
<Enemy>:
source: root.frame_path #the frame_path variable in the enenmy class
size_hint: None, None
size: "150dp", "150dp"
x: root.xpos
y: root.ypos
baseenemy.py:
from kivy.app import App
from kivy.atlas import Atlas
from kivy.properties import Clock, StringProperty, NumericProperty
from kivy.uix.image import Image
from kivy.core.image import Image as CoreImage
import itertools
"""Method to set up the frame names"""
def frame_setup(src):
return [f"1_enemies_1_{src}_{i}" for i in range(20)]
"""Path to the image folder with the atlas"""
def create_frame_path(frame_name):
return {'my_source':
f"atlas://{frame_name}/{frame_name}_atlas/1_enemies_1_{frame_name}_0",
'my_atlas': f"atlas://{frame_name}/{frame_name}_atlas/"}
class Enemy(Image):
xpos = NumericProperty(10)
ypos = NumericProperty(50)
init_frame = create_frame_path('walk')#default frame state 'walk'
frame_path = StringProperty(init_frame['my_source'])#path to the image, this var is also the images source in the .kv file
frame_atlas_path = None
updated_frames = []
previous_frame = None
frame_cycle_called = False
updated_frames_cycle = None
fliped = 0
def __init__(self, spawn_pos, speed, *args, **kwargs):
super().__init__(**kwargs)
self.goal = 0
self.methods_dict = {'walk': self.walk}
self.methods_call = None
self.step = 3
self.xpos = spawn_pos
"""this method, takes a param, and sets up everything
the new frame if the frame is diferent than the current frames'walk'
creates the frame list..."""
def methods_caller(self, new_src):
#The src param is the state of the enemy
#which will tell which frames to set up
#now its just "walk"
self.src = new_src
self.updated_frames = frame_setup(self.src)
#if a state change has occured these lines will run and set up
# a new set of frames according to the new state but for now its just the 'walk' state
if self.previous_frame is not new_src:
self.previous_frame = new_src
self.methods_call = self.methods_dict[new_src]#this get the dict element which is a function and assign to the var
self.frame_path = str(create_frame_path(new_src))
self.frame_atlas_path = create_frame_path(new_src)
self.frame_cycle_called = False
if not self.frame_cycle_called:
self.updated_frames_cycle = itertools.cycle(self.updated_frames)
self.frame_cycle_called = True
self.methods_call()#calling the method we got from the dict element which call the walk method
def walk(self):
#this is where the walking animation happen
if self.xpos >= 700:
self.goal = 1
if self.xpos <= -300:
self.goal = 0
self.frame_path =
self.frame_atlas_path['my_atlas']+next(self.updated_frames_cycle)
image_side = Image(source=self.frame_path).texture
if self.goal == 0 and int(round(image_side.tex_coords[0])) != 0:
image_side.flip_horizontal()
self.fliped = 0
if self.goal == 1 and int(round(image_side.tex_coords[0])) != 1:
image_side.flip_horizontal()
self.fliped = 1
if self.fliped == 0:
self.xpos += self.step
if self.fliped == 1:
self.xpos -= self.step
And finally heres the link to a zip file with the image folder:
https://drive.google.com/file/d/11eGTS_pI690eI5EhxAAoRmT05uNW3VOM/view?usp=sharing
and sorry for the long text.
How to access raw image data of a kivy.core.image.Image object? The docs say that the image property of the Image object has the raw data of the image, but when I print it, it returns None:
This is what I did:
#paintwg is the widget
img = paintwg.export_to_image()
print(img.image)
Output:
>>> None
From kivy.uix.widget.Widget.export_as_image:
Return an core Image of the actual widget.
From kivy.core.image:
Core classes for loading images and converting them to a Texture. The raw image data can be keep in memory for further access.
The latter might relate to the kwarg keep_data. From the constructor kivy.core.image.Image(arg, **kwargs):
arg: can be a string (str), Texture, BytesIO or Image object
Let's check these four possibilitìes:
from kivy.app import App
from kivy.core.image import Image as Core_Image
from kivy.core.window import Window
from PIL import Image as PILImage
from io import BytesIO
class MyApp(App):
def build(self):
img_path = 'path/to/your/image.png'
# From string path
img = Core_Image(img_path)
print(img.image) # <kivy.core.image.img_sdl2.ImageLoaderSDL2 object at ...
# From Texture object
img = Core_Image(img.texture)
print(img.image) # None
# From BytesIO object
pil_img = PILImage.open(img_path)
img_bytes = BytesIO()
pil_img.save(img_bytes, format='PNG')
img_bytes.seek(0)
img = Core_Image(BytesIO(img_bytes.read()), ext='png')
print(img.image) # <kivy.core.image.img_sdl2.ImageLoaderSDL2 object at ...
# From existing image object
img = Core_Image(img_path)
img = Core_Image(img)
print(img.image) # <kivy.core.image.img_sdl2.ImageLoaderSDL2 object at ...
myapp = MyApp().run()
Even setting the kwarg keep_data=True doesn't store anything in the image property when using some Texture object as source.
Now – guess what! – how does export_as_image generate the image object? Correct:
img = Image(fbo.texture)
So, re-reading the second link, we know, that everything is stored in the corresponding texture property! Let's see (code taken from this Q&A):
from kivy.app import App
from kivy.uix.image import Image
from kivy.clock import Clock
class MyApp(App):
def build(self):
self.my_image = Image(source='path/to/your/image.png')
Clock.schedule_once(self.export, 1)
return self.my_image
def export(self, dt):
img = self.my_image.export_as_image()
print(img.image) # None
print(img.texture) # <Texture ... size=(800, 600) colorfmt='rgba' ...
myapp = MyApp().run()
In the pixels property of the Texture object, the raw data is stored. If you want to have that somehow "readable", e.g. using some Pillow Image object, you'd need to convert:
from kivy.app import App
from kivy.uix.image import Image
from kivy.clock import Clock
from PIL import Image as PILImage
class MyApp(App):
def build(self):
self.my_image = Image(source='path/to/your/image.png')
Clock.schedule_once(self.export, 1)
return self.my_image
def export(self, dt):
img = self.my_image.export_as_image()
pil_img = PILImage.frombytes('RGBA',
img.texture.size,
img.texture.pixels)
print(pil_img) # <PIL.Image.Image image mode=RGBA size=800x600 at ...
myapp = MyApp().run()
Now, you should be able to access single pixel values from the result of export_as_image during runtime!
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
PyCharm: 2021.1.1
kivy: 2.0.0
Pillow: 8.2.0
----------------------------------------
I want to make a class that has a picture and it is changed to the next one by mouse click.I'm new to oop, my idea here was to make class similar to real life where there is new class instance for every new picture, is it possible to do it this way? Here is my code
import tkinter as tk
from PIL import Image,ImageTk
class Picture():
_count=1
def __init__(self,window):
self.id=Picture._count
Picture._count+=1
self.img=Image.open(r'C:\ImgArchive\img%s.png' % self.id)
self.pimg = ImageTk.PhotoImage(self.img)
self.lab=tk.Label(window,image=self.pimg)
self.lab.pack()
self.lab.bind('<1>',self.click)
def click(self,event):
self.lab.destroy()
self=self.__init__(window)
window = tk.Tk()
window.title('Album')
window.geometry('1200x900')
pic=Picture(window)
window.mainloop()
It works fine, but i'm not sure that old instances of my class is deleted, are they? And i use self.lab.destroy() because if i dont new picture appears down, like this
instead of this
So why it happens?What is elegant way for it?
Below example produces a simple image viewer tested with path of C:\Users\Public\Pictures\Sample Pictures, let me know if anything's unclear:
import tkinter as tk
from PIL import Image, ImageTk
#required for getting files in a path
import os
class ImageViewer(tk.Label):
def __init__(self, master, path):
super().__init__(master)
self.path = path
self.image_index = 0
self.list_image_files()
self.show_image()
self.bind('<Button-1>', self.show_next_image)
def list_files(self):
(_, _, filenames) = next(os.walk(self.path))
return filenames
def list_image_files(self):
self.image_files = list()
for a_file in self.list_files():
if a_file.lower().endswith(('.jpg', '.png', '.jpeg')):
self.image_files.append(a_file)
def show_image(self):
img = Image.open(self.path + "\\" + self.image_files[self.image_index])
self.img = ImageTk.PhotoImage(img)
self['image'] = self.img
def show_next_image(self, *args):
self.image_index = (self.image_index + 1) % len(self.image_files)
self.show_image()
root = tk.Tk()
mypath = r"C:\Users\Public\Pictures\Sample Pictures"
a = ImageViewer(root, mypath)
a.pack()
root.mainloop()
I'm attempting to make a slideshow with Tkinter but I'm having trouble sizing the images. They only show as the default size while I'd like to make them all uniform. I can do it for individual images using Image.open and resize, but I can't sort out how to get it to work over an iteration. I'd appreciate the help:
import Tkinter as tk
from PIL import Image, ImageTk
from itertools import cycle
class App(tk.Tk):
def __init__(self, image_files, x, y, delay):
tk.Tk.__init__(self)
self.geometry('+{}+{}'.format(x,y))
self.delay = delay
self.pictures = cycle((ImageTk.PhotoImage(file=image), image) for image in image_files)
self.pictures = self.pictures
self.picture_display = tk.Label(self)
self.picture_display.pack()
def show_slides(self):
img_object, img_name = next(self.pictures)
self.picture_display.config(image=img_object)
self.title(img_name)
self.after(self.delay, self.show_slides)
def run(self):
self.mainloop()
delay = 3500
image_files = [
'c:/users/xxx/pictures/47487_10100692997065139_1074926086_n.jpg',
'E:\\1415\\20141216_105336.jpg'
]
x = 100
y = 50
app = App(image_files,x,y,delay)
app.show_slides()
app.run()
You were close, but not quite there yet. Thus, I changed your example to make it work:
import Tkinter as tk
from PIL import Image, ImageTk
from itertools import cycle
class App(tk.Tk):
def __init__(self, image_files, x, y, delay):
tk.Tk.__init__(self)
self.geometry('+{}+{}'.format(x,y))
self.delay = delay
#self.pictures = cycle((ImageTk.PhotoImage(file=image), image) for image in image_files)
self.pictures = cycle(image for image in image_files)
self.pictures = self.pictures
self.picture_display = tk.Label(self)
self.picture_display.pack()
self.images = [] # to keep references to images.
def show_slides(self):
img_name = next(self.pictures)
image_pil = Image.open(img_name).resize((300, 300)) #<-- resize images here
self.images.append(ImageTk.PhotoImage(image_pil))
self.picture_display.config(image=self.images[-1])
self.title(img_name)
self.after(self.delay, self.show_slides)
def run(self):
self.mainloop()
delay = 3500
image_files = [
'./empty.gif',
'./empty2.gif',
'./empty1.gif'
]
x = 200
y = 150
app = App(image_files,x,y,delay)
app.show_slides()
app.run()
Basicly, image resizing must be done using PIL image, before you make an instance of ImageTk.PhotoImage. In two critical points I made comments in the example, so you know where to look for. Hope this helps.