Search code examples
pythonkivypng

Can't export a file to an PNG file in Python-Kivy


I'm working on a python paint project using kivy but I really cant export the png file, and when I can in the save and exit button (quitter et sauvegarder) on it, it creates a png file, but a completly empty one.

from kivy.config import Config

# Les configurations doivent être définies avant d'importer d'autres modules Kivy
Config.set('graphics', 'resizable', False)
Config.set('graphics', 'fullscreen', 'auto')
Config.set('kivy', 'exit_on_escape', '0')

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line, Rectangle, Fbo, ClearColor, ClearBuffers
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.colorpicker import ColorPicker
from kivy.uix.slider import Slider
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.textinput import TextInput
from kivy.uix.image import Image


class WidgetPaint(Widget):
    def __init__(self, **kwargs):
        super(WidgetPaint, self).__init__(**kwargs)
        self.largeur_ligne = 15
        self.largeur_point = 30
        self.dessin = False
        self.couleur = (1, 1, 1, 1)

    def definir_largeur_ligne(self, largeur):
        self.largeur_ligne = largeur

    def definir_largeur_point(self, largeur):
        self.largeur_point = largeur

    def on_touch_down(self, touch):
        self.dessin = True
        with self.canvas:
            Color(*self.couleur)
            Ellipse(pos=(touch.x - self.largeur_point / 2, touch.y - self.largeur_point / 2), size=(self.largeur_point, self.largeur_point))
            touch.ud['ligne'] = Line(points=(touch.x, touch.y), width=self.largeur_ligne)

    def definir_couleur(self, couleur):
        self.couleur = couleur

    def on_touch_move(self, touch):
        ligne = touch.ud.get('ligne')
        if ligne:
            ligne.points += [touch.x, touch.y]

    def effacer_toile(self):
        self.canvas.clear()
        self.dessin = False


