[PYTHON] GUI programming with kivy ~ Part 5 Creating buttons with images ~

Introduction

Last time, I introduced various buttons. It is also related to buttons. Previously, I tried to attach an image to a button and it didn't work, so I did a little research. I think it will be a memorandum. By the way, I would like to show an example of how to use it (although the content of the title is not the main one ...)

How I made a mistake

I was simply trying to use Image with add_widget for the `` `Button``` widget. I think it will look like this. The button appears firmly behind the image, which makes it look awkward. (Maybe if you write a lot, it may look good) Also, if you shift the position of the button, the image will not follow, so I do not understand why. Therefore, I gave up the implementation before.

What to do It looks like this.

sippai.gif

Make an image button like this!

To create an image button, use a module called kivy.uix.behaviors. By using `` `behaviors.ButtonBehavior```, you can add button functions to labels and images. There seems to be many other things you can do! https://kivy.org/doc/stable/api-kivy.uix.behaviors.html

seikou.gif

The source code is as follows. Create a new class that inherits `` `behaviors.ButtonBehaviorand what you want to be a button, hereImage```. And if you change the image of Image and specify the image when you press the button and the image when you talk, it works well.

from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior


class MyButton(ButtonBehavior, Image):
    def __init__(self, **kwargs):
        super(MyButton, self).__init__(**kwargs)
        #Appropriate image
        self.source = 'data/val2017/000000000285.jpg'

    def on_press(self):
        #Image when pressed
        self.source = 'data/val2017/000000000776.jpg'

    def on_release(self):
        #Revert to original image
        self.source = 'data/val2017/000000000285.jpg'


class SampleApp(App):
    def build(self):
        return MyButton()


SampleApp().run()

If you want to use toggle buttons, inherit `` `ToggleButtonBehavior```.


from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.behaviors import ToggleButtonBehavior


class MyButton(ToggleButtonBehavior, Image):
    def __init__(self, **kwargs):
        super(MyButton, self).__init__(**kwargs)
        self.source = 'data/val2017/000000000285.jpg'

    def on_state(self, widget, value):
        if value == 'down':
            self.source = 'data/val2017/000000000776.jpg'
        else:
            self.source = 'data/val2017/000000000285.jpg'


class SampleApp(App):
    def build(self):
        return MyButton()


SampleApp().run()

Made using the image button

While investigating how to make an image button, an image viewer was created before I knew it. The image looks like this.

image.png

It's like displaying all the images in any image folder in the scroll view and displaying the image of the clicked image button in the image above.

Source

The result is as below.

imageviewer.gif


import os
import cv2
import numpy as np

from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.clock import Clock
from kivy.graphics.texture import Texture

#Image button class
class MyButton(ToggleButtonBehavior, Image):
    def __init__(self, **kwargs):
        super(MyButton, self).__init__(**kwargs)
        #Stores the image name of the image button
        self.source = kwargs["source"]
        #Treat the image as a texture so you can edit it
        self.texture = self.button_texture(self.source)

    #The image changes depending on the state of the toggle button and the state.
    def on_state(self, widget, value):
        if value == 'down':
            self.texture = self.button_texture(self.source, off=True)
        else:
            self.texture = self.button_texture(self.source)

    #Change the image, rectangular when pressed+Darken the color
    def button_texture(self, data, off=False):
        im = cv2.imread(data)
        im = self.square_image(im)
        if off:
            im = self.adjust(im, alpha=0.6, beta=0.0)
            im = cv2.rectangle(im, (2, 2), (im.shape[1]-2, im.shape[0]-2), (255, 255, 0), 10)

        #flip upside down
        buf = cv2.flip(im, 0)
        image_texture = Texture.create(size=(im.shape[1], im.shape[0]), colorfmt='bgr')
        image_texture.blit_buffer(buf.tostring(), colorfmt='bgr', bufferfmt='ubyte')
        return image_texture

    #Make the image square
    def square_image(self, img):
        h, w = img.shape[:2]
        if h > w:
            x = int((h-w)/2)
            img = img[x:x + w, :, :]
        elif h < w:
            x = int((w - h) / 2)
            img = img[:, x:x + h, :]

        return img

    #Darken the color of the image
    def adjust(self, img, alpha=1.0, beta=0.0):
        #Performs a product-sum operation.
        dst = alpha * img + beta
        # [0, 255]Clip with to make uint8 type.
        return np.clip(dst, 0, 255).astype(np.uint8)


class Test(BoxLayout):
    def __init__(self, **kwargs):
        super(Test, self).__init__(**kwargs)
        #Directory to read
        image_dir = "data/val2017"

        #Vertical arrangement
        self.orientation = 'vertical'

        #Manage image file names
        self.image_name = ""

        #Preparing a widget to display an image
        self.image = Image(size_hint=(1, 0.5))
        self.add_widget(self.image)

        #Definition of scroll view to place image buttons
        sc_view = ScrollView(size_hint=(1, None), size=(self.width, self.height*4))

        #Because only one widget can be placed in the scroll view
        box = GridLayout(cols=5, spacing=10, size_hint_y=None)
        box.bind(minimum_height=box.setter('height'))

        #Batch definition of image buttons, arranged in grid layout
        box = self.image_load(image_dir, box)

        sc_view.add_widget(box)
        self.add_widget(sc_view)

    #Load image button
    def image_load(self, im_dir, grid):
        images = sorted(os.listdir(im_dir))

        for image in images:
            button = MyButton(size_hint_y=None,
                              height=300,
                              source=os.path.join(im_dir, image),
                              group="g1")
            button.bind(on_press=self.set_image)
            grid.add_widget(button)

        return grid

    #When you press the image button, the image is displayed in the image widget
    def set_image(self, btn):
        if btn.state=="down":
            self.image_name = btn.source
            #Update screen
            Clock.schedule_once(self.update)

    #Screen update
    def update(self, t):
        self.image.source = self.image_name


class SampleApp(App):
    def build(self):
        return Test()


SampleApp().run()

A little commentary

In the above explanation, I think that it was not clear whether the button was pressed, just by switching the image of the button. Therefore, we added a process so that when you press the image, a frame is attached and the image becomes a little dark and you can tell that you pressed it. In addition, in order to arrange them neatly in the grid layout, we have also added a process of trimming at the center of the image.

In the image button class, there is a variable `` `source``` that stores the file name of the image to be the button, and this function is used to process the image with opencv from the image name. Also, in order to use the processed image, it is necessary to use texture, so texture is specified as the return value.

    #Change the image, rectangular when pressed+Darken the color
    def button_texture(self, data, off=False):
        im = cv2.imread(data)
        im = self.square_image(im)
        if off:
            im = self.adjust(im, alpha=0.6, beta=0.0)
            im = cv2.rectangle(im, (2, 2), (im.shape[1]-2, im.shape[0]-2), (255, 255, 0), 10)

        #flip upside down
        buf = cv2.flip(im, 0)
        image_texture = Texture.create(size=(im.shape[1], im.shape[0]), colorfmt='bgr')
        image_texture.blit_buffer(buf.tostring(), colorfmt='bgr', bufferfmt='ubyte')
        return image_texture

It is a process when the button on the application side is pressed, but when the button is pressed, the image does not change just by changing the `source``` of the ```Image``` at the top of the screen. You need to refresh the screen to change the image. Therefore, when you press the image, `Clock``` is moved once to update the screen.


    #When you press the image button, the image is displayed in the image widget
    def set_image(self, btn):
        if btn.state=="down":
            self.image_name = btn.source
            #Update screen
            Clock.schedule_once(self.update)

    #Screen update
    def update(self, t):
        self.image.source = self.image_name

References

-OpenCV-Change image brightness and contrast, gamma correction, etc.-Pynote I referred to the source of the process of darkening the image.

COCO Dataset This is the link of the image used this time.

Recommended Posts

GUI programming with kivy ~ Part 5 Creating buttons with images ~
GUI programming with kivy ~ Part 4 Various buttons ~
GUI programming with kivy ~ Part 3 Video and seek bar ~
GUI programming using kivy ~ Part 2 Progress bar ~
Bordering images with python Part 1
Make sci-fi-like buttons with Kivy
Try GUI programming with Hy
Creating GUI tools with pyinstaller
GUI programming with kivy ~ Part 4 Various buttons ~
GUI programming using kivy ~ Part 2 Progress bar ~
GUI programming with kivy ~ Part 5 Creating buttons with images ~
GUI programming with kivy ~ Part 3 Video and seek bar ~
GUI programming in Python using Appjar
GUI creation in python using tkinter part 1
Creating an unknown Pokemon with StyleGAN2 [Part 1]
Creating an unknown Pokemon with StyleGAN2 [Part 2]
GUI creation with Pyside Part 2 <Use of class>
Upload images to S3 with GUI using tkinter
Display and shoot webcam video with Python Kivy [GUI]