lib/standard/stream: Renamed streams for more explicit denomination
[nit.git] / contrib / inkscape_tools / src / svg_to_png_and_nit.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # This script extracts pngs from a single svg for all objects with ids
18 # beginning by 0. Requires Inkscape.
19 module svg_to_png_and_nit
20
21 import opts
22 import template
23
24 class Image
25 var name: String
26 var x: Int
27 var y: Int
28 var w: Int
29 var h: Int
30 fun right: Int do return x+w
31 fun bottom: Int do return y+h
32
33 redef fun to_s do return name
34 end
35
36 # The Nit source file to retreive all images
37 class ImageSetSrc
38 super Template
39
40 var name: String
41 init(name: String) do self.name = name
42
43 var attributes = new Array[String]
44 var load_exprs = new Array[String]
45
46 redef fun rendering
47 do
48 add """
49 # file generated by svg_to_png, do not modify, redef instead
50
51 import mnit::image_set
52
53 class {{{name}}}
54 super ImageSet
55
56 """
57 add_all attributes
58 add """
59
60 redef fun load_all(app: App)
61 do
62 """
63 add_all load_exprs
64 add """
65 end
66 end
67 """
68 end
69 end
70
71 redef class Int
72 fun adapt(d: Int, scale: Float): Int
73 do
74 var corrected = self-d
75 return (corrected.to_f*scale).to_i
76 end
77
78 fun next_pow2: Int
79 do
80 var p = 2
81 while p < self do p = p*2
82 return p
83 end
84 end
85
86 var opt_out_src = new OptionString("Path to output source file", "--src", "-s")
87 var opt_assets = new OptionString("Path to assert dir where to put PNG files", "--assets", "-a")
88 var opt_scale = new OptionFloat("Apply scaling to exported images (defaut at 1.0 of 90dpi)", 1.0, "--scale", "-x")
89 var opt_help = new OptionBool("Print this help message", "--help", "-h")
90
91 var opt_context = new OptionContext
92 opt_context.add_option(opt_out_src, opt_assets, opt_scale, opt_help)
93
94 opt_context.parse(args)
95 var rest = opt_context.rest
96 var errors = opt_context.errors
97 if rest.is_empty and not opt_help.value then errors.add "You must specify at least one source drawing file"
98 if not errors.is_empty or opt_help.value then
99 print errors.join("\n")
100 print "Usage: svg_to_png_and_nit [Options] drawing.svg [Other files]"
101 print "Options:"
102 opt_context.usage
103 exit 1
104 end
105
106 if not "inkscape".program_is_in_path then
107 print "This tool needs the external program `inkscape`, make sure it is installed and in your PATH."
108 exit 1
109 end
110
111 var drawings = rest
112 for drawing in drawings do
113 if not drawing.file_exists then
114 stderr.write "Source drawing file '{drawing}' does not exist."
115 exit 1
116 end
117 end
118
119 var assets_path = opt_assets.value
120 if assets_path == null then assets_path = "assets"
121 if not assets_path.file_exists then
122 stderr.write "Assets dir '{assets_path}' does not exist (use --assets)\n"
123 exit 1
124 end
125
126 var src_path = opt_out_src.value
127 if src_path == null then src_path = "src"
128 if not src_path.file_exists then
129 stderr.write "Source dir '{src_path}' does not exist (use --src)\n"
130 exit 1
131 end
132
133 var scale = opt_scale.value
134
135 var arrays_of_images = new Array[String]
136
137 for drawing in drawings do
138 var drawing_name = drawing.basename(".svg")
139
140 # Get the page dimensions
141 # Inkscape doesn't give us this information
142 var page_width = -1
143 var page_height = -1
144 var svg_file = new FileReader.open(drawing)
145 while not svg_file.eof do
146 var line = svg_file.read_line
147
148 if page_width == -1 and line.search("width") != null then
149 var words = line.split("=")
150 var n = words[1]
151 n = n.substring(1, n.length-2) # remove ""
152 page_width = n.to_f.ceil.to_i
153 else if page_height == -1 and line.search("height") != null then
154 var words = line.split("=")
155 var n = words[1]
156 n = n.substring(1, n.length-2) # remove ""
157 page_height = n.to_f.ceil.to_i
158 end
159 end
160 svg_file.close
161
162 assert page_width != -1
163 assert page_height != -1
164
165 # Query Inkscape
166 var prog = "inkscape"
167 var proc = new ProcessReader.from_a(prog, ["--without-gui", "--query-all", drawing])
168
169 var min_x = 1000000
170 var min_y = 1000000
171 var max_x = -1
172 var max_y = -1
173 var images = new Array[Image]
174
175 # Gather all images beginning with 0
176 # also get the bounding box of all images
177 while not proc.eof do
178 var line = proc.read_line
179 var words = line.split(",")
180
181 if words.length == 5 then
182 var id = words[0]
183
184 var x = words[1].to_f.floor.to_i
185 var y = words[2].to_f.floor.to_i
186 var w = words[3].to_f.ceil.to_i+1
187 var h = words[4].to_f.ceil.to_i+1
188
189 if id.has_prefix("0") then
190 var nit_name = id.substring_from(1)
191 nit_name = nit_name.replace('-', "_")
192
193 var image = new Image(nit_name, x, y, w, h)
194 min_x = min_x.min(x)
195 min_y = min_y.min(y)
196 max_x = max_x.max(image.right)
197 max_y = max_y.max(image.bottom)
198
199 images.add image
200 end
201 end
202 end
203 proc.close
204
205 # Nit class
206 var nit_class_name = drawing_name.chars.first.to_s.to_upper + drawing_name.substring_from(1) + "Images"
207 var nit_src = new ImageSetSrc(nit_class_name)
208 nit_src.attributes.add "\tprivate var main_image: Image is noinit\n"
209 nit_src.load_exprs.add "\t\tmain_image = app.load_image(\"images/{drawing_name}.png\")\n"
210
211 # Sort images by name, it prevents Array errors and looks better
212 alpha_comparator.sort(images)
213
214 # Add images to Nit source file
215 for image in images do
216 # Adapt coordinates to new top left and scale
217 var x = image.x.adapt(min_x, scale)
218 var y = image.y.adapt(min_y, scale)
219 var w = (image.w.to_f*scale).to_i
220 var h = (image.h.to_f*scale).to_i
221
222 var nit_name = image.name
223 var last_char = nit_name.chars.last
224 if last_char.to_s.is_numeric then
225 # Array of images
226 # TODO support more than 10 images in an array
227
228 nit_name = nit_name.substring(0, nit_name.length-1)
229 if not arrays_of_images.has(nit_name) then
230 # Create class attribute to store Array
231 arrays_of_images.add(nit_name)
232 nit_src.attributes.add "\tvar {nit_name} = new Array[Image]\n"
233 end
234 nit_src.load_exprs.add "\t\t{nit_name}.add(main_image.subimage({x}, {y}, {w}, {h}))\n"
235 else
236 # Single image
237 nit_src.attributes.add "\tvar {nit_name}: Image is noinit\n"
238 nit_src.load_exprs.add "\t\t{nit_name} = main_image.subimage({x}, {y}, {w}, {h})\n"
239 end
240 end
241
242 # Output source file
243 var src_file = new FileWriter.open("{src_path}/{drawing_name}.nit")
244 nit_src.write_to(src_file)
245 src_file.close
246
247 # Find closest power of 2
248 var dx = max_x - min_x
249 max_x = dx.next_pow2 + min_x
250
251 var dy = max_y - min_y
252 max_y = dy.next_pow2 + min_y
253
254 # Inkscape's --export-area inverts the Y axis. It uses the lower left corner of
255 # the drawing area where as queries return coordinates from the top left.
256 var y0 = page_height - max_y
257 var y1 = page_height - min_y
258
259 # Output png file to assets
260 var png_path = "{assets_path}/images/{drawing_name}.png"
261 var proc2 = new Process.from_a(prog, [drawing, "--without-gui",
262 "--export-dpi={(90.0*scale).to_i}",
263 "--export-png={png_path}",
264 "--export-area={min_x}:{y0}:{max_x}:{y1}",
265 "--export-background=#000000", "--export-background-opacity=0.0"])
266 proc2.wait
267 end