Painting app

Made this simple painting program for my 4-year old boy. He put aside Asphalt 8, and started playing with colors. Well, at least for an hour or so he did :wink:

Change the brush color by pressing and holding a colored paint button - it will gradually mix it with the current foreground color. The rectangular button on the right flips the foreground and background colors. The “Clear” button fills the canvas with current background color.

Nothing particularly special about this program, but i felt the need to share it because it is less than 250 lines of code. The power of expression and compactness of code in Codea is most impressive.



Gist
https://gist.github.com/anonymous/9d8ac43e687f8d162891

Or copy from here:


--# Button
Button = class()

function Button:init(label, x, y, w, h, fillColor)
    self.label = label
    self.x, self.y = x, y
    self.w, self.h = w, h
    self.fillColor = fillColor or color(149, 145, 156, 255)
    local c = color(0, 0, 0, 255)
    self.strokeColor = self.fillColor:mix(c, 0.3)
    self._strokeColor = self.strokeColor
    self._fillColor = self.fillColor
    self._white = color(200, 200, 200, 255)
    self._black = color(0, 0, 0, 255)
end

function Button:draw()
    strokeWidth(1.5)
    fill(self.fillColor)
    stroke(self.strokeColor)
    rectMode(CORNER)
    rect(self.x, self.y, self.w, self.h)
    textMode(CORNER)
    font(self.font or "GillSans")
    fill(self.pressed and self._white or self._black)
    fontSize(30)
    text(self.label, self.x+10, self.y+10)
end

function Button:touched(touch)
    local x, y, w, h = self.x, self.y, self.w, self.h
    local clicked = false

    if (x <= touch.x and touch.x <= x+w) then
        if (y <= touch.y and touch.y <= y+h) then
            if touch.state == BEGAN then
                self.pressed = true
                local black = color(0, 0, 0, 255)
                self.fillColor, self.strokeColor = self.strokeColor, self.fillColor
                sound(DATA, "ZgJANwAiQHM6QEBAAAAAADQfND7Cvvs+fwBAf0BAQEA8QEBA")
            elseif touch.state == ENDED and self.pressed then
                clicked = true
            end
        end
    end
    
    if self.pressed and touch.state == ENDED then
        -- reset state
        self.pressed = false
        self.strokeColor = self._strokeColor
        self.fillColor = self._fillColor
    end
    
    if clicked then
        self:clicked()
    end
end

function Button:clicked()
    -- redefine this in subclasses
end

--# Main
-- Painter

supportedOrientations(CurrentOrientation)

function setup()
    displayMode(FULLSCREEN)

    local cw, ch = WIDTH, HEIGHT*0.85
    parameter.integer("brush_radius", 2, 50, 20)
    
    canvas_bc = color(197, 197, 197, 255)
    canvas_fc = color(50, 50, 50, 255)
    
    colors = {}
    colors.red = color(255, 0, 0, 255)
    colors.green = color(2, 255, 0, 255)
    colors.blue = color(9, 0, 255, 255)
    colors.yellow = color(251, 255, 0, 255)
    colors.white = color(255, 255, 255, 255)
    colors.black = color(0, 0, 0, 255)
    colors.orange = color(255, 151, 0, 255)
    colors.violet = color(204, 0, 255, 255)
    
    paints = {
        {clr = colors.red},
        {clr = colors.orange},
        {clr = colors.yellow},
        {clr = colors.green},
        {clr = colors.blue},
        {clr = colors.violet},
        {clr = colors.black},
        {clr = colors.white},
    }
    
    canvas = image(cw, ch)
    setContext(canvas)
    background(canvas_bc)
    setContext()
    
    lastX, lastY = nil, nil
       
    clearButton = Button("Clear", WIDTH-100, HEIGHT-ch-65, 100, 60)
    clearButton.clicked = function(self)
        clearImage(canvas)
    end
    
    invertButton = Button("Invert", WIDTH-205, HEIGHT-ch-65, 100, 60)
    invertButton.clicked = function(self)
        canvas_bc, canvas_fc = canvas_fc, canvas_bc
    end
    invertButton.draw = function(self)
        local x, y, w, h = self.x, self.y, self.w, self.h
        pushStyle()
        strokeWidth(3)
        stroke(canvas_fc)
        fill(canvas_bc)
        smooth()
        rect(x, y, w, h)
        fill(canvas_fc)
        noSmooth()
        rect(x+14, y+14, w-28, h-28)
        popStyle()
    end
