|
|
@@ -0,0 +1,198 @@
|
|
|
+from krita import DockWidget
|
|
|
+from PyQt5.QtWidgets import QWidget, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton
|
|
|
+
|
|
|
+DOCKER_TITLE = 'Bleed Generator'
|
|
|
+
|
|
|
+class BleedGenerator(DockWidget):
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super().__init__()
|
|
|
+ self.setWindowTitle(DOCKER_TITLE)
|
|
|
+ widget = QWidget()
|
|
|
+ layout = QVBoxLayout()
|
|
|
+ widget.setLayout(layout)
|
|
|
+
|
|
|
+ layout.addSpacing(10)
|
|
|
+ layout.addWidget(QLabel('Adds bleed to the active paint layer.'))
|
|
|
+
|
|
|
+ # Count and unit inputs. Default to 24px (2mm at 300dpi).
|
|
|
+ self.countInput = QLineEdit('24')
|
|
|
+ self.unitInput = QComboBox()
|
|
|
+ self.unitInput.addItems( ['px', 'mm', 'inch'] )
|
|
|
+ row1 = QHBoxLayout()
|
|
|
+ row1.addWidget(QLabel('Bleed amount:'))
|
|
|
+ row1.addWidget(self.countInput)
|
|
|
+ row1.addWidget(self.unitInput)
|
|
|
+ layout.addLayout(row1)
|
|
|
+
|
|
|
+ # Option to bleed to boundaries.
|
|
|
+ self.bleedToBoundsCheck = QCheckBox('Bleed to nearest guides/boundaries')
|
|
|
+ self.bleedToBoundsCheck.setChecked(False)
|
|
|
+ layout.addWidget(self.bleedToBoundsCheck)
|
|
|
+
|
|
|
+ # Offset input i.e. how far from edge to start copying lines.
|
|
|
+ self.offsetInput = QLineEdit('0')
|
|
|
+ row2 = QHBoxLayout()
|
|
|
+ row2.addWidget(QLabel('Offset (0 = use edge):'))
|
|
|
+ row2.addWidget(self.offsetInput)
|
|
|
+ row2.addWidget(QLabel('px'))
|
|
|
+ layout.addLayout(row2)
|
|
|
+
|
|
|
+ # Side selection
|
|
|
+ self.topCheck = QCheckBox('Top')
|
|
|
+ self.topCheck.setChecked(True)
|
|
|
+ self.botCheck = QCheckBox('Bottom')
|
|
|
+ self.botCheck.setChecked(True)
|
|
|
+ self.leftCheck = QCheckBox('Left')
|
|
|
+ self.leftCheck.setChecked(True)
|
|
|
+ self.rightCheck = QCheckBox('Right')
|
|
|
+ self.rightCheck.setChecked(True)
|
|
|
+ layout.addWidget(QLabel('Sides to bleed:'))
|
|
|
+ row3 = QHBoxLayout()
|
|
|
+ row3.addWidget(self.topCheck)
|
|
|
+ row3.addWidget(self.botCheck)
|
|
|
+ row3.addWidget(self.leftCheck)
|
|
|
+ row3.addWidget(self.rightCheck)
|
|
|
+ layout.addLayout(row3)
|
|
|
+
|
|
|
+ # Button!
|
|
|
+ layout.addSpacing(20)
|
|
|
+ goButton = QPushButton("Add Bleed")
|
|
|
+ goButton.setIcon( Krita.instance().icon('animation_play') )
|
|
|
+ layout.addWidget(goButton)
|
|
|
+
|
|
|
+ # Add a stretch to prevent the rest of the content from stretching.
|
|
|
+ layout.addStretch()
|
|
|
+
|
|
|
+ # Add widget to the docker.
|
|
|
+ self.setWidget(widget)
|
|
|
+
|
|
|
+ # Hook up the action to the button.
|
|
|
+ goButton.clicked.connect( self.generateBleed )
|
|
|
+
|
|
|
+
|
|
|
+ # notifies when views are added or removed
|
|
|
+ # 'pass' means do not do anything
|
|
|
+ def canvasChanged(self, canvas):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+ ##########
|
|
|
+ # Slots
|
|
|
+ ##########
|
|
|
+
|
|
|
+ # Actually generates the bleed.
|
|
|
+ def generateBleed(self, e):
|
|
|
+
|
|
|
+ # Get the current layer
|
|
|
+ self.doc = Krita.instance().activeDocument()
|
|
|
+ self.layer = self.doc.activeNode()
|
|
|
+ if self.layer.type() != 'paintlayer':
|
|
|
+ dialog = QDialog()
|
|
|
+ dialog.setWindowTitle("Paint Layer Required")
|
|
|
+ layout = QVBoxLayout()
|
|
|
+ layout.addWidget(QLabel('Bleed generator only works on paint layers. Please select one.'))
|
|
|
+ dialog.setLayout(layout)
|
|
|
+ dialog.exec_()
|
|
|
+ return
|
|
|
+ self.offset = round(float(self.offsetInput.text()))
|
|
|
+
|
|
|
+ # Calculate how many lines of pixels to copy for each side
|
|
|
+ amounts = self.calculateBleedAmounts()
|
|
|
+
|
|
|
+ # Copy lines for selected sides.
|
|
|
+ # Bounds are fetched each time as layer dimensions change.
|
|
|
+ if self.topCheck.checkState():
|
|
|
+ bds = self.layer.bounds()
|
|
|
+ xpos = bds.left()
|
|
|
+ ypos = bds.top() - self.offset
|
|
|
+ len = bds.width()
|
|
|
+ bleedline = self.layer.pixelData(xpos, ypos, len, 1)
|
|
|
+ for c in range(1, amounts['top']+1):
|
|
|
+ self.layer.setPixelData(bleedline, xpos, (ypos - c), len, 1)
|
|
|
+ if self.botCheck.checkState():
|
|
|
+ bds = self.layer.bounds()
|
|
|
+ xpos = bds.left()
|
|
|
+ ypos = bds.bottom() + self.offset
|
|
|
+ len = bds.width()
|
|
|
+ bleedline = self.layer.pixelData(xpos, ypos, len, 1)
|
|
|
+ for c in range(1, amounts['bottom']+1):
|
|
|
+ self.layer.setPixelData(bleedline, xpos, (ypos + c), len, 1)
|
|
|
+ if self.leftCheck.checkState():
|
|
|
+ bds = self.layer.bounds()
|
|
|
+ xpos = bds.left() - self.offset
|
|
|
+ ypos = bds.top()
|
|
|
+ len = bds.height()
|
|
|
+ bleedline = self.layer.pixelData(xpos, ypos, 1, len)
|
|
|
+ for c in range(1, amounts['left']+1):
|
|
|
+ self.layer.setPixelData(bleedline, (xpos - c), ypos, 1, len)
|
|
|
+ if self.rightCheck.checkState():
|
|
|
+ bds = self.layer.bounds()
|
|
|
+ xpos = bds.right() + self.offset
|
|
|
+ ypos = bds.top()
|
|
|
+ len = bds.height()
|
|
|
+ bleedline = self.layer.pixelData(xpos, ypos, 1, len)
|
|
|
+ for c in range(1, amounts['right']+1):
|
|
|
+ self.layer.setPixelData(bleedline, (xpos + c), ypos, 1, len)
|
|
|
+
|
|
|
+ # Refresh the view, or the cloned pixels will not be immediately shown.
|
|
|
+ self.doc.refreshProjection()
|
|
|
+
|
|
|
+
|
|
|
+ def calculateBleedAmounts(self):
|
|
|
+ amounts = {}
|
|
|
+ if self.bleedToBoundsCheck.checkState():
|
|
|
+ # Bleed amounts determined from boundaries.
|
|
|
+
|
|
|
+ # Get all boundaries.
|
|
|
+ vPoints = self.doc.horizontalGuides()
|
|
|
+ hPoints = self.doc.verticalGuides()
|
|
|
+ vPoints.insert(0, 0)
|
|
|
+ vPoints.append(self.doc.height())
|
|
|
+ vPoints.sort()
|
|
|
+ hPoints.insert(0, 0)
|
|
|
+ hPoints.append(self.doc.width())
|
|
|
+ hPoints.sort()
|
|
|
+ bds = self.layer.bounds()
|
|
|
+
|
|
|
+ # Calculate bleed from layer's edge to next relevant boundary.
|
|
|
+ prevV = 0
|
|
|
+ top = bds.top()
|
|
|
+ bottom = bds.bottom() + 1
|
|
|
+ for v in vPoints:
|
|
|
+ v = round(v)
|
|
|
+ if 'top' not in amounts and v >= top:
|
|
|
+ amounts['top'] = top - prevV
|
|
|
+ if 'bottom' not in amounts and v > bottom:
|
|
|
+ amounts['bottom'] = v - bottom
|
|
|
+ break
|
|
|
+ prevV = v
|
|
|
+
|
|
|
+ prevH = 0
|
|
|
+ left = bds.left()
|
|
|
+ right = bds.right() + 1
|
|
|
+ for h in hPoints:
|
|
|
+ h = round(h)
|
|
|
+ if 'left' not in amounts and h >= left:
|
|
|
+ amounts['left'] = left - prevH
|
|
|
+ if 'right' not in amounts and h > right:
|
|
|
+ amounts['right'] = h - right
|
|
|
+ prevH = h
|
|
|
+
|
|
|
+ else:
|
|
|
+ # Bleed amounts based on the user's input.
|
|
|
+ unit = self.unitInput.currentIndex()
|
|
|
+ count = float(self.countInput.text())
|
|
|
+ ppi = self.doc.resolution()
|
|
|
+ if unit == 1: # mm
|
|
|
+ count = count * (ppi / 24.5)
|
|
|
+ if unit == 2: # inch
|
|
|
+ count = count * ppi
|
|
|
+ count = round(count) - self.offset
|
|
|
+ amounts['top'] = count
|
|
|
+ amounts['bottom'] = count
|
|
|
+ amounts['left'] = count
|
|
|
+ amounts['right'] = count
|
|
|
+
|
|
|
+ return amounts
|
|
|
+
|