Title: Let the user zoom on a PIL image in Python and tkinter
The example Let the user zoom on a picture in Python and tkinter shows how to let the user click and drag to select an area for the program to zoom in on. That example quickly redrew its image as necessary when the area of interest changed. For example, if you resize that program's window, the program immediately redraws its picture.
Sometimes, however, it may take too long to redraw a picture. For example, it could take several seconds to draw a Mandelbrot set depending on the area and the window size. In that case, the program shouldn't redraw while you're resizing the window because it can't know when you're finished.
This example is similar to the previous one except it displays a bitmap image generated with PIL and it doesn't redraw as you resize its window. See the previous example for most of the program. This page just describes two new details: how the program draws its PIL image and how it svaes that image when you select the File menu's Save As command.
The following code shows how this example draws its image.
def draw(self):
'''Draw an image.'''
# Set scale factors for drawing.
self.x_scale = (self.vxmax - self.vxmin) / (self.wxmax - self.wxmin)
self.y_scale = (self.vymax - self.vymin) / (self.wymax - self.wymin)
# Make an image to fit the viewport.
mode = 'RGB'
image_wid = self.vxmax - self.vxmin
image_hgt = self.vymax - self.vymin
bg_color = (0, 0, 0)
self.image = Image.new(mode, (image_wid, image_hgt), bg_color)
# Access the image's pixels.
self.pixels = self.image.load()
# Get the viewport's bounds in world coordinates.
w_vxmin, w_vymin = self.d_to_w(0, 0)
w_vxmax, w_vymax = self.d_to_w(image_wid - 1, image_hgt - 1)
# Loop over the pixels.
wwid = self.full_wxmax - self.full_wxmin
whgt = self.full_wymax - self.full_wymin
wcx = 0
wcy = 0
for x in range(image_wid):
for y in range(image_hgt):
# Convert (x, y) from viewport coordinates to world coordinates.
wx, wy = self.d_to_w(x, y)
# Calculate this point's color.
# If it's not within -1 <= x, y <= 1, make it black.
if wx < self.full_wxmin or wx > self.full_wxmax or \
wy < self.full_wymin or wy > self.full_wymax:
color = (0, 0, 0)
else:
# Distance from center.
dx = wx - wcx
dy = wy - wcy
distance = math.sqrt(dx * dx + dy * dy)
angle = math.atan2(dy, dx)
r = int(255 * math.sin(angle))
g = int(255 * distance % 50)
b = int(255 * math.cos(angle))
color = (r, g, b)
# Set the pixel's color.
self.pixels[x, y] = color
# Convert the PIL image into an ImageTk.PhotoImage.
self.photo_image = ImageTk.PhotoImage(self.image)
# Display the result.
self.canvas.delete('all')
self.display_image = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo_image)
This code gets the X and Y scales as before. It then calls Image.new to create an image to fit the viewport on the canvas control. It uses black as the new image's background color.
Next, the code calls the image's load to get it ready for pixel manipulation. Then it converts the viewport's coordinates into world coordinates so we can draw in world coordinates.
The program sets wcx and wcy equal to the drawing area's center at (0, 0). (The image will occupy the area -1 ≤ x, y ≤ 1.) The code then loops over the image's pixels.
For each pixel, te code uses the d_to_w method to convert the pixel's device coordinates into world coordinates. If the world coordinates fall outside of the range -1 ≤ x, y ≤ 1, the program makes it black.
If the point lies within the desired bounds, the program calculates the distance from the point to the center point. It also calculates the angle the point makes from the center point. The code then uses the distance and angle to set the pixel's red, green, and blue color components. Notice that those values must be integers. The program sets the pixel's color and continues its loop.
After it has set all of the pixel values, the program uses the PIL image to make a ImageTk.PhotoImage. It then deletes any objects that are currently on the program's canvas and creates a new image on the canvas to display the image.
The following code shows how the program saves its image when you select the File menu's Save As command.
def save_as(self):
if self.image is None: return
file = asksaveasfile(initialfile = 'Colorful.png',
defaultextension='.png', filetypes=[('All Files', '*.*'), ('Image Files', '*.png *.jpg *.bmp')])
if file is None: return
self.image.save(file.name)
If the program has no image yet, the method returns. (This should not happen because the program creates an image as soon as it loads.)
The program then displays a File Save dialog. If the user cancels the dialog, the method again returns.
If the user selects an output file, the program calls the image's save method passing it the file name.
that's all there is to it! Download the example to see all of the details.
|