summaryrefslogtreecommitdiff
path: root/img/load_pnm.lua
blob: af866ee83d3a2ab11c93796d39f63395693b5e88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
-- only going to support binary ones
-- because the textual ones are annoying

-- types:
-- P1	ascii	pbm
-- P2	ascii	pgm
-- P3	ascii	ppm
-- P4	binary	pbm
-- P5	binary	pgm
-- P6	binary	ppm


-- function nextpixel(imgf,cur,depth) -> pixel, ncur
-- where pixel is {r,g,b}, with r,g,b in [0,1]

local pixel_mt = {__tostring=function(s) return string.format("rgb(%f,%f,%f)",s[1],s[2],s[3]) end}

local function np_p1(imgf,cur,depth)
	local val,ncur = imgf:match("([01])%s*()",cur)
	val = tonumber(val)
	assert(val,"bad image format at "..cur)
	local p = (1-val) -- 1 is black (0), 0 is white (1)
	local pixel = {p,p,p}
	return pixel,ncur
end
local function np_p2(imgf,cur,depth)
	local val,ncur = imgf:match("(%d+)%s+()",cur)
	val = tonumber(val)
	assert(val,"bad image format at "..cur)
	assert(0<=val and val<=depth, "image value out of range at "..cur)
	local p = val/depth
	local pixel = {p,p,p}
	return pixel,ncur
end
local function np_p3(imgf,cur,depth)
	local tr,tg,tb,ncur = imgf:match("(%d+)%s+(%d+)%s+(%d+)%s+()",cur)
	local vr,vg,vb = tonumber(tr),tonumber(tg),tonumber(tb)
	assert(vr,"bad image format at "..cur)
	for _,v in ipairs{vr,vg,vb} do
		assert(0<=v and v<=depth,"image value out of range at "..cur)
	end
	local pr,pg,pb = vr/depth,vg/depth,vb/depth
	local pixel = {pr,pg,pb}
	return pixel,ncur
end
-- i will figure this one out later
local function np_p4() error("p4 isn't supported yet") end
local function np_p5(imgf,cur,depth)
	local val = imgf:byte(cur)
	assert(val<=depth,"image value out of range at "..cur)
	local p = val/depth
	local pixel = {p,p,p}
	return pixel,cur+1
end
local function np_p6(imgf,cur,depth)
	local vr,vg,vb = imgf:byte(cur,cur+3)
	for _,v in ipairs{vr,vg,vb} do
		assert(0<=v and v<=depth, "image value out of range at "..cur)
	end
	local pr,pg,pb = vr/depth,vg/depth,vb/depth
	local pixel = {pr,pg,pb}
	return pixel,cur+3
end

local nextpixel_fns = {
	[1]=np_p1,
	[2]=np_p2,
	[3]=np_p3,
	[4]=np_p4,
	[5]=np_p5,
	[6]=np_p6,
}


local function parse(imgf)
	assert(imgf:sub(1,1) == "P","invalid header")
	local typech = tonumber(imgf:sub(2,2))
	assert(typech and 1<=typech and typech<=6,"invalid header type")

	local has_depth = not(typech == 1 or typech == 4)

	local width,height,cur = imgf:match("\x0a(%d+) (%d+)\x0a()",3)
	width,height = tonumber(width),tonumber(height)
	assert(width,"couldn't find image dimensions")

	-- by 'depth' i mean 'maximum value'
	local depth = 1
	if has_depth then
		local ndepth,ncur = imgf:match("(%d+)\x0a()",cur)
		ndepth=tonumber(ndepth)
		assert(ndepth,"couldn't find image depth (max pixel value), in a format that needs it")
		depth=ndepth
		cur=ncur
	end
	assert(depth<=255,"depth too high (shouldn't be above 255)")

	-- img[y][x]; 1,1 is top left
	local img = setmetatable({},{__index=function(t,k)
		if type(k) ~= "number" then return nil end
		local r = {}
		rawset(t,k,r)
		return r
	end})

	local x,y=1,1
	local nextpixel = nextpixel_fns[typech]

	while cur <= #imgf do
		local npixel,ncur = nextpixel(imgf,cur,depth)
		setmetatable(npixel,pixel_mt)
		img[y][x] = npixel
		cur = ncur
		x = x + 1
		if x > width then
			x = 1
			y = y + 1
		end
		if y > height then
			break
		end
	end

	img.width = width
	img.height = height
	return img
end

return {load=parse}