-- ostro_zx.lua : converts a color image into a 
-- ZX-like image (8+8 fixed colors with color clash) 
-- using Ostromoukhov's error diffusion algorithm.
--
-- Version: 03/21/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('../../thomson/lib/ostromoukhov.lua')

-- get screen size
local screen_w, screen_h = getpicturesize()

OtherDither = {}
function OtherDither:new(a)
	local o = { -- default ZX values
		-- width of the screen
		width=a and a.width or 256,
		-- height of the screen
		height=a and a.height or 192,
		-- size of Nx1 clash size
		clash_size=a and a.clash_size or 8,
		-- normalize the picture levels (like in imagemagick)
		normalize=a and a.normalize or 0.005,
		-- put a pixel
		pset=a and a.pset or function(self,x,y,c)
			if c<0 then c=-c-1 end
			self.screen[x+y*self.width] = c
		end,
		-- init gfx data
		setGfx=a and a.setGfx or function(self)
			self.screen={}
		end,
		-- update gfx to screen
		updatescreen=a and a.updatescreen or function(self)
			for i=0,255 do setcolor(i,0,0,0) end
			for y=0,self.height-1 do
				for x=0,self.width-1 do
					putpicturepixel(x,y,self.screen[x+y*self.width] or 0)
				end
			end
			-- refresh palette
			for i,v in ipairs(self.pal) do
				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,
		-- palette with thomson ordering (to use thomson's
		-- lib support)
		pal= a and a.pal or {
			0x000,0xF00,0x00F,0xF0F,0x0F0,0xFF0,0x0FF,0xFFF,
			0x000,0x200,0x002,0x202,0x020,0x220,0x022,0x222
		}
	}
	setmetatable(o, self)
	self.__index = self
	return o
end

-- Converts ZX coordinates (0-255,0-191) into screen coordinates
function OtherDither:to_screen(x,y)
	local i,j;
	if screen_w/screen_h < self.width/self.height then
		i = x*screen_h/self.height
		j = y*screen_h/self.height
	else
		i = x*screen_w/self.width
		j = y*screen_w/self.width
	end
	return math.floor(i), math.floor(j)
end

-- return the Color @(x,y) in linear space (0-255)
-- corresonding to the other platform screen
OtherDither._getLinearPixel = {} -- cache
function OtherDither:getLinearPixel(x,y)
	local k=x+y*self.width
	local p = self._getLinearPixel and self._getLinearPixel[k]
	if not p then
		local x1,y1 = self:to_screen(x,y)
		local x2,y2 = self:to_screen(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 self._getLinearPixel then 
			self._getLinearPixel[k]=p 
		end
	end
	
	return self._getLinearPixel and p:clone() or p
end

function OtherDither:ccAcceptCouple(c1,c2)
	-- bright colors can't mix with dimmed ones
	return c1~=c2 and ((c1<=8 and c2<=8) or (c1>8 and c2>8))
end

function OtherDither:dither()
	local NORMALIZE=Color.NORMALIZE
	Color.NORMALIZE=self.normalize
	
	local dither=OstroDither:new(self.pal)
	dither.ccAcceptCouple = function(dither,c1,c2) return self:ccAcceptCouple(c1,c2) end
	dither.clash_size = self.clash_size
	dither.attenuation = .9
	
	self:setGfx()
	dither:ccDither(self.width,self.height, 
				  function(x,y) return self:getLinearPixel(x,y) end, 
				  function(x,y,c) self:pset(x,y,c) end, 
				  true, 
				  function(y) 
					thomson.info("Converting...",
							math.floor(y*100/self.height),"%") 
				  end,true)
	-- refresh screen
	setpicturesize(self.width,self.height)
	self:updatescreen()
	finalizepicture()
	Color.NORMALIZE=NORMALIZE
end
