samples_2.4 => samples
This commit is contained in:
committed by
Adrien Destugues
parent
428235636a
commit
0a38b78a9c
@@ -0,0 +1,629 @@
|
||||
-- ostromoukhov.lua : Color dithering using variable
|
||||
-- coefficients.
|
||||
--
|
||||
-- https://liris.cnrs.fr/victor.ostromoukhov/publications/pdf/SIGGRAPH01_varcoeffED.pdf
|
||||
--
|
||||
-- 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/>
|
||||
|
||||
run('color.lua')
|
||||
run('thomson.lua')
|
||||
|
||||
if not OstroDither then
|
||||
|
||||
OstroDither = {}
|
||||
|
||||
local function default_levels()
|
||||
return {r={0,Color.ONE},g={0,Color.ONE},b={0,Color.ONE}}
|
||||
end
|
||||
|
||||
function OstroDither:new(palette,attenuation,levels)
|
||||
local o = {
|
||||
attenuation = attenuation or .9, -- works better than 1
|
||||
palette = palette or thomson.default_palette,
|
||||
levels = levels or default_levels(),
|
||||
clash_size = 8 -- for color clash
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function OstroDither:setLevelsFromPalette()
|
||||
local rLevels = {[1]=true,[16]=true}
|
||||
local gLevels = {[1]=true,[16]=true}
|
||||
local bLevels = {[1]=true,[16]=true}
|
||||
local default_palette = true
|
||||
for i,pal in ipairs(self.palette) do
|
||||
local r,g,b=pal%16,math.floor(pal/16)%16,math.floor(pal/256)
|
||||
rLevels[1+r] = true
|
||||
gLevels[1+g] = true
|
||||
bLevels[1+b] = true
|
||||
if pal~=thomson.default_palette[i] then
|
||||
default_palette = false
|
||||
end
|
||||
end
|
||||
local levels = {r={},g={},b={}}
|
||||
for i,v in ipairs(thomson.levels.linear) do
|
||||
if false then
|
||||
if rLevels[i] and gLevels[i] and bLevels[i] then
|
||||
table.insert(levels.r, v)
|
||||
table.insert(levels.g, v)
|
||||
table.insert(levels.b, v)
|
||||
end
|
||||
else
|
||||
if rLevels[i] then table.insert(levels.r, v) end
|
||||
if gLevels[i] then table.insert(levels.g, v) end
|
||||
if bLevels[i] then table.insert(levels.b, v) end
|
||||
end
|
||||
end
|
||||
self.levels = levels
|
||||
if default_palette then
|
||||
self.attenuation = .98
|
||||
self.levels = default_levels()
|
||||
else
|
||||
self.attenuation = .9
|
||||
self.levels = levels
|
||||
end
|
||||
end
|
||||
|
||||
function OstroDither:_coefs(linearLevel,rgb)
|
||||
if self._ostro==nil then
|
||||
-- original coefs, about to be adapted to the levels
|
||||
local t={
|
||||
13, 0, 5,
|
||||
13, 0, 5,
|
||||
21, 0, 10,
|
||||
7, 0, 4,
|
||||
8, 0, 5,
|
||||
47, 3, 28,
|
||||
23, 3, 13,
|
||||
15, 3, 8,
|
||||
22, 6, 11,
|
||||
43, 15, 20,
|
||||
7, 3, 3,
|
||||
501, 224, 211,
|
||||
249, 116, 103,
|
||||
165, 80, 67,
|
||||
123, 62, 49,
|
||||
489, 256, 191,
|
||||
81, 44, 31,
|
||||
483, 272, 181,
|
||||
60, 35, 22,
|
||||
53, 32, 19,
|
||||
237, 148, 83,
|
||||
471, 304, 161,
|
||||
3, 2, 1,
|
||||
459, 304, 161,
|
||||
38, 25, 14,
|
||||
453, 296, 175,
|
||||
225, 146, 91,
|
||||
149, 96, 63,
|
||||
111, 71, 49,
|
||||
63, 40, 29,
|
||||
73, 46, 35,
|
||||
435, 272, 217,
|
||||
108, 67, 56,
|
||||
13, 8, 7,
|
||||
213, 130, 119,
|
||||
423, 256, 245,
|
||||
5, 3, 3,
|
||||
281, 173, 162,
|
||||
141, 89, 78,
|
||||
283, 183, 150,
|
||||
71, 47, 36,
|
||||
285, 193, 138,
|
||||
13, 9, 6,
|
||||
41, 29, 18,
|
||||
36, 26, 15,
|
||||
289, 213, 114,
|
||||
145, 109, 54,
|
||||
291, 223, 102,
|
||||
73, 57, 24,
|
||||
293, 233, 90,
|
||||
21, 17, 6,
|
||||
295, 243, 78,
|
||||
37, 31, 9,
|
||||
27, 23, 6,
|
||||
149, 129, 30,
|
||||
299, 263, 54,
|
||||
75, 67, 12,
|
||||
43, 39, 6,
|
||||
151, 139, 18,
|
||||
303, 283, 30,
|
||||
38, 36, 3,
|
||||
305, 293, 18,
|
||||
153, 149, 6,
|
||||
307, 303, 6,
|
||||
1, 1, 0,
|
||||
101, 105, 2,
|
||||
49, 53, 2,
|
||||
95, 107, 6,
|
||||
23, 27, 2,
|
||||
89, 109, 10,
|
||||
43, 55, 6,
|
||||
83, 111, 14,
|
||||
5, 7, 1,
|
||||
172, 181, 37,
|
||||
97, 76, 22,
|
||||
72, 41, 17,
|
||||
119, 47, 29,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
65, 18, 17,
|
||||
95, 29, 26,
|
||||
185, 62, 53,
|
||||
30, 11, 9,
|
||||
35, 14, 11,
|
||||
85, 37, 28,
|
||||
55, 26, 19,
|
||||
80, 41, 29,
|
||||
155, 86, 59,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
305, 176, 119,
|
||||
155, 86, 59,
|
||||
105, 56, 39,
|
||||
80, 41, 29,
|
||||
65, 32, 23,
|
||||
55, 26, 19,
|
||||
335, 152, 113,
|
||||
85, 37, 28,
|
||||
115, 48, 37,
|
||||
35, 14, 11,
|
||||
355, 136, 109,
|
||||
30, 11, 9,
|
||||
365, 128, 107,
|
||||
185, 62, 53,
|
||||
25, 8, 7,
|
||||
95, 29, 26,
|
||||
385, 112, 103,
|
||||
65, 18, 17,
|
||||
395, 104, 101,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
395, 104, 101,
|
||||
65, 18, 17,
|
||||
385, 112, 103,
|
||||
95, 29, 26,
|
||||
25, 8, 7,
|
||||
185, 62, 53,
|
||||
365, 128, 107,
|
||||
30, 11, 9,
|
||||
355, 136, 109,
|
||||
35, 14, 11,
|
||||
115, 48, 37,
|
||||
85, 37, 28,
|
||||
335, 152, 113,
|
||||
55, 26, 19,
|
||||
65, 32, 23,
|
||||
80, 41, 29,
|
||||
105, 56, 39,
|
||||
155, 86, 59,
|
||||
305, 176, 119,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
5, 3, 2,
|
||||
155, 86, 59,
|
||||
80, 41, 29,
|
||||
55, 26, 19,
|
||||
85, 37, 28,
|
||||
35, 14, 11,
|
||||
30, 11, 9,
|
||||
185, 62, 53,
|
||||
95, 29, 26,
|
||||
65, 18, 17,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
4, 1, 1,
|
||||
119, 47, 29,
|
||||
72, 41, 17,
|
||||
97, 76, 22,
|
||||
172, 181, 37,
|
||||
5, 7, 1,
|
||||
83, 111, 14,
|
||||
43, 55, 6,
|
||||
89, 109, 10,
|
||||
23, 27, 2,
|
||||
95, 107, 6,
|
||||
49, 53, 2,
|
||||
101, 105, 2,
|
||||
1, 1, 0,
|
||||
307, 303, 6,
|
||||
153, 149, 6,
|
||||
305, 293, 18,
|
||||
38, 36, 3,
|
||||
303, 283, 30,
|
||||
151, 139, 18,
|
||||
43, 39, 6,
|
||||
75, 67, 12,
|
||||
299, 263, 54,
|
||||
149, 129, 30,
|
||||
27, 23, 6,
|
||||
37, 31, 9,
|
||||
295, 243, 78,
|
||||
21, 17, 6,
|
||||
293, 233, 90,
|
||||
73, 57, 24,
|
||||
291, 223, 102,
|
||||
145, 109, 54,
|
||||
289, 213, 114,
|
||||
36, 26, 15,
|
||||
41, 29, 18,
|
||||
13, 9, 6,
|
||||
285, 193, 138,
|
||||
71, 47, 36,
|
||||
283, 183, 150,
|
||||
141, 89, 78,
|
||||
281, 173, 162,
|
||||
5, 3, 3,
|
||||
423, 256, 245,
|
||||
213, 130, 119,
|
||||
13, 8, 7,
|
||||
108, 67, 56,
|
||||
435, 272, 217,
|
||||
73, 46, 35,
|
||||
63, 40, 29,
|
||||
111, 71, 49,
|
||||
149, 96, 63,
|
||||
225, 146, 91,
|
||||
453, 296, 175,
|
||||
38, 25, 14,
|
||||
459, 304, 161,
|
||||
3, 2, 1,
|
||||
471, 304, 161,
|
||||
237, 148, 83,
|
||||
53, 32, 19,
|
||||
60, 35, 22,
|
||||
483, 272, 181,
|
||||
81, 44, 31,
|
||||
489, 256, 191,
|
||||
123, 62, 49,
|
||||
165, 80, 67,
|
||||
249, 116, 103,
|
||||
501, 224, 211,
|
||||
7, 3, 3,
|
||||
43, 15, 20,
|
||||
22, 6, 11,
|
||||
15, 3, 8,
|
||||
23, 3, 13,
|
||||
47, 3, 28,
|
||||
8, 0, 5,
|
||||
7, 0, 4,
|
||||
21, 0, 10,
|
||||
13, 0, 5,
|
||||
13, 0, 5}
|
||||
local function process(tab)
|
||||
local tab2={}
|
||||
local function add(i)
|
||||
i=3*math.floor(i+.5)
|
||||
local c0,c1,c2=t[i+1],t[i+2],t[i+3]
|
||||
local norm=self.attenuation/(c0+c1+c2)
|
||||
table.insert(tab2,c0*norm)
|
||||
table.insert(tab2,c1*norm)
|
||||
table.insert(tab2,c2*norm)
|
||||
end
|
||||
local function level(i)
|
||||
return tab[i]*255/Color.ONE
|
||||
end
|
||||
local a,b,j=level(1),level(2),3
|
||||
for i=0,255 do
|
||||
if i>b then a,b,j=b,level(j),j+1; end
|
||||
add(255*(i-a)/(b-a))
|
||||
end
|
||||
return tab2
|
||||
end
|
||||
self._ostro = {r=process(self.levels.r),
|
||||
g=process(self.levels.g),
|
||||
b=process(self.levels.b)}
|
||||
end
|
||||
local i = math.floor(linearLevel[rgb]*255/Color.ONE+.5)
|
||||
i = 3*(i<0 and 0 or i>255 and 255 or i)
|
||||
return self._ostro[rgb][i+1],self._ostro[rgb][i+2],self._ostro[rgb][i+3]
|
||||
end
|
||||
|
||||
function OstroDither:_linearPalette(colorIndex)
|
||||
if self._linear==nil then
|
||||
self._linear = {}
|
||||
local t=thomson.levels.linear
|
||||
for i,pal in ipairs(self.palette) do
|
||||
local r,g,b=pal%16,math.floor(pal/16)%16,math.floor(pal/256)
|
||||
self._linear[i] = Color:new(t[1+r],t[1+g],t[1+b])
|
||||
end
|
||||
end
|
||||
return self._linear[colorIndex]
|
||||
end
|
||||
|
||||
function OstroDither:getColorIndex(linearPixel)
|
||||
local k=linearPixel:hash(64)
|
||||
local c=self[k]
|
||||
if c==nil then
|
||||
local dm=1e30
|
||||
for i=1,#self.palette do
|
||||
local d = self:_linearPalette(i):dist2(linearPixel)
|
||||
if d<dm then dm,c=d,i end
|
||||
end
|
||||
self[k] = c
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
function OstroDither:_diffuse(linearColor,err, err0,err1,err2)
|
||||
local c=self:getColorIndex(err:add(linearColor))
|
||||
local M = Color.ONE
|
||||
|
||||
err:sub(self:_linearPalette(c))
|
||||
local function d(rgb)
|
||||
local e = err[rgb]
|
||||
function f(a,c)
|
||||
a=a+c*e
|
||||
return a<-M and -M or
|
||||
a> M and M or a
|
||||
end
|
||||
local c0,c1,c2=self:_coefs(linearColor,rgb)
|
||||
if err0 and c0>0 then err0[rgb] = f(err0[rgb],c0) end
|
||||
if err1 and c1>0 then err1[rgb] = f(err1[rgb],c1) end
|
||||
if err2 and c2>0 then err2[rgb] = f(err2[rgb],c2) end
|
||||
end
|
||||
d("r"); d("g"); d("b")
|
||||
|
||||
return c
|
||||
end
|
||||
|
||||
function OstroDither:dither(screen_w,screen_h,getLinearPixel,pset,serpentine,info)
|
||||
if not info then info = function(y) thomson.info() end end
|
||||
if not serpentine then serpentine = true end
|
||||
|
||||
local err1,err2 = {},{}
|
||||
for x=-1,screen_w do
|
||||
err1[x] = Color:new(0,0,0)
|
||||
err2[x] = Color:new(0,0,0)
|
||||
end
|
||||
|
||||
for y=0,screen_h-1 do
|
||||
-- permute error buffers
|
||||
err1,err2 = err2,err1
|
||||
-- clear current-row's buffer
|
||||
for i=-1,screen_w do err2[i]:mul(0) end
|
||||
|
||||
local x0,x1,xs=0,screen_w-1,1
|
||||
if serpentine and y%2==1 then x0,x1,xs=x1,x0,-xs end
|
||||
|
||||
for x=x0,x1,xs do
|
||||
local p = getLinearPixel(x,y,xs,err1)
|
||||
local c = self:_diffuse(p,err1[x],err1[x+xs],
|
||||
err2[x-xs],err2[x])
|
||||
pset(x,y,c-1)
|
||||
end
|
||||
info(y)
|
||||
end
|
||||
end
|
||||
|
||||
function OstroDither:ccAcceptCouple(c1,c2)
|
||||
return c1~=c2
|
||||
end
|
||||
|
||||
function OstroDither:ccDither(screen_w,screen_h,getLinearPixel,pset,serpentine,info) -- dither with color clash
|
||||
local c1,c2
|
||||
self.getColorIndex = function(self,p)
|
||||
return p:dist2(self:_linearPalette(c1))<p:dist2(self:_linearPalette(c2)) and c1 or c2
|
||||
end
|
||||
|
||||
local function _pset(x,y,c)
|
||||
pset(x,y,c==c1-1 and c or -c2)
|
||||
end
|
||||
|
||||
local findC1C2 = function(x,y,xs,err1)
|
||||
-- collect the data we are working on
|
||||
local gpl = {
|
||||
clone = function(self)
|
||||
local r={}
|
||||
for i,v in ipairs(self) do
|
||||
r[i] = {pix=v.pix:clone(),
|
||||
err=v.err:clone()}
|
||||
end
|
||||
return r
|
||||
end,
|
||||
fill = function(self,dither)
|
||||
for i=x,x+(dither.clash_size-1)*xs,xs do
|
||||
table.insert(self,
|
||||
{pix=getLinearPixel(i,y),
|
||||
err=err1[i]})
|
||||
end
|
||||
table.insert(self, {pix=Color:new(),
|
||||
err=Color:new()})
|
||||
end
|
||||
}
|
||||
gpl:fill(self)
|
||||
|
||||
local histo = {
|
||||
fill = function(self,dither)
|
||||
local t=gpl:clone()
|
||||
for i=1,#dither.palette do self[i] = {n=0,c=i} end
|
||||
local back = dither.getColorIndex
|
||||
dither.getColorIndex = OstroDither.getColorIndex
|
||||
for i=1,dither.clash_size do
|
||||
local c = dither:_diffuse(t[i].pix,t[i].err,
|
||||
t[i+1].err)
|
||||
self[c].n = self[c].n+1
|
||||
end
|
||||
dither.getColorIndex = back
|
||||
table.sort(self, function(a,b)
|
||||
return a.n>b.n or a.n==b.n and a.c<b.c end)
|
||||
end,
|
||||
get = function(self,i,...)
|
||||
if i then
|
||||
return self[i].c,self:get(...)
|
||||
end
|
||||
end,
|
||||
num = function(self,i,...)
|
||||
if i then
|
||||
return self[i].n,self:num(...)
|
||||
end
|
||||
end,
|
||||
sum = function(self,i,...)
|
||||
return i and self[i].n+self:sum(...) or 0
|
||||
end
|
||||
}
|
||||
histo:fill(self)
|
||||
|
||||
c1,c2=histo:get(1,2)
|
||||
|
||||
if not self:ccAcceptCouple(c1,c2) or histo:sum(1,2)<=self.clash_size-2 then
|
||||
info(y)
|
||||
local dm=1e30
|
||||
local function eval()
|
||||
if self:ccAcceptCouple(c1,c2) then
|
||||
local d,t = 0,gpl:clone()
|
||||
for i=1,self.clash_size do
|
||||
local err=t[i].err
|
||||
self:_diffuse(t[i].pix,err,t[i+1].err)
|
||||
d = d + err.r^2 + err.g^2 + err.b^2
|
||||
if d>dm then break end
|
||||
end
|
||||
return d
|
||||
else
|
||||
return dm
|
||||
end
|
||||
end
|
||||
dm=eval()
|
||||
|
||||
if histo:num(1)>=self.clash_size/2+1 then
|
||||
local z=c2
|
||||
for i=1,#self.palette do c2=i
|
||||
local d=eval()
|
||||
if d<dm then dm,z=d,i end
|
||||
end
|
||||
c2=z
|
||||
else
|
||||
local a,b=c1,c2
|
||||
for i=1,#self.palette-1 do c1=i
|
||||
for j=1+i,#self.palette do c2=j
|
||||
local d=eval()
|
||||
if d<dm then dm,a,b=d,i,j end
|
||||
end
|
||||
end
|
||||
c1,c2=a,b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _getLinearPixel(x,y,xs,err1)
|
||||
if x%self.clash_size==(xs>0 and 0 or self.clash_size-1) then
|
||||
findC1C2(x,y,xs,err1)
|
||||
end
|
||||
return getLinearPixel(x,y)
|
||||
end
|
||||
|
||||
self:dither(screen_w,screen_h,_getLinearPixel,_pset,serpentine,info)
|
||||
end
|
||||
|
||||
function OstroDither:dither40cols(getpalette,serpentine)
|
||||
-- get screen size
|
||||
local screen_w, screen_h = getpicturesize()
|
||||
|
||||
-- Converts thomson coordinates (0-159,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 Color @(x,y) in linear space (0-255)
|
||||
-- corresonding to the thomson screen (x in 0-319,
|
||||
-- y in 0-199)
|
||||
local function getLinearPixel(x,y)
|
||||
local with_cache = true
|
||||
if not self._getLinearPixel then self._getLinearPixel = {} end
|
||||
local k=x+y*thomson.w
|
||||
local p = self._getLinearPixel[k]
|
||||
if not p then
|
||||
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
|
||||
|
||||
p = Color:new(0,0,0);
|
||||
for j=y1,y2-1 do
|
||||
for i=x1,x2-1 do
|
||||
p:add(getLinearPictureColor(i,j))
|
||||
end
|
||||
end
|
||||
p:div((y2-y1)*(x2-x1)) --:floor()
|
||||
|
||||
if with_cache then self._getLinearPixel[k]=p end
|
||||
end
|
||||
|
||||
return with_cache and p:clone() or p
|
||||
end
|
||||
|
||||
-- MO5 mode
|
||||
thomson.setMO5()
|
||||
self.palette = getpalette(thomson.w,thomson.h,getLinearPixel)
|
||||
|
||||
-- compute levels from palette
|
||||
self:setLevelsFromPalette()
|
||||
|
||||
-- convert picture
|
||||
self:ccDither(thomson.w,thomson.h,
|
||||
getLinearPixel, thomson.pset,
|
||||
serpentine or true, function(y)
|
||||
thomson.info("Converting...",
|
||||
math.floor(y*100/thomson.h),"%")
|
||||
end,true)
|
||||
|
||||
-- refresh screen
|
||||
setpicturesize(thomson.w,thomson.h)
|
||||
thomson.updatescreen()
|
||||
thomson.savep()
|
||||
finalizepicture()
|
||||
end
|
||||
|
||||
end -- OstroDither
|
||||
Reference in New Issue
Block a user