Skip to content

Instantly share code, notes, and snippets.

@GymbylCoding
Last active January 6, 2018 19:20
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save GymbylCoding/8fc95e1e8d1ce2c152ef to your computer and use it in GitHub Desktop.
Save GymbylCoding/8fc95e1e8d1ce2c152ef to your computer and use it in GitHub Desktop.
Pixels: Corona SDK Pixel-Based Drawing POC
--------------------------------------------------------------------------------
--[[
Pixels
A simple POC of "pixel-based" drawing in Corona SDK. Implements a simple queue-based flood fill algorithm.
--]]
--------------------------------------------------------------------------------
display.setStatusBar(display.HiddenStatusBar)
--------------------------------------------------------------------------------
-- Localize
--------------------------------------------------------------------------------
local display_newRect = display.newRect
local math_ceil = math.ceil
local table_insert = table.insert
local table_remove = table.remove
--------------------------------------------------------------------------------
-- Variables
--------------------------------------------------------------------------------
local canvas = display.newGroup()
local pixels = {}
local pixelSize = 4
local halfPixelSize = pixelSize * 0.5
local penRadius = 3
local penPixelRadius = penRadius * pixelSize
local penColor = {r = 1, g = 0, b = 0}
local floodFillColor = {r = 0, g = 1, b = 1}
local prevEvent -- So that we can track the previous location of touch
--------------------------------------------------------------------------------
-- Helper Functions
--------------------------------------------------------------------------------
local distanceBetween = function(obj1, obj2) return ((obj2.x - obj1.x) ^ 2 + (obj2.y - obj1.y) ^ 2) ^ 0.5 end
local equalColors = function(t1, t2) return (t2.r == t1.r) and (t2.g == t1.g) and (t2.b == t1.b) end
local getGridXY = function(x, y) return math_ceil(x / pixelSize), math_ceil(y / pixelSize) end
--------------------------------------------------------------------------------
-- Make a Pixel
--------------------------------------------------------------------------------
local function makePixel(x, y)
local pixel = display_newRect(x * pixelSize - halfPixelSize, y * pixelSize - halfPixelSize, pixelSize, pixelSize)
pixel.pixelColor = {r = 1, g = 1, b = 1, a = 1}
pixel.gridX, pixel.gridY = x, y
------------------------------------------------------------------------------
-- Set Pixel Color
------------------------------------------------------------------------------
function pixel:setPixelColor(r, g, b, a)
pixel.pixelColor.r, pixel.pixelColor.g, pixel.pixelColor.b, pixel.pixelColor.a = r, g, b, a or 1
pixel:setFillColor(pixel.pixelColor.r, pixel.pixelColor.g, pixel.pixelColor.b, pixel.pixelColor.a)
end
pixel:setFillColor(pixel.pixelColor.r, pixel.pixelColor.g, pixel.pixelColor.b, pixel.pixelColor.a)
return pixel
end
--------------------------------------------------------------------------------
-- Get Pixels in Range
--------------------------------------------------------------------------------
local function getPixelsInRange(x, y, radius)
local p = {}
for xPos = x - radius, x + radius do
for yPos = y - radius, y + radius do
if pixels[xPos] and pixels[xPos][yPos] then
table_insert(p, pixels[xPos][yPos])
end
end
end
return p
end
--------------------------------------------------------------------------------
-- Color Dot
--------------------------------------------------------------------------------
local function colorDot(dotX, dotY)
local x, y = getGridXY(dotX, dotY)
local pixelsInRange = getPixelsInRange(x, y, penRadius)
for i = 1, #pixelsInRange do
local pixel = pixelsInRange[i]
local dist = distanceBetween(pixel, {x = dotX, y = dotY})
if dist <= penPixelRadius then
pixel:setPixelColor(penColor.r, penColor.g, penColor.b)
end
end
end
--------------------------------------------------------------------------------
-- Basic Flood Fill
--------------------------------------------------------------------------------
local function floodFill(x, y, targetColor, replacementColor)
if equalColors(targetColor, replacementColor) then return end
if pixels[x] and pixels[x][y] then
local queue = {}
local processed = {}
table_insert(queue, pixels[x][y])
while #queue > 0 do
local topNode = table_remove(queue, #queue)
if equalColors(topNode.pixelColor, targetColor) then
topNode:setPixelColor(replacementColor.r, replacementColor.g, replacementColor.b)
local gridX, gridY = topNode.gridX, topNode.gridY
processed[gridX] = processed[gridX] or {}
processed[gridX][gridY] = true
if pixels[gridX - 1] and pixels[gridX - 1][gridY] and (not processed[gridX - 1] or not processed[gridX - 1][gridY]) then
table_insert(queue, pixels[gridX - 1][gridY])
end
if pixels[gridX] and pixels[gridX][gridY - 1] and (not processed[gridX] or not processed[gridX][gridY - 1]) then
table_insert(queue, pixels[gridX][gridY - 1])
end
if pixels[gridX + 1] and pixels[gridX + 1][gridY] and (not processed[gridX + 1] or not processed[gridX + 1][gridY]) then
table_insert(queue, pixels[gridX + 1][gridY])
end
if pixels[gridX] and pixels[gridX][gridY + 1] and (not processed[gridX] or not processed[gridX][gridY + 1]) then
table_insert(queue, pixels[gridX][gridY + 1])
end
end
end
end
end
--------------------------------------------------------------------------------
-- Touch Listener
--------------------------------------------------------------------------------
local function onTouch(event)
if "began" == event.phase then
display.getCurrentStage():setFocus(canvas)
canvas.isFocus = true
prevEvent = event
penColor = {r = math.random(), g = math.random(), b = math.random()}
elseif canvas.isFocus then
if "moved" == event.phase then
local dist = distanceBetween(event, prevEvent) / pixelSize
local xAdd = (prevEvent.x - event.x) / dist
local yAdd = (prevEvent.y - event.y) / dist
for i = 1, dist do
colorDot(event.x + xAdd * i, event.y + yAdd * i)
end
prevEvent = event
elseif "ended" == event.phase or "cancelled" == event.phase then
end
end
end
--------------------------------------------------------------------------------
-- Tap Listener
--------------------------------------------------------------------------------
local function onTap(event)
if event.numTaps == 2 then
local x, y = getGridXY(event.x, event.y)
if pixels[x] and pixels[x][y] then
floodFillColor = {r = math.random(), g = math.random(), b = math.random()}
local pxColor = pixels[x][y].pixelColor
floodFill(x, y, {r = pxColor.r, g = pxColor.g, b = pxColor.b}, floodFillColor)
end
end
end
--------------------------------------------------------------------------------
-- Draw Pixels
--------------------------------------------------------------------------------
for x = 1, display.contentWidth / pixelSize do
pixels[x] = {}
for y = 1, display.contentHeight / pixelSize do
pixels[x][y] = makePixel(x, y)
canvas:insert(pixels[x][y])
end
end
Runtime:addEventListener("touch", onTouch)
Runtime:addEventListener("tap", onTap)
@mulawa1
Copy link

mulawa1 commented Nov 2, 2014

Great work - thank you!

@samwit
Copy link

samwit commented Nov 17, 2014

Very cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment