Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Harmony CModule Release v2.10 -A drawing application with lots of brush styles.
-- AirBrush
--[==[
local Brush = cimport "Brush"
local AirBrush = class(Brush)
function AirBrush:init(tb)
Brush.init(self,"AirBrush",tb)
end
function AirBrush:stroke(x,id,t)
local dx = x - self.lines[id].finish
self.lines[id].finish = x
local dist = 100/(dx:len() +1) + 4
self:addDisc({
centre = x,
radius = dist * self.style.thickness / 2,
edgeOpacity = 0,
smooth = false
})
end
function AirBrush:strokeEnd(v,id)
self.lastline = self.lines[id]
self.lines[id] = nil
end
return AirBrush
--]==]
-- Brush
--[==[
local Drawable = cimport "Drawable"
local Colour = unpack(cimport (BaseLib .. "Colour"))
cimport(BaseLib .. "Path")
cimport(BaseLib .. "Slider")
local Brush = class(Drawable)
function Brush:init(name,tb)
Drawable.init(self,name,tb)
self.lines = {}
self.touches = {}
self.mesh = mesh()
self.mesh:resize(30000)
self.nlines = 0
end
function Brush:reset()
self.lines = {}
self.lastline = nil
end
function Brush:clear()
self.isfinished = false
self.touches = {}
self.mesh = mesh()
self.mesh:resize(30000)
self.nlines = 0
self:reset()
end
function Brush:undo()
end
function Brush:redo()
end
function Brush:setAlpha(aim)
self.mesh.texture = aim
end
function Brush:strokeStart(x,id)
if self.style.join == JOINNONE then
self.lines[id] = {}
self.lines[id].finish = x
elseif self.style.join == JOINMOVE then
self.lines[id] = {}
self.lines[id].finish = x
elseif self.style.join == JOINLINE then
if self.lastline then
self.lines[id] = self.lastline
self.lastline = nil
self:stroke(x,id)
else
self.lines[id] = {}
self.lines[id].finish = x
end
self.style.join = self.style.oldjoin or JOINNONE
end
end
function Brush:stroke(x,id,t)
end
function Brush:basicStroke(x,id,t)
if self.lines[id] then
t = t or {}
local y,th
local l = self.lines[id]
if l.start then
if l.start:distSqr(l.finish) > 100
--and math.abs((l.finish - l.start):normalize():rotate90():dot((x - l.finish):normalize())) > .2
then
-- if false then
-- if true then
local bza,bzb
bza,bzb,th = QuickHobby(l.start,l.finish,x,l.th)
local ca = Bezierpt(1/3,unpack(bza))
local cb = Bezierpt(2/3,unpack(bza))
local a,b
if l.previousSegment then
if l.previousSegment.start then
a = (l.previousSegment.finish -
l.previousSegment.start):normalize()
b = (ca -
l.previousSegment.finish):normalize()
if a:dot(b) < -0.5 then
l.previousSegment.after =
l.previousSegment.finish - b
l.before = l.previousSegment.finish + a
else
l.previousSegment.after = ca
end
self:addLine(l.previousSegment)
end
end
local c = {
l.before,
l.start,
ca,
cb,
l.finish,
x}
for i=2,4 do
l.before = c[i-1]
l.start = c[i]
l.finish = c[i+1]
a = (c[i+1] - c[i]):normalize()
b = (c[i+2] - c[i+1]):normalize()
if a:dot(b) < -0.5 then
l.after = l.finish - b
c[i] = l.finish + a
else
l.after = c[i+2]
end
self:addLine(l)
if i ~= 4 then l.position = nil end
end
y = c[4]
else
local a,b
a = (l.finish - l.start):normalize()
b = (x - l.finish):normalize()
if a:dot(b) < -0.5 then
l.after = l.finish - b
y = l.finish + a
else
l.after = x
y = l.start
end
self:addLine(l)
end
end
l.previousSegment = nil
t.start = l.finish
t.before = y
t.finish = x
t.th = th
t.previousSegment = l
t.startCapData = l.startCapData
t.endCapData = l.endCapData
t.startCap = l.startCap
t.endCap = l.endCap
self.lines[id] = self:addLine(t)
end
end
function Brush:strokeEnd(v,id)
self:stroke(v,id)
self.lastline = self.lines[id]
self.lines[id] = nil
end
function Brush:processTouches(g)
self.touches = {}
self.ntouches = 0
for _,t in ipairs(g.touchesArr) do
if t.updated then
table.insert(self.touches,t)
end
end
if g.type.ended then
if self.style.join == JOINNONE then
self.resetafter = true
end
self.isfinished = true
g:reset()
else
g:noted()
end
end
function Brush:predraw()
for _,t in ipairs(self.touches) do
local v = OrientationInverse(
PORTRAIT, vec2(t.touch.x,t.touch.y))
if t.touch.state == BEGAN then
self.lines[t.touch.id] = nil
self:strokeStart(v,t.touch.id)
elseif t.touch.state == MOVING then
self:stroke(v,t.touch.id)
else
self:strokeEnd(v,t.touch.id)
end
end
self.touches = {}
if self.resetafter then
self:reset()
self.resetafter = false
end
end
function Brush:draw()
self.mesh:draw()
end
function Brush:addLine(t)
local s,e,ws,we,b,a,sw,ew,wl,wn,n,sm,bl,sc,ec
t.startThickness = t.startThickness or t.thickness or self.style.thickness
t.endThickness = t.endThickness or t.thickness or self.style.thickness
t.startColour = t.startColour or t.colour or self.style.colour
t.endColour = t.endColour or t.colour or self.style.colour
t.startOpacity = t.startOpacity or
t.opacity or self.style.opacity or 100
t.endOpacity = t.endOpacity or
t.opacity or self.style.opacity or 100
t.smooth = t.smooth or self.style.smooth
t.blur = t.blur or self.style.blur
t.startCap = t.startCap or self.style.cap
t.endCap = t.endCap or self.style.cap
t.startCapData = t.startCapData or {}
t.endCapData = t.endCapData or {}
ws = t.startThickness
we = t.endThickness
s = t.start
e = t.finish
b = t.before
a = t.after
bl = t.blur
sm = t.smooth
sc = Colour.opacity(t.startColour,t.startOpacity)
ec = Colour.opacity(t.endColour,t.endOpacity)
if sm then
we = math.max(0,we-2*bl)
ws = math.max(0,ws-2*bl)
end
we = we/2
ws = ws/2
wl = (e - s):normalize()
wn = wl:rotate90()
if b then
sw = (s - b):normalize() - wl
if wn:dot(sw) == 0 then
sw = wn
else
sw = sw/wn:dot(sw)
end
else
sw = wn
end
if a then
ew = (a - e):normalize() - wl
if wn:dot(ew) == 0 then
ew = wn
else
ew = ew/wn:dot(ew)
end
else
ew = wn
end
if ew:dot(sw) < 0 then
ew = - ew
end
if not b then
-- update start cap data
local ct = t.startCapData
ct.thickness = t.startThickness
ct.colour = t.startColour
ct.opacity = t.startOpacity
ct.smooth = t.smooth
ct.blur = t.blur
ct.cap = t.startCap
ct.start = s
ct.direction = -wl
t.startCapData = self:addCap(ct)
end
if not a then
-- update end cap data
local ct = t.endCapData
ct.thickness = t.endThickness
ct.colour = t.endColour
ct.opacity = t.endOpacity
ct.smooth = t.smooth
ct.blur = t.blur
ct.cap = t.endCap
ct.start = e
ct.direction = wl
t.endCapData = self:addCap(ct)
end
t.position = t.position or self.nlines
n = t.position
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{s-ws*sw,sc},
{s+ws*sw,sc},
{e-we*ew,ec},
{e+we*ew,ec}
}
})
if sm then
local l
local sct = Colour.opacity(sc,0)
local ect = Colour.opacity(ec,0)
local corners = {
{{s+ws*sw,sc},{s+(ws+bl)*sw,sct}},
{{e+we*ew,ec},{e+(we+bl)*ew,ect}},
{{e-we*ew,ec},{e-(we+bl)*ew,ect}},
{{s-ws*sw,sc},{s-(ws+bl)*sw,sct}},
}
for _,k in ipairs({1,3}) do
l=k%4+1
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
corners[k][1],
corners[k][2],
corners[l][1],
corners[l][2],
}
})
end
end
self.nlines = math.max(self.nlines,n)
return t
end
function Brush:addCircle(t)
local w,c,r,n,bl,sm,col,e
t.colour = t.colour or self.style.colour
t.opacity = t.opacity or self.style.opacity or 100
t.position = t.position or self.nlines
t.smooth = t.smooth or self.style.smooth
t.blur = t.blur or self.style.blur
t.accuracy = t.accuracy or 5
t.thickness = t.thickness or self.style.thickness
w = t.thickness
c = t.centre
r = t.radius
n = t.position
bl = t.blur
sm = t.smooth
col = Colour.opacity(t.colour,t.opacity)
e = t.accuracy
if sm then
w = math.max(0,w-2*bl)
end
w = w/2
local m = math.max(math.floor(2*math.pi*r/e),15)
local a = 2*math.pi/m
local n = self.nlines
local v = vec2(1,0)
local u = vec2(math.cos(a),math.sin(a))
for i=1,m do
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{c+(r-w)*v,col},
{c+(r+w)*v,col},
{c+(r-w)*u,col},
{c+(r+w)*u,col},
}
})
v = u
u = vec2(math.cos((i+1)*a),math.sin((i+1)*a))
end
if sm then
local tcol = Colour.opacity(col,0)
v = vec2(1,0)
u = vec2(math.cos(a),math.sin(a))
for i=1,m do
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{c+(r+w)*v,col},
{c+(r+w+bl)*v,tcol},
{c+(r+w)*u,col},
{c+(r+w+bl)*u,tcol},
}
})
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{c+(r-w)*v,col},
{c+(r-w-bl)*v,tcol},
{c+(r-w)*u,col},
{c+(r-w-bl)*u,tcol},
}
})
v = u
u = vec2(math.cos((i+1)*a),math.sin((i+1)*a))
end
end
self.nlines = math.max(self.nlines,n)
return t
end
function Brush:addDisc(t)
local c,r,n,bl,sm,sc,ec,e
t.centreColour = t.centreColour or t.colour or self.style.colour
t.edgeColour = t.edgeColour or t.colour or self.style.colour
t.centreOpacity = t.centreOpacity
or t.opacity or self.style.opacity or 100
t.edgeOpacity = t.edgeOpacity or
t.opacity or self.style.opacity or 100
t.position = t.position or self.nlines
t.smooth = t.smooth or self.style.smooth
t.blur = t.blur or self.style.blur
t.accuracy = t.accuracy or 5
c = t.centre
r = t.radius
n = t.position
bl = t.blur
sm = t.smooth
sc = Colour.opacity(t.centreColour,t.centreOpacity)
ec = Colour.opacity(t.edgeColour,t.edgeOpacity)
e = t.accuracy
if sm then
r = math.max(0,r-bl)
end
local m = math.max(math.floor(2*math.pi*r/e),15)
local a = 2*math.pi/m
local n = t.position
local v = vec2(1,0)
local u = vec2(math.cos(a),math.sin(a))
for i=1,m do
n = addTriangle({
mesh = self.mesh,
position = n,
vertices = {
{c,sc},
{c+r*v,ec},
{c+r*u,ec},
}
})
v = u
u = vec2(math.cos((i+1)*a),math.sin((i+1)*a))
end
if sm then
local tec = Colour.opacity(ec,0)
v = vec2(1,0)
u = vec2(math.cos(a),math.sin(a))
for i=1,m do
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{c+r*v,ec},
{c+(r+bl)*v,tec},
{c+r*u,ec},
{c+(r+bl)*u,tec},
}
})
v = u
u = vec2(math.cos((i+1)*a),math.sin((i+1)*a))
end
end
self.nlines = math.max(self.nlines,n)
return t
end
function Brush:addHalfDisc(t)
local c,r,n,bl,sm,sc,ec,e,nv,m,a,v,u
t.centreColour = t.centreColour or t.colour or self.style.colour
t.edgeColour = t.edgeColour or t.colour or self.style.colour
t.centreOpacity = t.centreOpacity
or t.opacity or self.style.opacity or 100
t.edgeOpacity = t.edgeOpacity or
t.opacity or self.style.opacity or 100
t.position = t.position or self.nlines
t.smooth = t.smooth or self.style.smooth
t.blur = t.blur or self.style.blur
t.accuracy = t.accuracy or 5
c = t.centre
r = t.radius
nv = t.normal:normalize()
n = t.position
bl = t.blur
sm = t.smooth
sc = Colour.opacity(t.centreColour,t.centreOpacity)
ec = Colour.opacity(t.edgeColour,t.edgeOpacity)
e = t.accuracy
if sm then
r = math.max(0,r-bl)
end
m = math.max(math.floor(2*math.pi*r/e),15)
a = math.pi/m
v = nv
u = nv:rotate(a)
for i=1,m do
n = addTriangle({
mesh = self.mesh,
position = n,
vertices = {
{c,sc},
{c+r*v,ec},
{c+r*u,ec},
}
})
v = u
u = nv:rotate((i+1)*a)
end
if sm then
local tec = Colour.opacity(ec,0)
v = nv
u = nv:rotate(a)
for i=1,m do
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{c+r*v,ec},
{c+(r+bl)*v,tec},
{c+r*u,ec},
{c+(r+bl)*u,tec},
}
})
v = u
u = nv:rotate((i+1)*a)
end
end
self.nlines = math.max(self.nlines,n)
return t
end
function Brush:addCap(t)
local s,w,sw,wl,wn,n,sm,bl,sc
t.thickness = t.thickness or t.thickness or self.style.thickness
t.colour = t.colour or t.colour or self.style.colour
t.opacity = t.opacity or
t.opacity or self.style.opacity or 100
t.smooth = t.smooth or self.style.smooth
t.blur = t.blur or self.style.blur
t.cap = t.cap or self.style.cap
t.position = t.position or self.nlines
if t.cap == CAPNONE then return t end
if t.cap == CAPSQUARE and not t.smooth then return t end
w = t.thickness
s = t.start
bl = t.blur
sm = t.smooth
sc = Colour.opacity(t.colour,t.opacity)
if sm then
w = math.max(0,w-2*bl)
end
w = w/2
wl = t.direction:normalize()
wn = wl:rotate90()
if t.cap == CAPROUND then
t.normal = -wn
t.centre = s
t.radius = t.thickness/2
t.centreColour = t.colour
t.edgeColour = t.colour
t.centreOpacity = t.opacity
t.edgeOpacity = t.opacity
return self:addHalfDisc(t)
end
if t.cap == CAPBUTT then
local e = s + w*wl
n = t.position
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{s-w*wn,sc},
{s+w*wn,sc},
{e-w*wn,sc},
{e+w*wn,sc}
}
})
if sm then
local l
local sct = Colour.opacity(sc,0)
local edges = {1,2,3}
local corners = {
{{s+w*wn,sc},{s+(w+bl)*wn,sct}},
{{e+w*wn,sc},{e+(w+bl)*wn+bl*wl,sct}},
{{e-w*wn,sc},{e-(w+bl)*wn+bl*wl,sct}},
{{s-w*wn,sc},{s-(w+bl)*wn,sct}},
}
for _,k in ipairs(edges) do
l=k%4+1
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
corners[k][1],
corners[k][2],
corners[l][1],
corners[l][2],
}
})
end
end
self.nlines = math.max(self.nlines,n)
return t
end
if t.cap == CAPSQUARE and sm then
local l
local sct = Colour.opacity(sc,0)
n = t.position
n = addQuad({
mesh = self.mesh,
position = n,
vertices = {
{s+w*wn,sc},
{s+(w+bl)*wn+bl*wl,sct},
{s-w*wn,sc},
{s-(w+bl)*wn+bl*wl,sct}
}
})
n = addTriangle({
mesh = self.mesh,
position = n,
vertices = {
{s+w*wn,sc},
{s+(w+bl)*wn+bl*wl,sct},
{s+(w+bl)*wn,sct},
}
})
n = addTriangle({
mesh = self.mesh,
position = n,
vertices = {
{s-w*wn,sc},
{s-(w+bl)*wn+bl*wl,sct},
{s-(w+bl)*wn,sct},
}
})
self.nlines = math.max(self.nlines,n)
return t
end
return t
end
return Brush
--]==]
-- BrushEx
--[==[
local Brush = cimport "Brush"
BrushEx = class(Brush)
function BrushEx:init(name,tb)
Brush.init(self,name,tb)
self.points = {}
self.count = 0
self.pcount = 0
self.lines = {}
self.touches = {}
end
function BrushEx:reset()
self.lines = {}
self.points = {}
self.count = 0
self.pcount = 0
self.lastline = nil
end
function BrushEx:undo()
self.pcount,self.count = self.count,self.pcount
end
function BrushEx:redo()
self.pcount,self.count = self.count,self.pcount
end
function BrushEx:predraw()
for _,t in ipairs(self.touches) do
local v = OrientationInverse(
PORTRAIT, vec2(t.touch.x,t.touch.y))
if t.touch.state == BEGAN then
self.pcount = self.count
self.lines[t.touch.id] = nil
self:strokeStart(v,t.touch.id)
elseif t.touch.state == MOVING then
self:stroke(v,t.touch.id)
else
self:strokeEnd(v,t.touch.id)
end
self.count = self.count + 1
self.points[self.count] = v
end
self.touches = {}
if self.resetafter then
self:reset()
self.resetafter = false
end
end
return BrushEx
--]==]
-- Canvas
--[==[
local JOINNONE = 0
local JOINMOVE = 1
local JOINLINE = 2
local CAPNONE = 0
local CAPSQUARE = 1
local CAPBUTT = 2
local CAPROUND = 3
local BLENDS = {
{"Normal",{NORMAL}},
{"Multiply",{MULTIPLY}},
{"Additive",{ADDITIVE}},
{"Eraser",{ZERO,ONE_MINUS_SRC_ALPHA}},
{"Inverter",{ONE_MINUS_DST_COLOR,ONE_MINUS_SRC_COLOR,ZERO,ONE}},
{"Mask",{ZERO,SRC_COLOR,ZERO,ONE}},
{"Replace",{ONE,ZERO,ONE,ZERO}}
}
local Canvas = class()
local Colour = unpack(cimport (BaseLib .. "Colour"))
cimport (BaseLib .. "ColourNames")
cimport (BaseLib .. "Keyboard")
cimport (BaseLib .. "PictureBrowser")
cimport (BaseLib .. "FontPicker")
cimport (BaseLib .. "Menu")
local TextBlock = cimport "TextBlock"
local Picture = cimport "Picture"
local Brushes = {
"FlowLines",
"AirBrush",
"Chrome",
"Circles",
"Discs",
"Fur",
"LongFur",
"Shaded",
"Simple",
"Sketchy",
"Squares",
"Web"
}
for _,v in ipairs(Brushes) do
cimport (v)
end
function Canvas:init(t)
t = t or {}
if t.touchHandler then
t.touchHandler:pushHandler(self)
end
self.ui = t.ui
self.style = {
thickness = 2,
minthickness = 2,
maxthickness = 15,
blur = 1,
smooth = true,
bgcolour = Colour.opacity(Colour.svg.White,25),
curve = false,
join = JOINMOVE,
cap = CAPBUTT,
hidden = false,
colour = Colour.svg.Black,
alpha = 100,
txtcolour = Colour.svg.Black,
txtbgcolour = Colour.transparent,
font = "Georgia",
fontsize = 32,
keyboard = "fullqwerty",
blendmode = BLENDS[1][2],
picture = {
red = color(255, 0, 0, 0),
green = color(0, 255, 0, 0),
blue = color(0, 0, 255, 0),
alpha = color(0, 0, 0, 255),
redcurve = vec4(0,1,0,0),
greencurve = vec4(0,1,0,0),
bluecurve = vec4(0,1,0,0),
alphacurve = vec4(0,1,0,0),
brightness = vec4(0,1,0,0)
}
}
if t.initialStyle then
self:setStyle(t.initialStyle)
end
local useText = true
local usePictures = true
local singleBrush = false
if t.enableText ~= nil then
useText = t.enableText
end
if t.enablePictures ~= nil then
usePictures = t.enablePictures
end
if t.brushes ~= nil then
if type(t.brushes) == "table" then
if #t.brushes == 1 then
singleBrush = true
end
elseif type(t.brushes) == "string" then
singleBrush = true
end
end
if useText then
t.ui:declareKeyboard({
name = "ArialMT",
type = self.style.keyboard
})
end
if usePictures then
t.ui:setPictureList({directory = "Documents",
camera = true,
filter = function(n,w,h)
return math.min(w,h) > 500
end})
end
-- meshes
-- Background mesh, holding the background image
self.bgmesh = mesh()
self.bgmesh:addRect(
Portrait[3]/2,Portrait[4]/2,Portrait[3],Portrait[4])
self.bgmesh:setColors(Colour.svg.White)
self.bgtcoords = {
vec2(0,0),vec2(1,0),vec2(1,1),vec2(0,0),vec2(1,1),vec2(0,1)
} -- normal
self.bgmesh.texCoords = self.bgtcoords
-- canvas mesh, everything drawn so far
self.canvas = image(Portrait[3],Portrait[4])
self.canvasmesh = mesh()
self.canvasmesh:addRect(
Portrait[3]/2,Portrait[4]/2,Portrait[3],Portrait[4])
self.canvasmesh.texture = self.canvas
self.canvasmesh:setRectTex(1,0,0,1,1)
-- undo mesh, very latest drawing
self.undoimage = image(Portrait[3],Portrait[4])
-- undo booleans
self.undoable = false
self.undo = true
self.redoable = false
-- for rendering alpha
self.cvs = image(Portrait[3],Portrait[4])
self.cvsmesh = mesh()
self.cvsmesh:addRect(
Portrait[3]/2,Portrait[4]/2,Portrait[3],Portrait[4])
self.cvsmesh.texture = self.cvs
self.alphaim = image(5,5)
-- menus
local attach = true
if t.attachMenu ~= nil then
attach = t.attachMenu
end
local m = t.ui:addMenu({
title = t.title or "Drawing",
attach = attach
})
local stm = t.ui:addMenu({
title = t.styleTitle or "Style",
attach = attach
})
self.menu = m
self.stylemenu = stm
local bm,txtm,picm
if not singleBrush then
bm = t.ui:addMenu({})
self.brushmenu = bm
bm:isChildOf(m)
end
local bgm = t.ui:addMenu({})
if useText then
txtm = t.ui:addMenu({})
txtm:isChildOf(m)
end
if usePictures then
picm = t.ui:addMenu({})
picm:isChildOf(stm)
end
local sbm = t.ui:addMenu({})
local bsm = t.ui:addMenu({})
local pcm = t.ui:addMenu({})
self.brushstylemenu = sbm
local sm = t.ui:addMenu({})
sbm:isChildOf(stm)
sm:isChildOf(m)
bgm:isChildOf(m)
bsm:isChildOf(sbm)
pcm:isChildOf(sbm)
if not singleBrush then
m:addItem({
title = "Brushes",
action = function(x,y)
bm.active = not bm.active
bm.x = x
bm.y = y
end,
highlight = function()
return bm.active
end,
deselect = function()
bm.active = false
end,
})
end
stm:addItem({
title = "Brush Style",
action = function(x,y)
if sbm.active then
sbm:deactivateDown()
else
sbm:activate()
sbm.x = x
sbm.y = y
end
end,
highlight = function()
return sbm.active
end,
deselect = function()
sbm:deactivateDown()
end,
})
sbm:addItem({
title = "Colour",
action = function()
t.ui:getColour(
self.style.colour,
function(c) self:setColour(c) return true end
)
return true
end
})
sbm:addItem({
title = "Thickness",
action = function()
t.ui:getParameter(
self.style.thickness,
self.style.minthickness,
self.style.maxthickness,
function(t)
self.style.thickness = t
return true
end,
function(t)
self.style.thickness = t
return true
end
)
return true
end
})
sbm:addItem({
title = "Blur",
action = function()
t.ui:getParameter(
self.style.blur,
0,
10,
function(t)
self.style.blur = t
return true
end,
function(t)
self.style.blur = t
return true
end
)
return true
end
})
sbm:addItem({
title = "Smooth",
action = function()
self.style.smooth = not self.style.smooth
return true
end,
highlight = function() return self.style.smooth end
})
sbm:addItem({
title = "Blend Style",
action = function(x,y)
if bsm.active then
bsm:deactivateDown()
else
bsm:activate()
bsm.x = x
bsm.y = y
end
end,
highlight = function()
return bsm.active
end,
deselect = function()
bsm:deactivateDown()
end,
})
--[[
sbm:addItem({
title = "Curve",
action = function()
self.style.curve = not self.style.curve
return true
end,
highlight = function() return self.style.curve end
})
--]]
sbm:addItem({
title = "Join paths (line)",
action = function()
if self.style.join == JOINLINE then
self.style.join = JOINNONE
else
self.style.oldjoin = self.style.join
self.style.join = JOINLINE
end
return true
end,
highlight = function() return self.style.join == JOINLINE end
})
sbm:addItem({
title = "Join paths (move)",
action = function()
if self.style.join == JOINMOVE then
self.style.join = JOINNONE
else
self.style.join = JOINMOVE
end
return true
end,
highlight = function() return self.style.join == JOINMOVE end
})
sbm:addItem({
title = "Path Cap",
action = function(x,y)
if pcm.active then
pcm:deactivateDown()
else
pcm:activate()
pcm.x = x
pcm.y = y
end
end,
highlight = function()
return pcm.active
end,
deselect = function()
pcm:deactivateDown()
end,
})
pcm:addItem({
title = "Butt",
action = function()
self.style.cap = CAPBUTT
return true
end,
highlight = function() return self.style.cap == CAPBUTT end
})
pcm:addItem({
title = "Square",
action = function()
self.style.cap = CAPSQUARE
return true
end,
highlight = function() return self.style.cap == CAPSQUARE end
})
pcm:addItem({
title = "Round",
action = function()
self.style.cap = CAPROUND
return true
end,
highlight = function() return self.style.cap == CAPROUND end
})
sbm:addItem({
title = "Clear Current History",
action = function()
self.drawable:reset()
return true
end
})
sbm:addItem({
title = "Unclog Pen",
action = function()
self.drawable.touchId = nil
return true
end
})
sbm:addItem({
title = "Specific Options",
action = function(x,y)
if self.drawable.menu then
self.drawable.menu.active =
not self.drawable.menu.active
self.drawable.menu.x = x
self.drawable.menu.y = y
end
end,
highlight = function()
if self.drawable.menu then
return self.drawable.menu.active
else
return false
end
end,
deselect = function()
if self.drawable.menu then
self.drawable.menu.active = false
end
end,
})
for _,v in ipairs(BLENDS) do
bsm:addItem({
title = v[1],
action = function()
self.style.blendmode = v[2]
return true
end,
highlight = function()
return self.style.blendmode == v[2]
end,
})
end
stm:addItem({
title = "Background",
action = function(x,y)
bgm.active = not bgm.active
bgm.x = x
bgm.y = y
end,
highlight = function()
return bgm.active
end,
deselect = function()
bgm.active = false
end
})
if useText then
m:addItem({
title = "Add Node",
action = function()
self:drawcanvas()
self.drawable = TextBlock(self,t.ui)
return true
end
})
stm:addItem({
title = "Text Style",
action = function(x,y)
txtm.active = not txtm.active
txtm.x = x
txtm.y = y
end,
highlight = function()
return txtm.active
end,
deselect = function()
txtm.active = false
end
})
end
if usePictures then
m:addItem({
title = "Add Picture",
action = function()
self:drawcanvas()
self.drawable = Picture(self,t.ui)
return true
end
})
stm:addItem({
title = "Picture Style",
action = function(x,y)
picm.active = not picm.active
picm.x = x
picm.y = y
end,
highlight = function()
return picm.active
end,
deselect = function()
picm.active = false
end
})
for _,v in ipairs({
{"Red", "red"},
{"Green", "green"},
{"Blue", "blue"},
{"Alpha", "alpha"}
}) do
picm:addItem({
title = v[1] .. " Channel",
action = function()
t.ui:getColour(
self.style.picture[v[2]],
function(c)
self.style.picture[v[2]] = c
self.drawable:updateStyle()
return true
end
)
picm:deactivateUp()
return true
end
})
picm:addItem({
title = v[1] .. " Curve",
action = function()
t.ui:getCurve(
self.style.picture[v[2] .. "curve"],
function(c)
self.style.picture[v[2] .. "curve"] = c
self.drawable:updateStyle()
return true
end,
function(c)
self.style.picture[v[2] .. "curve"] = c
self.drawable:updateStyle()
end
)
picm:deactivateUp()
return true
end
})
end
picm:addItem({
title = "Brightness",
action = function()
t.ui:getCurve(
self.style.picture.brightness,
function(c)
self.style.picture.brightness = c
self.drawable:updateStyle()
return true
end,
function(c)
self.style.picture.brightness = c
self.drawable:updateStyle()
end
)
picm:deactivateUp()
return true
end
})
picm:addItem({
title = "Reset Values",
action = function()
self.style.picture = {
red = color(255, 0, 0, 0),
green = color(0, 255, 0, 0),
blue = color(0, 0, 255, 0),
alpha = color(0, 0, 0, 255),
redcurve = vec4(0,1,0,0),
greencurve = vec4(0,1,0,0),
bluecurve = vec4(0,1,0,0),
alphacurve = vec4(0,1,0,0),
brightness = vec4(0,1,0,0)
}
self.drawable:updateStyle()
return true
end
})
bgm:addItem({
title = "Background Image",
action = function()
t.ui:getPicture(
function(i)
self.bgmesh.texture = i
return true
end
)
return true
end
})
for _,v in ipairs({
{"Rotate Clockwise", USRotateCCW}, -- rotate tex coords other way
{"Rotate Anticlockwise", USRotateCW},
{"Reflect Horizontally", USReflectH},
{"Reflect Vertically", USReflectV},
}) do
bgm:addItem({
title = v[1],
action = function()
for l,u in ipairs(self.bgtcoords) do
self.bgtcoords[l] = v[2](u)
end
self.bgmesh.texCoords = self.bgtcoords
return true
end
})
end
bgm:addItem({
title = "Background Image Tint",
action = function()
t.ui:getColour(
function(c)
self.bgmesh:setColors(c)
return true
end
)
return true
end
})
end
bgm:addItem({
title = "Background Colour",
action = function()
t.ui:getColour(
self.style.bgcolour,
function(c)
self.style.bgcolour = c
return true
end
)
return true
end
})
m:addItem({
title = "Undo",
action = function()
if self.undoable then
self.undo = true
self.redoable = true
self.undoable = false
self.drawable:undo()
return true
end
return false
end,
highlight = function()
return self.undoable
end
})
m:addItem({
title = "Redo",
action = function()
if self.redoable then
self.undo = false
self.undoable = true
self.redoable = false
self.drawable:redo()
return true
end
return false
end,
highlight = function()
return self.redoable
end
})
m:addItem({
title = "Save to ...",
action = function(x,y)
sm.active = not sm.active
sm.x = x
sm.y = y
end,
highlight = function()
return sm.active
end,
deselect = function()
sm.active = false
end
})
for _,d in ipairs({"Documents","Dropbox"}) do
sm:addItem({
title = d,
action = function()
t.ui:getText(
function(s)
self:export(d,s)
return true
end)
return true
end
})
end
m:addItem({
title = "Clear Canvas",
action = function()
self:clear()
return true
end
})
if useText then
txtm:addItem({
title = "Text Colour",
action = function()
t.ui:getColour(
self.style.txtcolour,
function(c)
self.style.txtcolour = c
self.drawable:updateStyle()
return true
end
)
return true
end
})
txtm:addItem({
title = "Text Background Colour",
action = function()
t.ui:getColour(
self.style.txtbgcolour,
function(c)
self.style.txtbgcolour = c
self.drawable:updateStyle()
return true
end
)
return true
end
})
txtm:addItem({
title = "Font",
action = function()
t.ui:getFont(
function(c)
self.style.font = c
self.drawable:updateStyle()
return true
end
)
return true
end
})
txtm:addItem({
title = "Font Size",
action = function()
t.ui:getNumber(
function(c)
self.style.fontsize = c
self.drawable:updateStyle()
return true
end
)
return true
end
})
end
t.ui:addHelp({
title = "Drawing",
text = "Select a brush to draw a path with different effects. A path can be drawn in more than one go by selecting one of the \"join\" methods (\"line\" means that the segments are actually joined together). In this case, the effect uses the whole path. The alpha value of the colour is applied after a segment is completed meaning that crossings on the same segment are not overlaid but crossings on different segments are."
})
self:initBrushes(t.brushes)
end
function Canvas:hide()
self.hidden = true
end
function Canvas:unhide()
self.hidden = false
end
function Canvas:exportIfDrawn(...)
if self.drawnon then
self:export(...)
end
end
function Canvas:export(d,s,o)
self:drawcanvas()
local sn = s
if d then
if not o then
local l = spriteList(d)
local lt = {}
for _,v in ipairs(l) do
lt[v] = true
end
local n = 0
while lt[sn] do
n = n + 1
sn = string.format("%s%03d",s,n)
end
end
sn = d .. ":" .. sn
end
saveImage(sn,self.canvas)
popStyle()
popMatrix()
end
function Canvas:setColour(c)
self.style.colour = Colour.opaque(c)
self.style.alpha = c.a/2.55
self:setLineAlpha()
end
function Canvas:setLineAlpha(a)
a = a or self.style.alpha
self.alphaim = image(5,5)
pushMatrix()
pushStyle()
resetMatrix()
resetStyle()
noSmooth()
fill(Colour.opacity(Colour.svg.White,a))
setContext(self.alphaim)
rect(0,0,5,5)
setContext()
popStyle()
popMatrix()
self.drawable:setAlpha(self.alphaim)
end
-- Brushes Management
function Canvas:initBrushes(br)
local t = Brushes
local brn
if br then
brn = {}
if type(br) == "string" then
brn[br] = true
else
for _,v in ipairs(br) do
brn[v] = true
end
end
end
local cb,dobrush,brush
local m = self.brushmenu
for _,v in ipairs(t) do
dobrush = true
if brn then
if not brn[v] then
dobrush = false
end
end
if dobrush then
brush = cimport (v)
local b = brush(self)
if not cb then cb = b end
if m then
m:addItem({
title = b:getName(),
action = function()
if self.drawable ~= b then
self:drawcanvas()
self.drawable = b
end
return true
end,
highlight = function()
return self.drawable == b
end
})
end
end
end
self.drawable = cb
end
function Canvas:setBrush(b)
if self.brushmenu then
self.brushmenu:invoke(b)
end
end
function Canvas:isTouchedBy(touch)
self.drawnon = true
return self.drawable:isTouchedBy(touch)
end
function Canvas:processTouches(g)
self.drawable:processTouches(g)
end
function Canvas:draw()
pushMatrix()
TransformOrientation(PORTRAIT)
local w,h = RectAnchorOf(Portrait,"size")
self.drawable:predraw()
if self.drawable:isFinished() then
self:drawcanvas()
end
pushStyle()
noSmooth()
blendMode(NORMAL)
--background(255, 255, 255, 255)
self.bgmesh:draw()
fill(self.style.bgcolour)
noStroke()
rectMode(CORNER)
rect(0,0,w,h)
self.canvasmesh:draw()
--blendMode(unpack(self.style.blendmode))
self.drawable:draw()
--blendMode(NORMAL)
popStyle()
popMatrix()
if not self.hidden then
self.drawable:drawIcon()
end
end
function Canvas:drawcanvas()
pushStyle()
pushMatrix()
resetStyle()
resetMatrix()
noSmooth()
setContext(self.undoimage)
blendMode(NORMAL)
background(Colour.transparent)
self.canvasmesh:draw()
setContext()
-- local cvs = image(Portrait[3],Portrait[4])
-- debug:log({name = "canvas", message = function() return cvs.width .. "," .. cvs.height end})
self:setLineAlpha(100)
setContext(self.cvs)
blendMode(NORMAL)
background(Colour.transparent)
self.drawable:bake()
setContext()
self.cvsmesh:setColors(Colour.opacity(
Colour.svg.White,self.style.alpha))
setContext(self.canvas)
blendMode(unpack(self.style.blendmode))
self.cvsmesh:draw()
setContext()
self.drawable:clear()
self.undoable = true
self.redoable = false
self.undo = false
popMatrix()
popStyle()
self:setLineAlpha()
end
function Canvas:clear()
setContext(self.canvas)
background(Colour.transparent)
setContext()
setContext(self.undoimage)
background(Colour.transparent)
setContext()
self.drawable:clear()
self.drawnon = false
end
function Canvas:saveStyle()
local t = {}
for k,v in pairs(self.style) do
t[k] = v
end
t.drawable = self.drawable:getName()
return t
end
function Canvas:setStyle(t)
if t.drawable then
self:setBrush(t.drawable)
t.drawable = nil
end
for k,v in pairs(t) do
self.style[k] = v
end
self:setLineAlpha()
end
cmodule.gexport {
JOINNONE = JOINNONE,
JOINMOVE = JOINMOVE,
JOINLINE = JOINLINE,
CAPNONE = CAPNONE,
CAPSQUARE = CAPSQUARE,
CAPBUTT = CAPBUTT,
CAPROUND = CAPROUND,
BLENDS = BLENDS
}
return Canvas
--]==]
--[[
ChangeLog
=========
v2.1 Uploaded wrong project!
v2.0 Converted to toadkick's cmodule class for importing
--]]
-- Chrome
--[==[
local BrushEx = cimport "BrushEx"
local Colour = unpack(cimport (BaseLib .. "Colour"))
local Chrome = class(BrushEx)
function Chrome:init(tb)
BrushEx.init(self,"Chrome",tb)
self.ntouches = 0
end
function Chrome:stroke(x,id,t)
self:basicStroke(x,id,t)
local c = Colour.opacity(self.style.colour,20)
if self.lines[id] then
local dx, d, a
local l = self.lines[id].start - x
for i = 1, self.count do
dx = self.points[i] - x
d = dx:lenSqr()
a = math.abs(dx:angleBetween(l))
if d < 1000 and a > .1 then
self:addLine({
start = x + dx * 0.2,
finish = self.points[i] - dx * 0.2,
colour = c,
ends = true
})
end
end
end
end
return Chrome
--]==]
-- Discs
--[==[
local Brush = cimport "Brush"
local Discs = class(Brush)
function Discs:init(tb)
Brush.init(self,"Discs",tb)
end
function Discs:stroke(x,id,t)
local dx = x - self.lines[id].finish
self.lines[id].finish = x
local d = 2*dx:len()
local cx = math.floor(x.x / 100) * 100 + 50
local cy = math.floor(x.y / 100) * 100 + 50
local c = vec2(cx,cy)
local steps = math.floor( math.random() * 10 )
local step_delta = d / steps
for i = 0, steps do
self:addCircle({
centre = c,
radius = (steps - i) * step_delta
})
end
end
return Discs
--]==]
-- Circles
--[==[
local Brush = cimport "Brush"
local Circles = class(Brush)
function Circles:init(tb)
Brush.init(self,"Circles",tb)
end
function Circles:stroke(x,id,t)
local dx = x - self.lines[id].finish
self.lines[id].finish = x
local dist = dx:len()
self:addCircle({
centre = x,
radius = dist * self.style.thickness / 2,
thickness = self.style.thickness / 4
})
end
return Circles
--]==]
--[==[
local Drawable = class()
function Drawable:init(name,tb)
self.style = tb.style
self.name = name
end
function Drawable:draw()
end
function Drawable:predraw()
end
function Drawable:getName()
return self.name
end
function Drawable:isTouchedBy(touch)
return true
end
function Drawable:processTouches(g)
g:noted()
end
function Drawable:bake()
self:draw()
end
function Drawable:isFinished()
return self.isfinished
end
function Drawable:drawIcon()
end
function Drawable:setAlpha(aim)
end
function Drawable:reset()
end
function Drawable:undo()
end
function Drawable:redo()
end
function Drawable:clear()
self.isfinished = false
end
function Drawable:updateStyle()
end
return Drawable
--]==]
-- FlowLines, version 1
--[==[
local Brush = cimport "Brush"
cimport (BaseLib .. "NumberSpinner")
local Colour = unpack(cimport (BaseLib .. "Colour"))
cimport (BaseLib .. "ColourNames")
local FlowLines = class(Brush)
function FlowLines:init(tb)
Brush.init(self,"Flow Lines",tb)
self.ntouches = 0
local om = tb.ui:addMenu({})
for _,v in ipairs({
{"Swirls", "swirls",0.001, 1, 0.01},
{"Length of path", "framesToLive", 1, 600, 20},
{"Curliness", "curliness", 1, 16, .1},
{"Speed", "gspeed", 0.1, 5, 5},
{"Size", "gsize", 0.1, 50, 1.5},
{"Brightness Variation", "varyBrightness", 0.0, 1.0, 0.45}
}) do
om:addItem({
title = v[1],
action = function()
tb.ui:getNumberSpinner({
value = self[v[2]],
action = function(n)
self[v[2]] = n
return true
end,
maxvalue = v[4],
minvalue = v[3]
})
return true
end,
})
self[v[2]] = v[5]
end
self.menu = om
om:isChildOf(tb.brushstylemenu)
self.seed = math.random(1000)
self.lastpts = {}
end
function FlowLines:clear()
Brush.clear(self)
self.seed = math.random(1000)
self.lastpts = {}
end
function FlowLines:stroke(x,id,t)
local v = 1.0 - self.varyBrightness * math.random()
local rc = Colour.shade(self.style.colour,v*100)
if self.lastpts[id] then
local dx = x - self.lastpts[id]
local l = dx:len()
dx = dx:normalize()
local sw = self.swirls
local s = self.seed
local speedRnd = self.gspeed * ( 1+ math.random()/2)
local r = 2 + self.gsize * ( 1 + math.random(-1,1)/2)
local angle = math.pi * self.curliness * (1+l)/(.5+l)
local px,sx,bx,ex,ax,sa,ea,st,et
local pts = {}
ex = x
ea = 100
px = noise((s+ex.x)*sw, (s+ex.y)*sw)
dx = dx:rotate(px * angle)
ax = ex - dx * speedRnd
et = self.style.thickness
for i = 1,self.framesToLive do
if
x.x < 0 or
x.x > RectAnchorOf(Portrait,"width") or
x.y < 0 or
x.y > RectAnchorOf(Portrait,"height")
then
break
end
bx = sx
sx = ex
ex = ax
sa = ea
st = et
px = noise((s+ex.x)*sw, (s+ex.y)*sw)
dx = dx:rotate(px * angle)
ax = ax - dx * speedRnd * (1 - .1*math.random())
ea = (1 - i / self.framesToLive) * 100
et = (1 - i / self.framesToLive) * self.style.thickness
self:addLine({
before = bx,
start = sx,
finish = ex,
after = ax,
colour = rc,
startOpacity = sa,
endOpacity = ea,
startThickness = st,
endThickness = et,
startCap = CAPNONE,
endCap = CAPNONE
})
end
end
self.lastpts[id] = x
end
return FlowLines
--]==]
-- Fur
--[==[
local BrushEx = cimport "BrushEx"
local Colour = unpack(cimport (BaseLib .. "Colour"))
local Fur = class(BrushEx)
function Fur:init(tb)
BrushEx.init(self,"Fur",tb)
end
function Fur:stroke(x,id,t)
self:basicStroke(x,id,t)
local c = Colour.opacity(self.style.colour,20)
local dx, d
for i = 1, self.count do
dx = self.points[i] - x
d = dx:lenSqr()
if d < 2000 and math.random() > d/2000 then
self:addLine({
start = x + dx * 0.5,
finish = x - dx * 0.5,
colour = c,
ends = true
})
end
end
end
return Fur
--]==]
--[[
HarmonyCodea
Original code Copyright (c) 2012 Luca Ferrara
Modifications Copyright (c) 2012 Andrew Stacey
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.
--]]
-- LongFur
--[==[
local BrushEx = cimport "BrushEx"
local Colour = unpack(cimport (BaseLib .. "Colour"))
local LongFur = class(BrushEx)
function LongFur:init(tb)
BrushEx.init(self,"Long Fur",tb)
end
function LongFur:stroke(x,id,t)
self:basicStroke(x,id,t)
local c = Colour.opacity(self.style.colour,10)
local dx, d
for i = 1, self.count do
size = -math.random()
dx = self.points[i] - x
d = dx:lenSqr()
if d < 4000 and math.random() > d/4000 then
self:addLine({
start = x + dx * size,
finish = x - dx * size,
colour = c,
ends = true
})
end
end
end
return LongFur
--]==]
-- Main
--[[
To load pictures for selection as background images:
1. Click on the brackets: sprite() to bring up the selector
2. Select "Documents"
3. Select "Photo Library"
4. Choose your photo
5. Name it
6. Repeat from 3 to select more
7. Click outside the sprite selector to dismiss it
--]]
supportedOrientations(ANY)
displayMode(FULLSCREEN_NO_BUTTONS)
BaseLib = "Library CModule:"
VERSION = 2.1
function setup()
AutoGist.setProjectInfo("Harmony CModule","Andrew Stacey","A drawing application with lots of brush styles.",VERSION)
AutoGist.backup(true)
cmodule "Harmony CModule"
local Touches = cimport (BaseLib .. "Touch")
local UI = cimport (BaseLib .. "UI")
local Debug = cimport (BaseLib .. "Debug")
local Canvas = cimport "Canvas"
touches = Touches()
ui = UI(touches)
ui:systemmenu()
ui:helpmenu()
debug = Debug({ui = ui})
ui.messages:deactivate()
canvas = Canvas({
ui = ui,
touchHandler = touches,
debug = debug,
--[[
enableText = false,
enablePictures = false,
brushes = {"Simple","Flow Lines"}
--]]
})
orientationChanged = _orientationChanged
end
function draw()
background(0, 0, 0, 255)
touches:draw()
canvas:draw()
ui:draw()
debug:draw()
AtEndOfDraw()
end
function touched(touch)
touches:addTouch(touch)
end
function _orientationChanged(o)
ui:orientationChanged(o)
debug:orientationChanged(o)
end
function hide()
ui:hide(5)
canvas:hide()
debug:hide()
end
function unhide()
ui:unhide()
canvas:unhide()
debug:unhide()
end
--[==[
local Drawable = cimport "Drawable"
cimport (BaseLib .. "PictureBrowser")
local Colour = unpack(cimport (BaseLib .. "Colour"))
cimport (BaseLib .. "ColourNames")
cimport (BaseLib .. "CubicSelector")
local Picture = class(Drawable)
local pictureShader
function Picture:init(tb,ui)
Drawable.init(self,"Picture",tb)
self.mesh = mesh()
self.shader = shader()
self.shader.vertexProgram,self.shader.fragmentProgram = pictureShader()
self:updateStyle()
ui:getPicture(
function(i)
self:setpicture(i)
return true
end
)
self.debug = 0
debug:log({ name = "Drawing",
message = function() return self.debug end})
end
function Picture:setpicture(i)
self.mesh.texture = i
self.mesh.shader = self.shader
local c = Colour.svg.White
local w,h = i.width, i.height
local s = math.min(1,
RectAnchorOf(Portrait,"width")/(2*w),
RectAnchorOf(Portrait,"height")/(2*h)
)
local x,y = RectAnchorOf(Portrait,"centre")
w,h = s*w/2,s*h/2
self.corners = {
{vec2(x-w,y-h),c,vec2(0,0)},
{vec2(x-w,y+h),c,vec2(0,1)},
{vec2(x+w,y-h),c,vec2(1,0)},
{vec2(x+w,y+h),c,vec2(1,1)},
}
addQuad({
mesh = self.mesh,
vertices = self.corners,
})
end
function Picture:draw()
self.mesh:draw()
end
function Picture:isTouchedBy(touch)
local a = self.corners[1][1]
local b = self.corners[2][1] - a
local c = self.corners[3][1] - a
local t = vec2(touch.x,touch.y) - a
a = b:cross(c)
if a == 0 then
return false
end
b = b:cross(t)/a
if b < 0 or b > 1 then
return false
end
c = - c:cross(t)/a
if c < 0 or c > 1 then
return false
end
return true
end
function Picture:processTouches(g)
if g.updated then
local c = {}
if g.num == 1 then
local ta = g.touchesArr[1]
local sa = OrientationInverse(PORTRAIT,
vec2(ta.firsttouch.x,ta.firsttouch.y))
local ea = OrientationInverse(PORTRAIT,
vec2(ta.touch.x,ta.touch.y))
for k,v in ipairs(self.corners) do
c[k] = {v[1] + ea - sa}
end
elseif g.num == 2 then
local ta,tb = g.touchesArr[1],g.touchesArr[2]
local sa = OrientationInverse(PORTRAIT,
vec2(ta.firsttouch.x,ta.firsttouch.y))
local ea = OrientationInverse(PORTRAIT,
vec2(ta.touch.x,ta.touch.y))
local sb = OrientationInverse(PORTRAIT,
vec2(tb.firsttouch.x,tb.firsttouch.y))
local eb = OrientationInverse(PORTRAIT,
vec2(tb.touch.x,tb.touch.y))
local s = (eb - ea):len()/ (sb - sa):len()
local ang = (sb - sa):angleBetween(eb - ea)
local sc = (sb + sa)/2
local ec = (ea + eb)/2
for k,v in ipairs(self.corners) do
c[k] = {s*(v[1]-sc):rotate(ang) + ec}
end
end
addQuad({
mesh = self.mesh,
position = 0,
vertices = c,
})
if g.type.ended then
self.corners = c
end
end
if g.type.ended then
g:reset()
end
end
function Picture:updateStyle()
for k,v in pairs(self.style.picture) do
self.shader[k] = v
end
end
pictureShader = function()
return [[
//
// A basic vertex shader
//
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = vec2(texCoord.x, texCoord.y);
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],[[
//
// A basic fragment shader
//
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform lowp vec4 red;
uniform lowp vec4 green;
uniform lowp vec4 blue;
uniform lowp vec4 alpha;
uniform lowp vec4 redcurve;
uniform lowp vec4 greencurve;
uniform lowp vec4 bluecurve;
uniform lowp vec4 alphacurve;
uniform lowp vec4 brightness;
mediump vec4 csat = red + green + blue + alpha;
mediump float cnorm = max(max(max(1.,csat.r),csat.g),csat.b);
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
lowp float cubic(lowp vec4 c, lowp float t)
{
return c.x + c.y * t + c.z * t * t + c.w * t * t * t;
}
void main()
{
//Sample the texture at the interpolated coordinate
mediump vec4 col = texture2D( texture, vTexCoord );
col *= vColor;
col.r = cubic(redcurve, col.r);
col.g = cubic(greencurve, col.g);
col.b = cubic(bluecurve, col.b);
col.a = cubic(alphacurve, col.a);
col.r = cubic(brightness, col.r);
col.g = cubic(brightness, col.g);
col.b = cubic(brightness, col.b);
col = col.r * red + col.g * green + col.b * blue + col.a * alpha;
col.rgb = col.rgb/cnorm;
//Set the output color to the texture color
gl_FragColor = col;
}
]]
end
return Picture
--]==]
-- Shaded
--[==[
local BrushEx = cimport "BrushEx"
local Colour = unpack(cimport (BaseLib .. "Colour"))
local Shaded = class(BrushEx)
function Shaded:init(tb)
BrushEx.init(self,"Shaded",tb)
end
function Shaded:stroke(x,id,t)
local dx, d
for i = 1, self.count do
dx = self.points[i] - x
d = dx:lenSqr()
if (d < 1000) then
self:addLine({
start = x,
finish = self.points[i],
colour = Colour.opacity(self.style.colour,(1 - (d / 1000)) * 10),
ends = true
})
end
end
end
return Shaded
--]==]
-- Simple
--[==[
local Brush = cimport "Brush"
local Simple = class(Brush)
function Simple:init(tb)
Brush.init(self,"Simple",tb)
end
function Simple:stroke(x,id,t)
self:basicStroke(x,id,t)
end
return Simple
--]==]
-- Sketchy
--[==[
local BrushEx = cimport "BrushEx"
local Colour = unpack(cimport (BaseLib .. "Colour"))
local Sketchy = class(BrushEx)
function Sketchy:init(tb)
BrushEx.init(self,"Sketchy",tb)
end
function Sketchy:stroke(x,id,t)
self:basicStroke(x,id,t)
local dx, d
for i = 1, self.count do
dx = self.points[i] - x
d = dx:lenSqr()
if (d < 4000 and math.random() > (d/2000)) then
self:addLine({
start = x + dx * 0.3,
finish = self.points[i] - dx * 0.3,
colour = Colour.opacity(self.style.colour,(1 - (d / 1000)) * 10),
ends = true
})
end
end
end
return Sketchy
--]==]
-- Squares
--[==[
local Brush = cimport "Brush"
local Squares = class(Brush)
function Squares:init(tb)
Brush.init(self,"Squares",tb)
end
function Squares:stroke(x,id,t)
local y = self.lines[id].finish
local dx = x - y
local angle = 1.57079633
local p = dx:rotate(angle)
local corners = {
y - p,
y + p,
x + p,
x - p
}
for k=1,4 do
table.insert(corners,table.remove(corners,1))
self:addLine({
before = corners[1],
start = corners[2],
finish = corners[3],
after = corners[4]
})
end
self.lines[id].finish = x
end
return Squares
--]==]
-- Test
--[[
--[==[
local Brush = cimport "Brush"
local Test = class(Brush)
table.insert(Brushes,Test)
function Test:init(tb)
Brush.init(self,"Test",tb)
end
function Brush:strokeStart(x,id)
self.lines[id] = {}
self.lines.start = vec2(300,300)
self.lines.finish = vec2(300,400)
self:addline(self.lines[id])
end
function Brush:strokeEnd(x,id)
if self.lines[id] then
t = t or {}
local y,th
local l = self.lines[id]
if l.start then
if l.start:distSqr(l.finish) > 100
and math.abs((l.finish - l.start):normalize():rotate90():dot((x - l.finish):normalize())) > .2
then
-- if true then
local bza,bzb
bza,bzb,th = QuickHobby(l.start,l.finish,x,l.th)
local ca = Bezierpt(1/3,unpack(bza))
local cb = Bezierpt(2/3,unpack(bza))
local a,b
if l.previousSegment then
if l.previousSegment.start then
a = (l.previousSegment.finish -
l.previousSegment.start):normalize()
b = (ca -
l.previousSegment.finish):normalize()
if a:dot(b) < -0.5 then
l.previousSegment.after =
l.previousSegment.finish - b
l.before = l.previousSegment.finish + a
else
l.previousSegment.after = ca
end
self:addLine(l.previousSegment)
end
end
local c = {
l.before,
l.start,
ca,
cb,
l.finish,
x}
for i=2,4 do
l.before = c[i-1]
l.start = c[i]
l.finish = c[i+1]
a = (c[i+1] - c[i]):normalize()
b = (c[i+2] - c[i+1]):normalize()
if a:dot(b) < -0.5 then
l.after = l.finish - b
c[i] = l.finish + a
else
l.after = c[i+2]
end
self:addLine(l)
if i ~= 4 then l.position = nil end
end
y = c[4]
else
y = l.start
l.after = x
self:addLine(l)
end
end
l.previousSegment = nil
t.start = l.finish
t.before = y
t.finish = x
t.th = th
t.previousSegment = l
t.startCapData = l.startCapData
t.endCapData = l.endCapData
t.startCap = l.startCap
t.endCap = l.endCap
self.lines[id] = self:addLine(t)
end
self.isfinished = false
end
return Test
--]==]
--]]
--[==[
local Drawable = cimport "Drawable"
local TextNode = cimport (BaseLib .. "TextNode")
local Font = unpack(cimport (BaseLib .. "Font"))
local TextBlock = class(Drawable)
function TextBlock:init(tb,ui)
Drawable.init(self,"TextBlock",tb)
self.node = TextNode({
font = Font({name = self.style.font,
size = self.style.fontsize}),
textColour = self.style.txtcolour,
colour = self.style.txtbgcolour,
width = "20em",
maxHeight = "10lh",
anchor = "centre",
pos = function() return RectAnchorOf(Screen,"centre") end,
fit = true,
ui = ui,
keyboard = self.style.keyboard
})
self.ostyle = {}
for _,v in ipairs({
"font",
"fontsize",
"txtcolour",
"txtbgcolour"
}) do
self.ostyle[v] = self.style[v]
end
end
function TextBlock:draw()
pushMatrix()
resetMatrix()
self.node:draw()
popMatrix()
end
function TextBlock:bake()
self.node:setEdit(false)
pushMatrix()
TransformInverseOrientation(PORTRAIT)
self.node:draw()
popMatrix()
end
function TextBlock:isTouchedBy(touch)
return self.node:isTouchedBy(touch)
end
function TextBlock:processTouches(g)
self.node:processTouches(g)
end
function TextBlock:reset()
end
function TextBlock:updateStyle()
if self.style.txtcolour ~= self.ostyle.txtcolour then
self.node:setTextColour(self.style.txtcolour)
end
if self.style.font ~= self.ostyle.font
or self.style.fontsize ~= self.ostyle.fontsize then
self.node:setFont(Font(
{name = self.style.font, size = self.style.fontsize}))
end
if self.style.txtbgcolour ~= self.ostyle.txtbgcolour then
self.node:setColour(self.style.txtbgcolour)
end
for _,v in ipairs({
"font",
"fontsize",
"txtcolour",
"txtbgcolour"
}) do
self.ostyle[v] = self.style[v]
end
end
return TextBlock
--]==]
-- Web
--[==[
local BrushEx = cimport "BrushEx"
local Colour = unpack(cimport (BaseLib .. "Colour"))
local Web = class(BrushEx)
function Web:init(tb)
BrushEx.init(self,"Web",tb)
end
function Web:stroke(x,id,t)
self:basicStroke(x,id,t)
local c = Colour.opacity(self.style.colour,20)
local dx, d
for i = 1, self.count do
dx = self.points[i] - x
d = dx:lenSqr()
if (d < 2500 and math.random() > 0.9) then
self:addLine({
start = x + dx,
finish = self.points[i] - dx,
colour = c,
ends = true
})
end
end
end
return Web
--]==]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.