Skip to content

ImagePainter App, Save and Draw on an Image!

GitHub Repository

AI Transcript provided by KivyWhisper

Okay, everybody, welcome back to Kivy School. Today, we're going to be answering this question. This question asks, I'm currently attempting to draw over an image and save the changes. I've got no clue how to do that. My objective is to load an image and draw lines over it by touching it. Afterward, I want to save the image with the drawn lines on it. Could someone please provide an example? Thanks in advance.

Okay, so this is a relatively complex question, but it's not actually that hard to do in Kivy.

There's one prerequisite for this video, which is Kivy how to install and run in Windows 2024 with examples. This is because I'm going to show you one example from Kivy examples. The example is going to be called touch tracer. This will explain to you how to install it. And then if you really want to run these codes, come back after you've watched this video.

Okay, so what's our goal? Our goal is to draw over an image and save the changes. Now, one question you should always ask is, is there already a Kivy example? So there's a trick in VS code, right? Where you go right here in search, then you can click the three dots here. Files to include. So you'd go, reveal and find file explorer. Right here, Kivy examples. These are all the examples. So you can add this to your search. Right here.

And then you can say, okay, I'm going to look up something. Because I already know what we're looking for. So what we're looking for, I'm going to say touch tracer. And here's where it is. You can come over here to touch tracer and then run it. So yeah, I've seen this trick before. It's touch tracer.

Touch tracer lets you draw with a mouse. So let's actually run this. So let's go here, reveal. Okay, copy and then CD here. I think I'm already there. And then Python. What is this? Python main.py.

Okay, so this is our touch tracer example. Now, if you touch, well, here I'm using a mouse click, right? Touch, you'll draw on it.

But there's two problems with this example. Number one, you need to draw on an image. Right now we're just drawing on a blank canvas, which is the flow layout.

And number two is you got to save what you're drawing. So those are the two changes. But touch tracer has already done the hard work for us, which is just drawing with our touch and our gesture. So we're almost there. We're probably like 50%, 75% of the way.

So now let's modify this touch tracer example. So the two things to add to this example are load an image and save an image. Okay, let me just clear all of these. Okay, so what's our plan? Our plan is to use one button to open a file chooser. The other part is going to be we'll set the canvas with an image. Why? Because this isn't really a canvas, but we can insert an image in here on top and then we can start drawing on it.

So that's what I mean by set canvas with an image. We will have an image, async image, and then we'll have an image. Set canvas with an image. We will have an image, async image, widget in here. What else?

Just let me tell you this trick because not everybody knows this, but you can save the widget using export to PNG. This works because the base Kivy widget has an export to PNG method.

And then if you look right here, let's remove touch tracer. It says export as image. There's export image, but we want export to PNG. Save an image of the widget and its children in PNG format at the specified file name. Works by removing the widget canvas from its parent, rendering to an FBO, and calling save. Okay, cool.

All we need to know is that we've got to give it some file name and that's it. No args, no keyword args. Okay, so that's the tricks. What else is the last trick? And then now I'll show you the solution, which is image painter. So Python image painter. So this is the example image painter. You see here, right? I don't draw up here. That's good. And then you can just draw, draw, draw, draw, draw. And let's see. Right. And then if we go here, reveal in file explorer. This is our printed image, right?

So that's the easy part, quote unquote, just saving an image. Now the question is how do we draw on top of an image, right? So we can go. There's two ways to load an image like off the top of my head.

Number one way would be like a drag and drop, which I didn't do here because I thought it was too easy and it's too cheesy because it's not really like a good solution for like a real app. But it's a quick and dirty solution.

What I did was I used a file chooser. So it file chooser and some screen manager. So here it says load image using file chooser. OK, I'll go to another screen and then here's a file chooser. You can look at everything.

This is actually the default C folder. We go right here. Right here. This is the C folder. And I just put Kivy.jpeg. So right here, Kivy.jpeg will load. This Kivy.jpeg. Now we can start drawing, drawing, drawing, drawing, and then we can save our PNG.

