Search code examples
pythonkivy

Kivy - How to add screen in recycle view in gallery?


I have used https://kivymd.readthedocs.io/en/latest/components/selection/ and used https://github.com/stefanpetrescu997/imageGalleryImageThresholdingKivy.

from kivy import app from kivy.uix.screenmanager import Screen from kivymd.app import MDApp from kivy.metrics import dp from kivy.lang.builder import Builder import os from PIL import Image as pillow import cv2 import numpy as np from kivy.uix.textinput import TextInput from kivy.uix.button import Button from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.image import Image from kivy.uix.dropdown import DropDown from kivy.core.window import Window

KV = """
<ImgCard@ButtonBehavior+BoxLayout>
    path: ""
    orientation: "vertical"
    size_hint_y: None
    on_release: app.root.viewimg(self)
    on_release: app.on_selected(*args)
    on_release: app.on_unselected(*args)
    on_release: app.set_selection_mode(*args)
    Image:
        source: root.path
        size_hint_y: .9

<GalleryApp>
    orientation: 'vertical'
    ScreenManager:
        Screen:
            BoxLayout:
                orientation: 'vertical'
            RecycleView:
                id: img_base
                viewclass: "ImgCard"
                canvas.before:
                    #$#Color:
                    #$rgba: (.4, .4, .4, .7)
                    Rectangle:
                        size: self.size
                        pos: self.pos
                RecycleGridLayout:
                    spacing: 10 
                    cols: 3
                    default_size: None, dp(48)
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    overlay_color: app.overlay_color[:-1] + [.2]
                    icon_bg_color: app.overlay_color
    
    
"""

class ImageManager(Screen):
    pass

