300 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			300 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
-- 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
 |