1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
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
30 fun right
: Int do return x
+w
31 fun bottom
: Int do return y
+h
33 redef fun to_s
do return name
36 # The Nit source file to retreive all images
41 init(name
: String) do self.name
= name
43 var attributes
= new Array[String]
44 var load_exprs
= new Array[String]
49 # file generated by svg_to_png, do not modify, redef instead
51 import mnit::image_set
60 redef fun load_all(app: App)
72 fun adapt
(d
: Int, scale
: Float): Int
74 var corrected
= self-d
75 return (corrected
.to_f
*scale
).to_i
81 while p
< self do p
= p
*2
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")
91 var opt_context
= new OptionContext
92 opt_context
.add_option
(opt_out_src
, opt_assets
, opt_scale
, opt_help
)
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]"
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."
112 for drawing
in drawings
do
113 if not drawing
.file_exists
then
114 stderr
.write
"Source drawing file '{drawing}' does not exist."
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"
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"
133 var scale
= opt_scale
.value
135 var arrays_of_images
= new Array[String]
137 for drawing
in drawings
do
138 var drawing_name
= drawing
.basename
(".svg")
140 # Get the page dimensions
141 # Inkscape doesn't give us this information
144 var svg_file
= new IFStream.open
(drawing
)
145 while not svg_file
.eof
do
146 var line
= svg_file
.read_line
148 if page_width
== -1 and line
.search
("width") != null then
149 var words
= line
.split
("=")
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
("=")
156 n
= n
.substring
(1, n
.length-2
) # remove ""
157 page_height
= n
.to_f
.ceil
.to_i
162 assert page_width
!= -1
163 assert page_height
!= -1
166 var prog
= "inkscape"
167 var proc
= new IProcess.from_a
(prog
, ["--without-gui", "--query-all", drawing
])
173 var images
= new Array[Image]
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
(",")
181 if words
.length
== 5 then
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
189 if id
.has_prefix
("0") then
190 var nit_name
= id
.substring_from
(1)
191 nit_name
= nit_name
.replace
('-', "_")
193 var image
= new Image(nit_name
, x
, y
, w
, h
)
196 max_x
= max_x
.max
(image
.right
)
197 max_y
= max_y
.max
(image
.bottom
)
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"
211 # Sort images by name, it prevents Array errors and looks better
212 alpha_comparator
.sort
(images
)
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
222 var nit_name
= image
.name
223 var last_char
= nit_name
.chars
.last
224 if last_char
.to_s
.is_numeric
then
226 # TODO support more than 10 images in an array
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"
234 nit_src
.load_exprs
.add
"\t\t{nit_name}.add(main_image.subimage({x}, {y}, {w}, {h}))\n"
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"
243 var src_file
= new OFStream.open
("{src_path}/{drawing_name}.nit")
244 nit_src
.write_to
(src_file
)
247 # Find closest power of 2
248 var dx
= max_x
- min_x
249 max_x
= dx
.next_pow2
+ min_x
251 var dy
= max_y
- min_y
252 max_y
= dy
.next_pow2
+ min_y
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
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"])