And look here. We drew on an image, which is Kivy.jpeg, and then we saved it as in PNG. And then from PNG, you can like convert to whatever file type you want. So this is the complete example.

It solves the concept of loading an image and saving an image. And then we also drew on it. But the drawing code was already done for us with touch tracer. So let's see. We showed that it can draw. We can show that it can save. Solution. All right. TLDR. That's the explanation.

Now we're going to go to code explanation line by line plus issues I had making this.

So there are three major parts to this example. Number one is that there's a screen manager. You can technically ignore this if you want. But for this, it's not required. But the app kind of looks ugly without a screen manager because I'm using a file chooser and the file chooser takes up so much space.

Like if you go back here, the file chooser takes up so much space of the widget. It doesn't feel right to like, you know, just have it on there. There's also cancel. We'll go back. Hey, what else? Let's see. And then the file chooser outside the scope of this video. File chooser will be like another one hour video. Don't have time for that today.

We'll just go directly to what's important for loading an image, drawing on an image, saving an image. File chooser outside the scope.

And then I will just talk briefly about the on drop file solution. So here it says drag and drop get file path. In KV, I'm trying to build an interface where the user can drag and drop a file into a widget and then my code would retrieve the file system path. Right. So you ideally would draw a drag and drop a correct image type, then drop it over and then it will set the canvas.

The answer is use on drop file event handler. Here's a working example. Then you can do it on your own at your home. But that's another solution instead of using a file chooser. And then number two, another big problem I had was how to set the canvas. Because you see here, right? When you go here and you're here, right?

When you press load, you should set the canvas on another screen. That's kind of complicated. I'll show you easy way to do it. Let's see what else.

It's going to be widget drilling. There will be an example below. Another part of setting the canvas is canvas instructions. It's also outside the scope of this video. That will be another one hour plus long tutorial. So we're gone.

Another one is actually printing the image. Right. So for any widget, you can do export to PNG. I think I've showed this to you already. Just export to PNG. There's also export this image.

So TLDR, if Python can do it, Kivy can do it. It's just up to you to search for the capability to do it. If that makes any sense. But you can definitely export any of your widget to PNG. So close like this. Now, what are the issues? It looks way better as an embedded Kivy example.

This is because the assets are not being located correctly. So to show this to you, I will show both apps side by side. Now you see, there's a background here that is not being loaded. Where is this background? One second. I think it's in... Oh. Let's go out like this. Okay, I'm just going to clear them. So there's a background here. If you go to... Share, Examples, it needs widgets and then demo. No, it's demo right here. Touch Tracer.

You see the Kivy. There's the source data, images, background, .jpeg, Kivy icon. This right here is... I'm going to use the trick again just to find this file. It's already extended Kivy examples. Let's see this. Okay, maybe it's... Library. Yeah, it's Kivy. Data. Images. Background.

So you see right here, this is the background that Touch Tracer is using. And because I just copied the example over to this folder, this structure is not going to work. It's not going to find it in 2024 June 11 data images background. But it's a quick fix.

You can just set it to whatever background you like. And that would be... Where's Touch Tracer? That would be here. That would be here. You would say source. Like, you know, whatever source I can find. So quick quick solution. I should just leave this as a comment. What else? Do simple copy paste. Okay, so... If that's the case, let's do it again. We'll go right here. File Explorer. And I'll just copy paste this. Oh, not correct. Just the new file like this. So Touch Tracer dot py, right? Paste. Then we'll just run Python Touch Tracer dot py. Oh, not here. Python Touch dot py. And you can see it's the app without the KV loaded.

And it's like a blank app. And so that's what I got and I inherited. And just to let you know what's the difference. It's just the finding the data in the KV. Actually, there's no KV, which is why it's just a blank window. So like this is done.

