Resolvi criar uma interfacezinha gráfica para o programa criado no primeiro post de conteúdo extra. Novamente, aqui serão abordados conceitos não comentados ainda. A intenção maior do tópico é mostrar mais uma possibilidade da programação, a criação de interfaces gráficas mais amigáveis para o usuário final. Tudo necessário para o entendimento de tudo que acontecerá abaixo irá acontecer em ORUTUFs posts e vídeos. Este é um tópico mais pra vocês pegarem o big picture do que para entenderem o código tintim por tintim.
Em Python, há uma biblioteca que já vem com a linguagem para a criação de interfaces gráficas que é o Tkinter.
O código não tá lá o melhor código do mundo, não foram usadas boas práticas de código, na verdade fiz aqui mais pra postar e comentar o resultado com vocês, então a galera mais avançada aí dá um desconto no código haha
Antes de mais nada, precisamos instalar a biblioteca de processamento de imagens Pillow. Abra um terminal e digite:
pip install Pillow
Isso irá instalar a biblioteca Pillow. Em seguida abra sua IDE preferida e comece a escrever o código.
Realizando os imports necessários:
import PIL.Image, PIL.ImageTk
from tkinter import *
from tkinter import filedialog
import os
Aqui, novamente irei usar o módulo PIL (Python Imaging Library), de processamento de imagens. PIL é um apelido, um "alias" pra Pillow. Desta vez trouxe uma forma diferente de importar o necessário da biblioteca, usando a sintaxe "PIL.Image" e "PIL.ImageTk". Foi só pra mostrar essa nova possibilidade.
Como dito, tkinter é a biblioteca de criação de interfaces gráficas. Ao realizar o import usando um asterisco queremos dizer que estamos importando tudo da biblioteca, e assim evitamos ter que digitar sempre o nome da biblioteca antes de digitar a função necessária (isso foi mais bem explicado no post anterior, 1.3 (cont)).
Poderia ter sido digitado também "import tkinter", entretanto, como comentado no post anterior, essa segunda forma pede que antes de cada função necessária digitemos o nome do módulo. Além de tudo da biblioteca, também precisei importar especificamente a função filedialog. A explicação para isso não convém muito ao caso agora, mas ao gravar o vídeo deste tópico podemos comentar sobre isso.
Por fim, importei um módulo de Python que simula o sistema operacional, que é o "os". Ele pode ser usado para obter nomes de arquivos, abrir arquivos, obter extensões de arquivos, etc. O motivo de todos esses imports ficará mais claro mais pra frente.
Em seguida, criaremos nossa janela de interface gráfica. Irei chamar a janela de "root".
from tkinter import *
(...)
root = Tk()
root.mainloop()
O que quis dizer acima é que root é uma instância, é um objeto da classe Tk(), que veio com a biblioteca tkinter. Não se preocupem com isso agora, mas entendam que o significado disso é que estou criando minha janela.
O comando "root.mainloop()" é o comando necessário para abrir nossa janela em nosso desktop.
O resultado é este:

