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