Okay, what's another issue? The problem is it also intercepts. Another problem I had when making this example was when you touch down and touch up, actually, because I added extra widgets, you would draw on the buttons. And I will show you that very quickly right here. Like this. Oh, and I should delete this. Yeah. Okay, so now here's the problem.

The problem is that the Touch Tracer intercepts button input. So if you see this, I'm going to draw. Oh, it actually works. There's one more I need to turn off. Untouch move. So if you look right here, it's actually possible to draw on the whole widget. And you don't want that. Okay, this is not what you want to see. You just want to save this like drawable instance. So if that's the case, what's the solution, right? The solution.

Okay, number one, what's the problem? The problem is that let me show you this again. The problem is that this canvas instruction is intercepting all the button inputs. This is also button input and it's being intercepted, right? So how do you make sure that you're not drawing on this little button widget? How are you sure you're not drawing on this button widget, right? Let's find out. Let's ask the app itself.

Let's use Python debugging tools, right? So if that's the case, we see right here, and when I was expecting, there's a touch move and a touchdown. So I was, through my gut, I was thinking, okay, it has something to do with touchdown. Or touch move or even touch up, which it was not.

It was one of these two. It was actually both of them. So here, touchdown, right? On touchdown, let's set trace, right? So PDB set trace. Let's see what's going on. So if I touch down right here, let's check. So where are a couple of things?

Who are the usual suspects? First self, right? Who's self? Touch tracer object, right? That's correct because if you go to the KV, let's put it right. You go to the KV. There's the touch tracer here. And then I clicked here, right? So load image using file chooser. The self is this touch tracer object.

Let's check args, right? The args are going to be two things itself. Touch tracer object and then this touch right here, right? So I'm asking, okay, how do you make sure that if I'm touching this button, I'm not drawing? You can also flip it where you're saying, okay, if I'm touching only the touch tracer, then you can draw. And if you're not touching on the touch tracer, you will never draw.

So the one way to do that is using collide point and touch pause. So one thing how collide point works is that this is your position right here that's being transmitted when you're using these touch events. That's one of the arguments. So you can say widget collide point with the touch. All of this asks for is for any widget, are you colliding? Yes or no? That was what it will give.

So because it's a touch tracer, let's say who's self? Touch tracer so we can say self dot collide point touch. I hope this works. False, right? Why? Because I'm actually touching the button widget. I was not touching the touch tracer.

So let's exit. It will die. We'll go again. Now I will deliberately touch on the touch tracer. Okay. And then we're going to do. Again, we'll ask who's self touch tracer. It's the same. What are the arcs? Similar. Then let's go self. So this is touch tracer dot collide point. Is my touch touching the touch tracer? It's true. Why? Because I'm actually touching this. And again, we'll go right here. I'm going to touch the third widget, which is this button.

Okay. Now we'll do. Self collide point. Okay. So first, let's always make sure we know what's going on. So we're in self, which is touch tracer. You're good. Arcs the same. We are good. Never assume anything. Everything always lies to you. Just live like that. And you will never get tricked by Python. Because a lot of the times you assume something is true and it's not. Always check your assumptions.

So here I touch the safety and G button. It collided with touch tracer. Yes. No. No, it's false. So that's one way to say, okay. If you are touching the touch tracer, like canvas or the widget area, then you can do all these canvas instructions on touchdown. Then on touch move, you can do all these canvas instructions. So let me undo these changes. And then uncomment. Undo changes. Okay. This tab, uncomment. Then hopefully it will work. Let's check it out. Oh, PDG is still on. Goodbye. Okay. Oh, and then see right here. I'm not touching the touch tracer widget anymore. So it just stopped. It just stopped over there.

That's the one problem I had because I was drawing over this. I don't think people would ask for it. You just draw on the draw area. Okay. It happened twice. I showed. Okay. Why root IDs file chooser path? Okay. What's the problem? Okay. Why root IDs file chooser path? Okay. What's this? Okay. So in the KV code, there's going to be something where it says root the ID file chooser dot chooser load, right? Where are you? This is the chooser screen. Okay. So let's just check it out.