Temos uma janela vazia. Vamos adicionar um título para a janela, definir um tamanho fixo, em pixels, adicionar um painel de layout e uma imagem pra decorar nosso programa:
import PIL.Image, PIL.ImageTk
from tkinter import *
from tkinter import filedialog
import os
root = Tk()
root.title('Python Image Resizer')
root.geometry('550x500')
panel_title = Label(root, text='Python Image Resizer 1.0', font=('Helvetica', 16), pady=20)
panel_title.pack()
img = PIL.ImageTk.PhotoImage(PIL.Image.open('meadow 3.jpg'))
panel = Label(root, image = img)
panel.pack()
root.mainloop()
Explicando o código:
root.title = define um título para a janela
root.geometry('550x500') = define que o tamanho da janela será de 550 pixels x 500 pixels
panel_title será uma variável de rótulo, de label, em inglês, e irá ser responsável por definir um título para o nosso painel. Irei usar a classe Label da biblioteca tkinter para criar nosso rótulo. Label receberá como parâmetros a nossa janela principal (root), o texto do nosso rótulo, a fonte escolhida e um espaçamento vertical (eixo y de um plano cartesiano) de 20 pixels
Então:
panel_title = Label(root, text='Python Image Resizer 1.0', font=('Helvetica', 16), pady=20)
E em seguida eu preciso apresentar essa label na nossa janela. O comando panel_title.pack() é o responsável por apresentar o título na janela. Caso não seja feito o pack() da label, nada será visualizado.
Em seguida crio uma nova variável chamada "img" e abro uma imagem qualquer presente dentro da minha pasta para embelezar nosso complexo programa. Como será uma imagem usada dentro de uma janela do tkinter, há a particularidade de se usar "PIL.ImageTk.PhotoImage(PIL.Image.open(arquivo))" para apresentação correta da imagem em nossa janela (em vez da abertura de imagem usada em tópico anterior, que foi somente "Image.open()").
Em seguida adiciono a imagem dentro de uma nova label (panel), e como tudo que virá agora em seguida, para correta apresentação em nossa interface, devemos executar o comando "pack()" para visualizarmos nossa label com nossa imagem.
O resultado:

Como é um programa de redimensionamento de imagens, preciso pedir do usuário qual as medidas que ele quer para largura (width) e altura (height) da nova imagem. Então precisamos criar novas labels com o texto pedindo esses dados e enviar os dados para dentro do nosso programa Python usando a classe "Entry" do tkinter. Também quero que o usuário digite um nome para a nova imagem que será gerada:
import PIL.Image, PIL.ImageTk
from tkinter import *
from tkinter import filedialog
import os
new_width_label = Label(root, text='New width value(px)')
new_width_label.pack()
new_width = Entry(background='white')
new_width.pack()
new_height_label = Label(root, text='New height value(px)')
new_height_label.pack()
new_height = Entry(background='white')
new_height.pack()
new_filename_label = Label(root, text='New filename')
new_filename_label.pack()
new_filename = Entry(background='white')
new_filename.pack()
O resultado até agora:

Já temos quase tudo pronto. Agora preciso de um botão para selecionar a imagem que eu quero editar. Iremos usar a classe "Button" do tkinter para isso:
import PIL.Image, PIL.ImageTk
from tkinter import *
from tkinter import filedialog
import os
root = Tk()
root.title('Python Image Resizer')
root.geometry('550x500')
panel_title = Label(root, text='Python Image Resizer 1.0', font=('Helvetica', 16), pady=20)
panel_title.pack()
img = PIL.ImageTk.PhotoImage(PIL.Image.open('meadow 3.jpg'))
panel = Label(root, image = img)
panel.pack()
new_width_label = Label(root, text='New width value(px)')
new_width_label.pack()
new_width = Entry(background='white')
new_width.pack()
new_height_label = Label(root, text='New height value(px)')
new_height_label.pack()
new_height = Entry(background='white')
new_height.pack()
new_filename_label = Label(root, text='New filename')
new_filename_label.pack()
new_filename = Entry(background='white')
new_filename.pack()
button = Button(root, text='Open file')
button.pack()
root.mainloop()
O resultado:

