#Boa:FramePanel:ImageEditorPanel import os, math, tempfile, string from cStringIO import StringIO import wx from wx.lib.anchors import LayoutAnchors from wx.tools import img2py import Utils, Plugins # draw destination consts ddCanvas = 1 ddGrid = 2 [wxID_IMAGEEDITORPANEL, wxID_IMAGEEDITORPANELBGCOLBTN, wxID_IMAGEEDITORPANELBRUSHCOLBTN, wxID_IMAGEEDITORPANELEDITWINDOW, wxID_IMAGEEDITORPANELFGCOLBTN, wxID_IMAGEEDITORPANELMODECHOICE, wxID_IMAGEEDITORPANELPENBRUSHWINDOW, wxID_IMAGEEDITORPANELSLIDER1, wxID_IMAGEEDITORPANELSPINBUTTON1, wxID_IMAGEEDITORPANELSPINBUTTON2, wxID_IMAGEEDITORPANELSPINBUTTON3, wxID_IMAGEEDITORPANELSTATICTEXT1, ] = [wx.NewId() for _init_ctrls in range(12)] class ImageEditorPanel(wx.Panel): def _init_utils(self): # generated method, don't edit self.cursorCross = wx.StockCursor(id=wx.CURSOR_CROSS) self.cursorMove = wx.StockCursor(id=wx.CURSOR_SIZING) self.cursorDraw = wx.StockCursor(id=wx.CURSOR_PENCIL) def _init_ctrls(self, prnt): # generated method, don't edit wx.Panel.__init__(self, id=wxID_IMAGEEDITORPANEL, name='ImageEditorPanel', parent=prnt, pos=wx.Point(466, 318), size=wx.Size(586, 356), style=wx.SUNKEN_BORDER | wx.TAB_TRAVERSAL) self._init_utils() self.SetAutoLayout(True) self.SetClientSize(wx.Size(578, 329)) self.modeChoice = wx.Choice(choices=self.drawingModes, id=wxID_IMAGEEDITORPANELMODECHOICE, name='modeChoice', parent=self, pos=wx.Point(8, 7), size=wx.Size(64, 21), style=0) self.modeChoice.SetToolTipString('Current drawing mode') self.modeChoice.Bind(wx.EVT_CHOICE, self.OnModeChoiceChoice, id=wxID_IMAGEEDITORPANELMODECHOICE) self.FGColBtn = wx.Button(id=wxID_IMAGEEDITORPANELFGCOLBTN, label='', name='FGColBtn', parent=self, pos=wx.Point(78, 5), size=wx.Size(24, 24), style=0) self.FGColBtn.SetBackgroundColour(wx.Colour(0, 0, 0)) self.FGColBtn.SetToolTipString('Pen colour') self.FGColBtn.Bind(wx.EVT_BUTTON, self.OnFgcolbtnButton, id=wxID_IMAGEEDITORPANELFGCOLBTN) self.spinButton3 = wx.SpinButton(id=wxID_IMAGEEDITORPANELSPINBUTTON3, name='spinButton3', parent=self, pos=wx.Point(106, 5), size=wx.Size(16, 24), style=wx.SP_VERTICAL) self.spinButton3.SetRange(0, 32) self.spinButton3.SetToolTipString('Pen width') self.spinButton3.Bind(wx.EVT_COMMAND_SCROLL, self.OnSpinbutton3CommandScroll, id=wxID_IMAGEEDITORPANELSPINBUTTON3) self.spinButton2 = wx.SpinButton(id=wxID_IMAGEEDITORPANELSPINBUTTON2, name='spinButton2', parent=self, pos=wx.Point(122, 5), size=wx.Size(16, 24), style=wx.SP_VERTICAL) self.spinButton2.SetRange(0, 11) self.spinButton2.SetToolTipString('Pen style') self.spinButton2.Bind(wx.EVT_COMMAND_SCROLL, self.OnSpinbutton2CommandScroll, id=wxID_IMAGEEDITORPANELSPINBUTTON2) self.penBrushWindow = wx.Window(id=wxID_IMAGEEDITORPANELPENBRUSHWINDOW, name='penBrushWindow', parent=self, pos=wx.Point(137, 5), size=wx.Size(32, 24), style=wx.SUNKEN_BORDER) self.penBrushWindow.SetToolTipString('Pen / Brush preview') self.penBrushWindow.Bind(wx.EVT_PAINT, self.OnPenBrushWindowPaint) self.spinButton1 = wx.SpinButton(id=wxID_IMAGEEDITORPANELSPINBUTTON1, name='spinButton1', parent=self, pos=wx.Point(168, 5), size=wx.Size(16, 24), style=wx.SP_VERTICAL) self.spinButton1.SetRange(0, 7) self.spinButton1.SetToolTipString('Brush style') self.spinButton1.Bind(wx.EVT_COMMAND_SCROLL, self.OnSpinbutton1CommandScroll, id=wxID_IMAGEEDITORPANELSPINBUTTON1) self.brushColBtn = wx.Button(id=wxID_IMAGEEDITORPANELBRUSHCOLBTN, label='', name='brushColBtn', parent=self, pos=wx.Point(189, 5), size=wx.Size(24, 24), style=0) self.brushColBtn.SetBackgroundColour(wx.Colour(255, 255, 255)) self.brushColBtn.SetToolTipString('Brush colour') self.brushColBtn.Bind(wx.EVT_BUTTON, self.OnBrushcolbtnButton, id=wxID_IMAGEEDITORPANELBRUSHCOLBTN) self.BGColBtn = wx.Button(id=wxID_IMAGEEDITORPANELBGCOLBTN, label='', name='BGColBtn', parent=self, pos=wx.Point(213, 5), size=wx.Size(24, 24), style=0) self.BGColBtn.SetBackgroundColour(wx.Colour(192, 192, 192)) self.BGColBtn.SetToolTipString('Background brush colour') self.BGColBtn.Bind(wx.EVT_BUTTON, self.OnBgcolbtnButton, id=wxID_IMAGEEDITORPANELBGCOLBTN) self.slider1 = wx.Slider(id=wxID_IMAGEEDITORPANELSLIDER1, maxValue=25, minValue=1, name='slider1', parent=self, point=wx.Point(244, 5), size=wx.Size(108, 24), style=wx.SL_HORIZONTAL, value=16) self.slider1.SetToolTipString('Zoom factor') self.slider1.Bind(wx.EVT_SCROLL, self.OnSlider1ScrollThumbtrack) self.editWindow = wx.ScrolledWindow(id=wxID_IMAGEEDITORPANELEDITWINDOW, name='editWindow', parent=self, pos=wx.Point(8, 34), size=wx.Size(561, 288), style=wx.SUNKEN_BORDER) self.editWindow.SetBackgroundColour(wx.Colour(255, 255, 255)) self.editWindow.SetConstraints(LayoutAnchors(self.editWindow, True, True, True, True)) self.editWindow.Bind(wx.EVT_PAINT, self.OnEditWindowPaint) self.editWindow.Bind(wx.EVT_LEFT_DOWN, self.OnEditWindowLeftDown) self.editWindow.Bind(wx.EVT_LEFT_UP, self.OnEditWindowLeftUp) self.editWindow.Bind(wx.EVT_MOTION, self.OnEditWindowMotion) self.editWindow.Bind(wx.EVT_SCROLLWIN, self.OnEditWindowScroll) self.staticText1 = wx.StaticText(id=wxID_IMAGEEDITORPANELSTATICTEXT1, label='Image info', name='staticText1', parent=self, pos=wx.Point(360, 2), size=wx.Size(208, 27), style=wx.ST_NO_AUTORESIZE) def __init__(self, parent): self.drawingModes = ['Select', 'Draw', 'Line', 'Circle', 'Box', 'Fill', 'Colour'] self._init_ctrls(parent) self.currentCursor = None self.drawingMethMap = {'Select': (ddCanvas|ddGrid, self.drawSelection), 'Draw' : (ddCanvas, self.drawPoint), 'Line' : (ddCanvas|ddGrid, self.drawLine), 'Circle': (ddCanvas|ddGrid, self.drawCircle), 'Box' : (ddCanvas|ddGrid, self.drawBox), 'Fill' : (ddCanvas, self.drawFill), 'Move' : (ddCanvas|ddGrid, self.drawMove), 'Colour': (ddCanvas, self.drawPickColour), } self.mode = '' self.setMode('Draw') self.mDC = self.bmp = None self.fgcol = wx.BLACK self.fgpen = wx.Pen(self.fgcol, 1, wx.SOLID) self.bgcol = wx.LIGHT_GREY self.bgbsh = wx.Brush(self.bgcol) self.brush = wx.Brush(wx.WHITE, wx.TRANSPARENT) self.invpen = wx.Pen(wx.BLUE, 0, wx.TRANSPARENT) self.selpen = wx.Pen(wx.WHITE, 2, wx.SOLID) self.x = self.y = 0 self.modeChoice.SetSelection(1) self.offset = 0, 0 self.prevSelRect = () self.prevLineSeg = () self.prevPointCol = () brushStyles = [wx.TRANSPARENT, wx.SOLID, wx.BDIAGONAL_HATCH, wx.CROSSDIAG_HATCH, wx.FDIAGONAL_HATCH, wx.CROSS_HATCH, wx.HORIZONTAL_HATCH, wx.VERTICAL_HATCH] penStyles = [wx.SOLID, wx.TRANSPARENT, wx.DOT, wx.LONG_DASH, wx.SHORT_DASH, wx.DOT_DASH, wx.BDIAGONAL_HATCH, wx.CROSSDIAG_HATCH, wx.FDIAGONAL_HATCH, wx.CROSS_HATCH, wx.HORIZONTAL_HATCH, wx.VERTICAL_HATCH] extTypeMap = {'.bmp': wx.BITMAP_TYPE_BMP, '.gif': wx.BITMAP_TYPE_GIF, '.jpg': wx.BITMAP_TYPE_JPEG, '.png': wx.BITMAP_TYPE_PNG, '.ico': wx.BITMAP_TYPE_ICO, } #---Public methods-------------------------------------------------------------- def initImageData(self, ext, data): """ Initialise editor with data """ if data: self.mDC = wx.MemoryDC() self.bmp = wx.BitmapFromImage(wx.ImageFromStream(StringIO(data))) self.mDC.SelectObject(self.bmp) else: self.mDC, self.bmp = self.getTempMemDC(16, 16) brush = wx.Brush(self.bgcol) self.mDC.SetBackground(brush) self.mDC.Clear() # Default back to png when opening data from source if ext == '.py': ext = '.png' self.imgExt = ext self.editWindow.Refresh() self.mDCundo, self.bmpundo = self.getTempMemDC(self.bmp.GetWidth(), self.bmp.GetHeight()) self.selundo = None self.snapshot() self.updateScrollbars() self.updateImageInfo() def getImageData(self, ext=''): """ Returns the current bitmap data """ if not ext: ext = self.imgExt fn = tempfile.mktemp() #if ext == '?': ext = '.png' tpe = self.extTypeMap[ext.lower()] self.bmp.SaveFile(fn, tpe) try: return open(fn, 'rb').read() finally: os.remove(fn) def imageModified(self): """ Called whenever image is modified, override to catch """ pass #---Utils----------------------------------------------------------------------- def setMode(self, mode, updateGUI=False): if self.mode != mode: if mode in ('Draw', 'Line', 'Circle', 'Box', 'Fill'): self.currentCursor = self.cursorDraw else: self.currentCursor = self.cursorCross self.editWindow.SetCursor(self.currentCursor) self.clearState() self.mode = mode self.drawDest, self.drawMeth = self.drawingMethMap[self.mode] if updateGUI: self.modeChoice.SetStringSelection(mode) def clearState(self): self.dragoffset = self.dragpos = self.dragbmp = self.dragsrcrect = None self.sel = self.line = self.circle = self.box = None self.editWindow.SetCursor(self.currentCursor) def snapshot(self): self.mDC.SetUserScale(1.0, 1.0) self.mDCundo.Blit(0, 0, self.bmp.GetWidth(), self.bmp.GetHeight(), self.mDC, 0, 0) if self.sel: self.selundo = self.sel[:] else: self.selundo = None def getImgPos(self, event): x, y = event.GetPositionTuple() x, y = self.editWindow.CalcUnscrolledPosition(x, y) scale = self.slider1.GetValue() return ((x-self.offset[0]) / scale, (y-self.offset[1]) / scale) def getTempMemDC(self, width, height): bmp = wx.EmptyBitmap(width, height) memDC = wx.MemoryDC() memDC.SelectObject(bmp) return memDC, bmp def getSelBmp(self): if self.sel: x1, y1, x2, y2 = self.sel mDC, bmp = self.getTempMemDC(x2-x1+1, y2-y1+1) self.mDC.SetUserScale(1.0, 1.0) mDC.Blit(0, 0, x2-x1+1, y2-y1+1, self.mDC, x1, y1) mDC.SelectObject(wx.NullBitmap) return bmp return None def setMemDCBmp(self, bmp): if not bmp or not bmp.Ok(): raise 'Invalid bitmap' self.mDC.SelectObject(wx.NullBitmap) self.bmp = bmp self.mDC.SelectObject(self.bmp) def updateScrollbars(self): scale = self.slider1.GetValue() xPos, yPos = self.editWindow.GetViewStart() self.editWindow.SetScrollbars(scale, scale, self.bmp.GetWidth(), self.bmp.GetHeight(), xPos, yPos) def updateImageInfo(self): if self.imgExt: ext = self.imgExt[1:].upper() else: ext = 'UNKNOWN' w, h, d = self.bmp.GetWidth(), self.bmp.GetHeight(), self.bmp.GetDepth() msk = self.bmp.GetMask() if msk: m = 'Image is masked' else: m = 'Image is not masked' if self.bmp.Ok(): x = '' else: x = 'The bitmap is not valid!' text = '%s: (%s, %s), depth: %s\n%s. %s'%(ext, w, h, d, m, x) self.staticText1.SetLabel(text) #---Drawing methods------------------------------------------------------------- def undo(self): self.mDC.SetUserScale(1.0, 1.0) self.mDC.Blit(0, 0, self.bmp.GetWidth(), self.bmp.GetHeight(), self.mDCundo, 0, 0) def drawSelection(self, event, state, dc=None): if not dc: if state == 'start': self.sel = self.getImgPos(event) * 2 elif state in ('drag', 'end'): self.sel = self.sel[:2] + self.getImgPos(event) if state == 'end': x1, y1, x2, y2 = self.sel self.sel = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2) if self.sel != self.prevSelRect: if state == 'end': self.prevSelRect = () else: self.prevSelRect = self.sel self.editWindow.Refresh() if dc and self.sel: x1, y1, x2, y2 = self.sel x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2) xoffset, yoffset = self.offset scale = self.slider1.GetValue() dc.SetLogicalFunction(wx.XOR) dc.SetPen(self.selpen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangle(xoffset + x1 * scale - 1, yoffset + y1 * scale - 1, (x2 - x1 + 1) * scale + 4, (y2 - y1 + 1) * scale + 4) def drawPoint(self, event, state): if state == 'start': self.snapshot() imgx, imgy = self.getImgPos(event) self.mDC.SetPen(self.fgpen) self.mDC.SetUserScale(1.0, 1.0) self.mDC.DrawPoint(imgx, imgy) self.editWindow.Refresh() if imgx >=0 and imgx < self.bmp.GetWidth() and \ imgy >=0 and imgy < self.bmp.GetHeight(): self.imageModified() def drawLine(self, event, state, dc=None): if not dc: if state == 'start': self.line = self.getImgPos(event) * 2 elif self.line: self.line = self.line[:2] + self.getImgPos(event) if state == 'end': self.snapshot() self.mDC.SetLogicalFunction(wx.COPY) self.mDC.SetPen(self.fgpen) self.mDC.SetUserScale(1.0, 1.0) self.mDC.DrawLine(*self.line) self.mDC.DrawPoint(self.line[2], self.line[3]) x1, y1, x2, y2 = self.line w, h = self.bmp.GetWidth(), self.bmp.GetHeight() if not ((x1 < 0 and x2 < 0) or (x1 >= w and x2 >= w) or \ (y1 < 0 and y2 < 0) or (y1 >= h and y2 >= h)): self.imageModified() self.line = None if self.prevLineSeg != self.line: if state == 'end': self.prevLineSeg = () else: self.prevLineSeg = self.line self.editWindow.Refresh() if dc and self.line: x1, y1, x2, y2 = self.line xoffset, yoffset = self.offset scale = self.slider1.GetValue() dc.SetLogicalFunction(wx.XOR) dc.SetPen(self.selpen) dc.DrawLine(xoffset + x1 * scale + scale/2, yoffset + y1 * scale + scale/2, xoffset + x2 * scale + scale/2, yoffset + y2 * scale + scale/2) def drawBox(self, event, state, dc=None): if not dc: if state == 'start': self.box = self.getImgPos(event) * 2 else: self.box = self.box[:2] + self.getImgPos(event) if state == 'end': self.snapshot() self.mDC.SetLogicalFunction(wx.COPY) self.mDC.SetPen(self.fgpen) self.mDC.SetUserScale(1.0, 1.0) self.mDC.SetBrush(self.brush) x1, y1, x2, y2 = self.box self.mDC.DrawRectangle(x1, y1, x2-x1+1, y2-y1+1) self.editWindow.Refresh() x1, y1, x2, y2 = self.box w, h = self.bmp.GetWidth(), self.bmp.GetHeight() if not ((x1 < 0 and x2 < 0) or (x1 >= w and x2 >= w) or \ (y1 < 0 and y2 < 0) or (y1 >= h and y2 >= h)): self.imageModified() self.box = None self.editWindow.Refresh() if dc and self.box: x1, y1, x2, y2 = self.box xoffset, yoffset = self.offset scale = self.slider1.GetValue() dc.SetLogicalFunction(wx.XOR) dc.SetPen(self.selpen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangle(xoffset + x1 * scale - 1, yoffset + y1 * scale - 1, (x2 - x1 + 1) * scale + 4, (y2 - y1 + 1) * scale + 4) def drawCircle(self, event, state, dc=None): if not dc: if state == 'start': self.circle = self.getImgPos(event) + (0,) elif state in ('drag', 'end') and self.circle: x1, y1, x2, y2 = self.circle[:2] + self.getImgPos(event) rad = math.sqrt(pow(x2-x1, 2) + pow(y2-y1, 2)) + 0.5 self.circle = (x1, y1, rad) if state == 'end': self.snapshot() self.mDC.SetLogicalFunction(wx.COPY) self.mDC.SetPen(self.fgpen) self.mDC.SetUserScale(1.0, 1.0) self.mDC.SetBrush(self.brush) self.mDC.DrawEllipse(int(x1-rad+0.5), int(y1-rad+0.5), int(rad*2), int(rad*2)) x, y, rad = self.circle x1, y1 = x-rad+0.5, y-rad+0.5 x2, y2 = x1 +rad*2, y1 +rad*2 w, h = self.bmp.GetWidth(), self.bmp.GetHeight() if not ((x1 < 0 and x2 < 0) or (x1 >= w and x2 >= w) or \ (y1 < 0 and y2 < 0) or (y1 >= h and y2 >= h)): self.imageModified() self.circle = None self.editWindow.Refresh() if dc and self.circle: x, y, rad = self.circle xoffset, yoffset = self.offset scale = self.slider1.GetValue() dc.SetLogicalFunction(wx.XOR) dc.SetPen(self.selpen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawEllipse(int(xoffset + (x-rad+0.5) * scale), int(yoffset + (y-rad+0.5) * scale), int(rad*scale*2), int(rad*scale*2)) def drawFill(self, event, state): self.snapshot() x, y = self.getImgPos(event) brush = wx.Brush(self.fgcol) self.mDC.SetBrush(brush) self.mDC.SetUserScale(1.0, 1.0) self.mDC.FloodFill(x, y, self.mDC.GetPixel(x, y)) self.editWindow.Refresh() if x >=0 and x < self.bmp.GetWidth() and \ y >=0 and y < self.bmp.GetHeight(): self.imageModified() def drawMove(self, event, state, dc=None): x1, y1, x2, y2 = self.sel if not dc: x, y = self.getImgPos(event) if state == 'start': self.dragoffset = (x1 - x, y1 - y) if not self.dragbmp: self.snapshot() self.dragbmp = self.getSelBmp() self.dragsrcrect = x1, y1, x2-x1+1, y2-y1+1 elif state in ('drag', 'end'): # better to only do this once self.undo() dox, doy = self.dragoffset dx, dy = self.dragpos = dox + x, doy + y self.mDC.SetUserScale(1.0, 1.0) self.mDC.SetLogicalFunction(wx.COPY) self.mDC.SetPen(self.invpen) self.mDC.SetBrush(self.bgbsh) self.mDC.DrawRectangle(*self.dragsrcrect) self.mDC.DrawBitmap(self.dragbmp, dx, dy) if state == 'end': self.sel = dx, dy, dx + x2-x1, dy + y2-y1 self.editWindow.Refresh() self.imageModified() if dc and self.sel and self.dragpos: xoffset, yoffset = self.offset dx, dy = self.dragpos scale = self.slider1.GetValue() dc.SetLogicalFunction(wx.XOR) dc.SetPen(self.selpen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangle(xoffset + dx * scale - 1, yoffset + dy * scale - 1, (x2 - x1 + 1) * scale + 4, (y2 - y1 + 1) * scale + 4) def drawPickColour(self, event, state): if state == 'end': x, y = self.getImgPos(event) newcol = self.mDC.GetPixel(x, y) self.fgcol = newcol self.fgpen.SetColour(newcol) self.FGColBtn.SetBackgroundColour(self.fgcol) self.setMode('Draw', updateGUI=True) def drawGrid(self, dc): pen = wx.Pen(wx.WHITE) dc.SetPen(pen) dc.SetLogicalFunction(wx.XOR) scale = self.slider1.GetValue() lines = [] height = self.bmp.GetHeight() width = self.bmp.GetWidth() xoffset, yoffset = self.offset for y in range(height): lines.append( (xoffset, y * scale + yoffset, width * scale + xoffset, y * scale + yoffset) ) for x in range(width): lines.append( (x * scale + xoffset, yoffset, x * scale + xoffset, height * scale + yoffset) ) dc.DrawLineList(lines) #---View control events--------------------------------------------------------- def OnEditWindowPaint(self, event): dc = wx.PaintDC(self.editWindow) self.editWindow.PrepareDC(dc) if not self.mDC or not self.bmp: return dc.BeginDrawing() try: scale = self.slider1.GetValue() self.mDC.SetUserScale(1.0/scale, 1.0/scale) width = self.bmp.GetWidth()*scale height = self.bmp.GetHeight()*scale xoffset = max((self.editWindow.GetSize().x - width) / 2, 0) yoffset = max((self.editWindow.GetSize().y - height) / 2, 0) dc.Blit(xoffset, yoffset, width, height, self.mDC, 0, 0) self.mDC.SetUserScale(1.0, 1.0) self.offset = xoffset, yoffset if scale > 5: self.drawGrid(dc) framesize = 3 else: framesize = 1 dc.SetLogicalFunction(wx.COPY) pen = wx.Pen(wx.BLACK, framesize, wx.SOLID) dc.SetPen(pen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangle(xoffset-1 , yoffset-1, width+2, height+2) if (self.drawDest & ddGrid) and self.drawMeth: self.drawMeth(None, '', dc) finally: dc.EndDrawing() def OnPenBrushWindowPaint(self, event): dc = wx.PaintDC(self.penBrushWindow) dc.SetBrush(self.brush) dc.SetBackground(self.bgbsh) dc.SetPen(self.invpen) dc.BeginDrawing() try: cs = self.penBrushWindow.GetClientSize() dc.DrawRectangle(0, 0, cs.x, cs.y) dc.SetPen(self.fgpen) dc.DrawRectangle(cs.x/3-3, cs.y/3-3, cs.x/3+8, cs.y/3+6) finally: dc.EndDrawing() def OnEditWindowLeftDown(self, event): self.editWindow.CaptureMouse() if self.mode == 'Select' and self.sel: x1, y1, x2, y2 = self.sel x, y = self.getImgPos(event) if x >= x1 and x <= x2 and y >= y1 and y <= y2: self.setMode('Move') self.sel = x1, y1, x2, y2 elif self.mode == 'Move' and self.sel: x1, y1, x2, y2 = self.sel x, y = self.getImgPos(event) if not (x >= x1 and x <= x2 and y >= y1 and y <= y2): self.setMode('Select') self.sel = x1, y1, x2, y2 if (self.drawDest & ddCanvas) and self.drawMeth: self.drawMeth(event, 'start') def OnEditWindowLeftUp(self, event): if self.editWindow.HasCapture(): self.editWindow.ReleaseMouse() if (self.drawDest & ddCanvas) and self.drawMeth: self.drawMeth(event, 'end') def OnEditWindowMotion(self, event): if event.Dragging() and event.LeftIsDown(): if (self.drawDest & ddCanvas) and self.drawMeth: self.drawMeth(event, 'drag') if self.sel and not event.Dragging() and not event.LeftIsDown(): x1, y1, x2, y2 = self.sel x, y = self.getImgPos(event) if x >= x1 and x <= x2 and y >= y1 and y <= y2: self.editWindow.SetCursor(self.cursorMove) else: self.editWindow.SetCursor(self.currentCursor) def OnSlider1ScrollThumbtrack(self, event): self.updateScrollbars() self.editWindow.Refresh() def showColDlg(self, col): data = wxStdColourData(col) dlg = wx.ColourDialog(self, data) try: if dlg.ShowModal() == wx.ID_OK: return dlg.GetColourData().GetColour() return None finally: dlg.Destroy() def OnFgcolbtnButton(self, event): newcol = self.showColDlg(self.fgcol) if newcol: self.fgcol = newcol self.fgpen.SetColour(newcol) self.FGColBtn.SetBackgroundColour(self.fgcol) self.penBrushWindow.Refresh() def OnBgcolbtnButton(self, event): newcol = self.showColDlg(self.bgcol) if newcol: self.bgcol = newcol self.bgbsh.SetColour(newcol) self.BGColBtn.SetBackgroundColour(self.bgcol) self.penBrushWindow.Refresh() def OnBrushcolbtnButton(self, event): newcol = self.showColDlg(self.brush.GetColour()) if newcol: self.brush.SetColour(newcol) self.brushColBtn.SetBackgroundColour(newcol) self.penBrushWindow.Refresh() def OnModeChoiceChoice(self, event): self.setMode(self.modeChoice.GetStringSelection()) self.editWindow.Refresh() def OnEditWindowScroll(self, event): event.Skip() self.editWindow.Refresh() def OnSpinbutton1CommandScroll(self, event): self.brush.SetStyle(self.brushStyles[self.spinButton1.GetValue()]) self.penBrushWindow.Refresh() def OnSpinbutton2CommandScroll(self, event): self.fgpen.SetStyle(self.penStyles[self.spinButton2.GetValue()]) self.penBrushWindow.Refresh() def OnSpinbutton3CommandScroll(self, event): self.fgpen.SetWidth(self.spinButton3.GetValue()) self.penBrushWindow.Refresh() #---View action events---------------------------------------------------------- def OnCopy(self, event): clip = wx.TheClipboard clip.Open() try: bmp = self.getSelBmp() clip.SetData(wx.BitmapDataObject(bmp)) finally: clip.Close() def OnPaste(self, event): clip = wx.TheClipboard clip.Open() try: data = wx.BitmapDataObject() try: clip.GetData(data) except: wx.LogError('Not a picture') else: self.modeChoice.SetSelection(0) self.setMode('Move') self.snapshot() self.dragbmp = data.GetBitmap() self.dragsrcrect = -1, -1, 0, 0 self.dragpos = 0, 0 self.sel = 0, 0, self.dragbmp.GetWidth()-1, self.dragbmp.GetHeight()-1 self.mDC.DrawBitmap(self.dragbmp, 0, 0) self.editWindow.Refresh() self.imageModified() finally: clip.Close() def OnUndo(self, event): self.undo() self.editWindow.Refresh() self.updateImageInfo() def OnClear(self, event): self.snapshot() if self.sel: x1, y1, x2, y2 = self.sel self.mDC.SetClippingRegion(x1, y1, x2-x1, y2-y1) self.mDC.Clear() self.mDC.DestroyClippingRegion() else: self.mDC.Clear() self.editWindow.Refresh() self.imageModified() def OnSetTransparentMask(self, event): self.mDC.SelectObject(wx.NullBitmap) self.bmp.SetMask(wx.Mask(self.bmp, self.fgcol)) self.mDC.SelectObject(self.bmp) self.updateImageInfo() self.imageModified() def OnClearTransparentMask(self, event): self.mDC.SelectObject(wx.NullBitmap) self.bmp.SetMask(None) self.mDC.SelectObject(self.bmp) self.updateImageInfo() self.imageModified() def OnResize(self, event): dlg = wx.TextEntryDialog(self, 'Enter a tuple for the new size', 'Resize', '%s, %s'%(self.bmp.GetWidth(), self.bmp.GetHeight())) try: if dlg.ShowModal() != wx.ID_OK: return width, height = eval(dlg.GetValue()) finally: dlg.Destroy() # Create new bitmap of required size and copy current one to it mDC, bmp = self.getTempMemDC(width, height) mDC.Blit(0, 0, self.bmp.GetWidth(), self.bmp.GetHeight(), self.mDC, 0, 0) mDC.SelectObject(wx.NullBitmap) self.setMemDCBmp(bmp) self.editWindow.Refresh() self.updateImageInfo() self.imageModified() def OnScale(self, event): dlg = wx.TextEntryDialog(self, 'Enter a tuple for the new size', 'Scale', '%s, %s'%(self.bmp.GetWidth(), self.bmp.GetHeight())) try: if dlg.ShowModal() != wx.ID_OK: return width1, height1 = self.bmp.GetWidth(), self.bmp.GetHeight() width2, height2 = eval(dlg.GetValue()) finally: dlg.Destroy() # draw a new version of the current bmp scaled to the user requested size xScale, yScale = float(width2)/width1, float(height2)/height1 self.mDC.SetUserScale(1/xScale, 1/yScale) width, height = self.bmp.GetWidth()*xScale, self.bmp.GetHeight()*yScale mDC, bmp = self.getTempMemDC(width, height) mDC.Blit(0, 0, width, height, self.mDC, 0, 0) mDC.SelectObject(wx.NullBitmap) self.mDC.SetUserScale(1.0, 1.0) self.setMemDCBmp(bmp) self.editWindow.Refresh() self.updateImageInfo() self.imageModified() def OnCrop(self, event): self.setMemDCBmp(self.getSelBmp()) self.clearState() self.editWindow.Refresh() self.updateImageInfo() self.imageModified() class wxStdColourData(wx.ColourData): stdcolours = [(0, 0, 0), (128, 128, 128), (128, 0, 0), (128, 128, 0), (0, 128, 0), (0, 128, 128), (0, 0, 128), (128, 0, 128), (255, 255, 255), (192, 192, 192), (255, 0, 0), (255, 255, 0), (0, 255, 0), (0, 255, 255), (0, 0, 255), (255, 0, 255)] def __init__(self, col=None): wx.ColourData.__init__(self) if col: self.SetColour(col) i = 0 for r, g, b in self.stdcolours: self.SetCustomColour(i, wx.Colour(r, g, b)) i = i + 1 #-Tester------------------------------------------------------------------------ if __name__ == '__main__': app = wx.PySimpleApp() wx.InitAllImageHandlers() frame = wx.Frame(None, -1, 'Image Edit Test') panel = ImageEditorPanel(frame) frame.Show(True) panel.initImageData('.png', open('Images/Modules/Pyrex.png', 'rb').read()) app.MainLoop() import sys sys.exit() #------------------------------------------------------------------------------- import Preferences from Models import EditorModels, Controllers, EditorHelper from Views import EditorViews class ImageView(wx.Panel, EditorViews.EditorView): viewName = 'View' def __init__(self, parent, model): wx.Panel.__init__(self, parent, -1, style= wx.SUNKEN_BORDER) self.staticBitmapSmall = wx.StaticBitmap(self, -1, wx.NullBitmap) self.staticBitmapBig = wx.StaticBitmap(self, -1, wx.NullBitmap) EditorViews.EditorView.__init__(self, model, (), -1) self.active = True imgsep = 32 def refreshCtrl(self): if self.model.data: sio = StringIO(self.model.data) bmp = wx.BitmapFromImage(wx.ImageFromStream(sio)) self.staticBitmapSmall.SetBitmap(bmp) self.staticBitmapSmall.SetDimensions(self.imgsep, self.imgsep, bmp.GetWidth(), bmp.GetHeight()) self.staticBitmapBig.SetBitmap(bmp) self.staticBitmapBig.SetDimensions(bmp.GetWidth()+self.imgsep*2, self.imgsep, bmp.GetWidth()*2, bmp.GetHeight()*2) class ImageEditorView(ImageEditorPanel, EditorViews.EditorView): viewName = 'Edit' refreshBmp = 'Images/Editor/Refresh.png' copyBmp = 'Images/Shared/Copy.png' pasteBmp = 'Images/Shared/Paste.png' undoBmp = 'Images/Shared/Undo.png' def __init__(self, parent, model, actions=()): ImageEditorPanel.__init__(self, parent) EditorViews.EditorView.__init__(self, model, ( ('Refresh', self.OnRefresh, self.refreshBmp, ''), ('-', None, '-', ''), ('Copy', self.OnCopy, self.copyBmp, ''), ('Paste', self.OnPaste, self.pasteBmp, ''), ('Undo last change', self.OnUndo, self.undoBmp, ''), ('Undo view changes', self.OnUndoViewChanges, '-', ''), ('Clear', self.OnClear, '-', ''), ('-', None, '-', ''), ('Crop', self.OnCrop, '-', ''), ('Resize...', self.OnResize, '-', ''), ('Scale...', self.OnScale, '-', ''), ('-', None, '-', ''), ('Use current colour to set transparent mask', self.OnSetTransparentMask, '-', ''), ('Clear current transparent mask', self.OnClearTransparentMask, '-', ''), ('-', None, '-', ''), ) + actions, -1) self.editWindow.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.editWindow.Bind(wx.EVT_RIGHT_UP, self.OnRightClick) self.active = True self.subImage = None def refreshCtrl(self, subImage=None): if subImage is None and self.subImage is None: data = self.model.data else: if not self.subImage: self.subImage = subImage data = self.subImage['data'] ext = os.path.splitext(self.model.filename)[-1] self.initImageData(ext, data) self.editWindow.Refresh() self.modified = False self.updateViewState() def refreshModel(self): ext = os.path.splitext(self.model.filename)[-1] if ext == '.py': ext = '.png' data = self.getImageData(ext) if self.subImage: modelData = self.subImage['data'] else: modelData = self.model.data if modelData != data: if self.subImage: self.model.updateData(data, self.subImage) else: self.model.data = data self.model.modified = True if self.model.viewsModified.count(self.viewName): self.model.viewsModified.remove(self.viewName) self.modified = False EditorViews.EditorView.refreshModel(self) self.updateEditor() self.updateViewState() def imageModified(self): self.modified = True self.updateViewState() def OnRefresh(self, event): self.refreshModel() def OnUndoViewChanges(self, event): self.refreshCtrl() class BitmapEditorFileController(Controllers.PersistentController): Model = EditorModels.BitmapFileModel DefaultViews = [ImageView] AdditionalViews = [ImageEditorView] editBmpBmp = 'Images/EditBitmap.png' conv2ModBmp = 'Images/Modules/PyResBitmap.png' def actions(self, model): return Controllers.PersistentController.actions(self, model) + [ ('-', None, '-', ''), ('Edit image', self.OnGotoEditView, self.editBmpBmp, ''), ('Convert to module', self.OnConvertToModule, self.conv2ModBmp, ''),] def OnGotoEditView(self, event): model = self.getModel() if not model.views.has_key('Edit'): modPge = self.editor.getActiveModulePage() for View, wid in modPge.adtViews: if View == ImageEditorView: self.editor.mainMenu.Check(wid, True) break view = modPge.addView(ImageEditorView) view.refreshCtrl() else: view = model.views['Edit'] view.focus() def OnConvertToModule(self, event): model = self.getModel() imgPath = model.localFilename() ConvertImgToPy(imgPath, self.editor) validFuncChars = string.letters+string.digits+'_' funcCharMap = {'-': '_', '.': '_'} def fileNameToFunctionName(fn): res = [] if fn and fn[0] in string.letters+'_': res.append(fn[0]) for c in fn[1:]: if c not in validFuncChars: if c in funcCharMap: res.append(funcCharMap[c]) else: res.append(c) return ''.join(res) zopt = '-u ' def ConvertImgToPy(imgPath, editor): funcName = fileNameToFunctionName(os.path.basename(os.path.splitext(imgPath)[0])) pyResPath, ok = editor.saveAsDlg(funcName+'_img.py') if ok: if pyResPath.find('://') != -1: pyResPath = pyResPath.split('://', 1)[1] # snip script usage, leave only options docs = img2py.__doc__[img2py.__doc__.find('Options:')+11:] cmdLine = zopt+'-n %s'%(funcName) if os.path.exists(pyResPath): cmdLine = '-a ' + cmdLine dlg = wx.TextEntryDialog(editor, 'Options:\n\n%s\n\nEdit options string:'%docs, 'img2py', cmdLine) try: if dlg.ShowModal() != wx.ID_OK: return cmdLine = dlg.GetValue().strip() finally: dlg.Destroy() opts = cmdLine.split() opts.extend([imgPath, pyResPath]) tmp = sys.argv[0] sys.argv[0] = 'Boa Constructor' try: img2py.main(opts) finally: sys.argv[0] = tmp import sourceconst header = (sourceconst.defSig%{'modelIdent':'PyImgResource', 'main':''}).strip() if os.path.exists(pyResPath): src = open(pyResPath, 'r').readlines() if not (src and src[0].startswith(header)): src.insert(0, header+'\n') src.insert(1, '\n') open(pyResPath, 'w').writelines(src) m, c = editor.openOrGotoModule(pyResPath) c.OnReload(None) else: wx.LogWarning('Resource module not found. img2py failed to create the module') class CloseableImageEditorView(ImageEditorView, EditorViews.CloseableViewMix): def __init__(self, parent, model): EditorViews.CloseableViewMix.__init__(self, 'image editor') ImageEditorView.__init__(self, parent, model, self.closingActionItems) from Models import ResourceSupport class PyResourceImagesViewPlugin: editImgBmp = 'Images/EditBitmap.png' def __init__(self, model, view, actions): self.model = model self.view = view actions.extend( ( ('Edit image', self.OnEditImage, self.editImgBmp, ''), ) ) def OnEditImage(self, event): if self.view.selected != -1: name, (dataStartLn, bmpStartLine), zipped, icon = \ self.view.imageSrcInfo[self.view.selected] viewName = ResourceSupport.PyResourceImagesView.viewName if name: viewName += ':'+name if not self.model.views.has_key(viewName): modPge = self.model.editor.getActiveModulePage() view = modPge.addView(CloseableImageEditorView, viewName) view.tabName = view.viewName = viewName data = self.view.functions.imageFunctions['get%sData'%name]() subImage = {'data': data, 'name': name, 'start': dataStartLn+1, 'end': bmpStartLine-2, 'zip': zipped, 'icon': icon, 'cat': self.view.cataloged, 'eol': self.view.eol} view.refreshCtrl(subImage) else: view = self.model.views[viewName] view.focus() ResourceSupport.PyResourceImagesView.plugins += (PyResourceImagesViewPlugin,) #------------------------------------------------------------------------------- Plugins.registerFileType(BitmapEditorFileController) #------------------------------------------------------------------------------- #Boa:PyImgResource:EditBitmap def getEditBitmapData(): return \ '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ \x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ \x00\x00\x91IDATx\x9c\xa5\x92\xbb\x11\xc30\x0c\xc5\xc0\xa4Q\xa9Q=\x02\xbd\ \x81F\xcaHo\x03\xa6\x89e\xe9\xf2\xa3\xce\xecX\x00\x07\x9dh\xb5V\xae\xccm\x15\ \xd8\xb6-\xc6\xddV\n$E\x00\x06\xd4Zm\xa9\xe0\x80\x01|(I\x15H\x8a\x88\xc0\xcc\ \xf0\x97\xe0(\xb8\x97RR0\x18f\xe0\x8f\x13\xfe\xfb\x84\x11\xdewp\x9f\xe1\x9f\ \x82\x0c\xfcU\x90\x85?\nV\xe07\xc1*<\t\xfa\xbfZ\x1e\x86\xe1\x0e$M\'\x9a\x81{\ \xc1x\xdf\xee\x9e\x86{\x81\xa4pwZkip\x12\\\x99\'\xc3{a\x05\x01 \x1c\xda\x00\ \x00\x00\x00IEND\xaeB`\x82' Preferences.IS.registerImage('Images/EditBitmap.png', getEditBitmapData())