220 lines
5.6 KiB
Lua
220 lines
5.6 KiB
Lua
-- bayer4_mo5.lua : converts an image into TO7/70-MO5
|
|
-- mode for thomson machines (MO6,TO8,TO9,TO9+)
|
|
-- using special bayer matrix that fits well with
|
|
-- color clashes.
|
|
--
|
|
-- Version: 02-jan-2017
|
|
--
|
|
-- Copyright 2016-2017 by Samuel Devulder
|
|
--
|
|
-- This program is free software; you can redistribute
|
|
-- it and/or modify it under the terms of the GNU
|
|
-- General Public License as published by the Free
|
|
-- Software Foundation; version 2 of the License.
|
|
-- See <http://www.gnu.org/licenses/>
|
|
|
|
-- get screen size
|
|
local screen_w, screen_h = getpicturesize()
|
|
|
|
run("lib/thomson.lua")
|
|
run("lib/color.lua")
|
|
run("lib/bayer.lua")
|
|
|
|
-- Converts thomson coordinates (0-319,0-199) into screen coordinates
|
|
local function thom2screen(x,y)
|
|
local i,j;
|
|
if screen_w/screen_h < 1.6 then
|
|
i = x*screen_h/200
|
|
j = y*screen_h/200
|
|
else
|
|
i = x*screen_w/320
|
|
j = y*screen_w/320
|
|
end
|
|
return math.floor(i), math.floor(j)
|
|
end
|
|
|
|
-- return the pixel @(x,y) in normalized linear space (0-1)
|
|
-- corresonding to the thomson screen (x in 0-319, y in 0-199)
|
|
local function getLinearPixel(x,y)
|
|
local x1,y1 = thom2screen(x,y)
|
|
local x2,y2 = thom2screen(x+1,y+1)
|
|
if x2==x1 then x2=x1+1 end
|
|
if y2==y1 then y2=y1+1 end
|
|
|
|
local p,i,j = Color:new(0,0,0);
|
|
for i=x1,x2-1 do
|
|
for j=y1,y2-1 do
|
|
p:add(getLinearPictureColor(i,j))
|
|
end
|
|
end
|
|
p:div((y2-y1)*(x2-x1)*Color.ONE)
|
|
|
|
return p
|
|
end
|
|
|
|
local dither = bayer.norm(bayer.double(bayer.double({{1,2},{3,4}})))
|
|
local dx,dy=#dither,#dither[1]
|
|
|
|
-- get thomson palette pixel (linear, 0-1 range)
|
|
local linearPalette = {}
|
|
function linearPalette.get(i)
|
|
local p = linearPalette[i]
|
|
if not p then
|
|
local pal = thomson.palette(i-1)
|
|
local b=math.floor(pal/256)
|
|
local g=math.floor(pal/16)%16
|
|
local r=pal%16
|
|
p = Color:new(thomson.levels.linear[r+1],
|
|
thomson.levels.linear[g+1],
|
|
thomson.levels.linear[b+1]):div(Color.ONE)
|
|
linearPalette[i] = p
|
|
end
|
|
return p:clone()
|
|
end
|
|
|
|
-- distance between two colors
|
|
local distance = {}
|
|
function distance.between(c1,c2)
|
|
local k = c1..','..c2
|
|
local d = distance[k]
|
|
if false and not d then
|
|
d = linearPalette.get(c1):euclid_dist2(linearPalette.get(c2))
|
|
distance[k] = d
|
|
end
|
|
if not d then
|
|
local x = linearPalette.get(c1):sub(linearPalette.get(c2))
|
|
local c,c1,c2,c3=1.8,8,11,8
|
|
local f = function(c,x) return math.abs(x)*c end
|
|
d = f(c1,x.r)^c + f(c2,x.g)^c + f(c3,x.b)^c
|
|
distance[k] = d
|
|
end
|
|
return d
|
|
end
|
|
|
|
-- compute a set of best couples for a given histogram
|
|
local best_couple = {n=0}
|
|
function best_couple.get(h)
|
|
local k = (((h[1]or 0)*8+(h[2]or 0))*8+(h[3]or 0))*8+(h[4]or 0)
|
|
.. ',' .. (((h[5]or 0)*8+(h[6]or 0))*8+(h[7]or 0))*8+(h[8]or 0)
|
|
local best_found = best_couple[k]
|
|
if not best_found then
|
|
local dm=1000000
|
|
for i=1,15 do
|
|
for j=i+1,16 do
|
|
local d=0
|
|
for p,n in pairs(h) do
|
|
local d1,d2=distance.between(p,i),distance.between(p,j)
|
|
d = d + n*(d1<d2 and d1 or d2)
|
|
if d>dm then break; end
|
|
end
|
|
if d< dm then dm,best_found=d,{} end
|
|
if d<=dm then table.insert(best_found, {c1=i,c2=j}) end
|
|
end
|
|
end
|
|
|
|
if best_couple.n>10000 then
|
|
-- keep memory usage low
|
|
best_couple = {n=0, get=best_couple.get}
|
|
end
|
|
best_couple[k] = best_found
|
|
best_couple.n = best_couple.n+1
|
|
end
|
|
return best_found
|
|
end
|
|
|
|
-- TO7/70 MO5 mode
|
|
thomson.setMO5()
|
|
|
|
-- convert picture
|
|
local err1,err2 = {},{}
|
|
local coefs = {0,0.6,0}
|
|
for x=-1,320 do
|
|
err1[x] = Color:new(0,0,0)
|
|
err2[x] = Color:new(0,0,0)
|
|
end
|
|
for y = 0,199 do
|
|
err1,err2 = err2,err1
|
|
for x=-1,320 do err2[x]:mul(0) end
|
|
|
|
for x = 0,319,8 do
|
|
local h,q = {},{} -- histo, expected color
|
|
for z=x,x+7 do
|
|
local d=dither[1+(y%dx)][1+(z%dx)]
|
|
local p=getLinearPixel(z,y):add(err1[z])
|
|
local c=((p.r>d) and 1 or 0) +
|
|
((p.g>d) and 2 or 0) +
|
|
((p.b>d) and 4 or 0) + 1 -- theorical color
|
|
|
|
table.insert(q,c)
|
|
h[c] = (h[c] or 0)+1
|
|
end
|
|
|
|
local c1,c2
|
|
for c,_ in pairs(h) do
|
|
if c1==nil then c1=c
|
|
elseif c2==nil then c2=c
|
|
else c1=nil; break; end
|
|
end
|
|
if c1~=nil then
|
|
c2 = c2 or c1
|
|
else
|
|
-- get best possible couples of colors
|
|
local best_found = best_couple.get(h)
|
|
if #best_found==1 then
|
|
c1,c2 = best_found[1].c1,best_found[1].c2
|
|
else
|
|
-- keep the best of the best depending on max solvable color clashes
|
|
function clamp(v) return v<0 and -v or v>1 and v-1 or 0 end
|
|
local dm=10000000
|
|
for _,couple in ipairs(best_found) do
|
|
local d=0
|
|
for k=1,8 do
|
|
local q=q[k]
|
|
local p=distance.between(q,couple.c1)<distance.between(q,couple.c2) and couple.c1 or couple.c2
|
|
-- error between expected and best
|
|
local e=linearPalette.get(q):sub(linearPalette.get(p)):mul(coefs[1])
|
|
local z=getLinearPixel(x+k-1,y+1):add(e)
|
|
d = d + clamp(z.r) + clamp(z.g) + clamp(z.b)
|
|
end
|
|
if d<=dm then dm,c1,c2=d,couple.c1,couple.c2 end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- thomson.pset(x,y,c1-1)
|
|
-- thomson.pset(x,y,-c2)
|
|
|
|
for k=0,7 do
|
|
local z=x+k
|
|
local q=q[k+1]
|
|
local p=distance.between(q,c1)<distance.between(q,c2) and c1 or c2
|
|
local d=linearPalette.get(q):sub(linearPalette.get(p))
|
|
err2[z]:add(d:mul(coefs[1]))
|
|
|
|
thomson.pset(z,y,p==c1 and c1-1 or -c2)
|
|
end
|
|
end
|
|
thomson.info("Converting...",math.floor(y/2),"%")
|
|
end
|
|
|
|
-- refresh screen
|
|
setpicturesize(320,200)
|
|
thomson.updatescreen()
|
|
finalizepicture()
|
|
|
|
-- save picture
|
|
do
|
|
local function exist(file)
|
|
local f=io.open(file,'rb')
|
|
if not f then return false else io.close(f); return true; end
|
|
end
|
|
local name,path = getfilename()
|
|
local mapname = string.gsub(name,"%.%w*$","") .. ".map"
|
|
local fullname = path .. '/' .. mapname
|
|
local ok = not exist(fullname)
|
|
if not ok then
|
|
selectbox("Ovr " .. mapname .. "?", "Yes", function() ok = true; end, "No", function() ok = false; end)
|
|
end
|
|
if ok then thomson.savep(fullname) end
|
|
end
|