Creating a "playing card" in Python (adding text to an image) - python

I'm working on a project that should automatically generate a "playing card" type of image.
I have a blank image with no text to work with. My original idea was to populate this blank image with text using Pillow. I do not think this is a good idea because:
There is no easy way to automatically place the text (always keep it horizontally centered but calculate the right height based on the contents)
To my knowledge, I can't autosize text. I essentially want a textbox, and if text gets too big the font size shrinks to fit rather than overflowing.
What is an alternative solution to this problem? I'm open to completely ditching any image libraries, I just don't know where to go from here.
Attached to this post is the blank image I previously referred to, as well as two images that demonstrate the desired result with "text fitting."
Thanks in advance.

I'm not sure what you want to do with the card image, but the below is an example of using a tk.Canvas to display the blank card with a text overlay.
Since the title and the description header are only one line, they are positioned by their absolute centers. The description is positioned by the top-left, so it will always start in the same spot, regardless of the amount of text it contains.
If you have any questions, ask them.
import tkinter as tk
from tkinter import EventType as tke
from PIL import Image, ImageTk, ImageGrab
from dataclasses import dataclass
#dataclass
class Card(tk.Canvas):
master :tk.Widget
blankname :str
width :int
height :int
title :str
subtitle :str
description:str
def __post_init__(self):
tk.Canvas.__init__(self, self.master, width=self.width, height=self.height, highlightthickness=0)
hw = self.width//2
self.img_id = self.create_image(hw, self.height//2, image=self.blankname, tags=self.blankname)
self.ttl_id = self.create_text(hw, 62 , width=300, font=('Ariel', 22, 'bold'), text=self.title)
self.sub_id = self.create_text(hw, 340, width=300, font=('Ariel', 18), text=self.subtitle, justify=tk.CENTER)
self.dsc_id = self.create_text(50, 360, width=300, font=('Ariel', 14), text=self.description, justify=tk.CENTER, anchor=tk.NW)
#https://stackoverflow.com/a/38645917/10292330
#this grabs a screenshot of the card at the exact position and size for the card
def save(self, *args):
root = self.nametowidget(self.winfo_toplevel())
x=root.winfo_rootx()+self.winfo_x()
y=root.winfo_rooty()+self.winfo_y()
x1=x+self.winfo_width()
y1=y+self.winfo_height()
ImageGrab.grab().crop((x,y,x1,y1)).save(f'{self.title}.png')
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('392x560')
#must be loaded before we start making cards, as-well-as any other blanks
self.blank = ImageTk.PhotoImage(Image.open('blank.png'), name='blank')
#base kwargs for ALL cards that use THIS blank
blank_kwargs = {'blankname':'blank', 'width':self.blank.width(), 'height':self.blank.height()}
#symbiotic link data
sym_link_desc = (
"Activate while over an ally to buff them "
"or activate while over an opponent to defuff them. "
"The links you create are destroyed when you receive damage from a non-ally."
)
sym_link_kwargs = {
**blank_kwargs,
'title':'Symbiotic Link',
'subtitle':'Linkstrider',
'description':sym_link_desc
}
#symbiotic sustain data ~
#whoops # "while while" ~ you get the point
#I left it cause I'm not remaking the image
sym_sustain_desc = (
"Activate while while linked to an enemy "
"to leech their health for a short duration. "
"The leeching ends if you are hit in it's duration."
)
sym_sustain_kwargs = {
**blank_kwargs,
'title':'Symbiotic Sustain',
'subtitle':'Linkstrider',
'description':sym_sustain_desc
}
#card
self.card = Card(self, **sym_link_kwargs)
self.card.pack()
#press space to save
self.bind('<space>', self.card.save)
if __name__ == '__main__':
App().mainloop()
Helpful Links:
tk.Canvas
tk.Canvas.create_image
tk.Canvas.create_text
tkinter docs main page

Related

How do I shop tkPDFViewer from combining PDF's when opening PDFs using buttons?

In this code, I'm trying to open PDFs as a separate window using tkinter and tkPDFViewer. The way this is done is a simple UI opens up with 2 buttons for 2 articles that are accessed from my computer in this case (but I just labeled it "filepath" for the file path to avoid revealing personal info). However, while the buttons work and open up a PDF window and a download window below that (as expected), when you close the PDF window (and the download window closes along with it) and then re-click on a button (the same one or a different one), it shows the previous pdf and the current one that appeared when you re-click combined. As you repeat this process, the PDFs just append to each other. However, what should happen is that the corresponding PDF to the article that is described in the button is shown and no other PDFs. How do I fix this? I've already tried to move the PDF initialization outside the event function then re-initialize it, but that results in a "local variable used before being defined" error, so that doesn't work, and I currently do not know of any other way to do this.
pdfArray = [
["skweak: Weak Supervision Made Easy for NLP",
r"filepath",
"Pierre Lison, Jeremy Barnes, and Aliaksandr Hubin",
"skweak, NLP, Natural Language Processing"],
["Information Theoretic-Based Quality Measures for Clustering",
r"filepath",
"Barry Drake, and Tiffany Huang",
"Quality Measures, Clustering, Data"
]
]
#Import tkinter library
import os
from tkinter import *
import tkinter as tk
from tkinter import ttk
from tkPDFViewer import tkPDFViewer as pdfViewer
from functools import partial
import shutil
#Create an instance of tkinter frame or window
mainWin= Tk()
#Set the geometry of tkinter frame
mainWin.geometry("1000x600+20+20")
download_icon = tk.PhotoImage(file=r'filepath')
paw_icon = tk.PhotoImage(file=r'filepath')
def download_pdf(original_file):
#this if-else statment detects if the original file exists.
if os.path.isfile(original_file):
newFileStem = os.path.expanduser('~') + r"\Downloads\ChatBox_download"
num = 0
while os.path.isfile(newFileStem + "(%d).pdf"%num):
num = num + 1;
newFile = newFileStem + "(%d).pdf"%num
f = open(newFile, "x")
shutil.copyfile(original_file, newFile)
completeWin = Toplevel(mainWin)
completeWin.geometry("400x75+660+480")
completeWin.title("Download Complete")
Label(completeWin, text = "Download Complete!", font = ('Gabriola', 14, 'bold')).pack()
Label(completeWin, text = str(newFile), font = ('Gabriola', 10)).pack(pady=2)
else:
notFoundWin = Toplevel(mainWin)
notFoundWin.geometry("200x75+660+480")
notFoundWin.title("File Not Found")
Label(notFoundWin, text = "File Not Found", font = ('Gabriola', 14, 'bold')).pack(pady=20)
#Define a new function to open the window
def open_win(pdf_ID):
pdf_title = pdfArray[pdf_ID][0] #title of pdf
file_location = pdfArray[pdf_ID][1] #location of pdf
authors = pdfArray[pdf_ID][2] #authors
keywords = pdfArray[pdf_ID][3] #keywords
pdfWin = Toplevel(mainWin)
pdfWin.geometry("600x350+640+20")
pdfWin.title(pdf_title)
# Adding pdf location and width and height.
pdfViewer.ShowPdf().pdf_view(pdfWin, pdf_location=file_location, width=70, height=100).pack(ipadx = 10, ipady = 10)
infoWin = Toplevel(pdfWin)
infoWin.geometry("600x200+640+420")
infoWin.title("PDF Information")
Label(infoWin, text = "Information: ", font = ('Gabriola', 15, 'bold')).pack(pady=2)
Label(infoWin, text = "Title: " + pdf_title, font = ('Gabriola', 12)).pack(pady=1)
Label(infoWin, text = "Author(s): " + authors, font = ('Gabriola', 12)).pack(pady=1)
Label(infoWin, text = "Keyword(s): " + keywords, font = ('Gabriola', 12)).pack(pady=1)
tk.Button(infoWin, image=download_icon, borderwidth = 0, command=partial(download_pdf, file_location)).pack(pady=1)
#Create a label
Label(mainWin, text= "Click any button below to open a PDF", font= ('Gabriola', 25, 'bold')).pack(pady=30, padx = 10)
#Create a button to open a New Window
for ID in range(0, len(pdfArray)):
tk.Button(mainWin, image=paw_icon, compound=LEFT, text=" Open \" " + pdfArray[ID][0]+" \"", font= ('Gabriola', 12), command=partial(open_win, ID)).pack(pady=2)
mainWin.mainloop()
I ran into this issue as well when using tkPDFViewer.
To fix I had to go into the tkPDFViewer module and add a line:
self.img_object_li.clear()
immediately after the line:
def add_img():
I'm just a beginner myself so this was my fix to get around the issue.
From what I understand the line:
self.img_object_li.append(timg)
adds images indefinitely, I was trying to destroy the frame and widgets containing the objects from my main program but couldn't get it to work at all. Issue just kept persisting. This is probably not the best way to do it and someone else will ahve a better fix.

Frame bg color not being displayed in tkinter window

I'm trying to display a frame with color #231303 on a tkinter window. The contents of the frame(some progress bars) are being displayed, no problem, but the color of the frame is not being displayed.
The link shows how the relevant part of the window looks.
https://drive.google.com/file/d/1VHYW0t9UhjMUeNbwFijkrIy6RiToN0BN/view?usp=sharing
As you can see, the frame color is not being displayed and it is not completely filling the width screen like I want it to either. There are no errors so I'm completely blank. I looked around for an explanation but could not find any.
These are the relevant parts of my code
'''
from tkinter import *
from tkinter.ttk import *
root = Tk()
root.title("Game")
root.geometry("1280x720")
root["bg"] = "gray"
style = Style()
'''
'''
def qn_func(self,qn_num,iteration) :
Game.clear(self)
global bar1,bar2,bar3,bar4
style.configure("var.TFrame",bg="#231303")
frame = Frame(root,height=120,width=1280,style="var.TFrame")
frame.grid(row=0,column=0,sticky=W+E+N)
'''
Here, bar1,bar2,bar3 and bar4 are the progress bars I display within the frame. I am actually very new to tkinter so please try to keep it simple. I also tried to display just the frame and its contents in a test file and this time, the color was displayed but the problem with the frame width still existed.
Does anyone know why this is happening? It would be a great help if anyone could tell me why this is happening. I am also using tkinter.ttk in my code.
Maybe you would like to consider a more solid implementation of OOP.
#don't pollute your namespace
import tkinter as tk
import tkinter.ttk as ttk
from dataclasses import dataclass, asdict
#create a "struct" to default to that represents ALL options for the widget
#dataclass
class Frame_dc:
background: str = 'black'
bordercolor: str = 'black'
darkcolor: str = 'black'
lightcolor: str = 'black'
relief: str = 'flat'
#prepare your data
HeaderFrame = asdict(Frame_dc(*['#231303']*4)) #overwrite the first 4 vars with your custom color
ErrorFrame = asdict(Frame_dc(*['red']*4))
#wrap all your styles in a custom theme
class CustomTheme(ttk.Style):
def __init__(self, basetheme='clam'):
ttk.Style.__init__(self)
#slim example
self.theme_create('custom', basetheme, {
'custom.TFrame': {
'configure': HeaderFrame,
},
'error.TFrame': {
'configure': ErrorFrame,
},
#add more custom styles here
#see: https://docs.python.org/3/library/tkinter.ttk.html#tkinter.ttk.Style.theme_create
})
self.theme_use('custom')
#extend your root
class App(tk.Tk):
#define application constants
WIDTH = 1280
HEIGHT = 720
TITLE = 'Game'
def __init__(self, **kwargs):
tk.Tk.__init__(self, **kwargs) #be more specific than "super()"
CustomTheme()
self.configure(bg="gray")
#name things what they are
self.header = ttk.Frame(self, height=120, style="custom.TFrame")
self.header.grid(row=0, column=0, sticky='wen')
#make `self.frame` fill width ~
#technically this is making the entire column fill the width
#but only because there is no other column with weight to disperse
self.grid_columnconfigure(0, weight=1)
#properly initialize your app
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.mainloop()
aside:
Stay away from global. I can't think of a single language where global is a good thing, and I know a lot of languages. A good design pattern implements encapsulation. global is the antithesis of encapsulation. global could also be considered a forced dependency, depending on the context. Then you have the whole problem that: if I was going to hack your game you just left all the guts wide open for me to play with. If you have a good design you could possibly never use global for the entire rest of your life, without making a single sacrifice in functionality and access.

How to set Margin or Offset tag?

I am attempting to add a small automatic margin inside my text widget however I am having a hard time with writing the tags.
I have a text box and I am trying to insert text into that box while keeping a margin.
I can get the text that is inserted to have a margin but when I type past the last line the margin is gone. So far all I can dig up is how to write the tag and use it with insert() but I want to keep the margin always.
Question: Is there a way to keep the margin on all lines and not just the ones that were inserted from a file or string?
Note the same question extends to Offset tag because I experience the same problem with typing after the inserted text.
Here is what I have tried in a Minimal, Complete, and Verifiable example example.
import tkinter as tk
root = tk.Tk()
text = tk.Text(root, width = 10, height = 10)
text.pack()
text.tag_configure("marg", lmargin1 = 10, lmargin2 = 10)
text.insert("end", "Some random text!", ("marg"))
root.mainloop()
Unfortunately, the edge cases of adding and deleting text at the very start and end of the widget makes working with tags difficult.
If your goal is to maintain a margin, one solution is to create a proxy for the text widget so that you can intercept all inserts and deletes, and always add the margin each time the contents of the widget changes.
For example, start with a custom widget that generates a <<TextModified>> event whenever the widget is modified:
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
(see https://stackoverflow.com/a/40618152/7432)
Next, modify your program to use this proxy to force the margin tag to always apply to the entire contents:
def add_margin(event):
event.widget.tag_add("marg", "1.0", "end")
text = CustomText(root, width = 10, height = 6)
text.bind("<<TextModified>>", add_margin)
If you add the tag to the entire range of text (including the final trailing newline), then new characters you type will inherit that tag.
Add the following, and perhaps it will work like you expect:
text.tag_add("marg", "1.0", "end")
Unfortunately, you'll lose this if you delete all of the text in the widget, but that can be worked around.

Setting up GUI with Tkinter for Image and Text inputs

Im using python 2.7 to build a satellite tracker for a cubesat project at my university. Now I've got the whole thing running as I want it to. I produce 4 images and a load of text based on the ISS's orbit parameters as our cubesat is not yet flying.
The 4 pictures are:
1) Satellite's current position on a globe based on its TLE straight from celestrak.
2) Satellite's current position on a mercator(flat) map.
3) Its current lat/lon position translated to a google earth view to physically interpret where it is.
4) Its current lat/lon position based on current weather maps, for whether or not there are clouds in the region, obscuring the satellites remote sensing imaging capabilities. (Reason why the picture looks ugly and purple, is because this region had no current weather data at the time of retrieving data)
Then I need to implement a text box with information on altitude, velocity, spatial resolution etc.
It's all nice and dandy. But I cant for the love of god figure out how to get this Tkinter GUI to play nice! I'm a geoscientist, not a computer scientist and python programming is very new to me :D
Anyway I did a quick layout in photoshop as of how I want the finished data to be produced in a Tkinter GUI. See the primitive image below:
Now here is my shameful code below:
import Tkinter as tk
from PIL import ImageTk, Image
#This creates the main window of an application
window = tk.Tk()
window.title("Satellite Control Center")
window.geometry("1000x800")
window.configure(background='grey')
#Imports the pictures.
pic1 = "Globeview.png"
pic2 = "MercatorView.png"
pic3 = "currentweathercroppedsmall.png"
pic4 = "GECurrentcroppedsmall.png"
#Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
img1 = ImageTk.PhotoImage(Image.open(pic1))
img2 = ImageTk.PhotoImage(Image.open(pic2))
img3 = ImageTk.PhotoImage(Image.open(pic3))
img4 = ImageTk.PhotoImage(Image.open(pic4))
#The Label widget is a standard Tkinter widget used to display a text or image on the screen.
globeview = tk.Label(window, image = img1)
mercatorview = tk.Label(window, image = img2)
currentweather= tk.Label(window, image = img3)
gearth = tk.Label(window, image = img4)
#The Pack geometry manager packs widgets in rows or columns.
globeview.pack(side = "top", fill = "both", expand = "yes")
mercatorview.pack(side = "top", fill = "both", expand = "yes")
currentweather.pack(side = "bottom", fill = "both", expand = "yes")
gearth.pack(side = "bottom", fill = "both", expand = "yes")
#Start the GUI
window.mainloop()
Which produces this horror show!
My issues are clearly:
1) Images are not aligned in any way I want. Normally in HTML I'd set up table rows, align and fit it with spacers as I want. But I don't know how to define that here and I've spend hours being frustrated of this by now.
2) I need to add a text box. Every time I've tried to add various versions of text boxes. I get weird pyimage'xx' errors and no text box seems to materialize.
3) For the future: A button under the images that will show the full size uncropped picture. But that's not imperative right now!
So I'm hoping one of you have a nice way to do this, or can point me in a direction.. or perhaps even have done something like it where I can see your code and just tweak the numbers of a bit for aligning the pixels.
Thank you in advance.
You are trying to lay everything out in a single frame. That can be done, but it's much easier to make subframes for things that fall in neat rows, columns or grids, and then put the subframes into the final arrangement.
Totally untested guess:
import Tkinter as tk
from tkFont import Font
from PIL import ImageTk, Image
#This creates the main window of an application
window = tk.Tk()
window.title("Aarhus University Satellite Control Center")
window.geometry("1000x800")
window.configure(background='grey')
#Imports the pictures.
pic1 = "Globeview.png"
pic2 = "MercatorView.png"
pic3 = "currentweathercroppedsmall.png"
pic4 = "GECurrentcroppedsmall.png"
#Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
img1 = ImageTk.PhotoImage(Image.open(pic1))
img2 = ImageTk.PhotoImage(Image.open(pic2))
img3 = ImageTk.PhotoImage(Image.open(pic3))
img4 = ImageTk.PhotoImage(Image.open(pic4))
header = tk.Label(window, text="Header", font=Font(size=40))
header.pack()
toprow = tk.Frame(window)
globeview = tk.Label(toprow, image = img1)
globeview.pack(side = "left") # the side argument sets this to pack in a row rather than a column
infobox = tk.Text(toprow)
infobox.pack(side = "left")
toprow.pack() # pack the toprow frame into the window
bottomrow = tk.Frame(window)
mercatorview = tk.Label(bottomrow, image = img2)
currentweather= tk.Label(bottomrow, image = img3)
gearth = tk.Label(bottomrow, image = img4)
mercatorview.pack(side = "left")
currentweather.pack(side = "left")
gearth.pack(side = "left")
bottomrow.pack()
#Start the GUI
window.mainloop()
import Tkinter as tk
from PIL import ImageTk, Image
#This creates the main window of an application
window = tk.Tk()
window.title("Aarhus University Satellite Control Center")
window.geometry("1000x800")
window.configure(background='grey')
#Imports the pictures.
pic1 = "Globeview.png"
pic2 = "MercatorView.png"
pic3 = "currentweathercroppedsmall.png"
pic4 = "GECurrentcroppedsmall.png"
#Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
img1 = ImageTk.PhotoImage(Image.open(pic1))
img2 = ImageTk.PhotoImage(Image.open(pic2))
img3 = ImageTk.PhotoImage(Image.open(pic3))
img4 = ImageTk.PhotoImage(Image.open(pic4))
#The Label widget is a standard Tkinter widget used to display a text or image on the screen.
globeview = tk.Label(window, image = img1).grid(row=0,column=0)
mercatorview = tk.Label(window, image = img2).grid(row=1,column=0)
currentweather= tk.Label(window, image = img3).grid(row=1,column=1)
gearth = tk.Label(window, image = img4).grid(row=1,column=2)
#Start the GUI
window.mainloop()
You need to add row and column in the label grid option. After you are creating the Label , put the grid and mention row and column . I have updated the code, just check it . If you face any problem write us back, and remove pack, which I deleted in the code :) Cheers

