File: fix_xpm.lua

package info (click to toggle)
wxpython3.0 3.0.2.0%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 482,760 kB
  • ctags: 518,293
  • sloc: cpp: 2,127,226; python: 294,045; makefile: 51,942; ansic: 19,033; sh: 3,013; xml: 1,629; perl: 17
file content (235 lines) | stat: -rw-r--r-- 7,205 bytes parent folder | download | duplicates (14)
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/lua
-------------------------------------------------------------------------------
-- Name:        fix_xpm.lua
-- Purpose:     Fix XPM files for use in Ribbon sample
-- Author:      Peter Cawley
-- Modified by:
-- Created:     2009-07-06
-- Copyright:   (C) Copyright 2009, Peter Cawley
-- Licence:     wxWindows Library Licence
-------------------------------------------------------------------------------
-- My preferred image editor (Paint Shop Pro 9) spits out XPM files, but with
-- some deficiencies:
-- 1) Specifies a 256 colour palette, even when less than 256 colours are used
-- 2) Transparency is replaced by a non-transparent colour
-- 3) Does not name the C array appropriately
-- 4) Array and strings not marked const

assert(_VERSION == "Lua 5.1", "Lua 5.1 is required")
local lpeg = require "lpeg"

-- Parse command line
local args = {...}
local filename = assert(...,"Expected filename as first command line argument")
local arg_transparent
local arg_name
local arg_out
for i = 2, select('#', ...) do
  local arg = args[i]
  if arg == "/?" or arg == "-?" or arg == "--help" then
    print("Usage: filename [transparent=<colour>|(x,y)] [name=<array_name>] "..
          "[out=<filename>]")
    print("In addition to the transparent colour and name changes, the "..
          "palette will be also be optimised")
    print "Examples:"
    print("  in.xpm transparent=Gray100 -- Modifies in.xpm, replacing "..
          "Gray100 with transparent")
    print("  in.xpm transparent=(0,0) -- Modifies in.xpm, replacing "..
          "whichever colour is at (0,0) with transparent")
    print("  in.xpm name=out_xpm out=out.xpm -- Copies in.xpm to out.xpm, "..
          "and changes the array name to out_xpm")
    return
  end
  arg_transparent = arg:match"transparent=(.*)" or arg_transparent
  arg_name = arg:match"name=(.*)" or arg_name
  arg_out = arg:match"out=(.*)" or arg_out
end

