【问题标题】:Tiles of a QPixmap (image) have overlapping regionsQPixmap(图像)的图块具有重叠区域
【发布时间】:2019-12-21 09:54:03
【问题描述】:

我想创建一个带有图像的按钮网格。每个按钮上的图像都是从一个更大的图像中剪下来的一块 - 所以按钮网格可以看作是原始图像的图块(我用来在按钮上放置图像的代码是from here)。

为了将图像 (image=QPixmap(path_to_image)) 切成小块,我在 for 循环中使用了方法image.copy(x, y, width, height)

起初我认为代码工作正常。更精确的检查表明图像片段有重叠部分。

这是我用 GIF 图像测试过的代码:

import os
import sys

from PyQt5.QtCore import QSize
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtWidgets import \
    QWidget, QApplication, \
    QGridLayout, \
    QLabel, QPushButton

def cut_image_into_tiles(image, rows=4, cols=4) -> dict:
    if isinstance(image, str) and os.path.exists(image):
        image = QPixmap(image)
    elif not isinstance(image, QPixmap):
        raise ValueError("image must be str or QPixmap object")

    # Dim of the tile
    tw = int(image.size().width() / cols)
    th = int(image.size().height() / rows)

    # prepare return value
    tiles = {"width": tw, "height": th}
    for r in range(rows):
        for c in range(cols):
            tile = image.copy(c * th, r * tw, tw, th)
            # args: x, y, width, height
            # https://doc.qt.io/qt-5/qpixmap.html#copy-1
            tiles[(r, c)] = tile

    return tiles


def create_pixmapbutton(pixmap, width=0, height=0) -> QPushButton:
    if isinstance(pixmap, QPixmap):
        button = QPushButton()
        if width > 0 and height > 0:
            button.setIconSize(QSize(width, height))
        else:
            button.setIconSize(pixmap.size())
        button.setIcon(QIcon(pixmap))
        return button


def run_viewer(imagepath, rows=4, cols=4):

    # ImageCutter.run_viewer(imagepath)
    app = QApplication(sys.argv)

    image = QPixmap(imagepath)

    tiles = cut_image_into_tiles(image=image, rows=rows, cols=cols)
    tilewidth = tiles["width"]
    tileheight = tiles["height"]
    # get dict tiles (keys:=(row, col) or width or height)

    viewer = QWidget()
    layout = QGridLayout()
    viewer.setLayout(layout)
    viewer.setWindowTitle("ImageCutter Viewer")

    lbl = QLabel()
    lbl.setPixmap(image)
    layout.addWidget(lbl, 0, 0, rows, cols)

    for r in range(rows):
        for c in range(cols):
            btn = create_pixmapbutton(
                tiles[r, c], width=tilewidth, height=tileheight)

            btninfo = "buttonsize={}x{}".format(tilewidth, tileheight)
            btn.setToolTip(btninfo)
            # logger.debug("    create button [{}]".format(btninfo))
            layout.addWidget(btn, r, cols + c)

    viewer.show()
    sys.exit(app.exec_())

我用run_viewer(path_to_a_gif_image, rows=3, cols=3)测试了代码

【问题讨论】:

  • imagepath 的大小是多少?
  • imagepath 只是 gif 文件的路径还是你的意思是 gif 的大小?
  • 是的,分享 gif
  • 如何分享 gif?
  • 上传到drive、dropbox、imgur等并分享链接。

标签: python image pyqt5 qpixmap


【解决方案1】:

每个网格的宽度必须取决于图像的宽度,在你的情况下它取决于 th,但是 th 取决于不正确的高度,你必须将 th 更改为 tw,高度相同。

综合以上,解决办法是:

import os
import sys

from PyQt5.QtCore import QSize
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QLabel, QPushButton


def cut_image_into_tiles(image, rows=4, cols=4) -> dict:
    if isinstance(image, str) and os.path.exists(image):
        image = QPixmap(image)
    elif not isinstance(image, QPixmap):
        raise ValueError("image must be str or QPixmap object")

    # Dim of the tile
    tw = int(image.size().width() / cols)
    th = int(image.size().height() / rows)

    # prepare return value
    tiles = {"width": tw, "height": th}
    for r in range(rows):
        for c in range(cols):
            tile = image.copy(c * tw, r * th, tw, th) # <----
            # args: x, y, width, height
            # https://doc.qt.io/qt-5/qpixmap.html#copy-1
            tiles[(r, c)] = tile

    return tiles


def create_pixmapbutton(pixmap, width=0, height=0) -> QPushButton:
    if isinstance(pixmap, QPixmap):
        button = QPushButton()
        if width > 0 and height > 0:
            button.setIconSize(QSize(width, height))
        else:
            button.setIconSize(pixmap.size())
        button.setIcon(QIcon(pixmap))
        button.setContentsMargins(0, 0, 0, 0)
        button.setFixedSize(button.iconSize())
        return button


def run_viewer(imagepath, rows=4, cols=4):

    # ImageCutter.run_viewer(imagepath)
    app = QApplication(sys.argv)

    image = QPixmap(imagepath)

    tiles = cut_image_into_tiles(image=image, rows=rows, cols=cols)
    tilewidth = tiles["width"]
    tileheight = tiles["height"]
    # get dict tiles (keys:=(row, col) or width or height)

    viewer = QWidget()
    layout = QGridLayout(viewer)
    layout.setContentsMargins(0, 0, 0, 0)
    layout.setSpacing(0)
    viewer.setWindowTitle("ImageCutter Viewer")

    lbl = QLabel()
    lbl.setPixmap(image)
    layout.addWidget(lbl, 0, 0, rows, cols)

    for r in range(rows):
        for c in range(cols):
            btn = create_pixmapbutton(tiles[r, c], width=tilewidth, height=tileheight)

            btninfo = "buttonsize={}x{}".format(tilewidth, tileheight)
            btn.setToolTip(btninfo)
            # logger.debug("    create button [{}]".format(btninfo))
            layout.addWidget(btn, r, cols + c)

    viewer.show()
    viewer.setFixedSize(viewer.sizeHint())
    sys.exit(app.exec_())


run_viewer("linux.gif", 3, 3)

【讨论】:

  • @eylianesc: "... 它取决于 th,但 th 取决于不正确的高度 ..." 为什么计算出的尺寸 (th tw) 不正确?
  • 好吧我的错(因为我的英语不好)!我在代码中交换了 width (tw) 和 height (th)。哦,伙计-谢谢
【解决方案2】:

试试看:

import sys
from PyQt5.QtCore import QSize, QRect
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtWidgets import (QWidget, QApplication, QGridLayout, 
                             QLabel, QPushButton, QSizePolicy)
from PIL import Image


def cut_image_into_tiles(image, r=4, c=4):
    im = Image.open(image)                                                           
    x, y = im.size 
    for i in range(r):
        for j in range(c):
            if i!=r and j!=c:
                im.crop(box=(x/r*i, y/c*j, x/r*(i+1)-1, y/c*(j+1)-1)).\
                save('image{}{}.png'.format(str(i+1), str(j+1)))      


def run_viewer(imagepath, rows=4, cols=4):
    app = QApplication(sys.argv)
    image = QPixmap(imagepath)

    tiles = cut_image_into_tiles(image=imagepath, r=rows, c=cols)

    viewer = QWidget()
    layout = QGridLayout()
    layout.setContentsMargins(0, 0, 0, 0)
    layout.setSpacing(0)
    viewer.setLayout(layout)
    viewer.setWindowTitle("ImageCutter Viewer")

    lbl = QLabel()
    lbl.setPixmap(image)
    layout.addWidget(lbl, 0, 0, rows, cols)

    for r in range(1, rows+1):
        for c in range(1, cols+1):    
            button = QPushButton()
            button.setStyleSheet('''QPushButton {background-color: yellow; border: 1px solid black;}
                                    QPushButton:pressed {background-color: green;}''')
            image = QPixmap(f"image{c}{r}.png")
            button.setIconSize(image.size())
            button.setIcon(QIcon(image))            
            layout.addWidget(button, r-1, c+cols)            

    viewer.show()
    sys.exit(app.exec_())