end

function drawPaint(b, x, y, w, h)
    pushStyle()
    strokeWidth(3)
    stroke(117, 117, 117, 255)
    fill(76, 76, 76, 255)
    smooth()
    rect(x, y, w, h)
    fill(b.clr)
    noSmooth()
    noStroke()
    local s = b.touched and 3 or 14
    rect(x+s, y+s, w-s*2, h-s*2)
    popStyle()
end

function touchPaint(p, t, touch)
    if touch.state == BEGAN then
        if inside(touch, t.x, t.y, t.w, t.h) then
            sound(SOUND_HIT, 8783)
            p.touched = true
        end 
    elseif touch.state == ENDED then
        p.touched = nil
    end
end

function clearImage(img, c)
    c = c or canvas_bc
    setContext(img)
    background(c)
    setContext()
end

function draw()
    background(0, 0, 0, 255)
    blendMode(NORMAL)
    local cw, ch = spriteSize(canvas)
        
    sprite(canvas, cw/2, HEIGHT-ch/2)
    invertButton:draw()
    clearButton:draw()
    
    local py, pw, ph = HEIGHT-ch-65, 60, 60
    for i,p in ipairs(paints) do
        local x = pw*(i-1)
        drawPaint(p, x, py, pw, ph)
        -- mix color
        if p.touched then
            canvas_fc = canvas_fc:mix(p.clr, 0.99)
        end
    end
end

function touched(touch)
    local cw, ch = spriteSize(canvas)
    
    -- first, handle button events
    invertButton:touched(touch)
    clearButton:touched(touch)
    
    -- paints
    local py, pw, ph = HEIGHT-ch-65, 60, 60
    for i,p in ipairs(paints) do
        local t = {x=pw*(i-1), y=py, w=pw, h=ph}
        touchPaint(p, t, touch)
    end
    
    -- canvas drawing
    canvasTouched(touch)
end
 
function canvasTouched(touch)
    local cw, ch = spriteSize(canvas)
    if inside(touch, 0, HEIGHT-ch, cw, ch) then
        local cx, cy = touch.x, touch.y
        
        if touch.state == MOVING then
            moving = true
            if lastX and lastY then
                pushStyle()
                setContext(canvas)
                strokeWidth(brush_radius)
                stroke(canvas_fc)
                smooth()
                noFill()
                lineCapMode(ROUND)
                line(cx, cy - HEIGHT + ch, lastX, lastY - HEIGHT + ch)
                setContext()
                popStyle()
            end
            setContext()
            lastX, lastY = cx, cy
            
        elseif touch.state == ENDED then
            local wasMoving
            wasMoving, moving = moving, nil
            if not wasMoving then
                setContext(canvas)
                stroke(canvas_fc)
                fill(canvas_fc)
                ellipse(cx, cy - HEIGHT + ch, brush_radius)
                setContext()
            end
            lastX, lastY = nil, nil
            
        elseif touch.state == BEGAN then
            lastX, lastY = cx, cy
        end
    end
end

function inside(touch, x, y, w, h)
    return x <= touch.x and touch.x <= x + w
        and y <= touch.y and touch.y <= y + h
end

Sweet! I like the colour mixing function

And I think you should change your profile pic to the image on the left.

nice?i wanna study painting

@yojimbo2000, lol - good one! :slight_smile:
the image on the left is not quite an auto-portrait, it’s more of this generic drunk dude guy that i keep drawing, for some reason…