-- XPM parsing
local grammars = {}
do
  local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
  local Ct = lpeg.Ct
  local comment = P"/*" * (1 - P"*/") ^ 0 * P"*/"
  local ws = (S" \r\n\t" + comment) ^ 0
  local function tokens(...)
    local result = ws
    for i, arg in ipairs{...} do
      if type(arg) == "table" then
        arg = P(arg[1]) ^ -1
      end
      result = result * P(arg) * ws
    end
    return result
  end
  grammars.file = P { "xpm";
    xpm = P"/* XPM */" * ws *
         tokens("static",{"const"},"char","*",{"const"}) * V"name" *
         tokens("[","]","=","{") * V"lines",
    name = C(R("az","AZ","__") * R("az","AZ","09","__") ^ 0),
    lines = Ct(V"line" ^ 1),
    line = ws * P'"' * C((1 - P'"') ^ 0) * P'"' * (tokens"," + V"eof"),
    eof = tokens("}",";") * P(1) ^ 0,
  }
  grammars.values = P { "values";
    values = Ct(V"value" * (S" \r\n\t" ^ 1 * V"value") ^ 3),
    value = C(R"09" ^ 1) / tonumber,
  }
  function make_remaining_grammars(cpp)
    local char = R"\32\126" - S[['"\]] -- Most of lower ASCII
    local colour = char
    for i = 2, cpp do
      colour = colour * char
    end
    grammars.colour = P { "line";
      line = C(colour) * Ct(Ct(ws * V"key" * ws * V"col") ^ 1),
      key = C(P"g4" + S"msgc"),
      col = V"name" + V"hex",
      name = C(R("az","AZ","__") * R("az","AZ","09","__") ^ 0),
      hex = C(P"#" * R("09","af","AF") ^ 3),
    }
    grammars.pixels = P { "line";
      line = Ct(C(colour) ^ 1),
    }
  end
end

-- Load file
local file = assert(io.open(filename,"rt"))
local filedata = file:read"*a"
file:close()

local xpm = {}
xpm.name, xpm.lines = grammars.file:match(filedata)
local values_table = assert(grammars.values:match(xpm.lines[1]))
xpm.width, xpm.height, xpm.ncolours, xpm.cpp = unpack(values_table)
make_remaining_grammars(xpm.cpp)
xpm.colours = {}
xpm.colours_full = {}
for i = 1, xpm.ncolours do
  local name, data = grammars.colour:match(xpm.lines[1 + i])
  local colour = ""
  for _, datum in ipairs(data) do
    if datum[1] == "c" then
      colour = datum[2]
      break
    end
  end
  assert(colour, "No colour data for " .. name)
  xpm.colours[name] = colour
  xpm.colours_full[i] = {name = name, unpack(data)}
end
xpm.pixels = {}
for y = 1, xpm.height do
  xpm.pixels[y] = grammars.pixels:match(xpm.lines[1 + xpm.ncolours + y])
  if not xpm.pixels[y] or #xpm.pixels[y] ~= xpm.width then
    error("Line " .. y .. " is invalid")
  end
end

-- Fix palette
repeat
  local n_colours_used = 0
  local colours_used = setmetatable({}, {__newindex = function(t, k, v)
    n_colours_used = n_colours_used + 1
    rawset(t, k, v)
  end})
  for y = 1, xpm.height do
    for x = 1, xpm.width do
      colours_used[xpm.pixels[y][x]] = true
    end
  end
  if n_colours_used == xpm.ncolours then
    break
  end
  local chars =" .abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567"
  local cpp = (n_colours_used > #chars) and 2 or 1
  local nalloc = 0
  local colour_map = setmetatable({}, {__index = function(t, k)
    nalloc = nalloc + 1
    local v
    if cpp == 1 then
      v = chars:sub(nalloc, nalloc)
    else
      local a, b = math.floor(nalloc / #chars) + 1, (nalloc % #chars) + 1
      v = chars:sub(a, a) .. chars:sub(b, b)
    end
    t[k] = v
    return v
  end})
  for y = 1, xpm.height do
    for x = 1, xpm.width do
      xpm.pixels[y][x] = colour_map[xpm.pixels[y][x]]
    end
  end
  local new_colours_full = {}
  for i, colour in ipairs(xpm.colours_full) do
    if colours_used[colour.name] then
      colour.name = colour_map[colour.name]
      new_colours_full[#new_colours_full + 1] = colour
    end
  end
  xpm.colours_full = new_colours_full
  local new_colours = {}
  for name, value in pairs(xpm.colours) do
    if colours_used[name] then
      new_colours[colour_map[name]] = value
    end
  end
  xpm.colours = new_colours
  xpm.cpp = cpp
  xpm.ncolours = nalloc
until true

-- Fix transparency
if arg_transparent then
  local name
  local x, y = arg_transparent:match"[(](%d+),(%d+)[)]"
  if x and y then
    name = xpm.pixels[y + 1][x + 1]
  else
    for n, c in pairs(xpm.colours) do
      if c == arg_transparent then
        name = n
        break
      end
    end
  end
  if not name then
    error("Cannot convert " .. arg_transparent .. " to transparent as the "..
          "colour is not present in the file")
  end
  xpm.colours[name] = "None"
  for i, colour in ipairs(xpm.colours_full) do
    if colour.name == name then
      for i, data in ipairs(colour) do
        if data[1] == "c" then
          data[2] = "None"
          break
        end
      end
      break
    end
  end
end

-- Fix name
xpm.name = arg_name or xpm.name

-- Save
local file = assert(io.open(arg_out or filename, "wt"))
file:write"/* XPM */\n"
file:write("static const char *const " .. xpm.name .. "[] = {\n")
file:write(('"%i %i %i %i",\n'):format(xpm.width, xpm.height, xpm.ncolours,
                                       xpm.cpp))
for _, colour in ipairs(xpm.colours_full) do
  file:write('"' .. colour.name)
  for _, data in ipairs(colour) do
    file:write(" " .. data[1] .. " " .. data[2])
  end
  file:write('",\n')
end
for i, row in ipairs(xpm.pixels) do
  file:write('"' .. table.concat(row) .. (i == xpm.height and '"\n' or '",\n'))
end
file:write("};\n")