Again, let's ask Python with PDB. Where is I think it's? Okay. So on the chooser screen in the box layout with load text, I know who this is right here. Right. So the question is, why am I doing this? So what am I trying to do? I'm trying to call root chooser load. So let's check who root is. Right. It's going to be chooser screen name.

It's going to be a screen. And the screen is, let's just copy it. No, that's not it. That's a change. Don't do that. Control F. Chooser screen name. Okay. Okay. So in chooser screen, I'm trying to go to root, which is chooser screen, which is right here. Then I'm trying to go to file chooser ID, which is copy paste. Let's look for it. This one. And in custom chooser, our custom chooser class, I'm going to call chooser load. So we'll go like this. Class, custom chooser, chooser load.

And this function says, make sure the selection is non-empty and has a JPEG extension. So here's a couple of things I checked. No selection. Will it run? Make sure it only runs on the JPEG, right, for this function. So no selection. Will it not run? Okay. Will it run on an actual JPEG? It should say yes. Not a JPEG. It should say no. If it's a folder, apparently it's not possible to choose a folder, which is really good, because I don't want to do that. So now this is what I've decided on. So let's check it out. Okay. Let me check where this PDB is. It's here. Let's turn this off. Let's turn on here. Let's see. It's Python. ImagePainter. Wowchooser. All right. So now we are in... All right. Let me just make it not respond so I can move it to the next page.

Now we're here, right? So let's check the usual suspects, right? Who's self? Right. CustomChooser object. Is that correct? Yeah, because it's right here. Let's check the art. Just self. Okay. Now this is something that's not obvious, but when I was looking at this chooser example, there's two things that stand out, which was the fact that it's not a JPEG.

So there's two things that stand out, which was the selection and path. So right here. Actually, I should show this to you so it becomes obvious. So it's Python file chooser example, right? This is just the generic file chooser example from Kivy Org. Let's see if I can tap.

So file chooser Kivy Org. So I chose this one, the list view. Let's see it. See if it's this example. So it's this example. It's the same. Now let's look at the app itself. And then you see if it loads, right? Here's the loads, select load, right? And this is the functionality that I wanted. And then here you can see already what had PDB on. Right.

So I wanted to know, OK, how did it determine what it was loading, right? I don't think this is the answer. Where is... Where's selection? OK, here it is. So button says load, right? On release, root.load, file chooser path, file chooser selection. Actually, the PDB was not the solution. It was in the KV. KV says root.load, file chooser path. So now let's ask, OK, where's self... object, right?

So here's the thing. I can't really determine if it's the same, but let's just say, OK, let's check the directory of this self. Control F, is there a path method? No, OK. OK, that's the case. I forgot to ask the other one, which is going to be args. Like this. OK. Let's check who's self, args. Yeah, see, you should always ask the common questions. Who's self, who's args. Check the directory of everyone.

So here in load, right, we're going to ask for the args. And here you get the path and the file name, right? And then how do you get path and file name? You go back to the KV and load, like dot load. You get file chooser path, file chooser selection, right? So who is this file chooser? This file chooser list view. And then you go to Kivy org. And then I was like,

OK, what's path and what's selection? You just control F, who's path. Right there, it says path is a string property that defaults to the current working directory as a Unicode string. It specifies the path on the file system that this controller should refer to. So you get the path, which is this, the directory. Let's get selection. There is selection. It contains the list of files that are currently selected. Selection is a read-only list property and defaults to empty list. So I think for this example, it's not multi-select. Let me just say multi.

Yeah, here it says multi-line false. OK, multi-line false, because the default is list of files. So apparently you can select multiple, but here you don't. OK, let's go clear this.

Now, why root IDs, file chooser ID path? Where are you here? And where are you here? The root IDs chooser load. So let's go back to Python image painter. Go to the file chooser. Let's choose this. So number one, the first one is if you choose nothing, right? Oh, it's cancel. Choose nothing. OK, it's saved. Let me go again.

