【问题标题】:How to rotate an image on a canvas without using PIL?如何在不使用 PIL 的情况下旋转画布上的图像?
【发布时间】:2016-12-20 17:52:19
【问题描述】:

有什么简单的方法可以在 tkinter 画布上旋转导入的图像吗?我宁愿不使用 PIL 模块,但我找不到任何可行的替代方案。 (如果有帮助,我想在十字路口转弯时旋转一些汽车图像。)

【问题讨论】:

标签: python-3.x tkinter tkinter-canvas


【解决方案1】:

以下是一种简单但不高效的方法,可将PhotoImage 旋转 90(右)、180 和 270(左)度:

def rotate_image(img, dir):
    w, h = img.width(), img.height()
    if dir in ['left', 'right']:
        newimg = PhotoImage(width=h, height=w)
    else: # 180 degree
        newimg = PhotoImage(width=w, height=h)
    for x in range(w):
        for y in range(h):
            rgb = '#%02x%02x%02x' % img.get(x, y)
            if dir == 'right': # 90 degrees
                newimg.put(rgb, (h-y,x))
            elif dir == 'left': # -90 or 270 degrees
                newimg.put(rgb, (y,w-x))
            else: # 180 degrees
                newimg.put(rgb, (w-x,h-y))
    return newimg

【讨论】:

    【解决方案2】:

    非常感谢@acw1668 在此页面上的回答,它帮助我开发了这个更有效的解决方案。

    事实证明,PhotoImage.put() 方法接受字符串数据,并将在循环中将您提供的任何模式写入字符串,以填充图像中的给定区域。因此,我们不必逐个读取每个像素,然后逐个写入每个像素,而是可以读取每个像素,然后只写入一次!

    下面的函数允许您旋转和镜像任何 PhotoImage 对象,并且这样做的时间是逐像素读写方法的一小部分。镜像是通过简单地从后到前读取每一行或每一列来完成的。旋转将每一行写成一列,这实际上将图像旋转了 90 度。

    import tkinter as tk
    
    def putToImage(brush, canvas, bbox, mirror_x=False, mirror_y=False, rotate=False):
        value1 = brush.height() if rotate else brush.width()
        value2 = brush.width() if rotate else brush.height()
        start1, end1, increment1 = (value1 - 1, -1, -1) if mirror_x else (0, value1, 1)
        start2, end2, increment2 = (value2 - 1, -1, -1) if mirror_y else (0, value2, 1)
    
        data = ""
        for col in range(start2, end2, increment2):
            data = data + "{"
            for row in range(start1, end1, increment1):
                data = data + "#%02x%02x%02x " % brush.get(col if rotate else row, row if rotate else col)
            data = data + "} "
        canvas.put(data, to=bbox)
    

    下面是一个简单的用法示例:

    window = tk.Tk()
    lbl1 = tk.Label(window)
    lbl2 = tk.Label(window)
    
    my_img = tk.PhotoImage(file="my_image.png")
    rotated_img = tk.PhotoImage(width=my_img.height(), height=my_img.width())
    putToImage(my_img, rotated_img, (0, 0, rotated_img.width(), rotated_img.height()), rotate=True)
    
    lbl1.configure(image=my_img)
    lbl1.pack()
    lbl2.configure(image=rotated_img)
    lbl2.pack()
    
    window.mainloop()  
    

    【讨论】:

      【解决方案3】:

      这是一种在不使用 PIL 模块的情况下使用 tkinter 旋转图像的方法。

      所以在我给出答案之前,这里有一些解释。

      首先,画布坐标有一个负 y 轴,因此在考虑数学时需要小心这一点。我选择转换为“标准”系统。然后后来我转换回来。不管你做什么,都要小心,因为它很容易混淆。

      这是一个基本大纲:
      • 制作一个新的空白图像,它可能需要更大以容纳旋转的图像。
      • 逐像素遍历,得到原始图像中的颜色。
      • 将颜色放入新图像的像素中,但在新图像的新区域中
      这是关于如何旋转图像的更深入的概述(我选择将轴更改为更“标准”的数学坐标,其中正 y 值向上,而不是像很多计算机一样向下。)
      • 将 y 轴更改为“标准”数学坐标 - 这意味着图像将位于笛卡尔坐标系的“象限 4”中(x 为正,y 为负)
      • 移动图片位置,使原点位于图片中间 - 现在图片位于所有 4 个象限中,图片中间为 (0,0)
      • 围绕原点 (0,0) 应用标准旋转公式以获得新的像素坐标
      • 现在将图像放回“象限 4”(图像可能更大,旋转后可能需要移动更多。
      • 将坐标系改回y向下是正坐标系。

      事实证明(您可以自己拿一张 3x5 的记事卡并在一张纸上旋转来验证这一点),使用对角线测量新的宽度和高度总是足够大以容纳新的旋转图像。

      所以这是一个旋转图像的函数:
      from math import sin, cos, sqrt, pi
      from tkinter import *
      
      #returns a rotated PhotoImage
      def rotatedPhotoImage(img, angle):
          angleInRads = angle * pi / 180
          diagonal = sqrt(img.width()**2 + img.height()**2)
          xmidpoint = img.width()/2
          ymidpoint = img.height()/2
          newPhotoImage = PhotoImage(width=int(diagonal), height=int(diagonal))
          for x in range(img.width()):
              for y in range(img.height()):
      
                  # convert to ordinary mathematical coordinates
                  xnew = float(x)
                  ynew = float(-y)
      
                  # shift to origin
                  xnew = xnew - xmidpoint
                  ynew = ynew + ymidpoint
      
                  # new rotated variables, rotated around origin (0,0) using simoultaneous assigment
                  xnew, ynew = xnew*cos(angleInRads) - ynew*sin(angleInRads), xnew * sin(angleInRads) + ynew*cos(angleInRads)
      
                  # shift back to quadrant iv (x,-y), but centered in bigger box
                  xnew = xnew + diagonal/2
                  ynew = ynew - diagonal/2
      
                  # convert to -y coordinates
                  xnew = xnew
                  ynew = -ynew
      
                  # get pixel data from the pixel being rotated in hex format
                  rgb = '#%02x%02x%02x' % img.get(x, y)
      
                  # put that pixel data into the new image
                  newPhotoImage.put(rgb, (int(xnew), int(ynew)))
      
                  # this helps fill in empty pixels due to rounding issues
                  newPhotoImage.put(rgb, (int(xnew+1), int(ynew)))
      
          return newPhotoImage
      

      这是用于旋转的函数,例如 34 度。

      root = Tk()
      mycanvas = Canvas(root, width=300, height=300)
      mycanvas.pack()
      myPhotoImage=PhotoImage(file="car.png")
      myPhotoImage=rotatedPhotoImage(myPhotoImage,34)
      canvasImage=mycanvas.create_image(150,150,image=myPhotoImage)
      
      这是旋转后的汽车截图:

      这是使用该函数旋转汽车图像的示例。请注意,转换图像需要一些时间,具体取决于大小,但在此之后可以使用图像数组。对于这张汽车图片:

      为我转换图像需要 3 秒。

      from math import sin, cos, sqrt, pi
      from tkinter import *
      from time import sleep
      
      def getImageIndex(angle):
          # resets 360 to 0
          angle = angle % 360
          # using 16 images: 360/16 is 22.5 degrees, 11.25 is 1/2 of 22.5
          index = (angle-11.25)/22.5 + 1
          index = int(index)
          index = index % 16
          return index
      
      
      root = Tk()
      
      originalImage = PhotoImage(file="car.png")
      
      carImages = []
      for i in range(16):  # using 16 images
          angle = i*22.5  # 360degrees/16 images = 22.5 degrees
          carImages.append(rotatedPhotoImage(originalImage, angle))
      
      mycanvas = Canvas(root, width=300, height=300)
      mycanvas.pack()
      
      canvasimage = mycanvas.create_image(150, 150, image=carImages[0], anchor=CENTER)
      for i in range(1440): #spins 4 times around
          mycanvas.delete(canvasimage)
          canvasimage = mycanvas.create_image(150, 150, image=carImages[getImageIndex(i)], anchor=CENTER)
          root.update()
          sleep(.001)
      
      
      root.mainloop()
      
      注意汽车背景不透明。

      这似乎仅限于使用基本的 tkinter 模块,但是如果您可以通过替换像白色这样的颜色来代替使用 alpha (r,g,b,a),这里稍微有点该功能的更高级版本,它跳过写入您选择的颜色的像素及其使用示例。

      这是汽车,我已将其背景颜色更改为白色。

      这是一个程序,清除白色像素,制作一组图像并在橙色背景上旋转它们。该程序中使用的函数包含一个可选的背景颜色去除器。
      from math import sin, cos, sqrt, pi
      from tkinter import *
      from time import sleep
      
      # returns a rotated PhotoImage
      def rotatedPhotoImage(img, angle, colorToMakeTransparentInHexFormat=""):
          angleInRads = angle * pi / 180
          diagonal = sqrt(img.width()**2 + img.height()**2)
          xmidpoint = img.width()/2
          ymidpoint = img.height()/2
          newPhotoImage = PhotoImage(width=int(diagonal), height=int(diagonal))
          for x in range(img.width()):
              for y in range(img.height()):
      
                  # convert to ordinary mathematical coordinates
                  xnew = float(x)
                  ynew = float(-y)
      
                  # shift to origin
                  xnew = xnew - xmidpoint
                  ynew = ynew + ymidpoint
      
                  # new rotated variables, rotated around origin (0,0) using simoultaneous assigment
                  xnew, ynew = xnew*cos(angleInRads) - ynew*sin(angleInRads), xnew * sin(angleInRads) + ynew*cos(angleInRads)
      
                  # shift back to quadrant iv (x,-y), but centered in bigger box
                  xnew = xnew + diagonal/2
                  ynew = ynew - diagonal/2
      
                  # convert to -y coordinates
                  xnew = xnew
                  ynew = -ynew
      
                  # get pixel data from the pixel being rotated in hex format
                  rgb = '#%02x%02x%02x' % img.get(x, y)
      
                  if rgb != colorToMakeTransparentInHexFormat:
                      # put that pixel data into the new image
                      newPhotoImage.put(rgb, (int(xnew), int(ynew)))
      
                      # this helps fill in empty pixels due to rounding issues
                      newPhotoImage.put(rgb, (int(xnew+1), int(ynew)))
      
          return newPhotoImage
      
      
      def getImageIndex(angle):
          # resets 360 to 0
          angle = angle % 360
          # using 16 images: 360/16 is 22.5 degrees, 11.25 is 1/2 of 22.5
          index = (angle-11.25)/22.5 + 1
          index = int(index)
          index = index % 16
          return index
      
      
      root = Tk()
      
      originalImage = PhotoImage(file="carwhitebg.png")
      
      carImages = []
      for i in range(16):  # using 16 images
          angle = i*22.5  # 360degrees/16 images = 22.5 degrees
          carImages.append(rotatedPhotoImage(originalImage, angle,"#ffffff"))
      
      mycanvas = Canvas(root, width=300, height=300,bg="orange")
      mycanvas.pack()
      
      canvasimage = mycanvas.create_image(150, 150, image=carImages[0], anchor=CENTER)
      for i in range(943): #some arbitrary number of degrees
          mycanvas.delete(canvasimage)
          canvasimage = mycanvas.create_image(150, 150, image=carImages[getImageIndex(i)], anchor=CENTER)
          root.update()
          sleep(.001)
      
      
      root.mainloop()
      
      下面是橙色背景上那辆车的示例:

      希望我在尝试回答这个问题时增加了一些价值。 H5>
      • 我没有使用 PIL
      • 我允许旋转到任意角度
      • 我展示了一个旋转汽车的演示
      • 我还展示了如何选择颜色以使图像透明

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-04-12
        • 1970-01-01
        • 1970-01-01
        • 2015-05-28
        • 1970-01-01
        • 2018-12-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多