def createImage(self): """ Creates a new image by separating out the cyan, magenta, or yellow component, depending on the mask color specified in the constructor. The amount of the component found in each pixel of the image is used to determine how much of a user-selected ink is used for each pixel in the new image for the label widget. """ self.newImage = newImage = self.originalImage.copy() # Create CMY components for the ink being used. cyanInk = float(255 - QColor(self.paintColor).red()) / 255.0 magentaInk = float(255 - QColor(self.paintColor).green()) / 255.0 yellowInk = float(255 - QColor(self.paintColor).blue()) / 255.0 convert = self.convertMap[self.maskColor] for y in range(newImage.height()): for x in range(newImage.width()): p = self.originalImage.pixel(x, y) # Separate the source pixel into its cyan component. if self.inverted: amount = convert(p) else: amount = 255 - convert(p) newColor = QColor( 255 - min(int(amount * cyanInk), 255), 255 - min(int(amount * magentaInk), 255), 255 - min(int(amount * yellowInk), 255)) newImage.setPixel(x, y, newColor.rgb()) self.imageLabel.setPixmap(QPixmap.fromImage(newImage))
def render(self, rend=True): if (rend): if (self.modeBtn.isChecked()): self.qsst.srctext = self.textEdit.toPlainText() self.qsst.loadVars() self.qsst.convertQss() self.setStyleSheet(self.qsst.qss) else: self.setStyleSheet('') for var, btn in self.dict.items(): if ("rgb" in btn.text()): t = btn.text().strip(r" rgba()") c = t.split(',') if (len(c) > 3): a = c[3] else: a = 255 color = QColor(r, g, b, a) else: color = QColor(btn.text()) s = '' if (qGray(color.rgb()) < 100): s += "color:white;" btn.setStyleSheet(s + "background:" + btn.text())
def loadColorPanel(self): self.qsst.srctext = self.editor.text() self.qsst.loadVars() # item = self.colorGridLayout.itemAt(0) # while (item != None): # self.colorGridLayout.removeItem(item) # # self.colorGridLayout.removeWidget(item.widget()) # # item.widget().setParent(None)#这三行没有作用 # sip.delete(item.widget()) # 虽然控件删除了,但是grid的行数列数没减少,但是不影响使用 # sip.delete(item) # item = self.colorGridLayout.itemAt(0) # self.colorGridLayout.update() # 不起作用 # a,b=list(self.clrBtnDict.keys()),list(self.qsst.varDict.keys());a.sort();b.sort() if sorted(list(self.clrBtnDict.keys())) != sorted( list(self.qsst.varDict.keys())): while self.colorPanelLayout.count() > 0: self.colorPanelLayout.removeItem( self.colorPanelLayout.itemAt(0)) self.clrBtnDict = {} for varName, clrStr in self.qsst.varDict.items(): contianerWidget = QWidget() contianerWidget.setMinimumSize(QSize(185, 25)) label = QLabel(varName, contianerWidget) btn = QPushButton(clrStr, contianerWidget) if sys.platform.startswith("win"): font1 = QFont("Arial", 10, QFont.Medium) font2 = QFont("sans-serif", 9, QFont.Medium) label.setFont(font1) btn.setFont(font2) self.clrBtnDict[varName] = btn # label.setFixedWidth(80) # btn.setFixedWidth(100) label.move(5, 5) btn.move(100, 5) self.colorPanelLayout.addWidget(contianerWidget) self.colorPanelLayout.setSpacing(5) btn.clicked.connect(lambda x, var=varName: self.chclr(var)) for varName, btn in self.clrBtnDict.items(): clrStr = self.qsst.varDict[varName] btn.setText(clrStr) if "rgb" in clrStr: t = clrStr.strip(r" rgba()") c = t.split(',') if len(c) > 3: lable = c[3] else: lable = 255 color = QColor(c[0], c[1], c[2], lable) else: color = QColor(clrStr) s = '' if qGray(color.rgb()) < 100: s += "color:white;" else: s += "color:black;" btn.setStyleSheet(s + "background:" + btn.text())
def change_color(self, value): invert = QColor(255, 255, 255) invert.setRgb(invert.rgb() - self.color.rgb()) self.ui.lineEdit.setStyleSheet( "QWidget { background-color: %s; color: %s}" % (self.color.name(), invert.name())) name = self.ui.listWidget.currentItem().text() info = self.settings.getClassFromName(name) rgb = self.color.getRgb() info['color'] = [rgb[0], rgb[1], rgb[2]]
def __init__(self, parent): super(HueSliderWidget, self).__init__(parent) self.setFixedSize(parent.width(), parent.height()) self.value = 0 size = 100 self.colorImage = QtGui.QImage(1, size, QtGui.QImage.Format_RGB32) c = QColor() for y in range(0, size): k = y / float(size) c.setHsvF(k, 1.0, 1.0) self.colorImage.setPixel(0, y, c.rgb()) self.setCursor(Qt.PointingHandCursor)
def __init__(self, parent): super(AlphaSliderWidget, self).__init__(parent) self.setFixedSize(parent.width(), parent.height()) self.setCursor(Qt.PointingHandCursor) self.value = 0 size = 100 self.colorImage = QtGui.QImage(size, 1, QtGui.QImage.Format_RGB32) c = QColor() for x in range(0, size): k = x / float(size) c.setHsvF(1.0, 0.0, k) self.colorImage.setPixel(x, 0, c.rgb())
def generateHSVImage(w, h, hue, img=None): if not img: img = QtGui.QImage(w, h, QtGui.QImage.Format_RGB32) du = 1.0 / w dv = 1.0 / h c = QColor() for y in range(0, h): for x in range(0, w): s = du * x v = 1.0 - dv * y c.setHsvF(hue, s, v) img.setPixel(x, y, c.rgb()) return img
def bright_button_clicked(self): constant = self.spinner_brightness.value() qimage = self.imported_image.pixmap().toImage() for x in range(0 , qimage.width()): for y in range(0 , qimage.height()): current_pixel = qimage.pixel(x,y) cur_color = QColor(current_pixel) red = self.adjust_to_limit(cur_color.red() + constant) green = self.adjust_to_limit(cur_color.green() + constant) blue = self.adjust_to_limit(cur_color.blue() + constant) value = QColor(red,green,blue) qimage.setPixel(x,y,value.rgb()) new_pixmap = QPixmap.fromImage(qimage) self.modified_image.setPixmap(new_pixmap)
def createImage(self): """ Creates an image by combining the contents of the three screens to present a page preview. The image associated with each screen is separated into cyan, magenta, and yellow components. We add up the values for each component from the three screen images, and subtract the totals from the maximum value for each corresponding primary color. """ newImage = self.scaledImage.copy() image1 = self.cyanWidget.image() image2 = self.magentaWidget.image() image3 = self.yellowWidget.image() darkness = 255 - self.brightness for y in range(newImage.height()): for x in range(newImage.width()): # Create three screens, using the quantities of the source CMY # components to determine how much of each of the inks are to # be put on each screen. p1 = image1.pixel(x, y) cyan1 = float(255 - qRed(p1)) magenta1 = float(255 - qGreen(p1)) yellow1 = float(255 - qBlue(p1)) p2 = image2.pixel(x, y) cyan2 = float(255 - qRed(p2)) magenta2 = float(255 - qGreen(p2)) yellow2 = float(255 - qBlue(p2)) p3 = image3.pixel(x, y) cyan3 = float(255 - qRed(p3)) magenta3 = float(255 - qGreen(p3)) yellow3 = float(255 - qBlue(p3)) newColor = QColor( max(255 - int(cyan1 + cyan2 + cyan3) - darkness, 0), max(255 - int(magenta1 + magenta2 + magenta3) - darkness, 0), max(255 - int(yellow1 + yellow2 + yellow3) - darkness, 0), ) newImage.setPixel(x, y, newColor.rgb()) self.finalWidget.setPixmap(QPixmap.fromImage(newImage))
def createImage(self): """ Creates an image by combining the contents of the three screens to present a page preview. The image associated with each screen is separated into cyan, magenta, and yellow components. We add up the values for each component from the three screen images, and subtract the totals from the maximum value for each corresponding primary color. """ newImage = self.scaledImage.copy() image1 = self.cyanWidget.image() image2 = self.magentaWidget.image() image3 = self.yellowWidget.image() darkness = 255 - self.brightness for y in range(newImage.height()): for x in range(newImage.width()): # Create three screens, using the quantities of the source CMY # components to determine how much of each of the inks are to # be put on each screen. p1 = image1.pixel(x, y) cyan1 = float(255 - qRed(p1)) magenta1 = float(255 - qGreen(p1)) yellow1 = float(255 - qBlue(p1)) p2 = image2.pixel(x, y) cyan2 = float(255 - qRed(p2)) magenta2 = float(255 - qGreen(p2)) yellow2 = float(255 - qBlue(p2)) p3 = image3.pixel(x, y) cyan3 = float(255 - qRed(p3)) magenta3 = float(255 - qGreen(p3)) yellow3 = float(255 - qBlue(p3)) newColor = QColor( max(255 - int(cyan1 + cyan2 + cyan3) - darkness, 0), max(255 - int(magenta1 + magenta2 + magenta3) - darkness, 0), max(255 - int(yellow1 + yellow2 + yellow3) - darkness, 0)) newImage.setPixel(x, y, newColor.rgb()) self.finalWidget.setPixmap(QPixmap.fromImage(newImage))
def apply_effect(self, effect, kwargs): if self.progressbar: self.progressbar.setFormat(self.hack_title.get(effect.__name__, '%p%')) if effect.__name__ != floodfill.__name__: for i in range(self.image.width()): for j in range(self.image.height()): pixel = self.image.pixel(i, j) r, g, b, a = QColor(pixel).getRgb() r, g, b = effect(r, g, b, **kwargs) new_pixel = QColor(r, g, b) self.image.setPixel(QPoint(i, j), new_pixel.rgb()) # if self.progressbar: # self.progressbar.setValue(i) self.sig_step.emit(i) if (i+1) % 100 == 0 or i+1 == self.image.width(): print('line: %s/%s' % (i+1, self.image.width())) else: self.image = floodfill(self.image, signal=self.sig_step)
def create_palette(window_color: QColor, highlight_color: QColor) -> QPalette: """ 根据窗口样式window_color 和 高亮(选中)颜色创建窗口风格的palette :param window_color: 目标窗口样式 :param highlight_color: 目标高亮样式 :return: QPalette """ # 根据HSV样式模型,获取该颜色的 hue色调 sat饱和度 和value明度(最后一个是透明度) hue, sat, window_value, _ = window_color.getHsv() color_from_value = lambda value: QColor.fromHsv(hue, sat, bound(0, value, 255)) is_light = window_value > 128 base_value = window_value + 48 if is_light else window_value - 24 light_text_value = min(255, window_value + 160) dark_text_value = max(0, window_value - 160) # 根据界面的元素设置字体颜色以便以后可以看清字体 light_text_color = QColor(light_text_value, light_text_value, light_text_value) dark_text_color = QColor(dark_text_value, dark_text_value, dark_text_value) light_disabled_text_color = QColor(light_text_value, light_text_value, light_text_value) dark_disabled_text_color = QColor(dark_text_value, dark_text_value, dark_text_value) palette = QPalette(color_from_value(window_value)) palette.setColor(QPalette.Base, color_from_value(base_value)) palette.setColor(QPalette.AlternateBase, color_from_value(base_value - 10)) palette.setColor(QPalette.WindowText, dark_text_color if is_light else light_text_color) palette.setColor(QPalette.ButtonText, dark_text_color if is_light else light_text_color) palette.setColor(QPalette.Text, dark_text_color if is_light else light_text_color) palette.setColor(QPalette.Light, color_from_value(window_value + 55)) palette.setColor(QPalette.Dark, color_from_value(window_value - 55)) palette.setColor(QPalette.Mid, color_from_value(window_value - 27)) palette.setColor(QPalette.Midlight, color_from_value(window_value + 27)) # 按组设置颜色 palette.setColor( QPalette.Disabled, QPalette.WindowText, dark_disabled_text_color if is_light else light_disabled_text_color) palette.setColor( QPalette.Disabled, QPalette.ButtonText, dark_disabled_text_color if is_light else light_disabled_text_color) palette.setColor( QPalette.Disabled, QPalette.Text, dark_disabled_text_color if is_light else light_disabled_text_color) # 高亮的颜色是否偏黑 from PyQt5.Qt import qGray highlight_is_dark = qGray(highlight_color.rgb()) < 120 palette.setColor(QPalette.Highlight, highlight_color) palette.setColor(QPalette.HighlightedText, Qt.white if highlight_is_dark else Qt.black) return palette
class Tileset(Object): ## # Constructor. # # @param name the name of the tileset # @param tileWidth the width of the tiles in the tileset # @param tileHeight the height of the tiles in the tileset # @param tileSpacing the spacing between the tiles in the tileset image # @param margin the margin around the tiles in the tileset image ## def __init__(self, name, tileWidth, tileHeight, tileSpacing = 0, margin = 0): super().__init__(Object.TilesetType) self.mName = name self.mTileWidth = tileWidth self.mTileHeight = tileHeight self.mTileSpacing = tileSpacing self.mMargin = margin self.mImageWidth = 0 self.mImageHeight = 0 self.mColumnCount = 0 self.mTerrainDistancesDirty = False self.mTileOffset = QPoint() self.mFileName = QString() self.mTiles = QList() self.mTransparentColor = QColor() self.mImageSource = QString() self.mTerrainTypes = QList() self.mWeakPointer = None ## # Destructor. ## def __del__(self): self.mTiles.clear() self.mTerrainTypes.clear() def create(name, tileWidth, tileHeight, tileSpacing = 0, margin = 0): tileset = Tileset(name, tileWidth, tileHeight, tileSpacing, margin) tileset.mWeakPointer = tileset return tileset def __iter__(self): return self.mTiles.__iter__() ## # Returns the name of this tileset. ## def name(self): return self.mName ## # Sets the name of this tileset. ## def setName(self, name): self.mName = name ## # Returns the file name of this tileset. When the tileset isn't an # external tileset, the file name is empty. ## def fileName(self): return self.mFileName ## # Sets the filename of this tileset. ## def setFileName(self, fileName): self.mFileName = fileName ## # Returns whether this tileset is external. ## def isExternal(self): return self.mFileName!='' ## # Returns the maximum width of the tiles in this tileset. ## def tileWidth(self): return self.mTileWidth ## # Returns the maximum height of the tiles in this tileset. ## def tileHeight(self): return self.mTileHeight ## # Returns the maximum size of the tiles in this tileset. ## def tileSize(self): return QSize(self.mTileWidth, self.mTileHeight) ## # Returns the spacing between the tiles in the tileset image. ## def tileSpacing(self): return self.mTileSpacing ## # Returns the margin around the tiles in the tileset image. ## def margin(self): return self.mMargin ## # Returns the offset that is applied when drawing the tiles in this # tileset. ## def tileOffset(self): return self.mTileOffset ## # @see tileOffset ## def setTileOffset(self, offset): self.mTileOffset = offset ## # Returns a const reference to the list of tiles in this tileset. ## def tiles(self): return QList(self.mTiles) ## # Returns the tile for the given tile ID. # The tile ID is local to this tileset, which means the IDs are in range # [0, tileCount() - 1]. ## def tileAt(self, id): if id < self.mTiles.size(): return self.mTiles.at(id) return None ## # Returns the number of tiles in this tileset. ## def tileCount(self): return self.mTiles.size() ## # Returns the number of tile columns in the tileset image. ## def columnCount(self): return self.mColumnCount ## # Returns the width of the tileset image. ## def imageWidth(self): return self.mImageWidth ## # Returns the height of the tileset image. ## def imageHeight(self): return self.mImageHeight ## # Returns the transparent color, or an invalid color if no transparent # color is used. ## def transparentColor(self): return QColor(self.mTransparentColor) ## # Sets the transparent color. Pixels with this color will be masked out # when loadFromImage() is called. ## def setTransparentColor(self, c): self.mTransparentColor = c ## # Load this tileset from the given tileset \a image. This will replace # existing tile images in this tileset with new ones. If the new image # contains more tiles than exist in the tileset new tiles will be # appended, if there are fewer tiles the excess images will be blanked. # # The tile width and height of this tileset must be higher than 0. # # @param image the image to load the tiles from # @param fileName the file name of the image, which will be remembered # as the image source of this tileset. # @return <code>true</code> if loading was successful, otherwise # returns <code>false</code> ## def loadFromImage(self, *args): l = len(args) if l==2: image, fileName = args tileSize = self.tileSize() margin = self.margin() spacing = self.tileSpacing() if (image.isNull()): return False stopWidth = image.width() - tileSize.width() stopHeight = image.height() - tileSize.height() oldTilesetSize = self.tileCount() tileNum = 0 for y in range(margin, stopHeight+1, tileSize.height() + spacing): for x in range(margin, stopWidth+1, tileSize.width() + spacing): tileImage = image.copy(x, y, tileSize.width(), tileSize.height()) tilePixmap = QPixmap.fromImage(tileImage) if (self.mTransparentColor.isValid()): mask = tileImage.createMaskFromColor(self.mTransparentColor.rgb()) tilePixmap.setMask(QBitmap.fromImage(mask)) if (tileNum < oldTilesetSize): self.mTiles.at(tileNum).setImage(tilePixmap) else: self.mTiles.append(Tile(tilePixmap, tileNum, self)) tileNum += 1 # Blank out any remaining tiles to avoid confusion while (tileNum < oldTilesetSize): tilePixmap = QPixmap(tileSize) tilePixmap.fill() self.mTiles.at(tileNum).setImage(tilePixmap) tileNum += 1 self.mImageWidth = image.width() self.mImageHeight = image.height() self.mColumnCount = self.columnCountForWidth(self.mImageWidth) self.mImageSource = fileName return True elif l==1: ## # Convenience override that loads the image using the QImage constructor. ## fileName = args[0] return self.loadFromImage(QImage(fileName), fileName) ## # This checks if there is a similar tileset in the given list. # It is needed for replacing this tileset by its similar copy. ## def findSimilarTileset(self, tilesets): for candidate in tilesets: if (candidate.tileCount() != self.tileCount()): continue if (candidate.imageSource() != self.imageSource()): continue if (candidate.tileSize() != self.tileSize()): continue if (candidate.tileSpacing() != self.tileSpacing()): continue if (candidate.margin() != self.margin()): continue if (candidate.tileOffset() != self.tileOffset()): continue # For an image collection tileset, check the image sources if (self.imageSource()==''): if (not sameTileImages(self, candidate)): continue return candidate return None ## # Returns the file name of the external image that contains the tiles in # this tileset. Is an empty string when this tileset doesn't have a # tileset image. ## def imageSource(self): return self.mImageSource ## # Returns the column count that this tileset would have if the tileset # image would have the given \a width. This takes into account the tile # size, margin and spacing. ## def columnCountForWidth(self, width): return int((width - self.mMargin + self.mTileSpacing) / (self.mTileWidth + self.mTileSpacing)) ## # Returns a const reference to the list of terrains in this tileset. ## def terrains(self): return QList(self.mTerrainTypes) ## # Returns the number of terrain types in this tileset. ## def terrainCount(self): return self.mTerrainTypes.size() ## # Returns the terrain type at the given \a index. ## def terrain(self, index): if index >= 0: _x = self.mTerrainTypes[index] else: _x = None return _x ## # Adds a new terrain type. # # @param name the name of the terrain # @param imageTile the id of the tile that represents the terrain visually # @return the created Terrain instance ## def addTerrain(self, name, imageTileId): terrain = Terrain(self.terrainCount(), self, name, imageTileId) self.insertTerrain(self.terrainCount(), terrain) return terrain ## # Adds the \a terrain type at the given \a index. # # The terrain should already have this tileset associated with it. ## def insertTerrain(self, index, terrain): self.mTerrainTypes.insert(index, terrain) # Reassign terrain IDs for terrainId in range(index, self.mTerrainTypes.size()): self.mTerrainTypes.at(terrainId).mId = terrainId # Adjust tile terrain references for tile in self.mTiles: for corner in range(4): terrainId = tile.cornerTerrainId(corner) if (terrainId >= index): tile.setCornerTerrainId(corner, terrainId + 1) self.mTerrainDistancesDirty = True ## # Removes the terrain type at the given \a index and returns it. The # caller becomes responsible for the lifetime of the terrain type. # # This will cause the terrain ids of subsequent terrains to shift up to # fill the space and the terrain information of all tiles in this tileset # will be updated accordingly. ## def takeTerrainAt(self, index): terrain = self.mTerrainTypes.takeAt(index) # Reassign terrain IDs for terrainId in range(index, self.mTerrainTypes.size()): self.mTerrainTypes.at(terrainId).mId = terrainId # Clear and adjust tile terrain references for tile in self.mTiles: for corner in range(4): terrainId = tile.cornerTerrainId(corner) if (terrainId == index): tile.setCornerTerrainId(corner, 0xFF) elif (terrainId > index): tile.setCornerTerrainId(corner, terrainId - 1) self.mTerrainDistancesDirty = True return terrain ## # Returns the transition penalty(/distance) between 2 terrains. -1 if no # transition is possible. ## def terrainTransitionPenalty(self, terrainType0, terrainType1): if (self.mTerrainDistancesDirty): self.recalculateTerrainDistances() self.mTerrainDistancesDirty = False if terrainType0 == 255: terrainType0 = -1 if terrainType1 == 255: terrainType1 = -1 # Do some magic, since we don't have a transition array for no-terrain if (terrainType0 == -1 and terrainType1 == -1): return 0 if (terrainType0 == -1): return self.mTerrainTypes.at(terrainType1).transitionDistance(terrainType0) return self.mTerrainTypes.at(terrainType0).transitionDistance(terrainType1) ## # Adds a new tile to the end of the tileset. ## def addTile(self, image, source=QString()): newTile = Tile(image, source, self.tileCount(), self) self.mTiles.append(newTile) if (self.mTileHeight < image.height()): self.mTileHeight = image.height() if (self.mTileWidth < image.width()): self.mTileWidth = image.width() return newTile def insertTiles(self, index, tiles): count = tiles.count() for i in range(count): self.mTiles.insert(index + i, tiles.at(i)) # Adjust the tile IDs of the remaining tiles for i in range(index + count, self.mTiles.size()): self.mTiles.at(i).mId += count self.updateTileSize() def removeTiles(self, index, count): first = self.mTiles.begin() + index last = first + count last = self.mTiles.erase(first, last) # Adjust the tile IDs of the remaining tiles for last in self.mTiles: last.mId -= count self.updateTileSize() ## # Sets the \a image to be used for the tile with the given \a id. ## def setTileImage(self, id, image, source = QString()): # This operation is not supposed to be used on tilesets that are based # on a single image tile = self.tileAt(id) if (not tile): return previousImageSize = tile.image().size() newImageSize = image.size() tile.setImage(image) tile.setImageSource(source) if (previousImageSize != newImageSize): # Update our max. tile size if (previousImageSize.height() == self.mTileHeight or previousImageSize.width() == self.mTileWidth): # This used to be the max image; we have to recompute self.updateTileSize() else: # Check if we have a new maximum if (self.mTileHeight < newImageSize.height()): self.mTileHeight = newImageSize.height() if (self.mTileWidth < newImageSize.width()): self.mTileWidth = newImageSize.width() ## # Used by the Tile class when its terrain information changes. ## def markTerrainDistancesDirty(self): self.mTerrainDistancesDirty = True ## # Sets tile size to the maximum size. ## def updateTileSize(self): maxWidth = 0 maxHeight = 0 for tile in self.mTiles: size = tile.size() if (maxWidth < size.width()): maxWidth = size.width() if (maxHeight < size.height()): maxHeight = size.height() self.mTileWidth = maxWidth self.mTileHeight = maxHeight ## # Calculates the transition distance matrix for all terrain types. ## def recalculateTerrainDistances(self): # some fancy macros which can search for a value in each byte of a word simultaneously def hasZeroByte(dword): return (dword - 0x01010101) & ~dword & 0x80808080 def hasByteEqualTo(dword, value): return hasZeroByte(dword ^ int(~0/255 * value)) # Terrain distances are the number of transitions required before one terrain may meet another # Terrains that have no transition path have a distance of -1 for i in range(self.terrainCount()): type = self.terrain(i) distance = QVector() for _x in range(self.terrainCount() + 1): distance.append(-1) # Check all tiles for transitions to other terrain types for j in range(self.tileCount()): t = self.tileAt(j) if (not hasByteEqualTo(t.terrain(), i)): continue # This tile has transitions, add the transitions as neightbours (distance 1) tl = t.cornerTerrainId(0) tr = t.cornerTerrainId(1) bl = t.cornerTerrainId(2) br = t.cornerTerrainId(3) # Terrain on diagonally opposite corners are not actually a neighbour if (tl == i or br == i): distance[tr + 1] = 1 distance[bl + 1] = 1 if (tr == i or bl == i): distance[tl + 1] = 1 distance[br + 1] = 1 # terrain has at least one tile of its own type distance[i + 1] = 0 type.setTransitionDistances(distance) # Calculate indirect transition distances bNewConnections = False # Repeat while we are still making new connections (could take a # number of iterations for distant terrain types to connect) while bNewConnections: bNewConnections = False # For each combination of terrain types for i in range(self.terrainCount()): t0 = self.terrain(i) for j in range(self.terrainCount()): if (i == j): continue t1 = self.terrain(j) # Scan through each terrain type, and see if we have any in common for t in range(-1, self.terrainCount()): d0 = t0.transitionDistance(t) d1 = t1.transitionDistance(t) if (d0 == -1 or d1 == -1): continue # We have cound a common connection d = t0.transitionDistance(j) # If the new path is shorter, record the new distance if (d == -1 or d0 + d1 < d): d = d0 + d1 t0.setTransitionDistance(j, d) t1.setTransitionDistance(i, d) # We're making progress, flag for another iteration... bNewConnections = True def sharedPointer(self): return self.mWeakPointer
def fill_zatravka(self, x_zatravka, y_zatravka): stack = [[x_zatravka, y_zatravka]] fill_color = self.colormain line_color = QColor(0, 0, 0) line_color = line_color.rgb() while (len(stack)): QApplication.processEvents() x, y = stack.pop() self.image.setPixel(x, y, fill_color) temp_x = x while (self.image.pixelColor(x, y).rgb() != line_color): self.image.setPixel(x, y, fill_color) x += 1 x_prav = x - 1 x = temp_x while (self.image.pixelColor(x, y).rgb() != line_color): self.image.setPixel(x, y, fill_color) x -= 1 self.repaint() x_lev = x + 1 #++++++++++++++++++++++++++ x = x_lev y += 1 while (x <= x_prav): flag = False while (self.image.pixelColor(x, y).rgb() != line_color and self.image.pixelColor(x, y).rgb() != fill_color and x <= x_prav): if flag == False: flag = True x += 1 if flag == True: if (self.image.pixelColor(x, y).rgb() != line_color and self.image.pixelColor(x, y).rgb() != fill_color and x == x_prav): stack.append([x, y]) else: stack.append([x - 1, y]) flag = False x_vhod = x while (self.image.pixelColor(x, y).rgb() != line_color and self.image.pixelColor(x, y).rgb() != fill_color and x < x_prav): x += 1 if x == x_vhod: x += 1 x = x_lev y -= 2 #Second part while (x <= x_prav): flag = False while (self.image.pixelColor(x, y).rgb() != line_color and self.image.pixelColor(x, y).rgb() != fill_color and x <= x_prav): if flag == False: flag = True x += 1 if flag == True: if (self.image.pixelColor(x, y).rgb() != line_color and self.image.pixelColor(x, y).rgb() != fill_color and x == x_prav): stack.append([x, y]) else: stack.append([x - 1, y]) flag = False x_vhod = x while (self.image.pixelColor(x, y).rgb() != line_color and self.image.pixelColor(x, y).rgb() != fill_color and x < x_prav): x += 1 if x == x_vhod: x += 1
def loadColorPanel(self): self.qsst.srctext = self.editor.text() if not self.qsst.loadVars(): return # item = self.colorGridLayout.itemAt(0) # while (item != None): # self.colorGridLayout.removeItem(item) # # self.colorGridLayout.removeWidget(item.widget()) # # item.widget().setParent(None)#这三行没有作用 # sip.delete(item.widget()) # 虽然控件删除了,但是grid的行数列数没减少,但是不影响使用 # sip.delete(item) # item = self.colorGridLayout.itemAt(0) # self.colorGridLayout.update() # 不起作用 # a,b=list(self.clrBtnDict.keys()),list(self.qsst.varDict.keys());a.sort();b.sort() if sorted(list(self.clrBtnDict.keys())) != sorted( list(self.qsst.varDict.keys())): while self.colorPanelLayout.count() > 0: self.colorPanelLayout.removeItem( self.colorPanelLayout.itemAt(0)) self.clrBtnDict = {} labels = {} widLabel = 0 widBtn = 0 for varName, clrStr in self.qsst.varDict.items(): label = QLabel(varName) # , contianerWidget) btn = QPushButton(clrStr) # , contianerWidget) if sys.platform.startswith("win"): font1 = QFont("Arial", 10, QFont.Medium) font2 = QFont("sans-serif", 9, QFont.Medium) label.setFont(font1) btn.setFont(font2) self.clrBtnDict[varName] = btn labels[varName] = label label.adjustSize() widLabel = label.width( ) if label.width() > widLabel else widLabel btn.adjustSize() widBtn = btn.width() if btn.width() > widBtn else widBtn # label.move(5, 5) # btn.move(100, 5) btn.clicked.connect(lambda x, var=varName: self.chclr(var)) for name, btn in self.clrBtnDict.items(): contianerWidget = QWidget() lay = QHBoxLayout() labels[name].setFixedWidth(widLabel) btn.setFixedWidth(widBtn) lay.addWidget(labels[name]) lay.addWidget(btn) contianerWidget.setLayout(lay) # contianerWidget.setMinimumSize(QSize(185, 25)) self.colorPanelLayout.addWidget(contianerWidget) for varName, btn in self.clrBtnDict.items(): clrStr = self.qsst.varDict[varName] btn.setText(clrStr) if "rgb" in clrStr: t = clrStr.strip(r" rgba()") c = t.split(',') if len(c) > 3: lable = c[3] else: lable = 255 try: color = QColor(int(c[0]), int(c[1]), int(c[2]), lable) except Exception: continue else: try: color = QColor(clrStr) except Exception: continue s = '' if qGray(color.rgb()) < 100: s += "color:white;" else: s += "color:black;" btn.setStyleSheet(s + "background:" + btn.text())
class STGWindow(QtWidgets.QMainWindow): def __init__(self, parent_settings, parent=None, callback=None): QtWidgets.QWidget.__init__(self, parent) self.ui = SettingsWindow.Ui_MainWindow() self.ui.setupUi(self) self.color = QColor() self.parent_settings = parent_settings self.settings = Settings('config/settings.json') self.settings.load_json() self.ui.pushButton_2.clicked.connect(self.close) self.ui.pushButton.clicked.connect(self.apply) self.ui.pushButton_3.clicked.connect(self.ok) self.ui.pushButton_4.clicked.connect(self.rollback) self.ui.pushButton_5.clicked.connect(self.select_color) self.ui.pushButton_6.clicked.connect(self.apply_all) self.ui.checkBox.stateChanged.connect(self.change_enabled) self.ui.spinBox.valueChanged.connect(self.change_border_size) self.ui.spinBox_2.valueChanged.connect(self.change_text_size) self.ui.doubleSpinBox.valueChanged.connect(self.change_confidence) self.ui.lineEdit.textChanged.connect(self.change_color) self.ui.listWidget.itemClicked.connect(self.list_select) for dct in self.settings.CLASSES: self.ui.listWidget.insertItem(0, dct['name']) self.ui.listWidget.setCurrentRow(0) self.list_select(self.ui.listWidget.currentItem()) def apply_all(self): rgb = self.color.getRgb() for info in self.settings.CLASSES: info['color'] = [rgb[0], rgb[1], rgb[2]] info['confidence'] = self.ui.doubleSpinBox.value() info['border_size'] = self.ui.spinBox.value() info['text_size'] = self.ui.spinBox_2.value() info['enabled'] = self.ui.checkBox.isChecked() def change_color(self, value): invert = QColor(255, 255, 255) invert.setRgb(invert.rgb() - self.color.rgb()) self.ui.lineEdit.setStyleSheet( "QWidget { background-color: %s; color: %s}" % (self.color.name(), invert.name())) name = self.ui.listWidget.currentItem().text() info = self.settings.getClassFromName(name) rgb = self.color.getRgb() info['color'] = [rgb[0], rgb[1], rgb[2]] def select_color(self): self.color = QColorDialog.getColor() self.ui.lineEdit.setText(self.color.name()) def change_confidence(self, value): name = self.ui.listWidget.currentItem().text() info = self.settings.getClassFromName(name) info['confidence'] = value def change_border_size(self, value): name = self.ui.listWidget.currentItem().text() info = self.settings.getClassFromName(name) info['border_size'] = value def change_text_size(self, value): name = self.ui.listWidget.currentItem().text() info = self.settings.getClassFromName(name) info['text_size'] = value def change_enabled(self): name = self.ui.listWidget.currentItem().text() info = self.settings.getClassFromName(name) info['enabled'] = self.ui.checkBox.isChecked() def rollback(self): self.settings.default() self.list_select(self.ui.listWidget.currentItem()) def ok(self): self.apply() self.close() def apply(self): self.settings.save_json() self.parent_settings.load_json() def list_select(self, item): info = self.settings.getClassFromName(item.text()) self.ui.checkBox.setChecked(info['enabled']) self.ui.spinBox.setValue(info['border_size']) self.ui.spinBox_2.setValue(info['text_size']) self.ui.doubleSpinBox.setValue(info['confidence']) self.color = QColor(info['color'][0], info['color'][1], info['color'][2]) self.ui.lineEdit.setText(self.color.name())
class ImageLayer(Layer): ## # Constructor. ## def __init__(self, name, x, y, width, height): super().__init__(Layer.ImageLayerType, name, x, y, width, height) self.mImage = QPixmap() self.mTransparentColor = QColor() self.mImageSource = QString() ## # Destructor. ## def __del__(self): pass def usedTilesets(self): return QSet() def referencesTileset(self, arg1): return False def replaceReferencesToTileset(self, arg1, arg2): pass def canMergeWith(self, arg1): return False def mergedWith(self, arg1): return None ## # Returns the transparent color, or an invalid color if no transparent # color is used. ## def transparentColor(self): return QColor(self.mTransparentColor) ## # Sets the transparent color. Pixels with this color will be masked out # when loadFromImage() is called. ## def setTransparentColor(self, c): self.mTransparentColor = c ## # Sets image source file name ## def setSource(self, source): self.mImageSource = source ## # Returns the file name of the layer image. ## def imageSource(self): return self.mImageSource ## # Returns the image of this layer. ## def image(self): return QPixmap(self.mImage) ## # Sets the image of this layer. ## def setImage(self, image): self.mImage = image ## # Resets layer image. ## def resetImage(self): self.mImage = QPixmap() self.mImageSource = '' ## # Load this layer from the given \a image. This will replace the existing # image. The \a fileName becomes the new imageSource, regardless of # whether the image could be loaded. # # @param image the image to load the layer from # @param fileName the file name of the image, which will be remembered # as the image source of this layer. # @return <code>true</code> if loading was successful, otherwise # returns <code>false</code> ## def loadFromImage(self, image, fileName): self.mImageSource = fileName if (image.isNull()): self.mImage = QPixmap() return False self.mImage = QPixmap.fromImage(image) if (self.mTransparentColor.isValid()): mask = image.createMaskFromColor(self.mTransparentColor.rgb()) self.mImage.setMask(QBitmap.fromImage(mask)) return True ## # Returns True if no image source has been set. ## def isEmpty(self): return self.mImage.isNull() def clone(self): return self.initializeClone(ImageLayer(self.mName, self.mX, self.mY, self.mWidth, self.mHeight)) def initializeClone(self, clone): super().initializeClone(clone) clone.mImageSource = self.mImageSource clone.mTransparentColor = self.mTransparentColor clone.mImage = self.mImage return clone
def get_vector(color: QColor) -> tuple: rgb = color.rgb() return qRed(rgb), qGreen(rgb), qBlue(rgb)
class ImageLayer(Layer): ## # Constructor. ## def __init__(self, name, x, y, width, height): super().__init__(Layer.ImageLayerType, name, x, y, width, height) self.mImage = QPixmap() self.mTransparentColor = QColor() self.mImageSource = QString() ## # Destructor. ## def __del__(self): pass def usedTilesets(self): return QSet() def referencesTileset(self, arg1): return False def replaceReferencesToTileset(self, arg1, arg2): pass def canMergeWith(self, arg1): return False def mergedWith(self, arg1): return None ## # Returns the transparent color, or an invalid color if no transparent # color is used. ## def transparentColor(self): return QColor(self.mTransparentColor) ## # Sets the transparent color. Pixels with this color will be masked out # when loadFromImage() is called. ## def setTransparentColor(self, c): self.mTransparentColor = c ## # Sets image source file name ## def setSource(self, source): self.mImageSource = source ## # Returns the file name of the layer image. ## def imageSource(self): return self.mImageSource ## # Returns the image of this layer. ## def image(self): return QPixmap(self.mImage) ## # Sets the image of this layer. ## def setImage(self, image): self.mImage = image ## # Resets layer image. ## def resetImage(self): self.mImage = QPixmap() self.mImageSource = '' ## # Load this layer from the given \a image. This will replace the existing # image. The \a fileName becomes the new imageSource, regardless of # whether the image could be loaded. # # @param image the image to load the layer from # @param fileName the file name of the image, which will be remembered # as the image source of this layer. # @return <code>true</code> if loading was successful, otherwise # returns <code>false</code> ## def loadFromImage(self, image, fileName): self.mImageSource = fileName if (image.isNull()): self.mImage = QPixmap() return False self.mImage = QPixmap.fromImage(image) if (self.mTransparentColor.isValid()): mask = image.createMaskFromColor(self.mTransparentColor.rgb()) self.mImage.setMask(QBitmap.fromImage(mask)) return True ## # Returns True if no image source has been set. ## def isEmpty(self): return self.mImage.isNull() def clone(self): return self.initializeClone( ImageLayer(self.mName, self.mX, self.mY, self.mWidth, self.mHeight)) def initializeClone(self, clone): super().initializeClone(clone) clone.mImageSource = self.mImageSource clone.mTransparentColor = self.mTransparentColor clone.mImage = self.mImage return clone
def setColor(self, color: QColor): # RGB channels are used for widget color bar display self.color = QColor(color.rgb()) # Alpha channel will be used as the new widget value self.value = color.alpha() self.update()
def grayscale_colortable(): table = [] for i in range(0, 256): color = QColor(i, i, i) table.append(color.rgb()) return table
class Setting: settingDefault = { 'X': 1320, 'Y': 850, 'W': 600, 'H': 300, 'TP': 0.5, 'CL': QColor(Qt.darkCyan).rgb(), 'FM': 'hh:mm' } settingMinMax = { 'X': [0, 9999], 'Y': [0, 9999], 'W': [0, 9999], 'H': [0, 9999], 'TP': [0.0, 1.0], 'CL': [QColor(0, 0, 0).rgb(), QColor(255, 255, 255).rgb()], 'FM': ['hh:mm:ss', 'hh:mm'] } def SettingSave(self): try: with open('setting.json', 'w', encoding='utf8') as file: setting = dict() setting['X'] = self.X setting['Y'] = self.Y setting['W'] = self.W setting['H'] = self.H setting['TP'] = self.TP setting['CL'] = self.CL setting['FM'] = self.FM json.dump(setting, file) except Exception as identifier: print(('setting.json save error!', identifier)) pass def SettingLoadOne(self, setting, name, continued): if continued: if name not in setting or type(setting[name]) != type( self.settingDefault[name] ) or setting[name] < self.settingMinMax[name][0] or setting[ name] > self.settingMinMax[name][1]: print('%s load bad!' % name) return self.settingDefault[name] else: return setting[name] else: if name not in setting or type(setting[name]) != type( self.settingDefault[name] ) or setting[name] not in self.settingMinMax[name]: print('%s load bad!' % name) return self.settingDefault[name] else: return setting[name] def SettingLoad(self): try: with open('setting.json', 'r', encoding='utf8') as file: setting = json.loads(file.read()) self.X = self.SettingLoadOne(setting, 'X', True) self.Y = self.SettingLoadOne(setting, 'Y', True) self.W = self.SettingLoadOne(setting, 'W', True) self.H = self.SettingLoadOne(setting, 'H', True) self.TP = self.SettingLoadOne(setting, 'TP', True) self.CL = self.SettingLoadOne(setting, 'CL', True) self.FM = self.SettingLoadOne(setting, 'FM', False) except Exception as identifier: print(('setting.json load error!', identifier)) self.X = self.settingDefault['X'] self.Y = self.settingDefault['Y'] self.W = self.settingDefault['W'] self.H = self.settingDefault['H'] self.TP = self.settingDefault['TP'] self.CL = self.settingDefault['CL'] self.FM = self.settingDefault['FM'] def SettingDialog(self, ApplyChange): from PyQt5.QtWidgets import (QDialog, QSpinBox, QComboBox, QDialogButtonBox, QFormLayout, QColorDialog, QPushButton, QSizePolicy) dialog = self.dialog = QDialog() dialog.setWindowTitle('设置') if hasattr(sys, "_MEIPASS"): dialog.setWindowIcon(QIcon(sys._MEIPASS + r'/Icon.ico')) else: dialog.setWindowIcon(QIcon(r'./Icon.ico')) boxX = QSpinBox(dialog) boxX.setRange(-100000, 100000) boxX.setValue(self.X) boxY = QSpinBox(dialog) boxY.setRange(-100000, 100000) boxY.setValue(self.Y) boxW = QSpinBox(dialog) boxW.setRange(0, 100000) boxW.setValue(self.W) boxH = QSpinBox(dialog) boxH.setRange(0, 100000) boxH.setValue(self.H) boxTP = QSpinBox(dialog) boxTP.setRange(0, 100) boxTP.setValue(self.TP * 100) self.buttonColorVal = QColor(self.CL) self.buttonColor = QPushButton(parent=dialog) self.buttonColor.setStyleSheet('QWidget {background-color:%s}' % self.buttonColorVal.name()) def ChangeCol(): qcd = QColorDialog(dialog) qcd.setWindowTitle('颜色选择') qcd.setCurrentColor(self.buttonColorVal) if qcd.exec() == QDialog.Accepted: self.buttonColorVal = qcd.selectedColor() self.buttonColor.setStyleSheet( 'QWidget {background-color:%s}' % self.buttonColorVal.name()) self.buttonColor.clicked.connect(ChangeCol) boxFM = QComboBox(dialog) boxFM.addItems(self.settingMinMax['FM']) boxFM.setCurrentIndex(boxFM.findText(self.FM)) def Apply(): self.X = boxX.value() self.Y = boxY.value() self.W = boxW.value() self.H = boxH.value() self.TP = boxTP.value() / 100.0 self.CL = self.buttonColorVal.rgb() self.FM = boxFM.currentText() ApplyChange() self.SettingSave() buttonBox = QDialogButtonBox( QDialogButtonBox.Apply | QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, dialog) buttonBox.button(QDialogButtonBox.Apply).setText("应用") buttonBox.button(QDialogButtonBox.Ok).setText("确定") buttonBox.button(QDialogButtonBox.Cancel).setText("取消") buttonBox.accepted.connect(dialog.accept) buttonBox.rejected.connect(dialog.reject) buttonBox.button(QDialogButtonBox.Apply).clicked.connect(Apply) form = QFormLayout(dialog) form.addRow(QLabel("设置:")) form.addRow("坐标X:", boxX) form.addRow("坐标Y:", boxY) form.addRow("大小W:", boxW) form.addRow("大小H:", boxH) form.addRow("透明度:", boxTP) form.addRow("颜色:", self.buttonColor) form.addRow("格式:", boxFM) form.addRow(buttonBox) dialog.setFixedSize(dialog.sizeHint()) if dialog.exec() == QDialog.Accepted: Apply()
class AppWindow(QMainWindow): def __init__(self): super(AppWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.img_area.setScaledContents(False) self.ui.img_area.setStyleSheet('border: 1px solid red') self.add_tool_bar() self.bind_actions() self.init_vars() def init_vars(self): self.open_mode = OPEN_MODE.OPEN_FILE self.opened_files = [] self.cur_img = None self.panel_pic = None # 用来在上面绘制矩阵等形状 self.origin_img = None # 保留原始大小的图片 self.labelimg = None self.scale = 1 self.cur_rect_list = [] self.cur_label_list = [] self.histroty = [] self.fill_color = QColor('green') self.pixel_range = [-5, 5] self.state = STATE.NORMAL self.start_point = None self.end_point = None self.polygon_points = None self.ellipse_points = None self.modified = False self.saved = False self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) handler = logging.StreamHandler() handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(levelname)s %(funcName)s %(message)s') handler.setFormatter(formatter) self.logger.addHandler(handler) def refresh_vars(self): self.cur_img = None self.panel_pic = None # 用来在上面绘制矩阵等形状 self.origin_img = None # 保留原始大小的图片 self.labelimg = None self.scale = 1 self.cur_rect_list = [] self.cur_label_list = [] self.histroty = [] self.state = STATE.NORMAL self.ui.actionRectangle.setChecked(False) self.ui.actionPolygon.setChecked(False) self.ui.actionEllipse.setChecked(False) self.start_point = None self.end_point = None self.polygon_points = None self.ellipse_points = None self.modified = False self.saved = False def bind_actions(self): self.ui.actionOpen_File.triggered.connect(self.open_file) self.ui.actionOpen_Dir.triggered.connect(self.open_dir) self.ui.file_list.itemClicked.connect(self.file_list_item_changed) self.ui.actionZoom_In.triggered.connect(self.zoom_in_pic) self.ui.actionZoom_Out.triggered.connect(self.zoom_out_pic) self.ui.img_area.mouse_pressed.connect(self.mouse_pressed) self.ui.img_area.mouse_move.connect(self.mouse_move) self.ui.img_area.mouse_release.connect(self.mouse_release) self.ui.actionfill_color.triggered.connect(self.set_fill_color) self.ui.actionSet_Pixel_Range.triggered.connect(self.set_pixel_range) self.ui.actionUndo.triggered.connect(self.undo) self.ui.actionSave.triggered.connect(self.save) self.ui.actionPolygon.triggered.connect(self.create_polygon) self.ui.actionRectangle.triggered.connect(self.create_rectangle) self.ui.actionEllipse.triggered.connect(self.create_ellipse) def save(self): name = str(self.selected_filename.absolute()) items = name.split('.') output_filename = '.'.join(items[:-1]) + '.npy' np.save(output_filename, self.labelimg) self.saved = True self.modified = False def undo(self, checked): if self.state == STATE.DRAW_POLYGON and self.polygon_points is not None and self.polygon_points[ 'finish'] is False: if len(self.polygon_points['points']) == 1: self.polygon_points = None elif len(self.polygon_points['points']) > 1: self.polygon_points['points'].pop(-1) self.draw_lines(self.polygon_points['points'], False) if self.labelimg is not None: pic = self.draw_points() self.show_pic(file_name=None, content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) else: if len(self.histroty) == 0: return x, y = self.histroty.pop(-1) self.labelimg[x, y] = 0 pic = self.draw_points() self.show_pic(file_name=None, content=pic) self.modified = True def set_fill_color(self, checked): color = QColorDialog.getColor() if color.isValid(): self.fill_color = color def set_pixel_range(self, checked): dialog = PixelRangeDialog() if dialog.exec_(): v1, v2 = dialog.get_result() self.pixel_range = [v1, v2] self.logger.debug(self.pixel_range) def create_polygon(self, checked): if checked: self.state = STATE.DRAW_POLYGON self.ui.actionEllipse.setChecked(False) self.ui.actionRectangle.setChecked(False) else: self.state = STATE.NORMAL def create_rectangle(self, checked): if checked: self.state = STATE.DRAW_RECTANGLE self.ui.actionEllipse.setChecked(False) self.ui.actionPolygon.setChecked(False) else: self.state = STATE.NORMAL def create_ellipse(self, checked): if checked: self.state = STATE.DRAW_ELLIPSE self.ui.actionPolygon.setChecked(False) self.ui.actionRectangle.setChecked(False) else: self.state = STATE.NORMAL def pos_in_img_area(self, pos: PyQt5.QtCore.QPoint): if self.cur_img is None: return False, None width = self.ui.img_area.width() height = self.ui.img_area.height() img_height, img_width, _ = self.cur_img.shape w = (width - img_width) // 2 h = (height - img_height) // 2 x_valid = w <= pos.x() <= w + img_width y_valid = h <= pos.y() <= h + img_height valid = x_valid and y_valid if valid: return True, (pos.x() - w, pos.y() - h) else: return False, None def mouse_pressed(self, ev: QMouseEvent): if self.state == STATE.NORMAL: return if ev.button() == Qt.LeftButton: pos = ev.pos() valid, relative_pos = self.pos_in_img_area(pos) self.logger.debug(valid) if valid: self.logger.debug(relative_pos) point = (int(relative_pos[0] / self.scale), int(relative_pos[1] / self.scale)) if self.state == STATE.DRAW_RECTANGLE: self.start_point = point self.end_point = None self.logger.debug(self.start_point) elif self.state == STATE.DRAW_POLYGON: if self.polygon_points is None or self.polygon_points[ 'finish'] is True: self.polygon_points = { 'finish': False, 'points': [point] } else: p1 = np.array([ self.polygon_points['points'][0][0] * self.scale, self.polygon_points['points'][0][1] * self.scale ]) p2 = np.array( [point[0] * self.scale, point[1] * self.scale]) dis = np.linalg.norm(p1 - p2, 2) if dis < 10: self.polygon_points['finish'] = True self.draw_lines(self.polygon_points['points'], True) else: self.polygon_points['points'].append(point) self.draw_lines(self.polygon_points['points'], False) if self.labelimg is not None: pic = self.draw_points() self.show_pic(file_name=None, content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) elif self.state == STATE.DRAW_ELLIPSE: if self.ellipse_points is None: self.ellipse_points = {'points': [point], 'info': None} elif len(self.ellipse_points['points']) == 1: self.ellipse_points['points'].append(point) self.draw_lines(self.ellipse_points['points'], False) if self.labelimg is not None: pic = self.draw_points() self.show_pic(file_name=None, content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) elif len(self.ellipse_points['points']) == 2: self.ellipse_points['points'].append(point) self.ellipse_points['info'] = self.draw_ellipse( self.ellipse_points['points']) if self.labelimg is not None: pic = self.draw_points() self.show_pic(file_name=None, content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) else: self.ellipse_points = {'points': [point], 'info': None} else: raise NotImplementedError() elif ev.button() == Qt.RightButton: pass def mouse_move(self, ev: QMouseEvent): if self.state == STATE.NORMAL: return pos = ev.pos() valid, relative_pos = self.pos_in_img_area(pos) if valid: relative_pos = (int(relative_pos[0] / self.scale), int(relative_pos[1] / self.scale)) if self.state == STATE.DRAW_RECTANGLE: self.draw_rect(self.start_point, relative_pos) if self.labelimg is not None: pic = self.draw_points() self.show_pic(file_name=None, content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) elif self.state == STATE.DRAW_POLYGON: pass elif self.state == STATE.DRAW_ELLIPSE: pass else: raise NotImplementedError() def draw_ellipse(self, points): color_r, color_g, color_b = self.fill_color.red( ), self.fill_color.green(), self.fill_color.blue() points = list( map( lambda item: (int(item[0] * self.scale), int(item[1] * self.scale)), points)) p1 = np.array(points[0]) p2 = np.array(points[1]) if p1[0] > p2[0]: p1, p2 = p2, p1 p3 = np.array(points[2]) center = tuple((p1 + p2) // 2) r1 = int(np.linalg.norm(p1 - p2, 2) / 2) a = p2[1] - p1[1] b = p1[0] - p2[0] c = p2[0] * p1[1] - p1[0] * p2[1] r2 = int(np.abs(a * p3[0] + b * p3[1] + c) / np.sqrt(a**2 + b**2)) if r1 > r2: theta = -1 * (np.arctan(a / b) * 180) / np.pi self.panel_pic = cv.ellipse(self.cur_img.copy(), center, (r1, r2), theta, 0, 360, (color_r, color_g, color_b), 1) return ((int(center[0] / self.scale), int(center[1] / self.scale)), (int(r1 / self.scale), int(r2 / self.scale)), theta) else: theta = (np.arctan(a / b) * 180) / np.pi if theta > 0: theta = 90 - theta else: theta = -90 - theta self.panel_pic = cv.ellipse(self.cur_img.copy(), center, (r2, r1), theta, 0, 360, (color_r, color_g, color_b), 1) return ((int(center[0] / self.scale), int(center[1] / self.scale)), (int(r2 / self.scale), int(r1 / self.scale)), theta) def draw_lines(self, points, end=False): b, g, r = self.fill_color.blue(), self.fill_color.green( ), self.fill_color.red() temp_img = self.cur_img.copy() points = list( map( lambda item: (int(item[0] * self.scale), int(item[1] * self.scale)), points)) for i in range(len(points) - 1): temp_img = cv.line(temp_img, points[i], points[i + 1], (r, g, b), 1) if end: temp_img = cv.line(temp_img, points[-1], points[0], (r, g, b), 1) self.panel_pic = temp_img def draw_rect(self, start_point, end_point): b, g, r = self.fill_color.blue(), self.fill_color.green( ), self.fill_color.red() self.fill_color.rgb() start_point = (int(start_point[0] * self.scale), int(start_point[1] * self.scale)) end_point = (int(end_point[0] * self.scale), int(end_point[1] * self.scale)) self.panel_pic = cv.rectangle(self.cur_img.copy(), start_point, end_point, (r, g, b), 1) def draw_points(self): if self.labelimg is None: return b, g, r = self.fill_color.blue(), self.fill_color.green( ), self.fill_color.red() x, y = np.where(self.labelimg == 1) temp = self.panel_pic.copy() for i in range(x.shape[0]): cv.circle(temp, (int(y[i] * self.scale), int(x[i] * self.scale)), 1, (r, g, b), 1) return temp def mouse_release(self, ev: QMouseEvent): if self.state == STATE.NORMAL: return if ev.button() == Qt.LeftButton: if self.state == STATE.DRAW_RECTANGLE: pos = ev.pos() valid, relative_pos = self.pos_in_img_area(pos) self.logger.debug(valid) if valid: self.logger.debug(relative_pos) self.end_point = (int(relative_pos[0] / self.scale), int(relative_pos[1] / self.scale)) elif ev.button() == Qt.RightButton: if self.state == STATE.NORMAL: return if self.state == STATE.DRAW_RECTANGLE: if self.start_point is None or self.end_point is None: return pos = ev.pos() valid, relative_pos = self.pos_in_img_area(pos) if valid: click_pos = (int(relative_pos[0] / self.scale), int(relative_pos[1] / self.scale)) min_x, max_x = min(self.start_point[0], self.end_point[0]), max( self.start_point[0], self.end_point[0]) min_y, max_y = min(self.start_point[1], self.end_point[1]), max( self.start_point[1], self.end_point[1]) self.logger.debug(click_pos) if min_x <= click_pos[0] <= max_x and min_y <= click_pos[ 1] <= max_y: pixel_value = self.origin_img[click_pos[1], click_pos[0], 0] low, high = pixel_value + \ self.pixel_range[0], pixel_value + \ self.pixel_range[1] selected_area = self.origin_img[min_y:max_y + 1, min_x:max_x + 1, 0] x, y = np.where( (selected_area >= low) * (selected_area <= high)) x = x + min_y y = y + min_x self.labelimg[x, y] = 1 pic = self.draw_points() self.show_pic(file_name=None, content=pic) self.histroty.append((x, y)) self.modified = True elif self.state == STATE.DRAW_POLYGON: if self.polygon_points is None or self.polygon_points[ 'finish'] is False: return pos = ev.pos() valid, relative_pos = self.pos_in_img_area(pos) if valid: click_pos = (int(relative_pos[0] / self.scale), int(relative_pos[1] / self.scale)) self.logger.debug(click_pos) x_pos = np.array( [item[0] for item in self.polygon_points['points']]) y_pos = np.array( [item[1] for item in self.polygon_points['points']]) min_x, max_x = x_pos.min(), x_pos.max() min_y, max_y = y_pos.min(), y_pos.max() p = np.array(self.polygon_points['points']).reshape( [1, len(self.polygon_points['points']), 2]) p[0, :, 0] -= min_x p[0, :, 1] -= min_y mask = np.zeros([max_y - min_y + 1, max_x - min_x + 1], np.uint8) mask = cv.fillPoly(mask, p, 1) pixel_value = self.origin_img[click_pos[1], click_pos[0], 0] low, high = pixel_value + \ self.pixel_range[0], pixel_value + self.pixel_range[1] selected_img = self.origin_img[min_y:max_y + 1, min_x:max_x + 1, 0] x, y = np.where( (selected_img >= low) * (selected_img <= high) * mask) x = x + min_y y = y + min_x self.labelimg[x, y] = 1 pic = self.draw_points() self.show_pic(file_name=None, content=pic) self.histroty.append((x, y)) self.modified = True elif self.state == STATE.DRAW_ELLIPSE: if self.ellipse_points is None or len( self.ellipse_points['points'] ) < 3 or self.ellipse_points['info'] is None: return pos = ev.pos() valid, relative_pos = self.pos_in_img_area(pos) if valid: click_pos = (int(relative_pos[0] / self.scale), int(relative_pos[1] / self.scale)) self.logger.debug(click_pos) center, r, theta = self.ellipse_points['info'] theta = int(np.around(theta)) p = cv.ellipse2Poly(center, r, theta, 0, 360, 1) num_p = p.shape[0] p = p.reshape((1, num_p, 2)) min_x, max_x = np.min(p[0, :, 0]), np.max(p[0, :, 0]) min_y, max_y = np.min(p[0, :, 1]), np.max(p[0, :, 1]) p[0, :, 0] -= min_x p[0, :, 1] -= min_y mask = np.zeros((max_y - min_y + 1, max_x - min_x + 1), np.uint8) mask = cv.fillPoly(mask, p, 1) pixel_value = self.origin_img[click_pos[1], click_pos[0], 0] low, high = pixel_value + \ self.pixel_range[0], pixel_value + self.pixel_range[1] selected_img = self.origin_img[min_y:max_y + 1, min_x:max_x + 1, 0] x, y = np.where( (selected_img >= low) * (selected_img <= high) * mask) x = x + min_y y = y + min_x self.labelimg[x, y] = 1 pic = self.draw_points() self.show_pic(file_name=None, content=pic) self.histroty.append((x, y)) self.modified = True self.saved = False def zoom_in_pic(self, checked): self.zoom_pic(True) def zoom_out_pic(self, checked): self.zoom_pic(False) def zoom_pic(self, zoom_in): if self.cur_img is None: return if zoom_in: if self.scale >= 5: pass elif self.scale < 1: self.scale += 0.1 else: self.scale += 1 else: if self.scale == 0: pass elif self.scale <= 1: self.scale -= 0.1 else: self.scale -= 1 self.logger.debug(self.scale) height, width, _ = self.origin_img.shape self.cur_img = cv.resize( self.origin_img, (int(height * self.scale), int(width * self.scale)), cv.INTER_LINEAR) if self.state == STATE.DRAW_RECTANGLE: if self.start_point is not None and self.end_point is not None: self.draw_rect(self.start_point, self.end_point) if self.labelimg is not None: pic = self.draw_points() self.show_pic(content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) elif self.state == STATE.DRAW_POLYGON: if self.polygon_points is not None and self.polygon_points[ 'finish'] is True: self.draw_lines(self.polygon_points['points'], True) if self.labelimg is not None: pic = self.draw_points() self.show_pic(content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) elif self.state == STATE.DRAW_ELLIPSE: if self.ellipse_points is not None and len( self.ellipse_points['points']) == 3: self.draw_ellipse(self.ellipse_points['points']) if self.labelimg is not None: pic = self.draw_points() self.show_pic(content=pic) else: self.show_pic(file_name=None, content=self.panel_pic) elif self.labelimg is not None: self.panel_pic = self.cur_img.copy() pic = self.draw_points() self.show_pic(file_name=None, content=pic) else: self.show_pic(file_name=None) def file_list_item_changed(self, item): if self.modified and not self.saved: res = QMessageBox.warning( self, 'warning', 'Changes not saved, do you want to save?', QMessageBox.Ok | QMessageBox.No, QMessageBox.Ok) if res == QMessageBox.Ok: self.save() base_pic_name = item.text() for index, filename in enumerate(self.opened_files): if filename.name == base_pic_name: selected_filename = filename break self.selected_filename = selected_filename self.refresh_vars() self.open(self.selected_filename) def add_tool_bar(self): self.toolbar = QToolBar() self.addToolBar(Qt.TopToolBarArea, self.toolbar) self.toolbar.addAction(self.ui.actionOpen_File) self.toolbar.addAction(self.ui.actionOpen_Dir) self.toolbar.addAction(self.ui.actionSave) self.toolbar.addAction(self.ui.actionZoom_In) self.toolbar.addAction(self.ui.actionZoom_Out) self.toolbar.addAction(self.ui.actionfill_color) self.toolbar.addAction(self.ui.actionSet_Pixel_Range) self.toolbar.addAction(self.ui.actionRectangle) self.toolbar.addAction(self.ui.actionEllipse) self.toolbar.addAction(self.ui.actionPolygon) self.toolbar.addAction(self.ui.actionUndo) def show_pic(self, file_name=None, content=None): if file_name is not None: file_name = str(file_name.absolute()) img = cv.imdecode(np.fromfile(file_name, dtype=np.uint8), -1) assert img is not None if len(img.shape) == 2: img = cv.cvtColor(img, cv.COLOR_GRAY2BGR) height, width, channel = img.shape self.cur_img = cv.cvtColor(img, cv.COLOR_BGR2RGB) self.origin_img = self.cur_img self.labelimg = np.zeros(self.origin_img.shape[:2], dtype=np.int) qimg = QImage(self.cur_img.data, width, height, width * channel, QImage.Format_RGB888) self.ui.img_area.setAlignment(Qt.AlignCenter | Qt.AlignHCenter) self.ui.img_area.setPixmap(QPixmap.fromImage(qimg)) self.ui.img_area.adjustSize() elif content is not None: height, width, channel = content.shape qimg = QImage(content.data, width, height, width * channel, QImage.Format_RGB888) self.ui.img_area.setAlignment(Qt.AlignCenter | Qt.AlignHCenter) self.ui.img_area.setPixmap(QPixmap.fromImage(qimg)) self.ui.img_area.adjustSize() else: height, width, channel = self.cur_img.shape qimg = QImage(self.cur_img.data, width, height, width * channel, QImage.Format_RGB888) self.ui.img_area.setAlignment(Qt.AlignCenter | Qt.AlignHCenter) self.ui.img_area.setPixmap(QPixmap.fromImage(qimg)) self.ui.img_area.adjustSize() def open_file(self, checked): if self.modified and not self.saved: res = QMessageBox.warning( self, 'warning', 'Changes not saved, do you want to save?', QMessageBox.Ok | QMessageBox.No, QMessageBox.Ok) if res == QMessageBox.Ok: self.save() filename, filetype = QFileDialog.getOpenFileName( self, '选取文件', '.', 'PNG Files(*.png);;JPG Files(*.jpg)') if filename == '': return filename = Path(filename) self.open_mode = OPEN_MODE.OPEN_FILE self.opened_files = [filename] self.selected_filename = filename self.open(self.selected_filename) self.refresh_list() def open_dir(self, checked): if self.modified and not self.saved: res = QMessageBox.warning( self, 'warning', 'Changes not saved, do you want to save?', QMessageBox.Ok | QMessageBox.No, QMessageBox.Ok) if res == QMessageBox.Ok: self.save() opened_dir = QFileDialog.getExistingDirectory(self, '打开文件夹', '.') if opened_dir == '': return opened_dir = Path(opened_dir) self.open_mode = OPEN_MODE.OPEN_DIR self.opened_files = list(opened_dir.iterdir()) self.opened_files = [ item for item in self.opened_files if util.is_pic(item) ] if len(self.opened_files) == 0: QMessageBox.warning(self, 'warning', 'No image found') else: self.selected_filename = self.opened_files[0] self.open(self.selected_filename) self.refresh_list() def refresh_list(self): self.ui.file_list.clear() for file_name in self.opened_files: base_name = file_name.name self.ui.file_list.addItem(base_name) def open(self, filename: Path): self.setWindowTitle(filename.name) self.show_pic(file_name=filename) temp = str(filename).split('.') npy_filename = '.'.join(temp[:-1]) + '.npy' if os.path.exists(npy_filename): self.labelimg = np.load(npy_filename) self.panel_pic = self.cur_img.copy() pic = self.draw_points() self.show_pic(file_name=None, content=pic)