-- 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 -- 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 ires[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=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