So I've chosen nothing, right? Let's see if it works. So what am I saying? What I'm saying is if self-selection is not equal to an empty list. So first, let's check two questions. Who's self? Custom chooser. Are we need custom chooser correct? It's args. Self. OK, so we're going to do what is self.selection? Empty list, right? Because we chose nothing. And then here, I'm just saying if we copy and paste. If your self.selection, your first element, and you split by a period, and you get the object on the right, if it's a JPEG.

So you'll see it later. But this will say, this actually won't run because self.selection is already not an empty string. It is an empty string, which means it's false. And this boolean will not run. So it's already says why it says list index out of range, right? Because if I swap them, this one will run first and it will error out. But because I check if it's not an empty list first, then this will run.

So if it's no selection, it already says false. This will not run. What's next? Now let's say an actual JPEG. Go here, file chooser. We'll choose an actual JPEG. Let's press load. Then let's say, OK, let's check self. Chooser, args, self. OK, good. Now let's check who is self.selection, right?

It's a list of your choice. And then I'm saying it's not an empty list. Right now that's true. True. And then here let's check out if it's a JPEG. True, right? So let's split it up into parts. Let's see, what's this? Right? It's going to be JPEG, right? But let's go from the beginning. Let's self.selection. Kivy, right?

What if you go select the first element of this list? Right? You remove the brackets. So this is not a list anymore. This is like a string. I actually have to check. But let's go copy. Let's check the type. String, right? Can I move this? Yes. OK. So this right here is a string. Now let's check the type of selection. So selection and selection. So this and this. It's a list.

So for Kivy, it's an observable list. All observable means is that if there's a change, it puts changes to the rest of the Kivy. Don't worry about it. Just know it's almost a list. It's basically a list. What else is there? So we did this. We forced turned it into a string. But it was already a string. I just do it just to force things into a string. Sometimes it's not.

What else? So what does split with a period mean? It just means that with every period, you split the string into two. So let's check it out. What's this? It's going to be Kivy, a JPEG. Now let's split. So we have two parts. It's going to be Kivy. And then the period is the splitter. So it's not counted anymore. And then it's JPEG. Now let's get the first element from the end.

It's going to be JPEG. OK, we got the JPEG. Now is it going to be equal to JPEG? True. So I picked an actual JPEG. It works. Now let's say not a JPEG. So you go like this. I'm going to select the other file. This is a log file. Let's see if... First, let's do the usual suspect, who's self? Who's args? OK, sounds correct. Now let's do first, what's self selection? VBOC post-install.

That's correct because I selected it. So is this selection not equal to empty list? OK, right click. True, right? It's not empty list. And then you can check this out. False, right? Because after you split the string, what is it? It's going to be log. It's not JPEG. So these are just like the obvious cases that I thought of. So no selection. It works. Actual JPEG it works. Not JPEG.

In a folder, let's try and select it. I haven't been able to select the folder. Right, so it just goes back up. Right. I haven't been able to select the folder. But if you're able to select the folder, then you should also handle this case. Let's turn this off. Now let's show the complete selection.

What happened? Right here. Right here. Load. It loaded, right? Go right here. Let's try and load a log file. Please choose a JPEG file. Right? Why? Because I'm only selecting for JPEG files if they fail. Else, parent, parent, load text. Please choose a JPEG file. Should I explain this now? Let's see if I... Okay, I'll explain it now. So why am I doing parent.parent? Like all these parents.

Let's go back with PDB. Okay, first let's finish with the other option. I did no selection. I didn't actual JPEG. I did not JPEG. The folder, okay. So no selection. Please choose a JPEG file. Right? If you have an actual JPEG file, it loads. Let's remove that. And then if you're not a JPEG file, please choose a JPEG file. And then you can't pick a folder.

