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:
299
share/grafx2/scripts/samples_2.4/picture/thomson/bayer4_to8.lua
Normal file
299
share/grafx2/scripts/samples_2.4/picture/thomson/bayer4_to8.lua
Normal file
@@ -0,0 +1,299 @@
|
||||
-- bayer4_to8.lua : converts an image into BM16
|
||||
-- mode for thomson machines (MO6,TO8,TO9,TO9+)
|
||||
-- using bayer matrix and a special palette.
|
||||
--
|
||||
-- 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/>
|
||||
|
||||
-- This is my first code in lua, so excuse any bad
|
||||
-- coding practice.
|
||||
|
||||
-- use a zig zag. If false (recommended value), this gives
|
||||
-- a raster look and feel
|
||||
local with_zig_zag = with_zig_zag or false
|
||||
|
||||
-- debug: displays histograms
|
||||
local debug = false
|
||||
|
||||
-- enhance luminosity since our mode divide it by two
|
||||
local enhance_lum = enhance_lum or true
|
||||
|
||||
-- use fixed levels (default=false, give better result)
|
||||
local fixed_levels = fixed_levels or false
|
||||
|
||||
-- use void-and-cluster 8x8 matrix (default=false)
|
||||
local use_vac = use_vac or false
|
||||
|
||||
-- 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-159,0-99) into screen coordinates
|
||||
local function thom2screen(x,y)
|
||||
local i,j;
|
||||
if screen_w/screen_h < 1.6 then
|
||||
i = x*screen_h/100
|
||||
j = y*screen_h/100
|
||||
else
|
||||
i = x*screen_w/160
|
||||
j = y*screen_w/160
|
||||
end
|
||||
return math.floor(i), math.floor(j)
|
||||
end
|
||||
|
||||
-- return the pixel @(x,y) in linear space corresonding to the thomson screen (x in 0-159, y in 0-99)
|
||||
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
|
||||
|
||||
return p:div((y2-y1)*(x2-x1)):floor()
|
||||
end
|
||||
|
||||
--[[ make a bayer matrix
|
||||
function bayer(matrix)
|
||||
local m,n=#matrix,#matrix[1]
|
||||
local r,i,j = {}
|
||||
for j=1,m*2 do
|
||||
local t = {}
|
||||
for i=1,n*2 do t[i]=0; end
|
||||
r[j] = t;
|
||||
end
|
||||
|
||||
-- 0 3
|
||||
-- 2 1
|
||||
for j=1,m do
|
||||
for i=1,n do
|
||||
local v = 4*matrix[j][i]
|
||||
r[m*0+j][n*0+i] = v-3
|
||||
r[m*1+j][n*1+i] = v-2
|
||||
r[m*1+j][n*0+i] = v-1
|
||||
r[m*0+j][n*1+i] = v-0
|
||||
end
|
||||
end
|
||||
|
||||
return r;
|
||||
end
|
||||
--]]
|
||||
|
||||
-- dither matrix
|
||||
local dither = bayer.make(4)
|
||||
|
||||
if use_vac then
|
||||
-- vac8: looks like FS
|
||||
dither = bayer.norm{
|
||||
{35,57,19,55,7,51,4,21},
|
||||
{29,6,41,27,37,17,59,45},
|
||||
{61,15,53,12,62,25,33,9},
|
||||
{23,39,31,49,2,47,13,43},
|
||||
{3,52,8,22,36,58,20,56},
|
||||
{38,18,60,46,30,5,42,28},
|
||||
{63,26,34,11,64,16,54,10},
|
||||
{14,48,1,44,24,40,32,50}
|
||||
}
|
||||
end
|
||||
|
||||
-- get color statistics
|
||||
local stat = {};
|
||||
function stat:clear()
|
||||
self.r = {}
|
||||
self.g = {}
|
||||
self.b = {}
|
||||
for i=1,16 do self.r[i] = 0; self.g[i] = 0; self.b[i] = 0; end
|
||||
end
|
||||
function stat:update(px)
|
||||
local pc2to = thomson.levels.pc2to
|
||||
local r,g,b=pc2to[px.r], pc2to[px.g], pc2to[px.b];
|
||||
self.r[r] = self.r[r] + 1;
|
||||
self.g[g] = self.g[g] + 1;
|
||||
self.b[b] = self.b[b] + 1;
|
||||
end
|
||||
function stat:coversThr(perc)
|
||||
local function f(stat)
|
||||
local t=-stat[1]
|
||||
for i,n in ipairs(stat) do t=t+n end
|
||||
local thr = t*perc; t=-stat[1]
|
||||
for i,n in ipairs(stat) do
|
||||
t=t+n
|
||||
if t>=thr then return i end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
return f(self.r),f(self.g),f(self.b)
|
||||
end
|
||||
stat:clear();
|
||||
for y = 0,99 do
|
||||
for x = 0,159 do
|
||||
stat:update(getLinearPixel(x,y))
|
||||
end
|
||||
thomson.info("Collecting stats...",y,"%")
|
||||
end
|
||||
|
||||
-- enhance luminosity since our mode divide it by two
|
||||
local gain = 1
|
||||
if enhance_lum then
|
||||
-- findout level that covers 98% of all non-black pixels
|
||||
local max = math.max(stat:coversThr(.98))
|
||||
|
||||
gain = math.min(2,255/thomson.levels.linear[max])
|
||||
|
||||
if gain>1 then
|
||||
-- redo stat with enhanced levels
|
||||
-- messagebox('gain '..gain..' '..table.concat({stat:coversThr(.98)},','))
|
||||
stat:clear();
|
||||
for y = 0,99 do
|
||||
for x = 0,159 do
|
||||
stat:update(getLinearPixel(x,y):mul(gain):floor())
|
||||
end
|
||||
thomson.info("Enhancing levels..",y,"%")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- find regularly spaced levels in thomson space
|
||||
local levels = {}
|
||||
function levels.compute(name, stat, num)
|
||||
local tot, max = -stat[1],0;
|
||||
for _,t in ipairs(stat) do
|
||||
max = math.max(t,max)
|
||||
tot = tot + t
|
||||
end
|
||||
local acc,full=-stat[1],0
|
||||
for i,t in ipairs(stat) do
|
||||
acc = acc + t
|
||||
if acc>tot*.98 then
|
||||
full=thomson.levels.linear[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
-- sanity
|
||||
if fixed_levels or full==0 then full=255 end
|
||||
local res = {1}; num = num-1
|
||||
for i=1,num do
|
||||
local p = math.floor(full*i/num)
|
||||
local q = thomson.levels.linear2to[p]
|
||||
if q==res[i] and q<16 then q=q+1 end
|
||||
if not fixed_levels and i<num then
|
||||
if q>res[i]+1 and stat[q-1]>stat[q] then q=q-1 end
|
||||
if q>res[i]+1 and stat[q-1]>stat[q] then q=q-1 end
|
||||
-- 3 corrections? no need...
|
||||
-- if q>res[i]+1 and stat[q-1]>stat[q] then q=q-1 end
|
||||
end
|
||||
res[1+i] = q
|
||||
end
|
||||
|
||||
-- debug
|
||||
if debug then
|
||||
local txt = ""
|
||||
for _,i in ipairs(res) do
|
||||
txt = txt .. i .. " "
|
||||
end
|
||||
for i,t in ipairs(stat) do
|
||||
txt = txt .. "\n" .. string.format("%s%2d:%3d%% ", name, i, math.floor(100*t/(tot+stat[1]))) .. string.rep('X', math.floor(23*t/max))
|
||||
end
|
||||
messagebox(txt)
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
function levels.computeAll(stat)
|
||||
levels.grn = levels.compute("GRN",stat.g,5)
|
||||
levels.red = levels.compute("RED",stat.r,4)
|
||||
levels.blu = levels.compute("BLU",stat.b,3)
|
||||
end
|
||||
levels.computeAll(stat)
|
||||
|
||||
-- put a pixel at (x,y) with dithering
|
||||
local function pset(x,y,px)
|
||||
local thr = dither[1+(y % #dither)][1+(x % #dither[1])]
|
||||
local function dither(val,thr,lvls)
|
||||
local i=#lvls
|
||||
local a,b = thomson.levels.linear[lvls[i]],1e30
|
||||
while i>1 and val<a do
|
||||
i=i-1;
|
||||
a,b=thomson.levels.linear[lvls[i]],a;
|
||||
end
|
||||
return i + ((val-a)>=thr*(b-a) and 0 or -1)
|
||||
end
|
||||
|
||||
local r = dither(px.r, thr, levels.red);
|
||||
local g = dither(px.g, thr, levels.grn);
|
||||
local b = dither(px.b, thr, levels.blu);
|
||||
|
||||
local i = r + b*4
|
||||
local j = g==0 and 0 or (11 + g)
|
||||
|
||||
if with_zig_zag and x%2==1 then
|
||||
thomson.pset(x,y*2+0,j)
|
||||
thomson.pset(x,y*2+1,i)
|
||||
else
|
||||
thomson.pset(x,y*2+0,i)
|
||||
thomson.pset(x,y*2+1,j)
|
||||
end
|
||||
end
|
||||
|
||||
-- BM16 mode
|
||||
thomson.setBM16()
|
||||
|
||||
-- define palette
|
||||
for i=0,15 do
|
||||
local r,g,b=0,0,0
|
||||
if i<12 then
|
||||
-- r = bit32.band(i,3)
|
||||
-- b = bit32.rshift(i,2)
|
||||
b = math.floor(i/4)
|
||||
r = i-4*b
|
||||
else
|
||||
g = i-11
|
||||
end
|
||||
r,g,b=levels.red[r+1],levels.grn[g+1],levels.blu[b+1]
|
||||
thomson.palette(i,b*256+g*16+r-273)
|
||||
end
|
||||
|
||||
-- convert picture
|
||||
for y = 0,99 do
|
||||
for x = 0,159 do
|
||||
pset(x,y, getLinearPixel(x,y):mul(gain):floor())
|
||||
end
|
||||
thomson.info("Converting...",y,"%")
|
||||
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
|
||||
-- fullname = 'D:/tmp/toto.map'
|
||||
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
|
||||
Reference in New Issue
Block a user