tkinter label image not updating with .update_idletasks()

I have an issue where my image label will not update. I have used a large combination of root.update() root.update_idletasks() etc, I have also gone through many posts around the internet attempting to use their solutions but to no avail.
I have the code split to one class and two functions, the first function will check if the user has the right spelling or not, the second will pick a new random word and image from a dict.
The issue is that the image will not update, the print command is working so the class and funcs are working fine.
here is the code thus far, I am new to using Class and init I thought the best way to test out the .update of tkinter is a spelling game
from tkinter import *
import random
words = {"apple": "apple.gif", "car": "car.gif"}
MAIN_FONT = "Helvetica", 16
root = Tk()
class mainFrame:
def __init__(self):
self.pick_random = "..."
#MAIN TITLE OF THE APP
main_title = Label(root, text="Spelling", font=MAIN_FONT)
main_title.pack()
#END OF MAIN TITLE
#START OF IMAGE
self.img = PhotoImage(file=self.pick_another() + ".png")
self.show_image = Label(root, image=self.img)
self.show_image.configure(image=self.img)
self.show_image.image = self.img
self.show_image.pack()
#END OF IMAGE
#START OF ENTRY AND BUTTON INPUTS
self.main_entry = Entry(root)
self.submit_btn = Button(root, text="Submit", command=self.submit)
self.main_entry.pack()
self.submit_btn.pack()
#END OF ENTRY AND BUTTON INPUTS
def submit(self):
if self.main_entry.get() == self.pick_random:
print("RIGHT")
self.pick_another()
else:
print("That's not right, try again")
def pick_another(self):
print("Called")
self.pick_random = random.choice(list(words.keys()))
print(self.pick_random)
root.update_idletasks()
return self.pick_random
app = mainFrame()
root.mainloop()
As I said this does kind of work, The first image will show up and inputting the correct spelling for the image will generate a new word but the image does not update.
I have spent a few days working on various scripts trying to get tkinter to update, but it will not.
I would be very grateful for any help in this

Categories

Resources