class GalleryApp(BoxLayout):
    overlay_color = get_color_from_hex("#6042e4")

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.manager_list = []
        self.dir = os.getcwd()
        self.available_image_format = ['.png', '.jpg', '.jpeg', '.bmp']  # etc

    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        self.load_images()

    def load_images(self):
        if not self.manager_list:
            for image in os.listdir(self.dir):
                target_filename, target_file_extension = os.path.splitext(image)
                if target_file_extension in self.available_image_format:
                    path_to_image = os.path.join(self.dir, image)
                    self.manager_list.append(
                        {
                            "ImgCard": "ImageManager",
                            "path": path_to_image,
                            "height": dp(200),
                        }
                    )
            self.root.ids.img_base.data = self.manager_list
            self.images=[self.dir]
    
    def next_image(self):
        images = self.images
        cur_idx = None
        last_idx = len(images) -1
        view_children = self.dir
        cur_img = None
        image_container = None

        for child in view_children:
            if str(child).find('BoxLayout') > -1:
                image_container = child.children[0]
                cur_img = image_container.source

        for i, img in enumerate(images):
            if img == cur_img:
                cur_idx = i 

        if cur_idx != last_idx:
            nxt_img = images[cur_idx+1]
        else:
            nxt_img = images[0]

        image_container.source = nxt_img
    
    def prev_image(self):
        images = self.images
        cur_idx = None
        last_idx = len(images) -1
        view_children = self.dir
        cur_img = None
        image_container = None

        for child in view_children:
            if str(child).find('BoxLayout') > -1:
                image_container = child.children[0]
                cur_img = image_container.source

        for i, img in enumerate(images):
            if img == cur_img:
                cur_idx = i 

        if cur_idx != 0:
            prev_img = images[cur_idx-1]
        else:
            prev_img = images[last_idx]

        image_container.source = prev_img
    
    def new_img_name(self):
        view_children = self.dir
        self.dir = view_children
        new_name = TextInput(hint_text='New Image Name', multiline=False)
        new_name.bind(on_text_validate=self.rename_img)

        new_name_modal = ImageManager(size_hint=(None, None), size=(400, 50))
        new_name_modal.add_widget(new_name)
        new_name_modal.open()

    def rename_img(self):
        self.dir.dismiss()
        new_name = self.dir.text
        view_children = self.dir
        cur_img = None
        image_container = None

        for child in view_children:
            if str(child).find('BoxLayout') > -1:
                image_container = child.children[0]
                cur_img = image_container.source
        ext = cur_img[cur_img.rfind('.'):]

    def callback1(self, inst):
        self.text1=self.txt_1.text
        return self.text1

    def callback2(self, inst):
        self.text2=self.txt_2.text
        return self.text2
    
    def viewimg(self):
        im = Image(source=app.path)
        view_size = self.img_resize(im)

        effects_drop = DropDown()

        btn_prev = Button(text='Prev', size_hint_y=None, height = 50)
        btn_prev.bind(on_release=self.prev_image)
        btn_rename = Button(text='Rename', size_hint_y=None, height = 50)
        btn_rename.bind(on_release=self.new_img_name)
        btn_effects = Button(text='Effects', size_hint_y=None, height = 50)
        btn_effects.bind(on_release=effects_drop.open)
        btn_next = Button(text='Next', size_hint_y=None, height = 50)
        btn_next.bind(on_release=self.next_image)

        btn_black = Button(text='Grayscale', size_hint_y=None, height = 50)
        btn_black.bind(on_release=self.black_image)
        btn_bin = Button(text='Binarization', size_hint_y=None, height = 50)
        btn_bin.bind(on_release=self.bin_image)

        self.txt_1 = TextInput(hint_text='min', size_hint_y=None, height = 50, multiline = False)
        #btn_1.bind(on_release=self.callback)
        self.txt_1.bind(on_text_validate=self.callback1)
        self.txt_2 = TextInput(hint_text='max', size_hint_y=None, height = 50, multiline = False)
        #btn_bin.bind(on_release=self.bin_image)
        self.txt_2.bind(on_text_validate=self.callback2)

        effects_drop.add_widget(btn_black)
        effects_drop.add_widget(btn_bin)
        effects_drop.add_widget(self.txt_1)
        effects_drop.add_widget(self.txt_2)
        

        image_ops = BoxLayout(size_hint=(None, None), size=(400, 30), spacing=4)
        image_ops.add_widget(btn_prev)
        image_ops.add_widget(btn_rename)
        image_ops.add_widget(btn_effects)
        image_ops.add_widget(btn_next)
        anchor = AnchorLayout(anchor_x='center', anchor_y='bottom')
        anchor.add_widget(image_ops)
        image_container = BoxLayout()

        view = ImageManager(size_hint=(None,None),size=view_size)
        image_container.add_widget(im)
        view.add_widget(image_container)
        view.add_widget(anchor)
        view.open()
        self.dir = view.children
    
    def bin_image(self):
        
        if self.txt_1.text == '' and self.txt_2.text=='':
            minimum_minimorum = 0
            maximum_maximorum = 255
        elif self.txt_1.text == '' and self.txt_2.text!='':
            minimum_minimorum = 0
            maximum_maximorum = int(self.txt_2.text)
        elif self.txt_1.text != '' and self.txt_2.text=='':
            minimum_minimorum = int(self.txt_1.text)
            maximum_maximorum = 255
        else:        
            minimum_minimorum = int(self.txt_1.text)
            maximum_maximorum = int(self.txt_2.text)
        # print(self.txt_1.text)
        
        # print(self.txt_2.text)
        
        view_children = self.dir
        cur_img = None
        image_container = None

        for child in view_children:
            if str(child).find('BoxLayout') > -1:
                image_container = child.children[0]
                cur_img = image_container.source
        im = pillow.open(cur_img)

        open_cv_image = np.array(im)


        retval, threshold = cv2.threshold(open_cv_image, minimum_minimorum, maximum_maximorum, cv2.THRESH_BINARY)
        print(minimum_minimorum, maximum_maximorum)
        new_im = pillow.fromarray(threshold)

        name = im.filename[:-4] + '_bin' + im.filename[-4:]
        im_cap = im.filename[im.filename.rfind('/')+1:]
        new_im.save(name)

        self.ids.img_base.data.insert(0, {'im_source':name, 'im_caption':im_cap})
        self.ids.img_base.refresh_from_data()
        image_container.source = name
    
    def img_resize(self, img):
        im_size_x, im_size_y = img.texture_size
        ratio = im_size_x/im_size_y
        aspect = self.aspect_ratio(ratio, 50)

        while im_size_x >= Window.width or im_size_y >= Window.height:
            if im_size_x > im_size_y:
                im_size_x -= aspect[0]
                im_size_y -= aspect[1]
            else:
                im_size_y -= aspect[1]
        return [im_size_x, im_size_y]
    
    def aspect_ratio(self, val, lim):

        lower = [0, 1]
        upper = [1, 0]

        while True:
            mediant = [lower[0] + upper[0], lower[1] + upper[1]]

            if (val * mediant[1] > mediant[0]) :
                if (lim < mediant[1]) :
                    return upper
                lower = mediant
            elif (val * mediant[1] == mediant[0]) :
                if (lim >= mediant[1]) :
                    return mediant
                
                if (lower[1] < upper[1]) :
                    return lower
                
                return upper
            else :
                if (lim < mediant[1]) :
                    return lower
                
                upper = mediant
    
class Gallery(MDApp):
    def build(self):
        return GalleryApp()

if __name__=='__main__':
    Gallery().run()

Here's gallery But why is an empty in the gallery ? but also add selection in that. Can anyone help me?


Solution

  • You are never loading your kv string. The build() method in your GalleryApp class is not used and can be removed. Then add the call to Builder into your build() method of the actual App class:

    class Gallery(MDApp):
        def build(self):
            Builder.load_string(KV)
            return GalleryApp()
    

    The basic problem with your code is confusion between the App class and other classes. Your GalleryApp class is not an App class, but you have App methods in it (like build() and on_start()). The actual App class in your code is the Gallery class. Perhaps a poor choice of class names.

    As noted earlier, you can remove the build() method from the GalleryApp class. Now you need to move the on_start() method into the App class (Gallery) with slight modification:

    class Gallery(MDApp):
        overlay_color = get_color_from_hex("#6042e4")
        def build(self):
            Builder.load_string(KV)
            return GalleryApp()
    
        def on_start(self):
            self.root.load_images()
    

    Note that the overlay_color that is referenced in the KV must also be moved into the correct class.