class AppPaint(App):
    def build(self):
        parent = Widget()
        self.peintre = WidgetPaint()

        slider = Slider(min=1, max=70, value=15, size_hint=(None, None), size=(200, 50), pos=(295, 60))
        slider.bind(value=self.adjust_brush_size)

        btn_ouvrir = Button(text='Ouvrir', size_hint=(None, None), size=(100, 50), pos=(150, 285))
        btn_ouvrir.bind(on_release=lambda instance: self.ouvrir_fichier())

        btn_effacer = Button(text='Effacer', size_hint=(None, None), size=(100, 50), pos=(40, 585))
        btn_effacer.bind(on_release=self.effacer_toile)

        btn_ligne_fine = Button(text='T1', size_hint=(None, None), size=(50, 50), pos=(105, 60))
        btn_ligne_fine.bind(on_release=lambda btn: self.configurer_peintre(5, 10))

        btn_ligne_epaisse = Button(text='T2', size_hint=(None, None), size=(50, 50), pos=(155, 60))
        btn_ligne_epaisse.bind(on_release=lambda btn: self.configurer_peintre(15, 30))

        btn_ligne_plus_epaisse = Button(text='T3', size_hint=(None, None), size=(50, 50), pos=(205, 60))
        btn_ligne_plus_epaisse.bind(on_release=lambda btn: self.configurer_peintre(25, 50))

        btn_ligne_encore_plus_epaisse = Button(text='T4', size_hint=(None, None), size=(50, 50), pos=(255, 60))
        btn_ligne_encore_plus_epaisse.bind(on_release=lambda btn: self.configurer_peintre(35, 70))

        btn_quitter = Button(text='Enregistrer & Quitter', size_hint=(None, None), size=(100, 50), pos=(40, 285))
        btn_quitter.bind(on_release=lambda instance: self.enregistrer())

        btn_rouge = Button(size_hint=(None, None), size=(50, 50), pos=(5, 5), background_color=(5, 0, 0, 1))
        btn_rouge.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 0, 0, 1)))

        btn_vert = Button(size_hint=(None, None), size=(50, 50), pos=(55, 5), background_color=(0, 5, 0, 1))
        btn_vert.bind(on_release=lambda btn: self.peintre.definir_couleur((0, 5, 0, 1)))

        btn_bleu = Button(size_hint=(None, None), size=(50, 50), pos=(105, 5), background_color=(0, 0, 5, 1))
        btn_bleu.bind(on_release=lambda btn: self.peintre.definir_couleur((0, 0, 5, 1)))

        btn_jaune = Button(size_hint=(None, None), size=(50, 50), pos=(155, 5), background_color=(5, 5, 0, 1))
        btn_jaune.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 5, 0, 1)))

        btn_turquoise = Button(size_hint=(None, None), size=(50, 50), pos=(205, 5), background_color=(0, 5, 5, 1))
        btn_turquoise.bind(on_release=lambda btn: self.peintre.definir_couleur((0, 5, 5, 1)))

        btn_rose = Button(size_hint=(None, None), size=(50, 50), pos=(255, 5), background_color=(5, 0, 5, 1))
        btn_rose.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 0, 5, 1)))

        btn_blanc = Button(size_hint=(None, None), size=(50, 50), pos=(305, 5), background_color=(5, 5, 5, 1))
        btn_blanc.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 5, 5, 1)))

        self.colorpicker_btn = Button(text='Couleurs', size_hint=(None, None), size=(100, 50), pos=(5, 60))
        self.colorpicker_btn.bind(on_press=self.roue_de_couleurs)

        # Create color boxes
        self.saved_colors = [Button(size_hint=(None, None), size=(50, 50), pos=(355 + 50 * i, 5)) for i in range(31)]
        for btn in self.saved_colors:
            btn.bind(on_release=lambda btn: self.peintre.definir_couleur(btn.background_color))
        btn_exporter = Button(text='Exporter', size_hint=(None, None), size=(100, 50), pos=(40, 385))
        btn_exporter.bind(on_release=lambda instance: self.export_to_png('dessin.png'))

        parent.add_widget(self.peintre)
        parent.add_widget(btn_effacer)
        parent.add_widget(btn_ligne_fine)
        parent.add_widget(btn_ligne_epaisse)
        parent.add_widget(btn_ligne_plus_epaisse)
        parent.add_widget(btn_ligne_encore_plus_epaisse)
        parent.add_widget(btn_quitter)
        parent.add_widget(btn_rouge)
        parent.add_widget(btn_vert)
        parent.add_widget(btn_bleu)
        parent.add_widget(btn_jaune)
        parent.add_widget(btn_turquoise)
        parent.add_widget(btn_rose)
        parent.add_widget(self.colorpicker_btn)
        parent.add_widget(btn_blanc)
        parent.add_widget(btn_ouvrir)
        for btn in self.saved_colors:
            parent.add_widget(btn)
        parent.add_widget(slider)
        parent.add_widget(btn_exporter)

        self.file_chooser = FileChooserListView()
        self.file_chooser.bind(on_submit=self.ouvrir_fichier_selectionné)
        self.file_popup = Popup(title='Choisir un fichier', content=self.file_chooser, size_hint=(None, None), size=(400, 400))
        return parent

    def adjust_brush_size(self, instance, value):
        # Met à jour la taille de la ligne et du point basée sur la valeur du slider
        self.peintre.definir_largeur_ligne(value)
        self.peintre.definir_largeur_point(value * 2)  # Par exemple, le point est toujours le double de la largeur de la ligne

    def roue_de_couleurs(self, instance):
        self.color_picker = ColorPicker()
        self.color_picker.bind(color=self.def_couleur)
        popup = Popup(title='Sélectionnez une couleur', content=self.color_picker, size_hint=(None, None), size=(400, 400))
        popup.open()
        popup.bind(on_dismiss=self.save_color_to_box)

    def def_couleur(self, instance, color):
        self.peintre.definir_couleur(color)

    def ajuster_couleur(self, couleur):
        facteur = 0.2
        moyenne = sum(couleur[:3]) / 3
        nouvelle_couleur = [(c + facteur * (c - moyenne)) for c in couleur[:3]]
        nouvelle_couleur = [min(max(0, c), 1) for c in nouvelle_couleur]
        return tuple(nouvelle_couleur + [couleur[3]])

    def save_color_to_box(self, instance):
        couleur = self.color_picker.color
        satcoul = self.ajuster_couleur(couleur)
        couleur_assignée = False
        for btn in self.saved_colors:
            if btn.background_color == [1, 1, 1, 1] and not couleur_assignée:
                btn.background_color = satcoul
                couleur_assignée = True

    def configurer_peintre(self, largeur_ligne, largeur_point):
        self.peintre.definir_largeur_ligne(largeur_ligne)
        self.peintre.definir_largeur_point(largeur_point)

    def effacer_toile(self, obj):
        self.peintre.effacer_toile()
        print('Effacé !')

    def ouvrir_fichier(self):
        # Afficher le FileChooser
        self.file_popup.open()

    def ouvrir_fichier_selectionné(self, instance, selection, _):
        # Fermer le FileChooser
        self.file_popup.dismiss()

        # Vérifier si un fichier a été sélectionné
        if selection:
            chemin_fichier = selection[0]
            self.peintre.effacer_toile()  # Effacer le dessin actuel
            with self.peintre.canvas:
                Rectangle(source=chemin_fichier, pos=self.peintre.pos, size=self.peintre.size)

            print(f'Fichier ouvert: {chemin_fichier}')

    def export_to_png(self, filename):
        '''Exporte le dessin actuel dans un fichier PNG.'''
        # Créer un objet Fbo avec la même taille que le widget de peinture
        fbo = Fbo(size=self.peintre.size)

        # Ajouter le widget de peinture à l'objet Fbo
        with fbo:
            # Effacer l'objet Fbo avec une couleur transparente
            ClearColor(0, 0, 0, 0)
            ClearBuffers()

            # Dessiner le contenu du widget de peinture
            self.peintre.canvas.draw()

        # Enregistrer la texture de l'objet Fbo dans un fichier PNG
        fbo.texture.save(filename)

        print(f'Dessin sauvegardé sous {filename} !')

    def enregistrer(self):
        if self.peintre.dessin:
            content = BoxLayout(orientation='vertical')
            msg = Label(text='Vous avez des changements non enregistrés. Voulez-vous enregistrer ?')
            btn_layout = BoxLayout(size_hint_y=None, height='70', orientation='horizontal')
            oui_btn = Button(text='Oui', on_release=lambda *args: self.sauvegarder_toile())
            non_btn = Button(text='Non', on_release=lambda *args: App().stop())
            btn_layout.add_widget(oui_btn)
            btn_layout.add_widget(non_btn)
            content.add_widget(msg)
            content.add_widget(btn_layout)
            self.save_popup = Popup(title='Confirmation de Sauvegarde', content=content, size_hint=(None, None), size=(400, 200), auto_dismiss=False)
            self.save_popup.open()
        else:
            print('Rien à enregistrer')
            self.stop()

    def sauvegarder_toile(self):
        if self.peintre.dessin:
            # Créer une boîte de dialogue d'entrée textuelle pour saisir le nouveau nom du fichier
            content = BoxLayout(orientation='vertical')
            msg = Label(text='Saisissez le nouveau nom du fichier (sans extension) :')
            input_filename = TextInput(hint_text='Nom du fichier')
            btn_layout = BoxLayout(size_hint_y=None, height='70', orientation='horizontal')
            sauvegarder_btn = Button(text='Sauvegarder', on_release=lambda *args: self.sauvegarder_avec_nom(input_filename.text))
            annuler_btn = Button(text='Annuler', on_release=lambda *args: self.save_popup.dismiss())
            btn_layout.add_widget(sauvegarder_btn)
            btn_layout.add_widget(annuler_btn)
            content.add_widget(msg)
            content.add_widget(input_filename)
            content.add_widget(btn_layout)
            self.save_popup = Popup(title='Sauvegarder le dessin', content=content, size_hint=(None, None), size=(500, 200), auto_dismiss=False)
            self.save_popup.open()
        else:
            print('Rien à enregistrer')

    def sauvegarder_avec_nom(self, filename):
        if filename:
            if not filename.endswith('.png'):
                filename += '.png'  # Ajouter l'extension .png si elle n'est pas déjà présente
            self.export_to_png(filename)
            print(f'Dessin sauvegardé sous {filename} !')
        else:
            print('Nom de fichier invalide.')
        self.save_popup.dismiss()
        AppPaint().stop()

    def profil(self):
        pass


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