Okay, so there's two big parts to this chooser load function. The first part is you have to set this canvas thing, right? So in order to do that, you're going to use this trick. There's two parts to this too. Number one is in the touch tracer, I've added an async image, right? And then this is from Kivy UX image before async image.

All this does is basically... It means you don't have to have like an image loaded, and it's good for performance. I also gave it an ID, right? So the real question is from this screen, from this button, how do you set this image here in this KV, right? The answer is going to be use widget drilling.

It's an easy quick solution or some sort of way if you're doing things quick and dirty like I am. So if that's the case, let's go back here. And we'll only run the port BDB. We'll only run if it's a JPEG file. BDB.set. Right, so from this location... So first let's see if it's only on JPEG. Please choose a JPEG file. I don't know what's this. Please choose a JPEG file, right? So I'll pick a JPEG file.

Please choose a JPEG file. Now our PDB is going to run. We'll actually ask that. Okay, what are we asking? We're asking the question of how to get to this async image. Now the most important trick you're ever going to learn is using this widget. So it's going to be app.getRunningApp. So right here is incorrect. Why? Because this is just a function. You actually need to call the function to get what you want to actually run it. So here you have app.getRunningApp, right?

Now what's the root widget? It's the root widget. So we're going to call it. We're going to call it. So here you have app.getRunningApp, right? Now what's the root widget? It's our manager object, right? Again, outside of the scope of this video, but it's a screen manager. It was just a basic example in QBScreenManager. I've just combined this screen manager example with the touch tracer example. Like this. This changes screens. It's literally this code.

What else? Okay, so we're at the manager object, right? Where is this? We're at ImagePainterManager, right? So from ImagePainter, how do I get to async image? Okay, so there's two ways. You can go top down, bottom up. Let's go bottom up. So I'm at async image. So let's go to touch tracer, right? Where's touch tracer? Here. It's here. Where is this? Main box. So you go up again. Where's main box used? Main box is used in StartScreen, okay? So where's StartScreen?

It's used here, and then you get to ImagePainterManager, right? So that's bottom up. Now let's go top down. So I'm at ImagePainter. ImagePainterManager, right? So we can do something like this. IDs. Now because I had defined an ID here, it will show up. But if I had not defined an ID, it will not show up. So this is going to be ImagePainterManager ID. Okay, it's not this. It's going to be the IDs of these screens. Sorry. StartScreen, ChooserScreen. So ChooserScreen.

So like this. Okay, it's going to be ChooserScreen ID. So now we're at ChooserScreen, right? So here or here, I have a box layout and custom chooser, right? Is that correct? Actually, it's wrong because I'm going here. I should go to StartScreen. Because this app has two screens, right? It has the StartScreen and the ChooserScreen, which has the file chooser. So I was actually going to the wrong screen. So we'll go to StartScreen ID right here, right?

And then now let's look for our IDs that we've already defined in our KB. It says there's a main box ID, right? Where is this main box? Okay, we can cheat. We can just go Control F, main box ID. That's right here. Main box. Okay. So then let's look at main box itself. So it's main box, which is a box layout. So copy like this. This main box ID. Okay, so it works. Now, why am I doing this very slowly? Because let's say I type it wrong like ID2. It's going to say key error, right?

So main box ID. What am I looking for? I'm looking for IDs. So now there's one guy. Because actually if you have a more complex app, like in this example right here, you have a choice. But because I'm just doing a minimal example, you only have touch tracer. So let's go like this. Let's go to touch tracer. Touch tracer ID. Okay, works. What does touch tracer have, right? Async image ID. Now from the top down, we've been able to go to async image ID or async image using an ID chain.

So why is this important? Well, simply put, let's go to IDs. Async image ID. Like this. So now we're at this async image, right? The most important part, all of the last maybe 10 minutes, it was just to say, set the source basically. Go like this. Go to directory. There's a source, right? Ah, here it says source. So what is source for async images? QV image. Go to async image.

