]> git.saurik.com Git - wxWidgets.git/blob - samples/ribbon/fix_xpm.lua
Make storing non-trivial data in wxThreadSpecificInfo possible.
[wxWidgets.git] / samples / ribbon / fix_xpm.lua
1 #!/usr/bin/lua
2 -------------------------------------------------------------------------------
3 -- Name: fix_xpm.lua
4 -- Purpose: Fix XPM files for use in Ribbon sample
5 -- Author: Peter Cawley
6 -- Modified by:
7 -- Created: 2009-07-06
8 -- Copyright: (C) Copyright 2009, Peter Cawley
9 -- Licence: wxWindows Library Licence
10 -------------------------------------------------------------------------------
11 -- My preferred image editor (Paint Shop Pro 9) spits out XPM files, but with
12 -- some deficiencies:
13 -- 1) Specifies a 256 colour palette, even when less than 256 colours are used
14 -- 2) Transparency is replaced by a non-transparent colour
15 -- 3) Does not name the C array appropriately
16 -- 4) Array and strings not marked const
17
18 assert(_VERSION == "Lua 5.1", "Lua 5.1 is required")
19 local lpeg = require "lpeg"
20
21 -- Parse command line
22 local args = {...}
23 local filename = assert(...,"Expected filename as first command line argument")
24 local arg_transparent
25 local arg_name
26 local arg_out
27 for i = 2, select('#', ...) do
28 local arg = args[i]
29 if arg == "/?" or arg == "-?" or arg == "--help" then
30 print("Usage: filename [transparent=<colour>|(x,y)] [name=<array_name>] "..
31 "[out=<filename>]")
32 print("In addition to the transparent colour and name changes, the "..
33 "palette will be also be optimised")
34 print "Examples:"
35 print(" in.xpm transparent=Gray100 -- Modifies in.xpm, replacing "..
36 "Gray100 with transparent")
37 print(" in.xpm transparent=(0,0) -- Modifies in.xpm, replacing "..
38 "whichever colour is at (0,0) with transparent")
39 print(" in.xpm name=out_xpm out=out.xpm -- Copies in.xpm to out.xpm, "..
40 "and changes the array name to out_xpm")
41 return
42 end
43 arg_transparent = arg:match"transparent=(.*)" or arg_transparent
44 arg_name = arg:match"name=(.*)" or arg_name
45 arg_out = arg:match"out=(.*)" or arg_out
46 end
47
48 -- XPM parsing
49 local grammars = {}
50 do
51 local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
52 local Ct = lpeg.Ct
53 local comment = P"/*" * (1 - P"*/") ^ 0 * P"*/"
54 local ws = (S" \r\n\t" + comment) ^ 0
55 local function tokens(...)
56 local result = ws
57 for i, arg in ipairs{...} do
58 if type(arg) == "table" then
59 arg = P(arg[1]) ^ -1
60 end
61 result = result * P(arg) * ws
62 end
63 return result
64 end
65 grammars.file = P { "xpm";
66 xpm = P"/* XPM */" * ws *
67 tokens("static",{"const"},"char","*",{"const"}) * V"name" *
68 tokens("[","]","=","{") * V"lines",
69 name = C(R("az","AZ","__") * R("az","AZ","09","__") ^ 0),
70 lines = Ct(V"line" ^ 1),
71 line = ws * P'"' * C((1 - P'"') ^ 0) * P'"' * (tokens"," + V"eof"),
72 eof = tokens("}",";") * P(1) ^ 0,
73 }
74 grammars.values = P { "values";
75 values = Ct(V"value" * (S" \r\n\t" ^ 1 * V"value") ^ 3),
76 value = C(R"09" ^ 1) / tonumber,
77 }
78 function make_remaining_grammars(cpp)
79 local char = R"\32\126" - S[['"\]] -- Most of lower ASCII
80 local colour = char
81 for i = 2, cpp do
82 colour = colour * char
83 end
84 grammars.colour = P { "line";
85 line = C(colour) * Ct(Ct(ws * V"key" * ws * V"col") ^ 1),
86 key = C(P"g4" + S"msgc"),
87 col = V"name" + V"hex",
88 name = C(R("az","AZ","__") * R("az","AZ","09","__") ^ 0),
89 hex = C(P"#" * R("09","af","AF") ^ 3),
90 }
91 grammars.pixels = P { "line";
92 line = Ct(C(colour) ^ 1),
93 }
94 end
95 end
96
97 -- Load file
98 local file = assert(io.open(filename,"rt"))
99 local filedata = file:read"*a"
100 file:close()
101
102 local xpm = {}
103 xpm.name, xpm.lines = grammars.file:match(filedata)
104 local values_table = assert(grammars.values:match(xpm.lines[1]))
105 xpm.width, xpm.height, xpm.ncolours, xpm.cpp = unpack(values_table)
106 make_remaining_grammars(xpm.cpp)
107 xpm.colours = {}
108 xpm.colours_full = {}
109 for i = 1, xpm.ncolours do
110 local name, data = grammars.colour:match(xpm.lines[1 + i])
111 local colour = ""
112 for _, datum in ipairs(data) do
113 if datum[1] == "c" then
114 colour = datum[2]
115 break
116 end
117 end
118 assert(colour, "No colour data for " .. name)
119 xpm.colours[name] = colour
120 xpm.colours_full[i] = {name = name, unpack(data)}
121 end
122 xpm.pixels = {}
123 for y = 1, xpm.height do
124 xpm.pixels[y] = grammars.pixels:match(xpm.lines[1 + xpm.ncolours + y])
125 if not xpm.pixels[y] or #xpm.pixels[y] ~= xpm.width then
126 error("Line " .. y .. " is invalid")
127 end
128 end
129
130 -- Fix palette
131 repeat
132 local n_colours_used = 0
133 local colours_used = setmetatable({}, {__newindex = function(t, k, v)
134 n_colours_used = n_colours_used + 1
135 rawset(t, k, v)
136 end})
137 for y = 1, xpm.height do
138 for x = 1, xpm.width do
139 colours_used[xpm.pixels[y][x]] = true
140 end
141 end
142 if n_colours_used == xpm.ncolours then
143 break
144 end
145 local chars =" .abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567"
146 local cpp = (n_colours_used > #chars) and 2 or 1
147 local nalloc = 0
148 local colour_map = setmetatable({}, {__index = function(t, k)
149 nalloc = nalloc + 1
150 local v
151 if cpp == 1 then
152 v = chars:sub(nalloc, nalloc)
153 else
154 local a, b = math.floor(nalloc / #chars) + 1, (nalloc % #chars) + 1
155 v = chars:sub(a, a) .. chars:sub(b, b)
156 end
157 t[k] = v
158 return v
159 end})
160 for y = 1, xpm.height do
161 for x = 1, xpm.width do
162 xpm.pixels[y][x] = colour_map[xpm.pixels[y][x]]
163 end
164 end
165 local new_colours_full = {}
166 for i, colour in ipairs(xpm.colours_full) do
167 if colours_used[colour.name] then
168 colour.name = colour_map[colour.name]
169 new_colours_full[#new_colours_full + 1] = colour
170 end
171 end
172 xpm.colours_full = new_colours_full
173 local new_colours = {}
174 for name, value in pairs(xpm.colours) do
175 if colours_used[name] then
176 new_colours[colour_map[name]] = value
177 end
178 end
179 xpm.colours = new_colours
180 xpm.cpp = cpp
181 xpm.ncolours = nalloc
182 until true
183
184 -- Fix transparency
185 if arg_transparent then
186 local name
187 local x, y = arg_transparent:match"[(](%d+),(%d+)[)]"
188 if x and y then
189 name = xpm.pixels[y + 1][x + 1]
190 else
191 for n, c in pairs(xpm.colours) do
192 if c == arg_transparent then
193 name = n
194 break
195 end
196 end
197 end
198 if not name then
199 error("Cannot convert " .. arg_transparent .. " to transparent as the "..
200 "colour is not present in the file")
201 end
202 xpm.colours[name] = "None"
203 for i, colour in ipairs(xpm.colours_full) do
204 if colour.name == name then
205 for i, data in ipairs(colour) do
206 if data[1] == "c" then
207 data[2] = "None"
208 break
209 end
210 end
211 break
212 end
213 end
214 end
215
216 -- Fix name
217 xpm.name = arg_name or xpm.name
218
219 -- Save
220 local file = assert(io.open(arg_out or filename, "wt"))
221 file:write"/* XPM */\n"
222 file:write("static const char *const " .. xpm.name .. "[] = {\n")
223 file:write(('"%i %i %i %i",\n'):format(xpm.width, xpm.height, xpm.ncolours,
224 xpm.cpp))
225 for _, colour in ipairs(xpm.colours_full) do
226 file:write('"' .. colour.name)
227 for _, data in ipairs(colour) do
228 file:write(" " .. data[1] .. " " .. data[2])
229 end
230 file:write('",\n')
231 end
232 for i, row in ipairs(xpm.pixels) do
233 file:write('"' .. table.concat(row) .. (i == xpm.height and '"\n' or '",\n'))
234 end
235 file:write("};\n")