1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2012-2015 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 # Extract images of objects from an SVG file using Inkscape
18 module svg_to_png_and_nit
23 # Image info extracted from the SVG file
25 # Name extracted from the object ID minus the `0` prefix and Nit safe
41 fun right
: Int do return x
+w
44 fun bottom
: Int do return y
+h
46 redef fun to_s
do return name
49 # Nit module with a single class to retrieve to access the extracted images
55 var attributes
= new Array[String]
56 var load_exprs
= new Array[String]
61 # File generated by svg_to_png_and_nit, do not modify, redef instead
63 import mnit::image_set
72 redef fun load_all(app: App)
84 # Magic adaption of this coordinates to the given `margin` and `scale`
85 fun adapt
(margin
: Int, scale
: Float): Int
87 var corrected
= self-margin
88 return (corrected
.to_f
*scale
).to_i
91 # The first power of to equal or greater than `self`
95 while p
< self do p
= p
*2
100 var opt_out_src
= new OptionString("Path to output source file (folder or file)", "--src", "-s")
101 var opt_assets
= new OptionString("Path to assert dir where to put PNG files", "--assets", "-a")
102 var opt_scale
= new OptionFloat("Apply scaling to exported images (default at 1.0 of 90dpi)", 1.0, "--scale", "-x")
103 var opt_help
= new OptionBool("Print this help message", "--help", "-h")
105 var opt_context
= new OptionContext
106 opt_context
.add_option
(opt_out_src
, opt_assets
, opt_scale
, opt_help
)
108 opt_context
.parse
(args
)
109 var rest
= opt_context
.rest
110 var errors
= opt_context
.errors
111 if rest
.is_empty
and not opt_help
.value
then errors
.add
"You must specify at least one source drawing file"
112 if not errors
.is_empty
or opt_help
.value
then
113 print errors
.join
("\n")
114 print
"Usage: svg_to_png_and_nit [Options] drawing.svg [Other files]"
120 if not "inkscape".program_is_in_path
then
121 print
"This tool needs the external program `inkscape`, make sure it is installed and in your PATH."
126 for drawing
in drawings
do
127 if not drawing
.file_exists
then
128 stderr
.write
"Source drawing file '{drawing}' does not exist."
133 var assets_path
= opt_assets
.value
134 if assets_path
== null then assets_path
= "assets"
135 if not assets_path
.file_exists
then
136 stderr
.write
"Assets dir '{assets_path}' does not exist (use --assets)\n"
140 var src_path
= opt_out_src
.value
141 if src_path
== null then src_path
= "src"
142 if not src_path
.file_exists
and src_path
.file_extension
!= "nit" then
143 stderr
.write
"Source dir '{src_path}' does not exist (use --src)\n"
147 var scale
= opt_scale
.value
149 var arrays_of_images
= new Array[String]
151 for drawing
in drawings
do
152 var drawing_name
= drawing
.basename
(".svg")
154 # Get the page dimensions
155 # Inkscape doesn't give us this information
158 var svg_file
= new FileReader.open
(drawing
)
159 while not svg_file
.eof
do
160 var line
= svg_file
.read_line
162 if page_width
== -1 and line
.search
("width") != null then
163 var words
= line
.split
("=")
165 n
= n
.substring
(1, n
.length-2
) # remove ""
166 page_width
= n
.to_f
.ceil
.to_i
167 else if page_height
== -1 and line
.search
("height") != null then
168 var words
= line
.split
("=")
170 n
= n
.substring
(1, n
.length-2
) # remove ""
171 page_height
= n
.to_f
.ceil
.to_i
176 assert page_width
!= -1
177 assert page_height
!= -1
180 var prog
= "inkscape"
181 var proc
= new ProcessReader.from_a
(prog
, ["--without-gui", "--query-all", drawing
])
187 var images
= new Array[Image]
189 # Gather all images beginning with 0
190 # also get the bounding box of all images
191 while not proc
.eof
do
192 var line
= proc
.read_line
193 var words
= line
.split
(",")
195 if words
.length
== 5 then
198 var x
= words
[1].to_f
.floor
.to_i
199 var y
= words
[2].to_f
.floor
.to_i
200 var w
= words
[3].to_f
.ceil
.to_i
+1
201 var h
= words
[4].to_f
.ceil
.to_i
+1
203 if id
.has_prefix
("0") then
204 var nit_name
= id
.substring_from
(1)
205 nit_name
= nit_name
.replace
('-', "_")
207 var image
= new Image(nit_name
, x
, y
, w
, h
)
210 max_x
= max_x
.max
(image
.right
)
211 max_y
= max_y
.max
(image
.bottom
)
220 var nit_class_name
= drawing_name
.chars
.first
.to_s
.to_upper
+ drawing_name
.substring_from
(1) + "Images"
221 var nit_src
= new ImageSetSrc(nit_class_name
)
222 nit_src
.attributes
.add
"\tprivate var main_image: Image is noinit\n"
223 nit_src
.load_exprs
.add
"\t\tmain_image = app.load_image(\"images
/{drawing_name}.png\
")\n"
225 # Sort images by name, it prevents Array errors and looks better
226 alpha_comparator
.sort
(images
)
228 # Add images to Nit source file
229 for image
in images
do
230 # Adapt coordinates to new top left and scale
231 var x
= image
.x
.adapt
(min_x
, scale
)
232 var y
= image
.y
.adapt
(min_y
, scale
)
233 var w
= (image
.w
.to_f
*scale
).to_i
234 var h
= (image
.h
.to_f
*scale
).to_i
236 var nit_name
= image
.name
237 var last_char
= nit_name
.chars
.last
238 if last_char
.to_s
.is_numeric
then
240 # TODO support more than 10 images in an array
242 nit_name
= nit_name
.substring
(0, nit_name
.length-1
)
243 if not arrays_of_images
.has
(nit_name
) then
244 # Create class attribute to store Array
245 arrays_of_images
.add
(nit_name
)
246 nit_src
.attributes
.add
"\tvar {nit_name} = new Array[Image]\n"
248 nit_src
.load_exprs
.add
"\t\t{nit_name}.add(main_image.subimage({x}, {y}, {w}, {h}))\n"
251 nit_src
.attributes
.add
"\tvar {nit_name}: Image is noinit\n"
252 nit_src
.load_exprs
.add
"\t\t{nit_name} = main_image.subimage({x}, {y}, {w}, {h})\n"
256 if not src_path
.file_extension
== "nit" then
257 src_path
= src_path
/drawing_name
+".nit"
261 var src_file
= new FileWriter.open
(src_path
)
262 nit_src
.write_to
(src_file
)
265 # Find closest power of 2
266 var dx
= max_x
- min_x
267 max_x
= dx
.next_pow2
+ min_x
269 var dy
= max_y
- min_y
270 max_y
= dy
.next_pow2
+ min_y
272 # Inkscape's --export-area inverts the Y axis. It uses the lower left corner of
273 # the drawing area where as queries return coordinates from the top left.
274 var y0
= page_height
- max_y
275 var y1
= page_height
- min_y
277 # Output png file to assets
278 var png_path
= "{assets_path}/images/{drawing_name}.png"
279 var proc2
= new Process.from_a
(prog
, [drawing
, "--without-gui",
280 "--export-dpi={(90.0*scale).to_i}",
281 "--export-png={png_path}",
282 "--export-area={min_x}:{y0}:{max_x}:{y1}",
283 "--export-background=#000000", "--export-background-opacity=0.0"])