lua.hexart/main.lua

417 lines
11 KiB
Lua

--[[
HexArt Image Editor
Copyright (C) 2019 Soni L.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
--]]
local kt = {}
local t = 0.05
local t0 = 0.5 -- initial cooldown
local TIMEOUT_FIRST = -32767 -- a very negative number
local saved = 0xFFFFFFFFFFFF
function love.keypressed(k)
if k == "escape" then
if saved and saved > love.timer.getTime() then
love.event.quit()
else
saved = love.timer.getTime() + 0.5
end
end
kt[k] = TIMEOUT_FIRST
end
function love.keyreleased(k)
kt[k] = nil
end
local visual_start
local cp = 1
local palt = {}
local imgpalt = {}
local imgt = {}
for i=1,256 do
imgt[i] = 0
end
for i=1,256 do
imgpalt[i] = 0
end
for i=0,255 do
palt[i] = 0
end
for i=0,15 do
palt[i] = i*0x11111100 + 0xFF
imgpalt[i*16 + 1] = 0x11*i
imgpalt[i*16 + 2] = 0x11*i
imgpalt[i*16 + 3] = 0x11*i
imgpalt[i*16 + 4] = 0xFF
end
local batch
local batchid = {} -- should have 512 entries
local hexquads = {}
local palettedata
local paletting
local imagedata
local backimage
local backimagedata
local image
-- PAL_MAX is the max value of a pixel.
-- this is 255 in paletting mode, 15 otherwise.
local PAL_MAX = 15
local mul = 36
local function markupdated(pos, redraw)
local x = (pos-1) % 16
local y = math.floor((pos-1) / 16)
if not batchid[pos * 2] then
batchid[pos * 2] = batch:add(hexquads[math.floor(imgt[pos]/16)], x * mul, y * mul)
batchid[pos * 2 + 1] = batch:add(hexquads[imgt[pos]%16], x * mul + 16, y * mul)
else
batch:set(batchid[pos * 2], hexquads[math.floor(imgt[pos]/16)], x * mul, y * mul)
batch:set(batchid[pos * 2 + 1], hexquads[imgt[pos]%16], x * mul + 16, y * mul)
end
local rgba = palt[imgt[pos]]
local r = math.floor(rgba / 16777216) % 256
local g = math.floor(rgba / 65536) % 256
local b = math.floor(rgba / 256) % 256
local a = rgba % 256
imagedata:setPixel(x, y, r/255, g/255, b/255, a/255)
if paletting then
local r, g, b, a = imgt[y*16+1], imgt[y*16+2], imgt[y*16+3], imgt[y*16+4]
imagedata:setPixel(3, y, r/255, g/255, b/255, a/255)
end
if redraw then
image:replacePixels(imagedata)
end
end
local function change_val(amt)
for i=math.min(visual_start or cp, cp), math.max(visual_start or cp, cp) do
imgt[i] = math.max(math.min(imgt[i] + amt, PAL_MAX), 0)
markupdated(i, true)
end
end
function love.wheelmoved(x, y)
saved = nil
change_val(y)
end
function setback(pos, highlight)
local x = (pos-1)%16
local y = math.floor((pos-1)/16)
if highlight then
backimagedata:setPixel(x, y, 0.5, 0, 0, 1)
else
backimagedata:setPixel(x, y, 0, 0, 0, 0)
end
end
local in_mouse = false
local function palette()
-- Called to enable/disable palette editing mode
paletting = not paletting
imagedata, palettedata = palettedata, imagedata
imgt, imgpalt = imgpalt, imgt
PAL_MAX = paletting and 255 or 15
if paletting then
for i=0,255 do
palt[i] = i*0x01010100 + 0xff
end
else
for i=0,15 do
local r = imgpalt[i*16+1]
local g = imgpalt[i*16+2]
local b = imgpalt[i*16+3]
local a = imgpalt[i*16+4]
palt[i] = ((r*256+g)*256+b)*256+a
end
end
for i=1,256 do
markupdated(i, false)
end
image:replacePixels(imagedata)
end
function love.mousepressed(x, y, button, istouch, presses)
if button == 1 then
local rawx, rawy = x, y
local x, y = math.floor(x / 36), math.floor(y / 36)
if x > 15 or y > 15 then
if rawx > 576 then
-- TODO replace with more efficient graphics
if rawy > 130 and rawy <= 130 + love.graphics.getFont():getHeight() then
palette()
end
end
return
end
if visual_start then
for i=math.min(visual_start or cp, cp), math.max(visual_start or cp, cp) do
setback(i, false)
end
visual_start = false
else
setback(cp, false)
end
cp = y * 16 + x + 1
setback(cp, true)
backimage:replacePixels(backimagedata)
in_mouse = true
end
end
function love.mousereleased(x, y, button, istouch, presses)
in_mouse = false
end
function love.mousemoved(x, y, dx, dy, istouch)
if in_mouse then
if visual_start then
local x, y = math.floor(x / 36), math.floor(y / 36)
if x > 15 or y > 15 then return end
cp = y * 16 + x + 1
else
visual_start = cp
end
end
end
function love.update(dt)
local function ktup(key, func)
if kt[key] then
local nt = kt[key] - dt
kt[key] = nt
if nt <= 0 then
if nt <= TIMEOUT_FIRST then
kt[key] = t0
else
kt[key] = t
end
return func()
end
end
end
local function imgup()
setback(cp, false)
cp = math.max(cp - 16, 1)
setback(cp, true)
backimage:replacePixels(backimagedata)
end
local function imgdown()
setback(cp, false)
cp = math.min(cp + 16, 256)
setback(cp, true)
backimage:replacePixels(backimagedata)
end
local function imgleft()
setback(cp, false)
cp = math.max(cp - 1, 1)
setback(cp, true)
backimage:replacePixels(backimagedata)
end
local function imgright()
setback(cp, false)
cp = math.min(cp + 1, 256)
setback(cp, true)
backimage:replacePixels(backimagedata)
end
local function decr()
saved = nil
local amt = -(kt['lshift'] and 16 or 1)
change_val(amt)
end
local function incr()
saved = nil
local amt = (kt['lshift'] and 16 or 1)
change_val(amt)
end
local function light_propagation_up()
if visual_start then
print("[NYI] light_propagation_up + visual_start")
return
end
saved = nil
local initialValue = imgt[cp]
local newValue = math.min(initialValue + 1, 15)
if initialValue == newValue then return end
local function recurse(pos, dx, dy, targetValue)
local xpos = (pos - 1) % 16 + 1
local ypos = ((pos - xpos)/16) + 1
local npos
if dx < 0 and xpos > 1 then
npos = pos - 1
elseif dx > 0 and xpos < 16 then
npos = pos + 1
elseif dy > 0 and ypos < 16 then
npos = pos + 16
flag = print
elseif dy < 0 and ypos > 1 then
npos = pos - 16
end
if npos and imgt[npos] < targetValue then
imgt[npos] = targetValue
markupdated(npos, false)
recurse(npos, dx, dy, targetValue - 1)
recurse(npos, dy, -dx, targetValue - 1)
end
end
imgt[cp] = newValue
markupdated(cp, false)
recurse(cp, 0, 1, newValue - 1)
recurse(cp, 1, 0, newValue - 1)
recurse(cp, 0, -1, newValue - 1)
recurse(cp, -1, 0, newValue - 1)
image:replacePixels(imagedata)
end
local function light_propagation_down()
if true then
print("[NYI] light_propagation_down")
return
end
if visual_start then
print("[NYI] light_propagation_down + visual_start")
return
end
saved = nil
local initialValue = imgt[cp]
local newValue = math.max(initialValue - 1, 0)
if initialValue == newValue then return end
local function recurse(pos, dx, dy, targetValue)
local xpos = (pos - 1) % 16 + 1
local ypos = ((pos - xpos)/16) + 1
local npos
if dx < 0 and xpos > 1 then
npos = pos - 1
elseif dx > 0 and xpos < 16 then
npos = pos + 1
elseif dy > 0 and ypos < 16 then
npos = pos + 16
flag = print
elseif dy < 0 and ypos > 1 then
npos = pos - 16
end
if npos and imgt[npos] < targetValue then
imgt[npos] = targetValue
markupdated(npos, false)
recurse(npos, dx, dy, targetValue - 1)
recurse(npos, dy, -dx, targetValue - 1)
end
end
imgt[cp] = newValue
markupdated(cp, false)
recurse(cp, 0, 1, newValue - 1)
recurse(cp, 1, 0, newValue - 1)
recurse(cp, 0, -1, newValue - 1)
recurse(cp, -1, 0, newValue - 1)
image:replacePixels(imagedata)
end
local function save()
saved = 0xFFFFFFFFFFFF
local data = imagedata:encode("png")
local name = love.data.encode("string", "hex", love.data.hash("sha1", data):sub(1, 7)) .. ".png"
love.filesystem.write("img-" .. name, data)
end
local function visual()
if visual_start then
for i=math.min(visual_start or cp, cp), math.max(visual_start or cp, cp) do
setback(i, false)
end
setback(cp, true)
backimage:replacePixels(backimagedata)
end
visual_start = not visual_start and cp
end
ktup('k', imgup)
ktup('up', imgup)
ktup('h', imgleft)
ktup('left', imgleft)
ktup('j', imgdown)
ktup('down', imgdown)
ktup('l', imgright)
ktup('right', imgright)
ktup('z', decr)
ktup('x', incr)
ktup('a', light_propagation_up)
ktup('s', light_propagation_down)
ktup('w', save)
ktup('p', palette)
ktup('v', visual)
if visual_start then
-- TODO optimize visual mode
for i=1,255 do
setback(i, false)
end
for i=math.min(visual_start, cp), math.max(visual_start, cp) do
setback(i, true)
end
backimage:replacePixels(backimagedata)
end
end
local quad
local backquad
local heximage
function love.load()
imagedata = love.image.newImageData(16, 16)
backimagedata = love.image.newImageData(16, 16)
palettedata = love.image.newImageData(16, 16)
image = love.graphics.newImage(imagedata, {mipmaps=false})
backimage = love.graphics.newImage(backimagedata, {mipmaps=false})
image:setFilter("nearest", "nearest")
backimage:setFilter("nearest", "nearest")
quad = love.graphics.newQuad(0, 0, 16, 16, 16, 16)
backquad = love.graphics.newQuad(0, 0, 16, 16, 16, 16)
heximage = love.graphics.newImage("hex.png")
for i = 0, 15 do
hexquads[i] = love.graphics.newQuad(i*16, 0, 16, 32, heximage:getDimensions())
end
batch = love.graphics.newSpriteBatch(heximage)
for i=1,256 do
markupdated(i, false)
end
image:replacePixels(imagedata)
for x=0, 15 do
for y=0, 15 do
backimagedata:setPixel(x, y, 0, 0, 0, 0)
end
end
local x = (cp-1)%16
local y = math.floor((cp-1)/16)
backimagedata:setPixel(x, y, 0.5, 0, 0, 1)
backimage:replacePixels(backimagedata)
end
function love.draw()
love.graphics.draw(image, quad, 576, 0, 0, 8)
love.graphics.draw(backimage, backquad, 0, 0, 0, mul)
local min, max = math.min(visual_start or cp, cp), math.max(visual_start or cp, cp)
love.graphics.print((visual_start and tostring(min) .. ":" or "") .. tostring(max), 0, 588)
love.graphics.print(paletting and "palette" or "normal", 576, 130)
love.graphics.draw(batch, 2, 2)
end
-- vim: set sw=2 sts=2: