1

I can't display images longer (height) than about 30612 pixels high. I've read that there is a maximum height to canvas. I'd like to get the source file and extend that to 90 or 100k pixels in height. Conversely, I've seen suggested that a canvas may be buffered, if this is true, I have no clue how to implement it.. Any help is appreciated!

I am using code I found off Stack that is supposed to deal with large images, it does alright, but ultimately hit's the cavas height limit. Canvas Limit

from tkinter import *
from PIL import ImageTk
from PIL import *

Image.MAX_IMAGE_PIXELS = None


class ScrolledCanvas(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        self.master.title("Spectrogram Viewer")
        self.pack(expand=YES, fill=BOTH)
        canv = Canvas(self, relief=SUNKEN)
        canv.config(width=400, height=500)
        # canv.config(scrollregion=(0,0,1000, 1000))
        # canv.configure(scrollregion=canv.bbox('all'))
        canv.config(highlightthickness=0)

        sbarV = Scrollbar(self, orient=VERTICAL)
        sbarH = Scrollbar(self, orient=HORIZONTAL)

        sbarV.config(command=canv.yview)
        sbarH.config(command=canv.xview)

        canv.config(yscrollcommand=sbarV.set)
        canv.config(xscrollcommand=sbarH.set)

        sbarV.pack(side=RIGHT, fill=Y)
        sbarH.pack(side=BOTTOM, fill=X)

        canv.pack(side=LEFT, expand=YES, fill=BOTH)
        self.im = Image.open("Test_3.tif")
        width, height = self.im.size
        canv.config(scrollregion=(0, 0, width, height))
        self.im2 = ImageTk.PhotoImage(self.im)
        self.imgtag = canv.create_image(0, 0, anchor="nw", image=self.im2)
ScrolledCanvas().mainloop()
GEOJOE
  • 41
  • 7

2 Answers2

1

I tried to put together a bigger image from displays of a grid of canvases. This looks like it might work, at least if you just want to display a big image. I have just tested with a small image and not paid any attention to memory or speed or anything...

from tkinter import *
from scrframe import VerticalScrolledFrame

root = Tk()
tiles = VerticalScrolledFrame(root)    # Scrolled frame
tiles.grid()

tw = 90     # Tile width
th = 110    # Tile height
rows = 4    # Number of tiles/row
cols = 4    # Number of tiles/column

tile_list = []      # List of image tiles
img = PhotoImage(file='pilner.png')

for r in range(rows):
    col_list = []
    for c in range(cols):
        tile = Canvas(tiles.interior, highlightthickness=0, bg='tan1', 
                      width=tw, height=th)
        tile.create_image(-c*tw, -r*th, image=img, anchor ='nw')
        tile.grid(row=r, column=c)
        col_list.append(tile)
    tile_list.append(col_list)

root.mainloop()

Now, scrolling a frame seems to raise some problems, but there also seems to be solutions. I tried to use VerticalScrolledFrame as described in Python Tkinter scrollbar for frame and it works fine. As it only provides for a vertical scrollbar you'd have to implement horizontal scrollbar yourself. Maybe a few additional functions as scrolling with the mouse wheel, keyboard shortcuts or other would be useful.

I got the VerticalScrolledFrame from TKinter scrollable frame and modified it for Python 3.

figbeam
  • 7,001
  • 2
  • 12
  • 18
  • This doesn't seem to work. I am only able to display the "first" image segment. I can't scroll down. When I try to add a scrollbar, it returns an error. Can't use .pack and .grid in the same class or def – GEOJOE May 09 '18 at 19:02
  • I updated my example to my exact code with `VerticalScrolledFrame`. This works with Python 3.6.5 on windows 10. – figbeam May 09 '18 at 21:15
  • Didn't think about it until now, but `VerticalScrolledFrame` uses a canvas as container for the widgets. This may not work for large canvases after all. – figbeam May 09 '18 at 21:18
  • Just another thought; does Label have the same size limitations? – figbeam May 09 '18 at 22:57
  • I don't know but I will look into it. I am willing to attempt to modify and recompile however (but I have no clue how to do that so....). I found another person with the same issue https://stackoverflow.com/questions/42830037/tkinter-maximum-canvas-size/42837629#42837629 – GEOJOE May 10 '18 at 14:43
  • I have been looking into writing a scrollable frame, by putting one frame inside another and moving the inner frame about with `place()` bound to scrollbars. Looks like it might work but I haven't yet thought about possible size limits. – figbeam May 11 '18 at 15:35
1

This is the code I've come up with from several sources - Thanks to figbeam for all the help. Also, this is not pretty!!!! The button shows up in the center of the Tkinter window. If you'd like to modify this, please do.

from tkinter import *
from PIL import ImageTk as itk
from PIL import Image
import math
import numpy as np
Image.MAX_IMAGE_PIXELS = None #prevents the "photo bomb" warning from popping up. Have to have this for really large images.

#----------------------------------------------------------------------
# makes a simple window with a button right in the middle that let's you go "down" an image.
class MainWindow():

    #----------------

    def __init__(self, main):
        # canvas for image
        _, th, tw, rows, cols = self.getrowsandcols()
        self.canvas = Canvas(main, width=tw, height=th)
        self.canvas.grid(row=0, column=0)

        # images
        self.my_images = self.cropimages() # crop the really large image down into several smaller images and append to this list
        self.my_image_number = 0 #

        # set first image on canvas
        self.image_on_canvas = self.canvas.create_image(0, 0, anchor = NW, image = self.my_images[self.my_image_number])

        # button to change image
        self.button = Button(main, text="DOWN", command=self.onDownButton)
        self.button.grid(row=0, column=0)

    #----------------
    def getimage(self):
        im = Image.open("Test_3.png") # import the image
        im = im.convert("RGBA") # convert the image to color including the alpha channel (which is the transparency best I understand)
        width, height = im.size # get the width and height
        return width, height, im # return relevent variables/objects
    def getrowsandcols(self):
        width, height, im = self.getimage()
        im = np.asarray(im) # Convert image to Numpy Array
        tw = width  # Tile width will equal the width of the image
        th = int(math.ceil(height / 100))  # Tile height
        rows = int(math.ceil(height / th))  # Number of tiles/row
        cols = int(math.ceil(width / tw))  # Number of tiles/column
        return im, th, tw, rows, cols #return selected variables
    def cropimages(self):
        self.my_images = [] # initialize list to hold Tkinter "PhotoImage objects"
        im, th, tw, rows, cols = self.getrowsandcols() # pull in needed variables to crop the really long image
        for r in range(rows): # loop row by row to crop all of the image
            crop_im =im[r * th:((r * th) + th), 0:tw] # crop the image for the current row (r). (th) stands for tile height.
            crop_im = Image.fromarray(crop_im) # convert the image from an Numpy Array to a PIL image.
            crop_im = itk.PhotoImage(crop_im) # convert the PIL image to a Tkinter Photo Object (whatever that is)
            self.my_images.append(crop_im) # Append the photo object to the list
            crop_im = None
        return self.my_images

    def onDownButton(self):
        # next image
        self.my_image_number += 1 #every button pressed will

        # return to first image
        if self.my_image_number == len(self.my_images):
            self.my_image_number = 0

        # change image
        self.canvas.itemconfig(self.image_on_canvas, image = self.my_images[self.my_image_number]) #attaches the image from the image list to the canvas

#----------------------------------------------------------------------

root = Tk()
MainWindow(root)
root.mainloop()
GEOJOE
  • 41
  • 7