A interface está pronta. Falta adicionar a lógica, que é abrir a imagem a ser processada e realizar seu processamento. Iremos adicionar o comando "command" dentro dos parâmetros de Button e inserir uma função que irá realizar a abertura da imagem e seu redimensionamento:
Em button, devemos adicionar:
button = Button(root, text='Open file', command=process_file)
E devemos criar uma função com o nome "process_file", que irá realizar toda a lógica do programa. Sei que não falei de funções ainda, então não se preocupem quanto a isso, a hora de falar de funções vai chegar.
def process_file():
file_wrapper = filedialog.askopenfile()
file_name = file_wrapper.name
file_extension = os.path.splitext(file_name)[1]
image = PIL.Image.open(file_name)
width = int(new_width.get())
height = int(new_height.get())
image = image.resize((width, height), PIL.Image.ANTIALIAS)
image.save(new_filename.get() + file_extension)
image.show()
file_wrapper é a variável que recebe o arquivo aberto. Ele é um objeto de um tipo bem esquisito:
<_io.TextIOWrapper name='/run/media/thiago/Backup HDD/Python Projects/Bastter programação/hello.png' mode='r' encoding='UTF-8'>
O que me interessa é somente o texto, a string, do nome do arquivo, e a string eu obtenho na linha "file_name = file_wrapper.name". O comando filedialog.askopenfile() é responsável por procurar a imagem.
Também preciso obter a extensão (.png no caso acima) para adicionar ao nome do arquivo que será criado pelo usuário, para ele não precisar digitar "nova imagem.jpg" no nome do arquivo, e sim somente "nova imagem" no campo "New filename". A extensão eu obtenho por meio do comando os.path.splitext().
O comando splitext retorna uma tupla de dois valores, o primeiro valor é o nome do arquivo e o segundo valor é a extensão. Por isso o "[1]" ao final da linha, pois eu quero o que está na posição [1] e não na posição [0].
O resto do código da função é aquilo já visto no primeiro post extra, de redimensionamento de imagem. A diferença acontece nos "get()" que vocês estão vendo.
Ao pedir uma nova largura e uma nova altura do usuário, o objeto obtido é parecido com o acima (io.TextIOWrapper), então por meio do comando get() eu pego o valor. No caso, se o usuário pediu uma largura de 400 px eu consigo obter o valor 400 por meio do comando get().
Mas tem um porém, o get() obtém o valor como string, como texto. Eu preciso que o valor esteja no formato de inteiro (int). Por isso o comando completo é int(new_width.get()), pois assim eu estou transformando uma string em um inteiro (lembrem do tópico de tipos de dados).
Por fim, eu salvo a imagem adicionando o nome de arquivo obtido pelo usuário concatenando com a extensão obtida na imagem original, e mostro pro usuário com o comando show().
O código completo fica assim:
import PIL.Image, PIL.ImageTk
from tkinter import *
from tkinter import filedialog
import os
root = Tk()
root.title('Python Image Resizer')
root.geometry('550x500')
window_title = Label(root, text='Python Image Resizer 1.0', font=('Helvetica', 16), pady=20)
window_title.pack()
img = PIL.ImageTk.PhotoImage(PIL.Image.open('meadow 3.jpg'))
panel = Label(root, image = img)
panel.pack()
new_width_label = Label(root, text='New width value(px)')
new_width_label.pack()
new_width = Entry(background='white')
new_width.pack()
new_height_label = Label(root, text='New height value(px)')
new_height_label.pack()
new_height = Entry(background='white')
new_height.pack()
new_filename_label = Label(root, text='New filename')
new_filename_label.pack()
new_filename = Entry(background='white')
new_filename.pack()
def process_file():
file_wrapper = filedialog.askopenfile()
print(file_wrapper)
file_name = file_wrapper.name
file_extension = os.path.splitext(file_name)[1]
image = PIL.Image.open(file_name)
width = int(new_width.get())
height = int(new_height.get())
image = image.resize((width, height), PIL.Image.ANTIALIAS)
image.save(new_filename.get() + file_extension)
image.show()
button = Button(root, text='Open file', command=process_file)
button.pack()
root.mainloop()
O programa em ação:

Ufa, enfim acabei esse quase capítulo de livro haha
Não fiquem preocupados se não entenderam muita coisa. Falei de muita coisa que não abordei em momento nenhum. A intenção até foi tentar ensinar como fiz tudo tintim por tintim, mas a principal intenção foi mostrar mais essa possibilidade de se fazer com programação, que é a criação de uma interface gráfica para o usuário final. Mas irei fazer o vídeo explicando como foi feito tudo aqui e talvez as coisas fiquem mais claras.
Podem perguntar abaixo qualquer coisa sobre o código que eu sei que não deve ter sido muito fácil de entender. Falei de conceitos que eu mesmo demorei bastante pra entender.