I'm using Python and tkinter. I have a Canvas widget that will display just one image. Most times the image will be larger than the canvas dimensions, but sometimes it will be smaller. Let's just focus on the first case (image larger than canvas).
I want to scroll the canvas to an absolute position that I have already calculated (in pixels). How can I do that?
After trying for around half hour, I got another solution that seems better:
self.canvas.xview_moveto(float(scroll_x+1)/img_width)
self.canvas.yview_moveto(float(scroll_y+1)/img_height)
img_width and img_height are the dimensions of the image. In other words, they are the full scrollable area.
scroll_x and scroll_y are the coordinates of the desired top-left corner.
+1 is a magic value to make it work precisely (but should be applied only if scroll_x/y is non-negative)
Note that the current widget dimension is not needed, only the dimension of the contents.
This solution works very well even if the image is smaller than the widget size (and thus the scroll_x/y can be negative).
EDIT: improved version:
offset_x = +1 if scroll_x >= 0 else 0
offset_y = +1 if scroll_y >= 0 else 0
self.canvas.xview_moveto(float(scroll_x + offset_x)/new_width)
self.canvas.yview_moveto(float(scroll_y + offset_y)/new_height)
This is what I have already done:
# Little hack to scroll by 1-pixel increments.
oldincx = self.canvas["xscrollincrement"]
oldincy = self.canvas["yscrollincrement"]
self.canvas["xscrollincrement"] = 1
self.canvas["yscrollincrement"] = 1
self.canvas.xview_moveto(0.0)
self.canvas.yview_moveto(0.0)
self.canvas.xview_scroll(int(scroll_x)+1, UNITS)
self.canvas.yview_scroll(int(scroll_y)+1, UNITS)
self.canvas["xscrollincrement"] = oldincx
self.canvas["yscrollincrement"] = oldincy
But... As you can see... it's very hackish and ugly. To much workaround for something that should be simple. (plus that magic +1 I was required to add, or it would be off-by-one)
Does anyone else have another better and cleaner solution?
In tkinter you can get the width and height of a PhotoImagefile. You can just call it when you use canvas.create_image
imgrender = PhotoImage(file="something.png")
##Other canvas and scrollbar codes here...
canvas.create_image((imgrender.width()/2),(imgrender.height()/2), image=imgrender)
## The top left corner coordinates is (width/2 , height/2)
I found this solution by using self.canvas.scan_dragto(x, y)
Edit: I develop an interface which can scroll, zoom, and rotate an image. Let's extract from my interface the code.
When I want to save the current position of image I use this:
# 1) Save image position
x0canvas = -self.canvas.canvasx(0)
y0canvas = -self.canvas.canvasy(0)
x0, y0 = self.canvas.coords(text)
ximg = x0
yimg = y0
# 2) Restore image position (for example: after a load)
self.text = canvas.create_text(ximg, yimg, anchor='nw', text='')
self.xyscroll(x0canvas, y0canvas)
# Rotate and zoom image
image = Image.open('fileImg.jpg')
..
imageMod = image.resize(new_size)
if rotate != 0:
imageMod = imageMod.rotate(rotate)
imagetk = ImageTk.PhotoImage(imageMod)
imageid = canvas.create_image(canvas.coords(text), anchor='nw', image=imagetk)
Related
I'm struggling with Tkinter now. I wanted to create layout but if I define window dimensions (800x600) and create frame which have to be wide 800 too, it have just half.
I tried googling and changing code, if I multiply width 2 times (to 1600) then frame fit the screen perfetly.
Here is code:
import tkinter as tk
SW, SH = 800, 600
win = tk.Tk()
win.geometry(f"{SW}x{SH}")
frm_appname = tk.Frame(
master = win,
bg = 'red'
)
frm_appname.place(
anchor = tk.N,
width = 800,
relheight = (1/6)
)
Here is output:
Can anyone explain me what happened here?
The anchor of "n" (tk.N) means that the top center portion of the frame is at the given coordinate. Since you didn't provide an x and y coordinate for place it defaults to 0,0. So, the top/middle (400,0) of the frame is at coordinate 0,0.
If you set the anchor to "n" or tk.NW, the top-left corner of your frame will be in the top-left corner of the window.
On an unrelated note, experience has taught me that layouts are nearly always easier to create with pack and/or grid. In my coupe of decades of using tk and tkinter, I've never used place for more than one or two special-case widgets.
I've been learning some basic tkinter and I've come across simple code to centre the window in the middle of your monitor, except when I run it, it's off horizontally. It's pedantic but it bothers me a lot.
The code I used
# Imports
from tkinter import *
# tkinter Application
root = Tk()
#Root Geometry
root_Width = 600
root_Length = 600
# Coordinates of top left pixel of application for centred
x_left = int(root.winfo_screenwidth()/2-root_Width/2)
y_top = int(root.winfo_screenheight()/2-root_Length/2)
root_Pos = "+" + str(x_left) + "+" + str(y_top)
# Window size and position
root.geometry(str(root_Width) + "x" + str(root_Length) + root_Pos)
root.mainloop()
Even if I go basic, and just try to open a window the size of my monitor (1920x1080) at 0,0, it's misaligned horizontally by 8px.
from tkinter import *
root = Tk()
root.geometry("1920x1080+0+0")
root.mainloop()
I took a screenshot of the result:
I have a dual monitor set up so I've added a red line where the right monitor starts. I don't know what the issue is or how to fix it. If I change it to,
root.geometry("1920x1080+-8+0")
It opens where it should, but I want to fix this in general preferably. I want 0,0 to be the top left pixel of the monitor. I acknowledge the issue may not be with python, but any advice would be helpful.
Ok, there are two thing that you are missing. The first, in your first example, is that you need to assure tkinter is getting the right values, and to do this you have to use update_idletasks() method before any winfo.
The second thing, which explains why you have to use -8 to center the full screen window, is that tkinter widows have outer-frames. You can determine the size of this frame by checking for the top left coordinate of the window (winfo_rootx()) and of the outer-frame (winfo_x()). Now the frame width is the difference between both: frame_width = root.winfo_rootx() - root.winfo_x() while the real window width is real_width = root.winfo_width() + (2*frame_width), since you have to consider the frame in both sides.
In summary, to center the window horizontally you do:
width = root.winfo_width()
frame_width = root.winfo_rootx() - root.winfo_x()
real_width = root.winfo_width() + (2*frame_width)
x = root.winfo_screenwidth() // 2 - real_width // 2
(here you can print the frame width and you will see it is 8)
and then use geometry method to place the window at position x.
Off course you can do the same for the vertical alignment
I'm trying to insert a picture that is re-sized to fit the dimensions of the picture placeholder from a template using python-pptx. I don't believe the API has direct access to this from what I can find out in the docs. Is there any suggestion of how I might be able to do this, using the library or other?
I have a running code that will insert a series of images into a set of template slides to automatically create a report using Powerpoint.
Here is the function that is doing the majority of the work relevant. Other parts of the app are creating the Presentation and inserting a slide etc.
def insert_images(slide, slide_num, images_path, image_df):
"""
Insert images into a slide.
:param slide: = slide object from Presentation class
:param slide_num: the template slide number for formatting
:param images_path: the directory to the folder with all the images
:param image_df: Pandas data frame regarding information of each image in images_path
:return: None
"""
placeholders = get_image_placeholders(slide)
#print(placeholders)
image_pool = image_df[image_df['slide_num'] == slide_num]
try:
assert len(placeholders) == len(image_pool.index)
except AssertionError:
print('Length of placeholders in slide does not match image naming.')
i = 0
for idx, image in image_pool.iterrows():
#print(image)
image_path = os.path.join(images_path, image.path)
pic = slide.placeholders[placeholders[i]].insert_picture(image_path)
#print(image.path)
# TODO: Add resize - get dimensions of pic placeholder
line = pic.line
print(image['view'])
if image['view'] == 'red':
line.color.rgb = RGBColor(255, 0, 0)
elif image['view'] == 'green':
line.color.rgb = RGBColor(0, 255, 0)
elif image['view'] == 'blue':
line.color.rgb = RGBColor(0, 0, 255)
else:
line.color.rgb = RGBColor(0, 0, 0)
line.width = Pt(2.25)
i+=1
The issue is that when I insert a picture into the picture placeholder, the image is cropped, not re-sized. I don't want the user to know the dimensions to hard code into my script. If the image used is relatively large it can crop a very large portion and just not be usable.
The picture object returned by PicturePlaceholder.insert_picture() has the same position and size as the placeholder it derives from. It is cropped to completely fill that space. Either the tops and bottoms are cropped or the left and right sides, depending on the relative aspect ratio of the placeholder and the image you insert. This is the same behavior PowerPoint exhibits when you insert a picture into a picture placeholder.
If you want to remove the cropping, simply set all cropping values to 0:
picture = placeholder.insert_picture(...)
picture.crop_top = 0
picture.crop_left = 0
picture.crop_bottom = 0
picture.crop_right = 0
This will not change the position (of the top-left corner) but will almost always change the size, making it either wider or taller (but not both).
So this solves the first problem easily, but of course presents you with a second one, which is how to position the picture where you want it and how to scale it appropriately without changing the aspect ratio (stretching or squeezing it).
This depends a great deal on what you're trying to accomplish and what outcome you find most pleasing. This is why it is not automatic; it's just not possible to predict.
You can find the "native" width and height of the image like this:
width, height = picture.image.size # ---width and height are int pixel-counts
From there you'll need to compare aspect ratios of the original placeholder and the image you inserted and either adjust the width or height of the picture shape.
So say you wanted to keep the same position but maintain the width and height of the placeholder as respective maximums such that the entire picture fits in the space but has a "margin" either on the bottom or the right:
available_width = picture.width
available_height = picture.height
image_width, image_height = picture.image.size
placeholder_aspect_ratio = float(available_width) / float(available_height)
image_aspect_ratio = float(image_width) / float(image_height)
# Get initial image placeholder left and top positions
pos_left, pos_top = picture.left, picture.top
picture.crop_top = 0
picture.crop_left = 0
picture.crop_bottom = 0
picture.crop_right = 0
# ---if the placeholder is "wider" in aspect, shrink the picture width while
# ---maintaining the image aspect ratio
if placeholder_aspect_ratio > image_aspect_ratio:
picture.width = int(image_aspect_ratio * available_height)
picture.height = available_height
# ---otherwise shrink the height
else:
picture.height = int(available_width/image_aspect_ratio)
picture.width = available_width
# Set the picture left and top position to the initial placeholder one
picture.left, picture.top = pos_left, pos_top
# Or if we want to center it vertically:
# picture.top = picture.top + int(picture.height/2)
This could be elaborated to "center" the image within the original space and perhaps to use "negative cropping" to retain the original placeholder size.
I haven't tested this and you might need to make some adjustments, but hopefully this gives you an idea how to proceed. This would be a good thing to extract to its own function, like adjust_picture_to_fit(picture).
This worked for me. My image is larger than the placeholder (slide.shapes[2]).
picture = slide.shapes[2].insert_picture(img_path)
picture.crop_top = 0
picture.crop_left = 0
picture.crop_bottom = 0
picture.crop_right = 0
hi is there any way to change width and height of widget even if there's widget?
i have code like this
form = Tk()
form.geometry("500x500")
def click():
global frame
frame.config(height = 0 ,width = 0)
frame = LabelFrame(form , text = "vaaja")
frame.place(x = 20 , y = 30)
Label(frame, text ="1").grid(row = 0,column = 0 )
Label(frame, text = "2").grid(row = 1 ,column = 0 )
Button(form , text="Click", command = click).place(x = 200 , y = 200)
form.mainloop()
and when I click the button the size of the frame is the same ( I'cant use grid_forget() for labels and then change the size of frame)
Because you are using place, you have two solutions: you can use place to set the width and height to zero, or you can turn geometry propagation off.
Using place to set the width and height
place allows you to define the width and the height of the placed widget, so in your click function you can do this:
def click():
frame.place_configure(width=0, height=0)
Turning geometry propagation off
A frame is resized to fit its contents by something called "geometry propagation". If you turn this off, you can control the size of the frame with the width and height options of the frame itself. Usually it's better to let Tkinter decide the size for you, but sometimes there's a need to have an explicit size, which is why it's possible to turn geometry propagation off.
Since you are using grid to manage the widgets internal to the frame, you need to use grid_propagate(False) to turn geometry propagation off for that frame:
frame.grid_propagate(False)
By doing so, you're responsible for setting the initial width and height of the widget, though you could leave propagation on to get the initial size, then turn it off with the button click in order to work around that issue.
There's an interesting bug (or feature...) in that if you set the width and height to zero, Tkinter won't redraw the window. At least, it doesn't on the Mac. I don't recall the workaround for that because I never, ever need to set a widget to a zero size, but setting it to 1x1 pixel makes it nearly invisible.
I am trying to input an image (image1) and flip it horizontally and then save to a file (image2). This works but not the way I want it to
currently this code gives me a flipped image but it just shows the bottom right quarter of the image, so it is the wrong size. Am I overwriting something somewhere? I just want the code to flip the image horizontally and show the whole picture flipped. Where did I go wrong?
and I cannot just use a mirror function or reverse function, I need to write an algorithm
I get the correct window size but the incorrect image size
def Flip(image1, image2):
img = graphics.Image(graphics.Point(0, 0), image1)
X, Y = img.getWidth(), img.getHeight()
for y in range(Y):
for x in range(X):
r, g, b = img.getPixel(x,y)
color = graphics.color_rgb(r, g, b)
img.setPixel(X-x, y, color)
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
img.draw(win)
img.save(image2)
I think your problem is in this line:
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
The first argument to the GraphWin constructor is supposed to be the title, but you are instead giving it an Image object. It makes me believe that maybe the width and height you are supplying are then being ignored. The default width and height for GraphWin is 200 x 200, so depending on the size of your image, that may be why only part of it is being drawn.
Try something like this:
win = graphics.GraphWin("Flipping an Image", img.getWidth(), img.getHeight())
Another problem is that your anchor point for the image is wrong. According to the docs, the anchor point is where the center of the image will be rendered (thus at 0,0 you are only seeing the bottom right quadrant of the picture). Here is a possible solution if you don't know what the size of the image is at the time of creation:
img = graphics.Image(graphics.Point(0, 0), image1)
img.move(img.getWidth() / 2, img.getHeight() / 2)
You are editing your source image. It would be
better to create an image copy and set those pixels instead:
create a new image for editing:
img_new = img
Assign the pixel values to that:
img_new.setPixel(X-x, y, color)
And draw that instead:
win = graphics.GraphWin(img_new, img_new.getWidth(), img_new.getHeight())
img_new.draw(win)
img_new.save(image2)
This will also check that your ranges are correct. if they are not, you will see both flipped and unflipped portions in the final image, showing which portions are outside of your ranges.
If you're not opposed to using an external library, I'd recommend the Python Imaging Library. In particular, the ImageOps module has a mirror function that should do exactly what you want.