def test(self): sandbox_width, sandbox_height = self.get_alloc() # Test if there is no need to rebuild. if hasattr(self, "pixbuf"): samewidth = sameheight = False if self.pixbuf.get_width() == sandbox_width: samewidth = True if self.pixbuf.get_height() == sandbox_height: sameheight = True if samewidth and sameheight: return # Remove old sandbox image and pixbuf. if hasattr(self, "image"): self.canvas.layout.remove(self.image) del (self.image) del (self.pixbuf) # Set sandbox pixbuf and image widget. self.pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, sandbox_width, sandbox_height) self.image = gtk.image_new_from_pixbuf(self.pixbuf) self.clear() # Set background. pattern_path = os.path.join(main.imgpath, "pattern.png") pattern = Layer("pattern", pattern_path) self.background = Layer("background", None, sandbox_width + pattern.width, sandbox_height + pattern.height) # Determine how many times the pattern needs to be drawn. xtimes = float(1) * self.background.width / pattern.width ytimes = float(1) * self.background.height / pattern.height xtimes = int(math.ceil(xtimes)) ytimes = int(math.ceil(ytimes)) # Draw the pattern into the background. for x in range(xtimes): for y in range(ytimes): pattern.composite( 1, self.background, x * pattern.width, y * pattern.height, x * pattern.width, y * pattern.height, pattern.width, pattern.height, ) # Set pattern dimensions, useful to set the correct background offset. self.background.pattern_width = pattern.width self.background.pattern_height = pattern.height # Add sandbox image to table and display it. self.canvas.layout.put(self.image, 0, 0) self.canvas.layout_reverse() self.image.show()
class Brush(PluginTool): def __init__(self): # Subclass it. PluginTool.__init__(self) # Plugin attributes. self.name = 'brush' self.author = 'nathive-dev' self.icon = 'tool-brush.png' # Setting up the plugin. self.brush = None self.layer = None self.default() main.config.push_to_plugin(self) #pregenerated softness mask self.softnessMask = [] # Init apply limits (left, top, right, bottom). self.apply = [0, 0, 0, 0] # Able simple overriding for subclassed plugins like eraser. self.composite_mode = 1 self.color_updated_todo = main.color.updated_todo self.count = 0 def enable(self): self.color_updated_todo.append(self.new_color) self.new() def disable(self): self.color_updated_todo.remove(self.new_color) hud = main.documents.active.canvas.hud hud.remove_cursor() def default(self): self.shape = 1 self.size = 100 self.opacity = 100 self.soft = 100 self.spacing = 0 def update(self): self.gui_shape.set_value(self.shape) self.gui_size.set_value(self.size) self.gui_opacity.set_value(self.opacity) self.gui_soft.set_value(self.soft) self.gui_spacing.set_value(self.spacing) self.new() @property def color(self): """Redirect self.color to global color value to able simple overriding in subclassed plugins like eraser.""" return main.color.rgb def set_then_new(self, variable, value): setattr(self, variable, value) self.new() def new(self): del(self.brush) self.brush = Layer('brush', None, self.size, self.size) brush.new( self.brush.pointer, True, self.shape, self.size, self.opacity, self.soft, self.color[0], self.color[1], self.color[2]) #regenerate pre computed softness mask self.generateSoftnessMask() if not main.documents.active: return hud = main.documents.active.canvas.hud fixed_size = self.size - ((self.size * (self.soft/2) / 100) / 2) if self.shape == 0: hud.set_cursor('square', fixed_size) elif self.shape == 1: hud.set_cursor('circle', fixed_size) def new_color(self): """Change brush color with no opacity re-calculation for better performance at color change.""" brush.new( self.brush.pointer, False, 0, self.size, 0, 0, self.color[0], self.color[1], self.color[2]) def button_primary(self, x, y, ux, uy): main.documents.active.actions.begin('layer-content') self.layer = main.documents.active.layers.active self.motion_primary(x, y, ux, uy) def motion_primary(self, x, y, ux, uy): # Update apply limits. if not self.apply[0] or x < self.apply[0]: self.apply[0] = x if not self.apply[1] or y < self.apply[1]: self.apply[1] = y if not self.apply[2] or x > self.apply[2]: self.apply[2] = x if not self.apply[3] or y > self.apply[3]: self.apply[3] = y # Set apply coordinates. x = x - (self.size / 2) y = y - (self.size / 2) #add color to color dictionary CD = main.gui.colorDictionary setItem = getattr(CD,'addAndShowNewColor') setItem(main.color.hex) #update pixel data self.updatePixData(x, y) # Brush. self.brush.composite( self.composite_mode, self.layer, x - self.layer.xpos, y - self.layer.ypos, x - self.layer.xpos, y - self.layer.ypos, self.brush.width, self.brush.height) # Redraw expired area. main.documents.active.canvas.redraw( x, y, self.size, self.size, True, True) def release_primary(self): # Calc apply area rectangle. area = [ self.apply[0] - (self.brush.width/2), self.apply[1] - (self.brush.height/2), self.apply[2] + (self.brush.width) - self.apply[0], self.apply[3] + (self.brush.height) - self.apply[1]] # Apply layer offset. layer = main.documents.active.layers.active area[0] -= layer.xpos area[1] -= layer.ypos # End action and reset area vars. main.documents.active.actions.end(area) self.apply = [0, 0, 0, 0] def button_secondary(self, x, y, ux, uy): self.resizing_root = [x, y] def motion_secondary(self, x, y, ux, uy): root_x, root_y = self.resizing_root rel_x = root_x - x rel_y = root_y - y self.size += rel_y - rel_x self.resizing_root = [x, y] if self.size < 1: self.size = 1 if self.size > 100: self.size = 100 if gtk.events_pending(): return hud = main.documents.active.canvas.hud hud.create_cursor() hud.move_cursor(x, y) hud.dump_cursor() self.gui_size.set_value(self.size, True) def gui(self): self.box = gtk.VBox(False, 0) self.gui_shape = MultiWidgetToggle( self.box, _('Shape'), ['square', 'circle'], self.shape, lambda x: self.set_then_new('shape', x)) gutils.separator(self.box) self.gui_size = MultiWidgetSpin( self.box, _('Size'), True, 1, 100, self.size, self.updateSize) self.gui_soft = MultiWidgetSpin( self.box, _('Smoothing'), True, 0, 100, self.soft, self.updateSoftness) self.gui_opacity = MultiWidgetSpin( self.box, _('Opacity'), True, 1, 100, self.opacity, self.updateOpacity) gutils.separator(self.box) self.gui_spacing = MultiWidgetCombo( self.box, _('Spacing'), ['not ported'], self.spacing, lambda x: None) return self.box def updatePixData(self, mouseX, mouseY): #foreground xOffset xOff = mouseX #foreground yOffset yOff = mouseY #foreground xEnd (must be <= layer width) xEnd = xOff + self.size xEnd = xEnd if(xEnd < self.layer.width) else self.layer.width #foreground yEnd (must be <= layer height) yEnd = yOff + self.size yEnd = yEnd if(yEnd < self.layer.height) else self.layer.height #Actual Row Number rowNum = 0 #get Color Index colorIndex = main.gui.colorDictionary.palette.index(main.color.hex) #iterate through relevant pixels for row in range(yOff,yEnd): #Actual Column Number columnNum = 0 for column in range(xOff,xEnd): #retrieve pre-calculated opacity pixelSoftness = self.getPixelSoftness(rowNum,columnNum) #handle for this column thisColumn = self.layer.pixData[row][column] #handle for brush opacity brushOpacity = self.opacity #only add if some opacity exists if(pixelSoftness != 0): #if column not empty and last element is the same color as current if((len(thisColumn) != 0) and (thisColumn[-1][0] == colorIndex)): #handle for this colorEntry thisEntry = thisColumn[-1] #combine opacity combinedOpacity = core.getOverAlpha(float(pixelSoftness),float(thisEntry[1])) #if their combined opacities == 255 --> remove the rest of the array and add if(combinedOpacity == 255): thisColumn = [[colorIndex, 255, brushOpacityy/100.0]] #else update old opacity else: thisEntry[1] = combinedOpacity thisEntry[2] = (thisColumn[-1][2] + brushOpacity/100.0) if(thisEntry[2] > 1.0): thisEntry[2] = 1.0 #else just append else: thisColumn.append([colorIndex,pixelSoftness, brushOpacity/100.0]) #increment column number columnNum = columnNum + 1 #increment row number rowNum = rowNum + 1 def updateSoftness(self,softness): self.soft = int(softness) self.new() def updateOpacity(self,opacity): self.opacity = int(opacity) self.new() def updateSize(self,size): self.size = int(size) self.pixBufFromPixData() self.new() # def generateSoftnessMask(self): # #clear old mask # self.softnessMask = [] # # for row in range(self.size): # # #Create Temporary Row To Hold Column Mask Values # tempRow = [] # for column in range(self.size): # # #radius # radius = self.size/2.0 # # #distance in columns (x) # distX = column - radius + 1.0 # # #distance in rows (y) # distY = row - radius + 1.0 # # #dist # dist = int(math.sqrt((distX ** 2) + (distY ** 2))) # # print "distance: " + str(dist) # print "radius: " + str(radius) # # #dist = sqrt( (dist_x*dist_x) + (dist_y*dist_y) ) # # # #Calculate Softness for the relative row and column # #then store it in a new array (column) and append the array to # #the row # tempSoftness = brush.getSoftness(radius , dist, self.opacity, self.soft) # # if(not(tempSoftness is None)): # tempSoftness = ord(tempSoftness) # else: # tempSoftness = 0 # ## #convert to float between 0 and 1 ## tempSoftness = (tempSoftness/255.0) # # #add column to row # tempRow.append([tempSoftness]) # # # # #Append Temporary Row to softnessMask # self.softnessMask.append(tempRow) def generateSoftnessMask(self): #new layer tempLayer = Layer('tempLayer', None, self.size, self.size) brush.new( tempLayer.pointer, True, self.shape, self.size, self.opacity, self.soft, self.color[0], self.color[1], self.color[2]) #get pixBuf array tempPixelArray = tempLayer.pixbuf.get_pixels_array() #init softnessArray self.softnessMask = [] for row in tempPixelArray: tempRow = [] for column in row: tempRow.append([column[3]]) self.softnessMask.append(tempRow) #returns the precomputed softness for a pixels position def getPixelSoftness(self,row,column): return self.softnessMask[row][column][0] def pixBufFromPixData(self): pass # #get pixbuf array # pixBufArray = self.layer.pixbuf.get_pixels_array() # # for i in range(self.brush.height): # for j in range(self.brush.width): # for k in range(3): # pixBufArray[i][j][k] = 0 # pixBufArray[i][j][3] = self.getPixelSoftness(i,j) # print self.getPixelSoftness(i,j) * 255 # # # Redraw expired area. # self.layer.pixbuf = gtk.gdk.pixbuf_new_from_array(pixBufArray, gtk.gdk.COLORSPACE_RGB, 8) # # main.documents.active.canvas.redraw_all() # main.documents.active.actions.end([0,0,699,699]) # # def compositeRBG(self,lowerValue,upperValue,upperValueOpacity): #algorithm for compositing two 8bit color channel values compositedChannel = (upperValueOpacity * upperValue) + ((1 - upperValueOpacity) * lowerValue) return compositedChannel