run_viewer("linux.gif", rows=3, cols=3)

【讨论】:

  • 嗨@S.Nick:这是一个很好的解决方案,可以保存每个图块 - 所以大图像和大网格需要大量空间和时间
【解决方案3】:

好的,我发现对于大图像和大网格,右侧按钮上的图像可能比其他按钮小。底部按钮也一样。

顶部和左侧的偏移可以防止这种情况发生。

所以def cut_image_into_tiles(image, rows=4, cols=4) -&gt; dict:这个函数必须适应:

def cut_image_into_tiles(image, rows=4, cols=4) -> dict:
    if isinstance(image, str) and os.path.exists(image):
        image = QPixmap(image)
    elif not isinstance(image, QPixmap):
        raise ValueError("image must be str or QPixmap object")

    # Dim of tiled images
    width = image.size().width()
    height = image.size().height()
    tw = int(width / cols)
    th = int(height / rows)
    offset_w = int((width - tw * cols)/2)    # !!!
    offset_h = int((height - th * rows)/2)   # !!!

    # prepare return value
    tiles = {"width": tw, "height": th}
    for r in range(rows):
        for c in range(cols):
            x = c * tw
            y = r * th
            if c == 0:
                x += offset_w    # !!!
            if r == 0:
                y += offset_h    # !!!
            tile = image.copy(x, y, tw, th)
            # args: x, y, width, height
            # https://doc.qt.io/qt-5/qpixmap.html#copy-1
            tiles[(r, c)] = tile

    return tiles

整个代码现在是:

def cut_image_into_tiles(image, rows=4, cols=4) -> dict:
    if isinstance(image, str) and os.path.exists(image):
        image = QPixmap(image)
    elif not isinstance(image, QPixmap):
        raise ValueError("image must be str or QPixmap object")

    # Dim of tiled images
    width = image.size().width()
    height = image.size().height()
    tw = int(width / cols)
    th = int(height / rows)
    offset_w = int((width - tw * cols)/2)
    offset_h = int((height - th * rows)/2)

    # prepare return value
    tiles = {"width": tw, "height": th}
    for r in range(rows):
        for c in range(cols):
            x = c * tw
            y = r * th
            if c == 0:
                x += offset_w
            if r == 0:
                y += offset_h
            tile = image.copy(x, y, tw, th)
            # args: x, y, width, height
            # https://doc.qt.io/qt-5/qpixmap.html#copy-1
            tiles[(r, c)] = tile

    return tiles


def create_pixmapbutton(pixmap, width=0, height=0) -> QPushButton:
    if isinstance(pixmap, QPixmap):
        button = QPushButton()
        if width > 0 and height > 0:
            button.setIconSize(QSize(width, height))
        else:
            button.setIconSize(pixmap.size())
        button.setIcon(QIcon(pixmap))
        return button


def run_viewer(imagepath, rows=4, cols=4):

    # ImageCutter.run_viewer(imagepath)
    app = QApplication(sys.argv)

    image = QPixmap(imagepath)

    tiles = cut_image_into_tiles(image=image, rows=rows, cols=cols)
    tilewidth = tiles["width"]
    tileheight = tiles["height"]
    # get dict tiles (keys:=(row, col) or width or height)

    viewer = QWidget()
    layout = QGridLayout()
    viewer.setLayout(layout)
    viewer.setWindowTitle("ImageCutter Viewer")

    lbl = QLabel()
    lbl.setPixmap(image)
    layout.addWidget(lbl, 0, 0, rows, cols)

    for r in range(rows):
        for c in range(cols):
            btn = create_pixmapbutton(
                tiles[r, c], width=tilewidth, height=tileheight)

            btninfo = "buttonsize={}x{}".format(tilewidth, tileheight)
            btn.setToolTip(btninfo)
            # logger.debug("    create button [{}]".format(btninfo))
            layout.addWidget(btn, r, cols + c)

    viewer.show()
    sys.exit(app.exec_())

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-02
    • 1970-01-01
    • 2019-01-31
    • 1970-01-01
    • 2019-04-07
    • 2023-03-20
    • 1970-01-01
    相关资源
    最近更新 更多