Okay, let's go to source. It just says the file name and source of your image. Source is a string property and defaults to none. So because we're going to use the default async image, you have a none guy right here, right? But now we're supposed to set the source to the file name. Okay, we'll go back. So it's the source to the file name and location of your image. Exit. And how do we do that? We use, oh, I lost it already.

Okay, so we go from the top of the app, we get the running app, we get the root widget, and then we start doing these ID chains to find the correct widget. And then we can just do like source. So that's why I'm using this ID chain. We set the source, safe path, right? And what's safe path? It just says join the path and join the selection. So let me actually show you this.

Okay, what's self? What's my args? Nothing. So what's self.path? See, it's this. It's your drive. What's self.selection? That's wrong. You know why? I'm going to give you five seconds. What is this type, right? So let's check it out. What's the type? It's going to be an observable list. Do you want an observable list? Mostly not, especially because here in image it says, Oh, it got resized. Where's source? It used to be a string, right? So that's already not good. You don't want a list, you want a string. So how do you get the first element of a string? You go self selection, zero, this.

And then let's check the type. It's a string. So good. Now we have two strings. We have path string and then we have the selection string, which is this. Right. Oh, see, this is why you should always use both path and join. But anyways.

Now you can use OS path dot join. Basically, it's like a cross platform way of making sure that your file locations are correct. Because, for example, for Linux, it's going to be like they use these slashes. Windows uses these slashes and then Mac has something different. I forgot off the top of my head.

But do you see here when I use OS path join, even if I had C and I had C right here, it gave the correct file path. So let's check it out. Right. If you just manually added two strings, what are you going to get? So the path self dot selection zero. Right.

Is this is this a file location? It's not because this is a duplicate source. Now, of course, you can say, OK, why don't you just use self selection? Right. Yeah, I could have. But path join means that it's kind of like an easy way to not mess up. And then make sure it also it also makes sure it works cross platform. So let's do see OS here. Good. OS dot path. path dot join. Self dot path comma self dot selection zero. See OS path makes everything work. While it also works on other platforms to use OS path join. Never plus never use any of these string techniques. Just use path join. What's another one? OK.

Also, another one I want to quickly show is a copy like this. Let's say I give it a list. Right. It's going to complain type error. Join argument must be string by its path object, not observable list, which is true. Right. Because the type of well, it's going to be observable list. But the type of the first element of your list, it's a string. Right. OK. I explained that.

I've explained how to explain this. There's one more I need to explain. Ah, here. OK. Why parent? Right. Again, Python Enter. We go here. And then you need to select a non JPEG image. OK. So again, let's do the two guys, the usual suspects. What's self? What's ours? OK. Now, why am I saying parent, parent, low text? Right. Because I need to set the text of this button.

But right now, the parent is custom chooser. So if you go to the KV, there's custom chooser. OK, this is the class definition. Right here. We're custom chooser, right? But we don't want to be a custom chooser. We want to go to this button right here. Right. If you're a custom chooser, you go to parent one. Wait a minute. Is this a child? Maybe I can do just self dot parent.

Who's the parent of chooser? Boxed out. Right. Could I have just done so that parents? Oh, OK. I know why. Because low text is where are you? Low text is just a string property and had chosen to put it in the chooser screen instead of the box layout. Because if you wanted to go to the box layout directly, you would have to like do custom box layout and then do a custom box layout right here. And then define it in the Python file. But because it was not a custom box layout, I just used a generic box layout. It doesn't have this string property.

So I just go up here to chooser screen. Right here. And then I set this string property. Why? Because the button itself gets root, which is this chooser screen, and then low text. Because the root of this button is chooser screen. So let's check root. Because I'm already at this button.

Right. Who's the root? Oh, it's not defined. Like this. It's not defined. But anyways, root is chooser screen. This is the root widget. And then so why do I need to do parent dot parent? Right. Because low text is a string property on chooser screen. And then we are in, where are we? We're in the file chooser. We need to set the string property here. And then by setting the string property here, it will cause the root low text to update. So let's go like this. Self dot parent. Because the parent, this is a box layout. It's not chooser screen.

