bleed_generator_docker.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. from krita import DockWidget
  2. from PyQt5.QtWidgets import QWidget, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QComboBox, QCheckBox, QPushButton
  3. DOCKER_TITLE = 'Bleed Generator'
  4. class BleedGenerator(DockWidget):
  5. def __init__(self):
  6. super().__init__()
  7. self.setWindowTitle(DOCKER_TITLE)
  8. widget = QWidget()
  9. layout = QVBoxLayout()
  10. widget.setLayout(layout)
  11. layout.addSpacing(10)
  12. layout.addWidget(QLabel('Adds bleed to the active paint layer.'))
  13. # Count and unit inputs. Default to 24px (2mm at 300dpi).
  14. self.countInput = QLineEdit('24')
  15. self.unitInput = QComboBox()
  16. self.unitInput.addItems( ['px', 'mm', 'inch'] )
  17. row1 = QHBoxLayout()
  18. row1.addWidget(QLabel('Bleed amount:'))
  19. row1.addWidget(self.countInput)
  20. row1.addWidget(self.unitInput)
  21. layout.addLayout(row1)
  22. # Option to bleed to boundaries.
  23. self.bleedToBoundsCheck = QCheckBox('Bleed to nearest guides/boundaries')
  24. self.bleedToBoundsCheck.setChecked(False)
  25. layout.addWidget(self.bleedToBoundsCheck)
  26. # Offset input i.e. how far from edge to start copying lines.
  27. self.offsetInput = QLineEdit('0')
  28. row2 = QHBoxLayout()
  29. row2.addWidget(QLabel('Offset (0 = use edge):'))
  30. row2.addWidget(self.offsetInput)
  31. row2.addWidget(QLabel('px'))
  32. layout.addLayout(row2)
  33. # Side selection
  34. self.topCheck = QCheckBox('Top')
  35. self.topCheck.setChecked(True)
  36. self.botCheck = QCheckBox('Bottom')
  37. self.botCheck.setChecked(True)
  38. self.leftCheck = QCheckBox('Left')
  39. self.leftCheck.setChecked(True)
  40. self.rightCheck = QCheckBox('Right')
  41. self.rightCheck.setChecked(True)
  42. layout.addWidget(QLabel('Sides to bleed:'))
  43. row3 = QHBoxLayout()
  44. row3.addWidget(self.topCheck)
  45. row3.addWidget(self.botCheck)
  46. row3.addWidget(self.leftCheck)
  47. row3.addWidget(self.rightCheck)
  48. layout.addLayout(row3)
  49. # Button!
  50. layout.addSpacing(20)
  51. goButton = QPushButton("Add Bleed")
  52. goButton.setIcon( Krita.instance().icon('animation_play') )
  53. layout.addWidget(goButton)
  54. # Add a stretch to prevent the rest of the content from stretching.
  55. layout.addStretch()
  56. # Add widget to the docker.
  57. self.setWidget(widget)
  58. # Hook up the action to the button.
  59. goButton.clicked.connect( self.generateBleedLoop )
  60. # notifies when views are added or removed
  61. # 'pass' means do not do anything
  62. def canvasChanged(self, canvas):
  63. pass
  64. ##########
  65. # Slots
  66. ##########
  67. def generateBleedLoop(self, e):
  68. self.doc = Krita.instance().activeDocument()
  69. # Get the selected layer(s).
  70. w = Krita.instance().activeWindow()
  71. v = w.activeView()
  72. layers = v.selectedNodes()
  73. didNotRun = True
  74. for layer in layers:
  75. if layer.type() == 'paintlayer':
  76. self.generateBleed(layer)
  77. didNotRun = False
  78. if didNotRun:
  79. dialog = QDialog()
  80. dialog.setWindowTitle("Paint Layer Required")
  81. layout = QVBoxLayout()
  82. layout.addWidget(QLabel('Bleed Generator only works on paint layers. Please select one.'))
  83. dialog.setLayout(layout)
  84. dialog.exec_()
  85. else:
  86. # Refresh the view, or changes will not be immediately reflected.
  87. self.doc.refreshProjection()
  88. # Actually generates the bleed.
  89. def generateBleed(self, layer):
  90. self.offset = round(float(self.offsetInput.text()))
  91. # Calculate how many lines of pixels to copy for each side
  92. amounts = self.calculateBleedAmounts(layer)
  93. # Copy lines for selected sides.
  94. # Bounds are fetched each time as layer dimensions change.
  95. if self.topCheck.checkState():
  96. bds = layer.bounds()
  97. xpos = bds.left()
  98. ypos = bds.top() - self.offset
  99. len = bds.width()
  100. bleedline = layer.pixelData(xpos, ypos, len, 1)
  101. for c in range(1, amounts['top']+1):
  102. layer.setPixelData(bleedline, xpos, (ypos - c), len, 1)
  103. if self.botCheck.checkState():
  104. bds = layer.bounds()
  105. xpos = bds.left()
  106. ypos = bds.bottom() + self.offset
  107. len = bds.width()
  108. bleedline = layer.pixelData(xpos, ypos, len, 1)
  109. for c in range(1, amounts['bottom']+1):
  110. layer.setPixelData(bleedline, xpos, (ypos + c), len, 1)
  111. if self.leftCheck.checkState():
  112. bds = layer.bounds()
  113. xpos = bds.left() - self.offset
  114. ypos = bds.top()
  115. len = bds.height()
  116. bleedline = layer.pixelData(xpos, ypos, 1, len)
  117. for c in range(1, amounts['left']+1):
  118. layer.setPixelData(bleedline, (xpos - c), ypos, 1, len)
  119. if self.rightCheck.checkState():
  120. bds = layer.bounds()
  121. xpos = bds.right() + self.offset
  122. ypos = bds.top()
  123. len = bds.height()
  124. bleedline = layer.pixelData(xpos, ypos, 1, len)
  125. for c in range(1, amounts['right']+1):
  126. layer.setPixelData(bleedline, (xpos + c), ypos, 1, len)
  127. # Refresh the view, or the cloned pixels will not be immediately shown.
  128. self.doc.refreshProjection()
  129. def calculateBleedAmounts(self, layer):
  130. amounts = {}
  131. if self.bleedToBoundsCheck.checkState():
  132. # Bleed amounts determined from boundaries.
  133. # Get all boundaries.
  134. vPoints = self.doc.horizontalGuides()
  135. hPoints = self.doc.verticalGuides()
  136. vPoints.insert(0, 0)
  137. vPoints.append(self.doc.height())
  138. vPoints.sort()
  139. hPoints.insert(0, 0)
  140. hPoints.append(self.doc.width())
  141. hPoints.sort()
  142. bds = layer.bounds()
  143. # Calculate bleed from layer's edge to next relevant boundary.
  144. prevV = 0
  145. top = bds.top()
  146. bottom = bds.bottom() + 1
  147. for v in vPoints:
  148. v = round(v)
  149. if 'top' not in amounts and v >= top:
  150. amounts['top'] = top - prevV
  151. if 'bottom' not in amounts and v > bottom:
  152. amounts['bottom'] = v - bottom
  153. break
  154. prevV = v
  155. prevH = 0
  156. left = bds.left()
  157. right = bds.right() + 1
  158. for h in hPoints:
  159. h = round(h)
  160. if 'left' not in amounts and h >= left:
  161. amounts['left'] = left - prevH
  162. if 'right' not in amounts and h > right:
  163. amounts['right'] = h - right
  164. prevH = h
  165. else:
  166. # Bleed amounts based on the user's input.
  167. unit = self.unitInput.currentIndex()
  168. count = float(self.countInput.text())
  169. ppi = self.doc.resolution()
  170. if unit == 1: # mm
  171. count = count * (ppi / 24.5)
  172. if unit == 2: # inch
  173. count = count * ppi
  174. count = round(count) - self.offset
  175. amounts['top'] = count
  176. amounts['bottom'] = count
  177. amounts['left'] = count
  178. amounts['right'] = count
  179. return amounts