Usually i use AI to try to help me but they didn't work, same for kivy documentation, maybe i'm not using the right modules idk. Export just make my app close


Solution

  • At some trials here a solutions which you may save it without problem:

    • I imported the Window module from kivy.core.window to access the window size.
    • Changed the export_to_png function as follow.
    # 
    from kivy.core.window import Window
    
    
     def export_to_png(self, filename):
            print(f"Exporting to {filename}...")
            
            try:
                print('Size', self.peintre.size, 'Filename: ', filename)
                self.peintre.size = (Window.size[0], Window.size[1])
                self.peintre.export_to_png(filename)
    
            except Exception as e:
                print(f"Error while exporting: {e}")
                raise e
            
    

    from my test, its works without problem. However, If you need, I will share full code as you shared.

    EDIT: Here full code:

    from kivy.config import Config
    from kivy.core.window import Window
    
    # Les configurations doivent être définies avant d'importer d'autres modules Kivy
    Config.set('graphics', 'resizable', False)
    Config.set('graphics', 'fullscreen', 'auto')
    Config.set('kivy', 'exit_on_escape', '0')
    
    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.uix.button import Button
    from kivy.graphics import Color, Ellipse, Line, Rectangle, Fbo, ClearColor, ClearBuffers
    from kivy.uix.popup import Popup
    from kivy.uix.label import Label
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.colorpicker import ColorPicker
    from kivy.uix.slider import Slider
    from kivy.uix.filechooser import FileChooserListView
    from kivy.uix.textinput import TextInput
    from kivy.uix.image import Image
    
    
    class WidgetPaint(Widget):
        def __init__(self, **kwargs):
            super(WidgetPaint, self).__init__(**kwargs)
            self.largeur_ligne = 15
            self.largeur_point = 30
            self.dessin = False
            self.couleur = (1, 1, 1, 1)
    
        def definir_largeur_ligne(self, largeur):
            self.largeur_ligne = largeur
    
        def definir_largeur_point(self, largeur):
            self.largeur_point = largeur
    
        def on_touch_down(self, touch):
            self.dessin = True
            with self.canvas:
                Color(*self.couleur)
                Ellipse(pos=(touch.x - self.largeur_point / 2, touch.y - self.largeur_point / 2), size=(self.largeur_point, self.largeur_point))
                touch.ud['ligne'] = Line(points=(touch.x, touch.y), width=self.largeur_ligne)
    
        def definir_couleur(self, couleur):
            self.couleur = couleur
    
        def on_touch_move(self, touch):
            ligne = touch.ud.get('ligne')
            if ligne:
                ligne.points += [touch.x, touch.y]
    
        def effacer_toile(self):
            self.canvas.clear()
            self.dessin = False
    
    
    class AppPaint(App):
        def build(self):
            parent = Widget()
            self.peintre = WidgetPaint()
    
            slider = Slider(min=1, max=70, value=15, size_hint=(None, None), size=(200, 50), pos=(295, 60))
            slider.bind(value=self.adjust_brush_size)
    
            btn_ouvrir = Button(text='Ouvrir', size_hint=(None, None), size=(100, 50), pos=(150, 285))
            btn_ouvrir.bind(on_release=lambda instance: self.ouvrir_fichier())
    
            btn_effacer = Button(text='Effacer', size_hint=(None, None), size=(100, 50), pos=(40, 585))
            btn_effacer.bind(on_release=self.effacer_toile)
    
            btn_ligne_fine = Button(text='T1', size_hint=(None, None), size=(50, 50), pos=(105, 60))
            btn_ligne_fine.bind(on_release=lambda btn: self.configurer_peintre(5, 10))
    
            btn_ligne_epaisse = Button(text='T2', size_hint=(None, None), size=(50, 50), pos=(155, 60))
            btn_ligne_epaisse.bind(on_release=lambda btn: self.configurer_peintre(15, 30))
    
            btn_ligne_plus_epaisse = Button(text='T3', size_hint=(None, None), size=(50, 50), pos=(205, 60))
            btn_ligne_plus_epaisse.bind(on_release=lambda btn: self.configurer_peintre(25, 50))
    
            btn_ligne_encore_plus_epaisse = Button(text='T4', size_hint=(None, None), size=(50, 50), pos=(255, 60))
            btn_ligne_encore_plus_epaisse.bind(on_release=lambda btn: self.configurer_peintre(35, 70))
    
            btn_quitter = Button(text='Enregistrer & Quitter', size_hint=(None, None), size=(100, 50), pos=(40, 285))
            btn_quitter.bind(on_release=lambda instance: self.enregistrer())
    
            btn_rouge = Button(size_hint=(None, None), size=(50, 50), pos=(5, 5), background_color=(5, 0, 0, 1))
            btn_rouge.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 0, 0, 1)))
    
            btn_vert = Button(size_hint=(None, None), size=(50, 50), pos=(55, 5), background_color=(0, 5, 0, 1))
            btn_vert.bind(on_release=lambda btn: self.peintre.definir_couleur((0, 5, 0, 1)))
    
            btn_bleu = Button(size_hint=(None, None), size=(50, 50), pos=(105, 5), background_color=(0, 0, 5, 1))
            btn_bleu.bind(on_release=lambda btn: self.peintre.definir_couleur((0, 0, 5, 1)))
    
            btn_jaune = Button(size_hint=(None, None), size=(50, 50), pos=(155, 5), background_color=(5, 5, 0, 1))
            btn_jaune.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 5, 0, 1)))
    
            btn_turquoise = Button(size_hint=(None, None), size=(50, 50), pos=(205, 5), background_color=(0, 5, 5, 1))
            btn_turquoise.bind(on_release=lambda btn: self.peintre.definir_couleur((0, 5, 5, 1)))
    
            btn_rose = Button(size_hint=(None, None), size=(50, 50), pos=(255, 5), background_color=(5, 0, 5, 1))
            btn_rose.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 0, 5, 1)))
    
            btn_blanc = Button(size_hint=(None, None), size=(50, 50), pos=(305, 5), background_color=(5, 5, 5, 1))
            btn_blanc.bind(on_release=lambda btn: self.peintre.definir_couleur((5, 5, 5, 1)))
    
            self.colorpicker_btn = Button(text='Couleurs', size_hint=(None, None), size=(100, 50), pos=(5, 60))
            self.colorpicker_btn.bind(on_press=self.roue_de_couleurs)
    
            # Create color boxes
            self.saved_colors = [Button(size_hint=(None, None), size=(50, 50), pos=(355 + 50 * i, 5)) for i in range(31)]
            for btn in self.saved_colors:
                btn.bind(on_release=lambda btn: self.peintre.definir_couleur(btn.background_color))
            btn_exporter = Button(text='Exporter', size_hint=(None, None), size=(100, 50), pos=(40, 385))
            btn_exporter.bind(on_release=lambda instance: self.export_to_png('dessin.png'))
    
            parent.add_widget(self.peintre)
            parent.add_widget(btn_effacer)
            parent.add_widget(btn_ligne_fine)
            parent.add_widget(btn_ligne_epaisse)
            parent.add_widget(btn_ligne_plus_epaisse)
            parent.add_widget(btn_ligne_encore_plus_epaisse)
            parent.add_widget(btn_quitter)
            parent.add_widget(btn_rouge)
            parent.add_widget(btn_vert)
            parent.add_widget(btn_bleu)
            parent.add_widget(btn_jaune)
            parent.add_widget(btn_turquoise)
            parent.add_widget(btn_rose)
            parent.add_widget(self.colorpicker_btn)
            parent.add_widget(btn_blanc)
            parent.add_widget(btn_ouvrir)
            for btn in self.saved_colors:
                parent.add_widget(btn)
            parent.add_widget(slider)
            parent.add_widget(btn_exporter)
    
            self.file_chooser = FileChooserListView()
            self.file_chooser.bind(on_submit=self.ouvrir_fichier_selectionné)
            self.file_popup = Popup(title='Choisir un fichier', content=self.file_chooser, size_hint=(None, None), size=(400, 400))
            return parent
    
        def adjust_brush_size(self, instance, value):
            # Met à jour la taille de la ligne et du point basée sur la valeur du slider
            self.peintre.definir_largeur_ligne(value)
            self.peintre.definir_largeur_point(value * 2)  # Par exemple, le point est toujours le double de la largeur de la ligne
    
        def roue_de_couleurs(self, instance):
            self.color_picker = ColorPicker()
            self.color_picker.bind(color=self.def_couleur)
            popup = Popup(title='Sélectionnez une couleur', content=self.color_picker, size_hint=(None, None), size=(400, 400))
            popup.open()
            popup.bind(on_dismiss=self.save_color_to_box)
    
        def def_couleur(self, instance, color):
            self.peintre.definir_couleur(color)
    
        def ajuster_couleur(self, couleur):
            facteur = 0.2
            moyenne = sum(couleur[:3]) / 3
            nouvelle_couleur = [(c + facteur * (c - moyenne)) for c in couleur[:3]]
            nouvelle_couleur = [min(max(0, c), 1) for c in nouvelle_couleur]
            return tuple(nouvelle_couleur + [couleur[3]])
    
        def save_color_to_box(self, instance):
            couleur = self.color_picker.color
            satcoul = self.ajuster_couleur(couleur)
            couleur_assignée = False
            for btn in self.saved_colors:
                if btn.background_color == [1, 1, 1, 1] and not couleur_assignée:
                    btn.background_color = satcoul
                    couleur_assignée = True
    
        def configurer_peintre(self, largeur_ligne, largeur_point):
            self.peintre.definir_largeur_ligne(largeur_ligne)
            self.peintre.definir_largeur_point(largeur_point)
    
        def effacer_toile(self, obj):
            self.peintre.effacer_toile()
            print('Effacé !')
    
        def ouvrir_fichier(self):
            # Afficher le FileChooser
            self.file_popup.open()
    
        def ouvrir_fichier_selectionné(self, instance, selection, _):
            # Fermer le FileChooser
            self.file_popup.dismiss()
    
            # Vérifier si un fichier a été sélectionné
            if selection:
                chemin_fichier = selection[0]
                self.peintre.effacer_toile()  # Effacer le dessin actuel
                with self.peintre.canvas:
                    Rectangle(source=chemin_fichier, pos=self.peintre.pos, size=self.peintre.size)
    
                print(f'Fichier ouvert: {chemin_fichier}')
    
        def export_to_png(self, filename):
                print(f"Exporting to {filename}...")
                
                try:
                    print('Size', self.peintre.size, 'Filename: ', filename)
                    self.peintre.size = (Window.size[0], Window.size[1])
                    self.peintre.export_to_png(filename)
    
                except Exception as e:
                    print(f"Error while exporting: {e}")
                    raise e
        def enregistrer(self):
            if self.peintre.dessin:
                content = BoxLayout(orientation='vertical')
                msg = Label(text='Vous avez des changements non enregistrés. Voulez-vous enregistrer ?')
                btn_layout = BoxLayout(size_hint_y=None, height='70', orientation='horizontal')
                oui_btn = Button(text='Oui', on_release=lambda *args: self.sauvegarder_toile())
                non_btn = Button(text='Non', on_release=lambda *args: App().stop())
                btn_layout.add_widget(oui_btn)
                btn_layout.add_widget(non_btn)
                content.add_widget(msg)
                content.add_widget(btn_layout)
                self.save_popup = Popup(title='Confirmation de Sauvegarde', content=content, size_hint=(None, None), size=(400, 200), auto_dismiss=False)
                self.save_popup.open()
            else:
                print('Rien à enregistrer')
                self.stop()
    
        def sauvegarder_toile(self):
            if self.peintre.dessin:
                # Créer une boîte de dialogue d'entrée textuelle pour saisir le nouveau nom du fichier
                content = BoxLayout(orientation='vertical')
                msg = Label(text='Saisissez le nouveau nom du fichier (sans extension) :')
                input_filename = TextInput(hint_text='Nom du fichier')
                btn_layout = BoxLayout(size_hint_y=None, height='70', orientation='horizontal')
                sauvegarder_btn = Button(text='Sauvegarder', on_release=lambda *args: self.sauvegarder_avec_nom(input_filename.text))
                annuler_btn = Button(text='Annuler', on_release=lambda *args: self.save_popup.dismiss())
                btn_layout.add_widget(sauvegarder_btn)
                btn_layout.add_widget(annuler_btn)
                content.add_widget(msg)
                content.add_widget(input_filename)
                content.add_widget(btn_layout)
                self.save_popup = Popup(title='Sauvegarder le dessin', content=content, size_hint=(None, None), size=(500, 200), auto_dismiss=False)
                self.save_popup.open()
            else:
                print('Rien à enregistrer')
    
        def sauvegarder_avec_nom(self, filename):
            if filename:
                if not filename.endswith('.png'):
                    filename += '.png'  # Ajouter l'extension .png si elle n'est pas déjà présente
                self.export_to_png(filename)
                print(f'Dessin sauvegardé sous {filename} !')
            else:
                print('Nom de fichier invalide.')
            self.save_popup.dismiss()
            AppPaint().stop()
    
        def profil(self):
            pass
    
    
    if __name__ == '__main__':
        AppPaint().run()
    
    
    • Note that your WidgetPaint class has inherited functions such as export_to_png that you have not defined.