summary refs log blame commit diff stats
path: root/main.lua
blob: fc26c209b00a1e1d09bf4b349ca9eba39507c245 (plain) (tree)
1
2
3

                       
                                     














                                                                               

                                       





















































































                                                                                      






                                                                             

                              
               











                                                

                      
























                                                  

                                                          










                                                                                







                                                                                 






















                                                           








































                                          

                                           



                                          
                   






















































































                                                                                                    
                         

                                                                                 




                                            
                                          
     
                  
                   
                    
                       
                    









                                   









                                                                   








































                                                                                  
                                                       

                                                                                             
                                                                    



                                 
--[[
    HexArt Image Editor
    Copyright (C) 2019, 2020  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 algorithms = require "algorithms"

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: