Picture zooming doesnt select the right area - python-3.x

I have an application that displays images. Since people need to read some information out of the image i implemented a zoom functionality.
I set the picture Widget to 600x600. To Preserve the aspect ratio i then scale the picture and draw it to the widget. This works really well.
For the zoom functionality the user should click anywhere on the picture and it should cut out the are 150x150 pixesl around where the cursor clicks. To be precises the click of the cursore should mark the middle of the rectangle i cut out. So if i click on x=300 y=300 the area should be x=225 y=225 width=150 height=150.
To Archive that i scale the coordinates where the user clicks back to the original image resolution, cut out the subimage and scale it back down. Cutting out the scaled image allready loaded in my programm would yield a far worse quality.
The error is simple. The area cut out is not exactly the aerea i would like to cut out. Sometimes it is to far left. Sometimes to far right. Somtes to high sometimes to low... And i fail to see where the problem lies.
I wrote a barebone prototype whith just the functionality needed. After you put in a path to a jpeg picture you should be able to run it.
# -*- coding: utf-8 -*-
"""
Created on Sun Jan 12 12:22:25 2020
#author: Paddy
"""
import wx
class ImageTest(wx.App):
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
self.frame = wx.Frame(None, title='Radsteuer Eintreiber')
self.panelleft = wx.Panel(self.frame)
self.picturepresent=False
self.index=0
self.PhotoMaxSize = 600
self.zoomed=False
#Change path here
self.imagepath='F:\Geolocation\Test\IMG_20191113_174257.jpg'
self.createUI()
self.frame.Centre()
self.frame.Show()
self.frame.Raise()
self.onView()
#Creates UI Elements on Initiation
def createUI(self):
#instructions = 'Bild'
img = wx.Image(self.PhotoMaxSize,self.PhotoMaxSize,clear=True)
self.imageCtrl = wx.StaticBitmap(self.panelleft, wx.ID_ANY,
wx.Bitmap(img),size=(self.PhotoMaxSize,self.PhotoMaxSize))
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.imageCtrl.Bind(wx.EVT_LEFT_UP, self.onImageClick)
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL|wx.ALIGN_CENTER, 5)
self.panelleft.SetSizer(self.mainSizer)
self.mainSizer.Fit(self.frame)
self.panelleft.Layout()
def onImageClick(self,event):
if self.zoomed:
self.onView()
self.zoomed=False
else :
#Determin position of mouse
ctrl_pos = event.GetPosition()
print(ctrl_pos)
picturecutof=self.PhotoMaxSize/4
if ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)>0:
xpos=ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)
else:
xpos=0
if ctrl_pos[0]+picturecutof>self.NewW:
xpos=self.NewW-picturecutof
if ctrl_pos[1]-((self.PhotoMaxSize-self.NewW)/2)>0:
ypos=ctrl_pos[1]-((self.PhotoMaxSize-self.NewW)/2)
else:
ypos=0
if ctrl_pos[1]+picturecutof>self.NewH:
ypos=self.NewH-picturecutof
xpos=xpos*self.W/self.NewW
ypos=ypos*self.H/self.NewH
picturecutofx=picturecutof*self.W/self.NewW
picturecutofy=picturecutof*self.H/self.NewH
rectangle=wx.Rect(xpos,ypos,picturecutofx,picturecutofy)
self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
self.img=self.img.GetSubImage(rectangle)
self.img=self.img.Scale(600,600,wx.IMAGE_QUALITY_BICUBIC)
self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
self.imageCtrl.Fit()
self.panelleft.Refresh()
self.zoomed=True
def onView(self,event=None):
self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
self.W = self.img.GetWidth()
self.H = self.img.GetHeight()
if self.W > self.H:
self.NewW = self.PhotoMaxSize
self.NewH = self.PhotoMaxSize * self.H / self.W
else:
self.NewH = self.PhotoMaxSize
self.NewW = self.PhotoMaxSize * self.W / self.H
self.img = self.img.Scale(self.NewW,self.NewH,wx.IMAGE_QUALITY_BICUBIC)
self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
self.imageCtrl.Fit()
self.panelleft.Refresh()
if __name__ == '__main__':
app = ImageTest()
app.MainLoop()
There maybe some wird stuff happening in the code that doesnt make entirely sense. Most of it is because the normal programm is much bigger and i removed many features in the prototpy that are having nothing to do with the zooming. It might very well be that i do the scaling wrong. But im out of ideas.
The Functionality for this protopye is simple: Replace the Path to a jpge image of your choiche. Run the programm. Click on the image and it should zoom. Click around your image and you will see zooming is wrong.
Thats it. Thanks for your help.

So i found the answer. But i changed something on the logic also. The picture will now be centered at the position where the user clicked. This is much more intuitive to use. I onle post the onImageClick Function. If you want to use the whole thing feel free to replace it in the original code from the question.
def onImageClick(self,event):
if self.zoomed:
self.onView()
self.zoomed=False
else :
#Determin position of mouse
ctrl_pos = event.GetPosition()
#Set magnification.
scalingfactor=4
#Set Picture size for rectangle
picturecutof=self.PhotoMaxSize/scalingfactor
##Find coordinates by adjusting for picture position
xpos=ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)
ypos=ctrl_pos[1]-((self.PhotoMaxSize-self.NewH)/2)
#if position is out of range adjust
if xpos>self.NewW:
xpos=self.NewW
if xpos<0:
xpos=0
if ypos>self.NewH:
ypos=self.NewH
if ypos<0:
ypos=0
#scale rectangle area to size of the unscaled image
picturecutofx=picturecutof*self.W/self.NewW
picturecutofy=picturecutof*self.H/self.NewH
#scale coordinates to unscaled image
xpos=xpos*self.W/self.NewW
ypos=ypos*self.H/self.NewH
#centeres image onto the coordinates where they were clicked
xpos=xpos-((ctrl_pos[0]*self.W/self.NewW)/scalingfactor)
ypos=ypos-((ctrl_pos[1]*self.H/self.NewH)/scalingfactor)
#if position is out of range adjust
if xpos>self.W-picturecutofx:
xpos=self.W-picturecutofx-5
if xpos<0:
xpos=0
if ypos>self.H-picturecutofy:
ypos=self.H-picturecutofy-5
if ypos<0:
ypos=0
#create rectangle to cut from original image
rectangle=wx.Rect(xpos,ypos,picturecutofx,picturecutofy)
#load original image again
self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
#get subimage
self.img=self.img.GetSubImage(rectangle)
#scale subimage to picture area
self.img=self.img.Scale(self.PhotoMaxSize,self.PhotoMaxSize,wx.IMAGE_QUALITY_BICUBIC)
self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
self.imageCtrl.Fit()
self.panelleft.Refresh()
self.zoomed=True
event.Skip()

Related

Qt6/PySide6 - how to position graphics elements

I'm new to Qt and trying to figure out how position graphics items?
For example, I want to overlay some text onto a video. However, instead of overlaying them, the text is vertically stacked above the video.
My code is below, I've tried setting the position of the video/text elements (e.g. video.setPos(0,0)) but it didn't work. I also tried using QGraphicsLayout but ran into problems adding the video/text elements.
import sys
from PySide6 import QtWidgets, QtCore, QtMultimedia, QtMultimediaWidgets
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
view = QtWidgets.QGraphicsView(self)
# Create a QT player
player = QtMultimedia.QMediaPlayer(self)
player.setSource(QtCore.QUrl("https://archive.org/download/SampleVideo1280x7205mb/SampleVideo_1280x720_5mb.mp4"))
video = QtMultimediaWidgets.QGraphicsVideoItem()
player.setVideoOutput(video)
text = QtWidgets.QGraphicsTextItem("Hello World")
scene = QtWidgets.QGraphicsScene()
scene.addItem(video)
scene.addItem(text)
view.setScene(scene)
view.setFixedSize(800,800)
player.play()
player.setLoops(-1)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
widget = MyWidget()
widget.resize(800, 800)
widget.show()
sys.exit(app.exec())
The problem is caused by two aspects:
QGraphicsVideoItem has a default size of 320x240; for conceptual and optimization reasons, it does not change its size when video content is loaded or changed;
the video you're using has a different aspect ratio: 320x240 is 4:3, while the video is 1280x720, which is the standard widescreen ratio, 16:9;
By default, QGraphicsVideoItem just adapts the video contents to its current size, respecting the aspectRatioMode. The result is that you get some blank space above and below the video, similarly to what was common when showing movies in old TVs ("letterboxing"):
Since graphics items are always positioned using the top left corner of their coordinate system, you see a shifted image. In fact, if you just print the boundingRect of the video item when playing starts, you'll see that it has a vertical offset:
player.mediaStatusChanged.connect(lambda: print(video.boundingRect()))
# result:
QtCore.QRectF(0.0, 30.0, 320.0, 180.0)
^^^^ down by 30 pixels!
There are various possibilities to solve this, but it all depends on your needs, and it all resorts on connecting to the nativeSizeChanged signal. Then it's just a matter of properly setting the size, adjust the content to the visible viewport area, or eventually always call fitInView() while adjusting the position of other items (and ignoring transformations).
For instance, the following code will keep the existing size (320x240) but will change the offset based on the adapted size of the video:
# ...
self.video = QtMultimediaWidgets.QGraphicsVideoItem()
# ...
self.video.nativeSizeChanged.connect(self.updateOffset)
def updateOffset(self, nativeSize):
if nativeSize.isNull():
return
realSize = self.video.size()
scaledSize = nativeSize.scaled(realSize,
QtCore.Qt.AspectRatioMode.KeepAspectRatio)
yOffset = (scaledSize.height() - realSize.height()) / 2
self.video.setOffset(QtCore.QPointF(0, yOffset))

Embedding Images and matplotlib Plots together on on tk canvas

A few years ago I wrote a script in python2 that would pull images and generate plots and show them together in one gui, following this old documentation. However, I'm trying to update this to python3, and I've run into an issue with the way that plots are put on tk windows in the latest version of matplotlib. There are updated guides for how to embed matplotlib graphs in tk windows, of course, but these don't seem to be exactly what I want, since I want to plot various images and graphs together.
Currently:
# Start by creating the GUI as root
root=Tk()
root.wm_title("JADESView")
# Create the canvas
canvas=Canvas(root, height=canvasheight, width=canvaswidth)
# Plot the EAZY SED
#image = Image.open(EAZY_files+str(current_id)+"_EAZY_SED.png")
image = getEAZYimage(current_id)
# Crop out the thumbnails
image = cropEAZY(image)
photo = resizeimage(image)
item4 = canvas.create_image(eazy_positionx, eazy_positiony, image=photo)
Label(root, text="EAZY FIT", font=('helvetica', int(textsizevalue*1.5))).place(x=eazytext_positionx, y = eazytext_positiony)
# Plot the BEAGLE SED
#new_image = Image.open(BEAGLE_files+str(current_id)+"_BEAGLE_SED.png")
new_image = getBEAGLEimage(current_id)
new_photo = resizeimage(new_image)
item5 = canvas.create_image(beagle_positionx, beagle_positiony, image=new_photo)
Label(root, text="BEAGLE FIT", font=('helvetica', int(textsizevalue*1.5))).place(x=beagletext_positionx, y = beagletext_positiony)
canvas.pack(side = TOP, expand=True, fill=BOTH)
# Plot the thumbnails
fig_photo_objects = np.empty(0, dtype = 'object')
fig_photo_objects = create_thumbnails(canvas, fig_photo_objects, current_id, current_index, defaultstretch)
I create a tk.canvas object, and then use canvas.create_image to place two PIL photo objects, with labels (the "EAZY SED" and BEAGLE SED"), and this still works with python3. However, the create_thumbnails function I've written creates individual figure objects and then calls a function "draw_figure" to embed them, which I'll post below, but it's from the tk example I linked above:
def draw_figure(canvas, figure, loc=(0, 0)):
""" Draw a matplotlib figure onto a Tk canvas
loc: location of top-left corner of figure on canvas in pixels.
Inspired by matplotlib source: lib/matplotlib/backends/backend_tkagg.py
"""
figure_canvas_agg = FigureCanvasAgg(figure)
figure_canvas_agg.draw()
figure_x, figure_y, figure_w, figure_h = figure.bbox.bounds
figure_w, figure_h = int(figure_w), int(figure_h)
photo = PhotoImage(master=canvas, width=figure_w, height=figure_h)
# Position: convert from top-left anchor to center anchor
canvas.create_image(loc[0] + figure_w/2, loc[1] + figure_h/2, image=photo)
# Unfortunately, there's no accessor for the pointer to the native renderer
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
# Return a handle which contains a reference to the photo object
# which must be kept live or else the picture disappears
return photo
And this breaks in all sorts of ways.
I'm open to overhauling how everything is done, but I don't see examples that show how this sort of thing might work. The examples for the latest version of matplotlib all explain that you should use FigureCanvasTkAgg, but I don't exactly know how to use this if I already have an existing canvas with photos embedded.
Any help would be appreciated, and I can also explain more if necessary!

Making parts of canvas transparent while still detecting and blocking mouse clicks in transparent areas in tkinter?

I'm trying to make a program where the user can paint on the screen. So I want to make an invisible canvas window in fullscreen where only the user's pen marks on the canvas will be visible. The closest thing I found is this function: root.attributes("-transparentcolor","color code here"), which will make all the parts of the window that's in the color you give transparent. So if I give the second parameter the background color of the canvas, then only the pen strokes on the canvas will be visible. This is so close to what I want, except for one thing, the transparent areas can't detect or block mouse clicks! Any mouse clicks will just go through to whatever is behind the tkinter window. Is there a way to make it so the transparent areas will still block mouse clicks? I really need help on this!
Here is a much better way to do this using only tkinter. Explanation is in code comments. Basically uses two windows, one for "blocking" the mouse and being transparent using the "-alpha" attribute and the other window for "hosting" canvas and having one completely transparent color while keeping others opaque using "-transparentcolor" attribute. That also means that this is cross-platform solution too (except I think the -transparentcolor attribute differs a little bit on other OS like Linux where I think it is -splash or sth and maybe something different on MacOS):
from tkinter import Tk, Toplevel, Canvas
# setting the starting coordinate of the line so that
# on motion it is possible to immediately draw it
def set_first(event):
points.extend([event.x, event.y])
# on motion append new coordinates to the list and if there are
# 4 (the minimum), create a new line and save the id
# otherwise update the existing line
def append_and_draw(event):
global line
points.extend([event.x, event.y])
if len(points) == 4:
line = canvas.create_line(points, **line_options)
else:
canvas.coords(line, points)
# when released clear the list to not waste space
# and not necessarily but also set "id" to None
def clear_list(event=None):
global line
points.clear()
line = None
line = None # this is a reference to the current line (id)
points = [] # list to keep track of current line coordinates
line_options = {} # dictionary to allow easier change of line options
# just a variable to more easily store the transparent color
transparent_color = 'grey15'
# creating the root window which will help with drawing the line
# because it will "block" mouse because `-alpha` (0.01 seems to be the lowest value)
# attribute is used, however it makes everything transparent on the window
# so need another window to "host" the canvas
root = Tk()
root.attributes('-alpha', 0.01)
root.attributes('-topmost', True)
root.attributes('-fullscreen', True)
# just press Esc key to close the whole thing, otherwise
# it is only doable by pressing Alt + F4 or turning off
# the computer
root.bind('<Escape>', lambda e: root.quit())
# create the host window, because it allows to have only
# one transparent color while keeping the other opaque and
# visible
top = Toplevel(root)
top.attributes('-transparentcolor', transparent_color)
top.attributes('-topmost', True)
top.attributes('-fullscreen', True)
# set the focus to root because that is where events are bound
root.focus_set()
# create the canvas to draw on
canvas = Canvas(top, bg=transparent_color, highlightthickness=0)
canvas.pack(fill='both', expand=True)
# bind all the events to `root` which "blocks" mouse
# but is also almost (because it has a very small alpha value
# it is not entirely invisible but human eye won't notice much)
# invisible
root.bind('<Button-1>', set_first)
root.bind('<B1-Motion>', append_and_draw)
root.bind('<ButtonRelease-1>', clear_list)
root.mainloop()
Here is an improvable example (you may need to pip install pyautogui, ctypes is a built-in library), it is also Windows only as far as I know:
Note: The other answer using two windows, however, is a lot better but I will keep this too just for the information.
from tkinter import Tk, Canvas
import pyautogui as pag
import ctypes
data = {
'draw': True,
'cur_line_points': [],
'cur_line_id': None
}
# function taken mainly from here: https://stackoverflow.com/a/46596592/14531062
def is_pressed(btn: str = 'left') -> bool:
if btn == 'left':
btn = 0x01
elif btn == 'right':
btn = 0x02
else:
raise Warning("incorrect argument, should be 'left' or 'right'")
return ctypes.windll.user32.GetKeyState(btn) not in (0, 1)
def draw_line(canvas_):
if not data['draw']:
root.after(10, draw_line, canvas_)
return
pressed = is_pressed('left')
cur_line_points = data['cur_line_points']
cur_line_id = data['cur_line_id']
if not pressed:
if cur_line_id is not None:
canvas_.coords(cur_line_id, cur_line_points)
data['cur_line_id'] = None
cur_line_points.clear()
else:
mouse_x, mouse_y = pag.position()
cur_line_points.extend((mouse_x, mouse_y))
len_points = len(cur_line_points)
if len_points == 4:
data['cur_line_id'] = canvas_.create_line(cur_line_points)
elif len_points > 4:
canvas_.coords(cur_line_id, cur_line_points)
root.after(10, draw_line, canvas_)
transparent_color = 'grey15'
root = Tk()
root.config(bg=transparent_color)
root.attributes('-transparentcolor', transparent_color)
root.attributes('-topmost', True)
root.attributes('-fullscreen', True)
canvas = Canvas(root, bg=transparent_color, highlightthickness=0)
canvas.pack(fill='both', expand=True)
draw_line(canvas)
root.mainloop()
Basically detects if mouse button is pressed using the built-in library ctypes and if it is adds the current mouse coordinates (does that using pyautogui library which may need be installed) to a list and then draws a line based on that list (it also keeps the reference of the currently drawn line and simply changes its coordinates instead of drawing a new line each time it loops), the only slight issue is that while drawing the mouse is also interacting with the window below, highlighting text and stuff, couldn't really figure out how to remove that yet but at least you get to draw a line.

wxPython - frame showing up mostly offscreen, self.Center() not working - related to system display settings?

*Note: I've determined that this issue has to do with my display settings. When I run my code on my laptop (win. 10 display settings: res. = 1920x1080, scale = 150%), the issue occurs. When I'm at work, with the laptop connected to a larger external monitor (w10 display settings: res. 1920x1080, scale=100%), everything is fine and dandy. Is there a way to create this frame such that it'll show up correctly regardless of the user's display settings? I suspect many of my users will run into this issue -- we all use the same model laptop and similar external displays, and the display settings I mentioned above are the W10 default (/'Recommended') display settings for the respective displays.
I've got a program that runs fine until the very last frame. In previous versions, the last frame showed up fine. Then when I added a plot canvas to the frame, needing it now to be wider, I was having a hell of a time getting the frame to resize itself to accommodate the plot -- Layout() didn't work, SetSizerAndFit(sizer) didn't work.. So I just hard-coded the size.
Now my issue is that when the frame is displayed (just resframe = ResultsFrame(None); resframe.Show(), it appears with most of the upper-left half waay off screen (see image below). Here's the code for a mockup of the issue. Actually this one shows up huge - still positioned outside the screen boundary but now also extending past the screen in all directions. The inspection tool still shows it's only 1290x945 (I'm using a laptop with a 1920x1080 screen) but that's obviously not right. Regardless, the frame is still positioned in the same location (offscreen) as the real one from my program.
What am I doing wrong?
Copy/pasting important bit I left out in response to comments: Forgot to mention the behavior is the same when using pos = wx.DefaultPosition as well as just leaving pos out of the __init__; I also can't drag the window around since the entire top bar is offscreen. The main issue isn't size, it's position. The dummy script I wrote (for whatever reason) appears huge onscreen (despite showing the correct size in the inspection tool), but in the actual app I am writing, the frame is the right size, just positioned offscreen. The size issue seen in the dummy script is weird (sizes in InspectionTool all return 1920x945 yet the frame appears far bigger than the screen size, despite being 1920x1080), but does not occur in my actual program, so I'm more concerned with the positioning.
import wx
class ResultFrame(wx.Frame):
def __init__(self, parent):
super(ResultFrame, self).__init__(parent, -1, title = "Results",
size = wx.Size(1290,945), pos = wx.Point(50,50), style = wx.CAPTION|
wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SYSTEM_MENU)
self.InitUI()
self.Layout()
self.Center()
def InitUI(self):
self.mainpnl = wx.Panel(self)
self.nbpnl = wx.Panel(self.mainpnl)
self.ctrlpnl = wx.Panel(self.mainpnl)
self.mainsizer = wx.BoxSizer(wx.HORIZONTAL)
self.ctrlsizer = wx.BoxSizer(wx.VERTICAL)
self.restart = wx.Button(self.ctrlpnl, wx.ID_ANY, "Start Over",\
size=(100,40))
self.interp = wx.Button(self.ctrlpnl, wx.ID_ANY, "Help Interpreting\n"
"Results", size=(100,40))
self.nb = wx.Notebook(self.mainpnl)
for tar in range(0,4):
self.nb.AddPage(wx.Panel(self.nb), f"page {tar}")
self.ctrlsizer.Add(self.restart, 0, wx.ALL, 5)
self.ctrlsizer.AddStretchSpacer()
self.ctrlsizer.Add(self.interp, 0, wx.ALL, 5)
self.ctrlpnl.SetSizerAndFit(self.ctrlsizer)
self.mainsizer.Add(self.ctrlpnl, 0, wx.ALL, 0)
self.mainsizer.Add(self.nb, 1, wx.EXPAND)
self.mainpnl.SetSizerAndFit(self.mainsizer)
self.CreateStatusBar()
self.SetStatusText("Results")
def main():
import wx.lib.mixins.inspection
app = wx.App()
frm = ResultFrame(None)
frm.Show()
wx.lib.inspection.InspectionTool().Show(refreshTree=True)
app.MainLoop()
if __name__ == "__main__":
main()

Tkinter Create buttons from list with different image

I want to create button from a list and I want them to have their own image.
I have tried this but only the last created button work.
liste_boutton = ['3DS','DS','GB']
for num,button_name in enumerate(liste_boutton):
button = Button(type_frame)
button['bg'] = "grey72"
photo = PhotoImage(file=".\dbinb\img\\{}.png".format(button_name))
button.config(image=photo, width="180", height="50")
button.grid(row=num, column=0, pady=5, padx=8)
Only your last button has an image as it is the only one that has reference in the global scope, or in any scope for that matter in your specific case. As is, you have only one referable button and image objects, namely button and photo.
Short answer would be:
photo = list()
for...
photo.append(PhotoImage(file=".\dbinb\img\\{}.png".format(button_name)))
But this would still have bad practice written all over it.
Your code appears okay, but you are need to keep a reference of the image effbot and also I do believe PhotoImage can only read GIF and PGM/PPM images from files, so you need another library PIL seems to be a good choice. I used a couple images from a google search for the images, they were placed in the same directory as my .py file, so i changed the code a little bit. Also the button width and height can cut off parts of the image if not careful.
from Tkinter import *
from PIL import Image, ImageTk
type_frame = Tk()
liste_boutton = ['3DS','DS','GB']
for num,button_name in enumerate(liste_boutton):
button = Button(type_frame)
button['bg'] = "grey72"
# this example works, if .py and images in same directory
image = Image.open("{}.png".format(button_name))
image = image.resize((180, 100), Image.ANTIALIAS) # resize the image to ratio needed, but there are better ways
photo = ImageTk.PhotoImage(image) # to support png, etc image files
button.image = photo # save reference
button.config(image=photo, width="180", height="100")
# be sure to check the width and height of the images, so there is no cut off
button.grid(row=num, column=0, pady=5, padx=8)
mainloop()
Output:
[https://i.stack.imgur.com/lxthT.png]
With all your comments I was able to achieve what I expected !
Thank ! I'm new to programming so this will not necessarily be the best solution but it works
import tkinter as tk
root = tk.Tk()
frame1 = tk.Frame(root)
frame1.pack(side=tk.TOP, fill=tk.X)
liste_boutton = ['3DS','DS','GB']
button = list()
photo = list()
for num,button_name in enumerate(liste_boutton):
button.append(tk.Button(frame1))
photo.append(tk.PhotoImage(file=".\dbinb\img\\{}.png".format(button_name)))
button[-1].config(bg="grey72",image=photo[-1], width="180", height="50")
button[-1].grid(row=num,column=0)
root.mainloop()

Resources