Add scripts to color-reduce pictures with constraints
Scripts for various Thomson machines, Oric and ZX. All the hard work of Samuel Devulder, with extensive research on color palette reduction algorithms, lots of testing, research and debugging. Thanks a lot!
This commit is contained in:
454
share/grafx2/scripts/samples_2.4/picture/thomson/lib/thomson.lua
Normal file
454
share/grafx2/scripts/samples_2.4/picture/thomson/lib/thomson.lua
Normal file
@@ -0,0 +1,454 @@
|
||||
-- thomson.lua : lots of utility for handling
|
||||
-- thomson screen.
|
||||
--
|
||||
-- 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/>
|
||||
|
||||
if not thomson then
|
||||
|
||||
run("color.lua") -- optionnal
|
||||
|
||||
thomson = {optiMAP=true}
|
||||
|
||||
-- RAM banks
|
||||
thomson.ramA = {}
|
||||
thomson.ramB = {}
|
||||
|
||||
function thomson.clear()
|
||||
for i=1,8000 do
|
||||
thomson.ramA[i] = 0
|
||||
thomson.ramB[i] = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- color levels
|
||||
thomson.levels = {
|
||||
-- in pc-space (0-255):
|
||||
pc = {0,100,127,142,163,179,191,203,215,223,231,239,
|
||||
243,247,251,255},
|
||||
-- in linear space (0-255):
|
||||
linear = {},
|
||||
-- maps pc-levels (0-255) to thomson levels (1-16)
|
||||
pc2to={},
|
||||
-- maps linear-levels (0-255) to thomson levels (1-16)
|
||||
linear2to={}
|
||||
};
|
||||
|
||||
-- pc space to linear space
|
||||
local function toLinear(val)
|
||||
-- use the version from Color library
|
||||
if not Color then
|
||||
val = val/255
|
||||
if val<=0.081 then
|
||||
val = val/4.5;
|
||||
else
|
||||
val = ((val+0.099)/1.099)^2.2;
|
||||
end
|
||||
val = val*255
|
||||
return val;
|
||||
else
|
||||
return Color:new(val,0,0):toLinear().r
|
||||
end
|
||||
end
|
||||
|
||||
for i=1,16 do
|
||||
thomson.levels.linear[i] = toLinear(thomson.levels.pc[i])
|
||||
end
|
||||
for i=0,255 do
|
||||
local r,cm,dm;
|
||||
r,cm,dm = toLinear(i),0,1e30
|
||||
for c,v in ipairs(thomson.levels.linear) do
|
||||
local d = math.abs(v-r);
|
||||
if d<dm then cm,dm = c,d; end
|
||||
end
|
||||
thomson.levels.pc2to[i] = cm;
|
||||
r,cm,dm = i,0,1e30
|
||||
for c,v in ipairs(thomson.levels.linear) do
|
||||
local d = math.abs(v-r);
|
||||
if d<dm then cm,dm = c,d; end
|
||||
end
|
||||
thomson.levels.linear2to[i] = cm;
|
||||
end
|
||||
|
||||
-- palette stuff
|
||||
function thomson.palette(i, pal)
|
||||
-- returns palette #i if pal is missing (nil)
|
||||
-- if pal is a number, sets palette #i
|
||||
-- if pal is an array, sets the palette #i, #i+1, ...
|
||||
if type(pal)=='table' then
|
||||
for j,v in ipairs(pal) do
|
||||
thomson.palette(i+j-1,v)
|
||||
end
|
||||
elseif pal and i>=0 and i<thomson._palette.max then
|
||||
thomson._palette[i+1] = pal
|
||||
elseif not pal and i>=0 and i<thomson._palette.max then
|
||||
return thomson._palette[i+1]
|
||||
end
|
||||
end;
|
||||
thomson._palette = {offset = 0, max=16}
|
||||
thomson.default_palette = {0,15,240,255,3840,3855,4080,4095,
|
||||
1911,826,931,938,2611,2618,3815,123}
|
||||
|
||||
-- border color
|
||||
function thomson.border(c)
|
||||
if c then
|
||||
thomson._border = c;
|
||||
else
|
||||
return thomson._border
|
||||
end
|
||||
end
|
||||
thomson.border(0)
|
||||
|
||||
-- helper to appen tables to tables
|
||||
function thomson._append(result, ...)
|
||||
for _,tab in ipairs({...}) do
|
||||
for _,v in ipairs(tab) do
|
||||
table.insert(result,v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- RLE compression of data into result
|
||||
function thomson._compress(result,data)
|
||||
local partial,p,pmax={},1,#data
|
||||
local function addCarToPartial(car)
|
||||
partial[2] = partial[2]+1
|
||||
partial[2+partial[2]] = car
|
||||
end
|
||||
while p<=pmax do
|
||||
local num,car = 1,data[p]
|
||||
while num<255 and p<pmax and data[p+1]==car do
|
||||
num,p = num+1,p+1
|
||||
end
|
||||
local default=true
|
||||
if partial[1] then
|
||||
-- 01 aa 01 bb ==> 00 02 aa bb
|
||||
if default and num==1 and partial[1]==1 then
|
||||
partial = {0,2,partial[2],car}
|
||||
default = false
|
||||
end
|
||||
-- 00 n xx xx xx 01 bb ==> 00 n+1 xx xx xx bb
|
||||
if default and num==1 and partial[1]==0 and partial[2]<255 then
|
||||
addCarToPartial(car)
|
||||
default = false
|
||||
end
|
||||
-- 00 n xx xx xx 02 bb ==> 00 n+2 xx xx xx bb bb (pas utile mais sert quand combiné à la regle ci-dessus)
|
||||
if default and num==2 and partial[1]==0 and partial[2]<254 then
|
||||
addCarToPartial(car)
|
||||
addCarToPartial(car)
|
||||
default = false
|
||||
end
|
||||
end
|
||||
if default then
|
||||
thomson._append(result, partial)
|
||||
partial = {num,car}
|
||||
end
|
||||
p=p+1
|
||||
end
|
||||
thomson._append(result, partial)
|
||||
return result
|
||||
end
|
||||
|
||||
-- save a map file corresponging to the current file
|
||||
-- if a map file already exist, a confirmation is
|
||||
-- prompted to the user
|
||||
local function save_current_file()
|
||||
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
|
||||
|
||||
-- saves the thomson screen into a MAP file
|
||||
function thomson.savep(name)
|
||||
if not name then return save_current_file() end
|
||||
|
||||
wait(0) -- allow for key handling
|
||||
local data = thomson._get_map_data()
|
||||
local tmp = {0, math.floor(#data/256), #data%256,0,0}
|
||||
thomson._append(tmp,data,{255,0,0,0,0})
|
||||
local function save(name, buf)
|
||||
local out = io.open(name,"wb")
|
||||
out:write(buf)
|
||||
out:close()
|
||||
end
|
||||
save(name, string.char(unpack(tmp)))
|
||||
|
||||
-- save raw data as well ?
|
||||
local moved, key, mx, my, mb = waitinput(0.01)
|
||||
if key==4123 then -- shift-ESC ==> save raw files as well
|
||||
save(name .. ".rama", string.char(unpack(thomson.ramA)))
|
||||
save(name .. ".ramb", string.char(unpack(thomson.ramB)))
|
||||
local pal = ""
|
||||
for i=0,15 do
|
||||
local val = thomson.palette(i)
|
||||
pal=pal..string.char(math.floor(val/256),val%256)
|
||||
end
|
||||
save(name .. ".pal", pal)
|
||||
messagebox('Saved MAP + RAMA/RAMB/PAL files.')
|
||||
end
|
||||
end
|
||||
waitbreak(0.01)
|
||||
|
||||
function thomson.info(...)
|
||||
local txt = ""
|
||||
for _,t in ipairs({...}) do txt = txt .. t end
|
||||
statusmessage(txt);
|
||||
if waitbreak(0)==1 then
|
||||
local ok=false
|
||||
selectbox("Abort ?", "Yes", function() ok = true end, "No", function() ok = false end)
|
||||
if ok then error('Operation aborted') end
|
||||
end
|
||||
end
|
||||
|
||||
-- copy ramA/B onto GrafX2 screen
|
||||
function thomson.updatescreen()
|
||||
-- back out
|
||||
for i=0,255 do
|
||||
setcolor(i,0,0,0)
|
||||
end
|
||||
-- refresh screen content
|
||||
clearpicture(thomson._palette.offset + thomson.border())
|
||||
for y=0,thomson.h-1 do
|
||||
for x=0,thomson.w-1 do
|
||||
local p = thomson.point(x,y)
|
||||
if p<0 then p=-p-1 end
|
||||
thomson._putpixel(x,y,thomson._palette.offset + p)
|
||||
end
|
||||
end
|
||||
-- refresh palette
|
||||
for i=1,thomson._palette.max do
|
||||
local v=thomson._palette[i]
|
||||
local r=v % 16
|
||||
local g=math.floor(v/16) % 16
|
||||
local b=math.floor(v/256) % 16
|
||||
setcolor(i+thomson._palette.offset-1,
|
||||
thomson.levels.pc[r+1],
|
||||
thomson.levels.pc[g+1],
|
||||
thomson.levels.pc[b+1])
|
||||
end
|
||||
updatescreen()
|
||||
end
|
||||
|
||||
-- bitmap 16 mode
|
||||
function thomson.setBM16()
|
||||
-- put a pixel onto real screen
|
||||
function thomson._putpixel(x,y,c)
|
||||
putpicturepixel(x*2+0,y,c)
|
||||
putpicturepixel(x*2+1,y,c)
|
||||
end
|
||||
-- put a pixel in thomson screen
|
||||
function thomson.pset(x,y,c)
|
||||
local bank = x%4<2 and thomson.ramA or thomson.ramB
|
||||
local offs = math.floor(x/4)+y*40+1
|
||||
if x%2==0 then
|
||||
bank[offs] = (bank[offs]%16)+c*16
|
||||
else
|
||||
bank[offs] = math.floor(bank[offs]/16)*16+c
|
||||
end
|
||||
-- c=c+thomson._palette.offset
|
||||
-- putpicturepixel(x*2+0,y,c)
|
||||
-- putpicturepixel(x*2+1,y,c)
|
||||
end
|
||||
-- get thomson pixel at (x,y)
|
||||
function thomson.point(x,y)
|
||||
local bank = x%4<2 and thomson.ramA or thomson.ramB
|
||||
local offs = math.floor(x/4)+y*40+1
|
||||
if x%2==0 then
|
||||
return math.floor(bank[offs]/16)
|
||||
else
|
||||
return bank[offs]%16
|
||||
end
|
||||
end
|
||||
-- return internal MAP file
|
||||
function thomson._get_map_data()
|
||||
local tmp = {}
|
||||
for x=1,40 do
|
||||
for y=x,x+7960,40 do
|
||||
table.insert(tmp, thomson.ramA[y])
|
||||
end
|
||||
for y=x,x+7960,40 do
|
||||
table.insert(tmp, thomson.ramB[y])
|
||||
end
|
||||
wait(0) -- allow for key handling
|
||||
end
|
||||
local pal = {}
|
||||
for i=1,16 do
|
||||
pal[2*i-1] = math.floor(thomson._palette[i]/256)
|
||||
pal[2*i+0] = thomson._palette[i]%256
|
||||
end
|
||||
-- build data
|
||||
local data={
|
||||
-- BM16
|
||||
0x40,
|
||||
-- ncols-1
|
||||
79,
|
||||
-- nlines-1
|
||||
24
|
||||
};
|
||||
thomson._compress(data, tmp)
|
||||
thomson._append(data,{0,0})
|
||||
-- padd to word
|
||||
if #data%2==1 then table.insert(data,0); end
|
||||
-- tosnap
|
||||
thomson._append(data,{0,128,0,thomson.border(),0,3})
|
||||
thomson._append(data, pal)
|
||||
thomson._append(data,{0xa5,0x5a})
|
||||
return data
|
||||
end
|
||||
|
||||
thomson.w = 160
|
||||
thomson.h = 200
|
||||
thomson.palette(0,thomson.default_palette)
|
||||
thomson.border(0)
|
||||
thomson.clear()
|
||||
end
|
||||
|
||||
-- mode MO5
|
||||
function thomson.setMO5()
|
||||
-- put a pixel onto real screen
|
||||
thomson._putpixel = putpicturepixel
|
||||
-- helpers
|
||||
local function bittst(val,mask)
|
||||
-- return bit32.btest(val,mask)
|
||||
return (val % (2*mask))>=mask;
|
||||
end
|
||||
local function bitset(val,mask)
|
||||
-- return bit32.bor(val, mask)
|
||||
return bittst(val,mask) and val or (val+mask)
|
||||
end
|
||||
local function bitclr(val,mask)
|
||||
-- return bit32.band(val,255-mask)
|
||||
return bittst(val,mask) and (val-mask) or val
|
||||
end
|
||||
-- put a pixel in thomson screen
|
||||
function thomson.pset(x,y,c)
|
||||
local offs = math.floor(x/8)+y*40+1
|
||||
local mask = 2^(7-(x%8))
|
||||
if c>=0 then
|
||||
thomson.ramB[offs] = (thomson.ramB[offs]%16)+c*16
|
||||
thomson.ramA[offs] = bitset(thomson.ramA[offs],mask)
|
||||
else
|
||||
c=-c-1
|
||||
thomson.ramB[offs] = math.floor(thomson.ramB[offs]/16)*16+c
|
||||
thomson.ramA[offs] = bitclr(thomson.ramA[offs],mask)
|
||||
end
|
||||
end
|
||||
-- get thomson pixel at (x,y)
|
||||
function thomson.point(x,y)
|
||||
local offs = math.floor(x/8)+y*40+1
|
||||
local mask = 2^(7-(x%8))
|
||||
if bittst(thomson.ramA[offs],mask) then
|
||||
return math.floor(thomson.ramB[offs]/16)
|
||||
else
|
||||
return -(thomson.ramB[offs]%16)-1
|
||||
end
|
||||
end
|
||||
-- convert color from MO5 to TO7 (MAP requires TO7 encoding)
|
||||
local function mo5to7(val)
|
||||
-- MO5: DCBA 4321
|
||||
-- __
|
||||
-- TO7: 4DCB A321
|
||||
local t=((val%16)>=8) and 0 or 128
|
||||
val = math.floor(val/16)*8 + (val%8)
|
||||
val = (val>=64 and val-64 or val+64) + t
|
||||
return val
|
||||
end
|
||||
-- return internal MAP file
|
||||
function thomson._get_map_data()
|
||||
-- create columnwise data
|
||||
local tmpA,tmpB={},{};
|
||||
for x=1,40 do
|
||||
for y=x,x+7960,40 do
|
||||
table.insert(tmpA, thomson.ramA[y])
|
||||
table.insert(tmpB, thomson.ramB[y])
|
||||
end
|
||||
wait(0) -- allow for key handling
|
||||
end
|
||||
if thomson.optiMAP then
|
||||
-- optimize
|
||||
for i=2,8000 do
|
||||
local c1,c2 = math.floor(tmpB[i-0]/16),tmpB[i-0]%16
|
||||
local d1,d2 = math.floor(tmpB[i-1]/16),tmpB[i-1]%16
|
||||
|
||||
if tmpA[i-1]==255-tmpA[i] or c1==d2 and c2==c1 then
|
||||
tmpA[i] = 255-tmpA[i]
|
||||
tmpB[i] = c2*16+c1
|
||||
elseif tmpA[i]==255 and c1==d1 or tmpA[i]==0 and c2==d2 then
|
||||
tmpB[i] = tmpB[i-1]
|
||||
end
|
||||
end
|
||||
else
|
||||
for i=1,8000 do
|
||||
local c1,c2 = math.floor(tmpB[i]/16),tmpB[i]%16
|
||||
|
||||
if tmpA[i]==255 or c1<c2 then
|
||||
tmpA[i] = 255-tmpA[i]
|
||||
tmpB[i] = c2*16+c1
|
||||
end
|
||||
end
|
||||
end
|
||||
-- convert into to7 encoding
|
||||
for i=1,#tmpB do tmpB[i] = mo5to7(tmpB[i]); end
|
||||
-- build data
|
||||
local data={
|
||||
-- BM40
|
||||
0x00,
|
||||
-- ncols-1
|
||||
39,
|
||||
-- nlines-1
|
||||
24
|
||||
};
|
||||
thomson._compress(data, tmpA); tmpA=nil;
|
||||
thomson._append(data,{0,0})
|
||||
thomson._compress(data, tmpB); tmpB=nil;
|
||||
thomson._append(data,{0,0})
|
||||
-- padd to word (for compatibility with basic)
|
||||
if #data%2==1 then table.insert(data,0); end
|
||||
|
||||
-- tosnap
|
||||
local orig_palette = true
|
||||
for i=0,15 do
|
||||
if thomson.default_palette[i+1]~=thomson.palette(i) then
|
||||
orig_palette = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if not orig_palette then
|
||||
local pal = {}
|
||||
for i=0,15 do
|
||||
local v = thomson.palette(i)
|
||||
pal[2*i+1] = math.floor(v/256)
|
||||
pal[2*i+2] = v%256
|
||||
end
|
||||
thomson._append(data,{0,0,0,thomson.border(),0,0})
|
||||
thomson._append(data, pal)
|
||||
thomson._append(data,{0xa5,0x5a})
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
thomson.w = 320
|
||||
thomson.h = 200
|
||||
thomson.palette(0,thomson.default_palette)
|
||||
thomson.border(0)
|
||||
thomson.clear()
|
||||
end
|
||||
|
||||
end -- thomson
|
||||
Reference in New Issue
Block a user