You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
582 lines
21 KiB
582 lines
21 KiB
# MIT License |
|
# |
|
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
|
# |
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|
# of this software and associated documentation files (the "Software"), to deal |
|
# in the Software without restriction, including without limitation the rights |
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
# copies of the Software, and to permit persons to whom the Software is |
|
# furnished to do so, subject to the following conditions: |
|
# |
|
# The above copyright notice and this permission notice shall be included in all |
|
# copies or substantial portions of the Software. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
# SOFTWARE. |
|
|
|
__all__ = ['CanvasLayer'] |
|
|
|
import TermTk as ttk |
|
|
|
from .const import ToolType |
|
|
|
# Canvas Layer structure |
|
# The data may include more areas than the visible one |
|
# This is helpful in case of resize to not lose the drawn areas |
|
# |
|
# |---| OffsetX |
|
# x |
|
# ╭────────────────╮ - - |
|
# │ │ | OffsetY | |
|
# y │ ┌───────┐ │ \ - | Data |
|
# │ │Visible│ │ | h | |
|
# │ └───────┘ │ / | |
|
# │ │ | |
|
# └────────────────┘ - |
|
# \---w--/ |
|
|
|
class CanvasLayer(): |
|
__slot__ = ('_pos','_name','_visible','_size','_data','_colors','_preview','_offset', |
|
'_snapVersion', '_snapshots', |
|
#signals |
|
'nameChanged','changed') |
|
def __init__(self,name:ttk.TTkString=ttk.TTkString('New')) -> None: |
|
self.changed = ttk.pyTTkSignal() |
|
self._name:ttk.TTkString = ttk.TTkString(name) if isinstance(name,str) else name |
|
self.nameChanged = ttk.pyTTkSignal(ttk.TTkString) |
|
self._snapVersion = 0 |
|
self._snapshots = {} |
|
self._pos = (0,0) |
|
self._size = (0,0) |
|
self._offset = (0,0) |
|
self._visible = True |
|
self._preview = None |
|
self._data: list[list[str ]] = [] |
|
self._colors:list[list[ttk.TTkColor]] = [] |
|
|
|
def clone(self) -> object: |
|
cl = CanvasLayer() |
|
cl._snapVersion = self._snapVersion |
|
cl._pos = self._pos |
|
cl._size = self._size |
|
cl._offset = self._offset |
|
cl._visible = self._visible |
|
cl._data = [row.copy() for row in self._data] |
|
cl._colors = [row.copy() for row in self._colors] |
|
return cl |
|
|
|
def restore(self, cl: object) -> None: |
|
self._preview = None |
|
self._snapVersion = cl._snapVersion |
|
self._pos = cl._pos |
|
self._size = cl._size |
|
self._offset = cl._offset |
|
self._visible = cl._visible |
|
self._data = [row.copy() for row in cl._data] |
|
self._colors = [row.copy() for row in cl._colors] |
|
self.changed.emit() |
|
|
|
def restoreSnapshot(self, id:int) -> None: |
|
if id == self._snapVersion: |
|
return |
|
ttk.TTkLog.debug(f"restore {id=}") |
|
if id in self._snapshots: |
|
self.restore(self._snapshots[id]) |
|
|
|
def saveSnapshot(self) -> int: |
|
self._snapshots = {key:self._snapshots[key] for key in self._snapshots if key <= self._snapVersion} |
|
if self._snapVersion not in self._snapshots: |
|
ttk.TTkLog.debug(f"{self._snapVersion=}") |
|
self._snapshots[self._snapVersion] = self.clone() |
|
return self._snapVersion |
|
|
|
def clearSnapshot(self) -> None: |
|
self._snapshots = {} |
|
self.saveSnapshot() |
|
|
|
def __eq__(self, value: object) -> bool: |
|
return ( |
|
issubclass(type(value),CanvasLayer) and |
|
self._pos == value._pos and |
|
self._size == value._size and |
|
self._offset == value._offset and |
|
self._visible == value._visible and |
|
all(a==b for a,b in zip(self._data, value._data)) and |
|
all(a==b for a,b in zip(self._colors,value._colors)) ) |
|
|
|
def update(self): |
|
self.changed.emit() |
|
|
|
def pos(self): |
|
return self._pos |
|
def size(self): |
|
return self._size |
|
|
|
def visible(self): |
|
return self._visible |
|
@ttk.pyTTkSlot(bool) |
|
def setVisible(self, visible): |
|
if visible == self._visible: return |
|
self._snapVersion += 1 |
|
self._visible = visible |
|
self.changed.emit() |
|
|
|
def name(self): |
|
return self._name |
|
@ttk.pyTTkSlot(str) |
|
def setName(self, name): |
|
self._snapVersion += 1 |
|
self._name = name |
|
|
|
def glyphColorAt(self, x, y): |
|
ox,oy = self._offset |
|
w,h = self._size |
|
data = self._data |
|
colors = self._colors |
|
return data[oy+y][ox+x], colors[oy+y][ox+x] |
|
|
|
def isOpaque(self,x,y): |
|
if not self._visible: return False |
|
ox,oy = self._offset |
|
w,h = self._size |
|
data = self._data |
|
colors = self._colors |
|
if 0<=x<w and 0<=y<h: |
|
return data[oy+y][ox+x] != ' ' or colors[oy+y][ox+x].background() |
|
return False |
|
|
|
def move(self,x,y): |
|
self._pos=(x,y) |
|
self._snapVersion += 1 |
|
self.changed.emit() |
|
|
|
def resize(self,w,h): |
|
self._size = (w,h) |
|
self._data = (self._data + [[] for _ in range(h)])[:h] |
|
self._colors = (self._colors + [[] for _ in range(h)])[:h] |
|
for i in range(h): |
|
self._data[i] = (self._data[i] + [' ' for _ in range(w)])[:w] |
|
self._colors[i] = (self._colors[i] + [ttk.TTkColor.RST for _ in range(w)])[:w] |
|
self._snapVersion += 1 |
|
self.changed.emit() |
|
|
|
def superResize(self,dx,dy,dw,dh): |
|
ox,oy = self._offset |
|
x,y = self.pos() |
|
w,h = self.size() |
|
daw = len(self._data[0]) |
|
dah = len(self._data) |
|
diffx = dx-x |
|
diffy = dy-y |
|
self._preview = None |
|
if ox < x-dx: # we need to resize and move ox |
|
_nw = x-dx-ox |
|
ox = x-dx |
|
self._data = [([' ']*_nw ) + _r for _r in self._data] |
|
self._colors = [([ttk.TTkColor.RST]*_nw) + _r for _r in self._colors] |
|
if dw+ox > daw: |
|
_nw = dw+ox-daw |
|
self._data = [_r + ([' ']*_nw ) for _r in self._data] |
|
self._colors = [_r + ([ttk.TTkColor.RST]*_nw) for _r in self._colors] |
|
daw = len(self._data[0]) |
|
if oy < y-dy: # we need to resize and move ox |
|
_nh = y-dy-oy |
|
oy = y-dy |
|
self._data = [[' ']*daw for _ in range(_nh)] + self._data |
|
self._colors = [[ttk.TTkColor.RST]*daw for _ in range(_nh)] + self._colors |
|
if dh+oy > dah: |
|
_nh = dh+oy-dah |
|
self._data = self._data + [[' ']*daw for _ in range(_nh)] |
|
self._colors = self._colors + [[ttk.TTkColor.RST]*daw for _ in range(_nh)] |
|
self._offset = (ox+diffx,oy+diffy) |
|
self._pos = (dx,dy) |
|
self._size = (dw,dh) |
|
self._snapVersion += 1 |
|
self.changed.emit() |
|
|
|
def clean(self): |
|
w,h = self._size |
|
self._offset = (0,0) |
|
self._preview = None |
|
for i in range(h): |
|
self._data[i] = [' ']*w |
|
self._colors[i] = [ttk.TTkColor.RST]*w |
|
self._snapVersion += 1 |
|
self.changed.emit() |
|
|
|
def cleanPreview(self): |
|
self._preview = None |
|
self.changed.emit() |
|
|
|
def toTTkString(self): |
|
ret = [] |
|
pw,ph = self._size |
|
ox,oy = self._offset |
|
if not (pw and ph) : return ttk.TTkString() |
|
for d,c in zip(self._data[oy:oy+ph],self._colors[oy:oy+ph]): |
|
ret.append(ttk.TTkString._importString1(''.join(d[ox:ox+pw]),c[ox:ox+pw])) |
|
return ttk.TTkString('\n').join(ret) |
|
|
|
def exportLayer(self, full=False, palette=True, crop=True): |
|
# xa|----------| xb |
|
# px |-----------| = max(px,px+xa-ox) |
|
# Offset |------| pw |
|
# Data |----------------------------| |
|
# daw |
|
|
|
# Don't try this at home |
|
ox,oy = self._offset |
|
px,py = self.pos() |
|
pw,ph = self.size() |
|
|
|
if full: |
|
data = self._data |
|
colors = self._colors |
|
else: |
|
data = [row[ox:ox+pw] for row in self._data[ oy:oy+ph] ] |
|
colors = [row[ox:ox+pw] for row in self._colors[oy:oy+ph] ] |
|
ox=oy=0 |
|
|
|
daw = len(data[0]) |
|
dah = len(data) |
|
|
|
# get the bounding box |
|
if crop: |
|
xa,xb,ya,yb = 0x10000,0,0x10000,0 |
|
for y,(drow,crow) in enumerate(zip(data,colors)): |
|
for x,(d,c) in enumerate(zip(drow,crow)): |
|
if d != ' ' or c.background(): |
|
xa,xb = min(x,xa),max(x,xb) |
|
ya,yb = min(y,ya),max(y,yb) |
|
if (xa,xb,ya,yb) == (0x10000,0,0x10000,0): |
|
xa=xb=ya=yb=0 |
|
else: |
|
xb+=1 |
|
yb+=1 |
|
else: |
|
xa,xb,ya,yb = 0,daw,0,dah |
|
|
|
# Visble Area intersecting the bounding box |
|
vxa,vya = max(px,px+xa-ox), max(py,py+ya-oy) |
|
vxb,vyb = min(px+pw,vxa+xb-xa),min(py+ph,vya+yb-ya) |
|
vw,vh = vxb-vxa, vyb-vya |
|
|
|
outData = { |
|
'version':'1.1.0', |
|
'size':[vw,vh], |
|
'pos': (vxa,vya), |
|
'name':str(self.name()), |
|
'data':[], 'colors':[]} |
|
|
|
if palette: |
|
palette = outData['palette'] = [] |
|
for row in colors: |
|
for c in row: |
|
colorType = c.colorType() |
|
fg = f"{c.getHex(ttk.TTkK.ColorType.Foreground)}" if (colorType&ttk.TTkK.ColorType.Foreground) else None |
|
bg = f"{c.getHex(ttk.TTkK.ColorType.Background)}" if (colorType&ttk.TTkK.ColorType.Background) else None |
|
if (pc:=(fg,bg)) not in palette: |
|
palette.append(pc) |
|
|
|
if full: |
|
wslice = slice(xa,xb+1) |
|
hslice = slice(ya,yb+1) |
|
outData['offset'] = (max(0,ox-xa),max(0,oy-ya)) |
|
else: |
|
wslice = slice(ox+vxa-px,ox+vxa-px+vw) |
|
hslice = slice(oy+vya-py,oy+vya-py+vh) |
|
|
|
for row in data[hslice]: |
|
outData['data'].append(row[wslice]) |
|
for row in colors[hslice]: |
|
outData['colors'].append([]) |
|
for c in row[wslice]: |
|
colorType = c.colorType() |
|
fg = f"{c.getHex(ttk.TTkK.ColorType.Foreground)}" if (colorType&ttk.TTkK.ColorType.Foreground) else None |
|
bg = f"{c.getHex(ttk.TTkK.ColorType.Background)}" if (colorType&ttk.TTkK.ColorType.Background) else None |
|
if palette: |
|
outData['colors'][-1].append(palette.index((fg,bg))) |
|
else: |
|
outData['colors'][-1].append((fg,bg)) |
|
|
|
return outData |
|
|
|
def _import_v1_1_0(self, dd): |
|
self._import_v1_0_0(dd) |
|
# Fix the correct size if the data has been trimmed in the wrong save |
|
ox,oy = self._offset = dd.get('offset',(0,0)) |
|
ttk.TTkLog.debug(f"{self._offset=} {self._size=} {self._pos=}") |
|
ttk.TTkLog.debug(f"{len(self._data[0])=}") |
|
w,h = self._size |
|
dw = len(self._data[0]) |
|
dh = len(self._data) |
|
w = min(w,dw-ox) |
|
h = min(h,dh-oy) |
|
self._size = w,h |
|
# px,py = self._pos |
|
# self.superResize(x,y,w,h) |
|
|
|
def _import_v1_0_0(self, dd): |
|
self._pos = dd['pos'] |
|
self._size = dd['size'] |
|
self._name = ttk.TTkString(dd['name']) |
|
self._data = dd['data'] |
|
def _getColor(cd): |
|
fg,bg = cd |
|
if fg and bg: return ttk.TTkColor.fg(fg)+ttk.TTkColor.bg(bg) |
|
elif fg: return ttk.TTkColor.fg(fg) |
|
elif bg: return ttk.TTkColor.bg(bg) |
|
else: return ttk.TTkColor.RST |
|
if 'palette' in dd: |
|
palette = [_getColor(c) for c in dd['palette']] |
|
self._colors = [[palette[c] for c in row] for row in dd['colors']] |
|
else: |
|
self._colors = [[_getColor(c) for c in row] for row in dd['colors']] |
|
|
|
def _import_v0_0_0(self, dd): |
|
# Legacy old import |
|
w = len(dd['data'][0]) + 10 |
|
h = len(dd['data']) + 4 |
|
x,y=5,2 |
|
self.resize(w,h) |
|
self._pos = (0,0) |
|
for i,rd in enumerate(dd['data']): |
|
for ii,cd in enumerate(rd): |
|
self._data[i+y][ii+x] = cd |
|
for i,rd in enumerate(dd['colors']): |
|
for ii,cd in enumerate(rd): |
|
fg,bg = cd |
|
if fg and bg: |
|
self._colors[i+y][ii+x] = ttk.TTkColor.fg(fg)+ttk.TTkColor.bg(bg) |
|
elif fg: |
|
self._colors[i+y][ii+x] = ttk.TTkColor.fg(fg) |
|
elif bg: |
|
self._colors[i+y][ii+x] = ttk.TTkColor.bg(bg) |
|
else: |
|
self._colors[i+y][ii+x] = ttk.TTkColor.RST |
|
|
|
def importLayer(self, dd): |
|
self.clean() |
|
|
|
if 'version' in dd: |
|
ver = dd['version'] |
|
if ver == ('1.0.0'): |
|
self._import_v1_0_0(dd) |
|
elif ver == ('1.1.0'): |
|
self._import_v1_1_0(dd) |
|
else: |
|
self._import_v0_0_0(dd) |
|
self._snapVersion += 1 |
|
self.changed.emit() |
|
|
|
def trim(self): |
|
pw,ph = self._size |
|
ox,oy = self._offset |
|
cl = CanvasLayer() |
|
if not (pw and ph) : return cl |
|
|
|
tmpd = [r[ox:ox+pw] for r in self._data[ oy:oy+ph]] |
|
tmpc = [r[ox:ox+pw] for r in self._colors[oy:oy+ph]] |
|
# Trim |
|
xa,xb,ya,yb = 0x10000,0,0x10000,0 |
|
for y,(drow,crow) in enumerate(zip(tmpd,tmpc)): |
|
for x,(d,c) in enumerate(zip(drow,crow)): |
|
if d != ' ' or c.background(): |
|
xa,xb = min(x,xa),max(x,xb) |
|
ya,yb = min(y,ya),max(y,yb) |
|
if (xa,xb,ya,yb) == (0x10000,0,0x10000,0): |
|
xa=xb=ya=yb=0 |
|
|
|
cl._data = [r[xa:xb+1] for r in tmpd[ya:yb+1]] |
|
cl._colors = [r[xa:xb+1] for r in tmpc[ya:yb+1]] |
|
|
|
w = xb-xa+1 |
|
h = yb-ya+1 |
|
|
|
cl._size = (w,h) |
|
cl._name = self._name |
|
return cl |
|
|
|
def importTTkString(self, txt:ttk.TTkString): |
|
tmpd = [] |
|
tmpc = [] |
|
for line in txt.split('\n'): |
|
d,c = line.getData() |
|
tmpd.append(list(d)) |
|
tmpc.append(list(c)) |
|
# Trim |
|
xa,xb,ya,yb = 0x10000,0,0x10000,0 |
|
for y,(drow,crow) in enumerate(zip(tmpd,tmpc)): |
|
for x,(d,c) in enumerate(zip(drow,crow)): |
|
if d != ' ' or c.background(): |
|
xa,xb = min(x,xa),max(x,xb) |
|
ya,yb = min(y,ya),max(y,yb) |
|
if (xa,xb,ya,yb) == (0x10000,0,0x10000,0): |
|
xa=xb=ya=yb=0 |
|
|
|
self._data = data = [r[xa:xb+1] for r in tmpd[ya:yb+1]] |
|
self._colors = colors = [r[xa:xb+1] for r in tmpc[ya:yb+1]] |
|
|
|
w = xb-xa+1 |
|
h = yb-ya+1 |
|
|
|
for i,(d,c) in enumerate(zip(data,colors)): |
|
data[i] = (d + [' ']*w)[:w] |
|
colors[i] = (c + [ttk.TTkColor.RST]*w)[:w] |
|
|
|
self._size = (w,h) |
|
self._name = ttk.TTkString("Pasted") |
|
self._snapVersion += 1 |
|
|
|
def placeFill(self,geometry,tool,glyph:str,color:ttk.TTkColor,glyphEnabled=True,preview=False): |
|
ox,oy = self._offset |
|
w,h = self._size |
|
ax,ay,bx,by = geometry |
|
ax = max(0,min(w-1,ax)) |
|
ay = max(0,min(h-1,ay)) |
|
bx = max(0,min(w-1,bx)) |
|
by = max(0,min(h-1,by)) |
|
fax,fay = ox+min(ax,bx), oy+min(ay,by) |
|
fbx,fby = ox+max(ax,bx), oy+max(ay,by) |
|
|
|
if preview: |
|
data = [_r.copy() for _r in self._data] |
|
colors = [_r.copy() for _r in self._colors] |
|
self._preview = {'data':data,'colors':colors} |
|
else: |
|
self._snapVersion += 1 |
|
self._preview = None |
|
data = self._data |
|
colors = self._colors |
|
|
|
if tool == ToolType.RECTFILL: |
|
for y in range(fay,fby+1): |
|
for x in range(fax,fbx+1): |
|
self._placeGlyph(data,colors,x,y,glyph,color,glyphEnabled,preview) |
|
if tool == ToolType.RECTEMPTY: |
|
for x in range(fax,fbx+1): |
|
self._placeGlyph(data,colors,x,fay,glyph,color,glyphEnabled,preview) |
|
self._placeGlyph(data,colors,x,fby,glyph,color,glyphEnabled,preview) |
|
for y in range(fay,fby+1): |
|
self._placeGlyph(data,colors,fax,y,glyph,color,glyphEnabled,preview) |
|
self._placeGlyph(data,colors,fbx,y,glyph,color,glyphEnabled,preview) |
|
self.changed.emit() |
|
return True |
|
|
|
def placeGlyph(self,x,y,glyph:str,color:ttk.TTkColor,glyphEnabled=True,preview=False): |
|
if preview: |
|
data = [_r.copy() for _r in self._data] |
|
colors = [_r.copy() for _r in self._colors] |
|
self._preview = {'data':data,'colors':colors} |
|
else: |
|
self._snapVersion += 1 |
|
self._preview = None |
|
data = self._data |
|
colors = self._colors |
|
|
|
self.changed.emit() |
|
return self._placeGlyph(data,colors,x,y,glyph,color,glyphEnabled,preview) |
|
|
|
def _placeGlyph(self,data,colors,x,y,glyph:str,color:ttk.TTkColor,glyphEnabled=True,preview=False): |
|
ox,oy = self._offset |
|
w,h = self._size |
|
|
|
if 0<=x<w and 0<=y<h: |
|
if glyphEnabled: |
|
color = color if glyph != ' ' else color.background() |
|
color = color if color else ttk.TTkColor.RST |
|
data[ oy+y][ox+x] = glyph |
|
colors[oy+y][ox+x] = color |
|
else: |
|
glyph = data[ oy+y][ox+x] |
|
oColorType = (oc:=colors[oy+y][ox+x]).colorType() |
|
nColorType = (nc:=color).colorType() |
|
if glyph==' ': |
|
if oColorType & ttk.TTkK.ColorType.Background: |
|
colors[oy+y][ox+x] = nc.background() |
|
else: |
|
fg = nc.foreground() if ttk.TTkK.ColorType.Foreground & nColorType else oc.foreground() |
|
bg = nc.background() if ttk.TTkK.ColorType.Background & nColorType & oColorType else oc.background() if ttk.TTkK.ColorType.Background & oColorType else fg |
|
color = fg+bg |
|
colors[oy+y][ox+x] = color |
|
return True |
|
return False |
|
|
|
def placeArea(self,x,y,area,transparent=False,preview=False): |
|
ox,oy = self._offset |
|
w,h = self._size |
|
|
|
dw,dh = area.size() |
|
darea = area._data |
|
carea = area._colors |
|
x-=dw//2 |
|
y-=dh//2 |
|
|
|
if preview: |
|
data = [_r.copy() for _r in self._data] |
|
colors = [_r.copy() for _r in self._colors] |
|
self._preview = {'data':data,'colors':colors} |
|
else: |
|
self._snapVersion += 1 |
|
self._preview = None |
|
data = self._data |
|
colors = self._colors |
|
|
|
for _y,(darow,carow) in enumerate(zip(darea,carea),oy+y): |
|
for _x,(da,ca) in enumerate(zip(darow,carow),ox+x): |
|
if 0<=_x<w and 0<=_y<h and ( da!=' ' or ca.background()): |
|
if not transparent or (da==' ' and ca._bg): |
|
data[_y][_x] = da |
|
colors[_y][_x] = ca |
|
elif da!=' ': |
|
data[_y][_x] = da |
|
cc = colors[_y][_x] |
|
newC = ca.copy() |
|
newC._bg = ca._bg if ca._bg else cc._bg |
|
colors[_y][_x] = newC |
|
|
|
self.changed.emit() |
|
|
|
def drawInCanvas(self, pos, canvas:ttk.TTkCanvas): |
|
if not self._visible: return |
|
px,py = pos |
|
pw,ph = self._size |
|
cw,ch = canvas.size() |
|
if px+pw<0 or py+ph<0:return |
|
if px>=cw or py>=ch:return |
|
# Data Offset |
|
ox,oy = self._offset |
|
# x,y position in the Canvas |
|
cx = max(0,px) |
|
cy = max(0,py) |
|
# x,y position in the Layer |
|
lx,ly = (cx-px),(cy-py) |
|
# Area to be copyed |
|
dw = min(cw-cx,pw-lx) |
|
dh = min(ch-cy,ph-ly) |
|
|
|
if _p := self._preview: |
|
data = _p['data'] |
|
colors = _p['colors'] |
|
else: |
|
data = self._data |
|
colors = self._colors |
|
for y in range(cy,cy+dh): |
|
for x in range(cx,cx+dw): |
|
gl = data[ oy+y+ly-cy][ox+x+lx-cx] |
|
c = colors[oy+y+ly-cy][ox+x+lx-cx] |
|
if gl==' ' and c._bg: |
|
canvas._data[y][x] = gl |
|
canvas._colors[y][x] = c |
|
elif gl!=' ': |
|
canvas._data[y][x] = gl |
|
cc = canvas._colors[y][x] |
|
newC = c.copy() |
|
newC._bg = c._bg if c._bg else cc._bg |
|
canvas._colors[y][x] = newC
|
|
|