If you check the properties, it won't exist. Self dot parent dot load text. It will not exist. Self dot parent dot parent. Who's that? It's chooser screen. Right. So self parent dot parent load text. Low text exists. It just says load. And then if I set it to another text like text, it will force all the other widgets to update. And then this will say another text. So that's why it's two parents. Because we are at custom chooser.

We need to go parent one, which is box layout. But the box layout is a generic box layout. It doesn't have the string property. So we went up one further, which is chooser screen. And then it makes it easier because you can just say root dot load text. If it was like a custom box layout, then you would say like parent root text. Let me see. What else do I need to explain? I did this. Oh yeah.

So something I learned maybe this week, you can have blocks. You can have Python code blocks in KV. So like this will not complain actually. Where does this cancel, right? So something cool just quick to point out. I always thought you had to have like a single line solution. So a lot of the I spent a lot of time maybe like think of a single line if statement or like a single line like what are they called? List comprehension to like correctly set all the states. Now you can actually just do Python code blocks, which I never knew. So I don't know. You should know. Right.

What else? I explained this basically. Touch tracer ID. Okay. I did this. Use OS path join. I did that. Yeah. So basically why OS path join? It's cross platform makes things a lot easier. Don't suffer doing like a manual string replacement. Okay. So I did that. Okay. I also show you the error. Right. Why would the type error fail? Because if you go up here, right. So selection is not a string, even though you think it's a string because you want it to be a string. It's not a string. You got to check the type. It's an observable list.

And then that will fail because OS path join only works on strings. So always ask what's the type. Everything works correctly. It's just like a Python thing and other languages. Everything is pre-typed for you. You shouldn't really encounter these problems, but this Python, there are no rules. Things just get done. Okay. Let's go like this. Export to PNG any widget.

Okay. So right here, again, every widget has this. Why? Because this is the base widget that all widgets inherit from the widget class. Right. So you go right here and the docs, it says there's an export to PNG. Saves an image of the widget and its children in a PNG format at the specified file name. So if you go to PNG, I just say printed image, not PNG. And because our current working directory is this 2024 611 directory, it will spit out the printed image right here.

But if let's say you're in a different current working directory, it would spit out the image in that directory. Why? Because I just say, okay, print it out with this name. But I never specify, think about it, I never specified where to place it. Right. Never specified. Yeah. Basically where to place it and if it should be placed correctly. So here are some arcs.

But the default, if you ever just say a file name, the default is always the current working directory. So you can just say like CWD, which is current working directory. Oh, it's not here. Okay. Let's go Python. What's my current working directory? I think it's CWD. Right. What's my current working directory? It's going to be this folder. Right here. This folder. So if you ever put just a file name and you say, oh, where is it? It's always current working directory.

So let's say, go up one level, right? Python. Import. import.getworkingdirectory. You see this and this are different. It's just one level up or one level down, whichever way you're looking at. Basically, you need to know your current working directory and this export to PNG just fits out the current working directory.

File name, scale. I'm not sure if you can set like the set like the correct location. Let's say like C, you know, plus, right? Or let's say like D drive, right? If you would go to D drive, that's something for you guys to experiment. There's already way too long. I'm pretty sure this one hour. But yeah, this will work. Sorry, one sec. I need to make sure there's no space.

Don't assume anything. There is a space there. OK. OK, I've said this. I've said this. So drawing order is Canvas edition order. If you want something to be on top, add it to the Canvas last. This is not relevant right now because we have just one image and we're replacing the image but for example, let's say we had another async image, right? Like two.

Then you would have to worry about the way you're adding these images. And it should be the way you add it is bottom and then to the top. And I'm not sure how to like determine the order. I wasn't able to find within like one hour, you know, the widget C level. So something to keep in mind for your app.

But yeah, anyways, thank you for watching. This is Kivy School.

Article Error Reporting

Message @BadMetrics on the Kivy Discord.