contrib: intro the svg_to_png_and_nit project
authorAlexis Laferrière <alexis.laf@xymus.net>
Sun, 6 Apr 2014 03:31:26 +0000 (23:31 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Thu, 17 Apr 2014 13:35:04 +0000 (09:35 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

contrib/inkscape_tools/Makefile [new file with mode: 0644]
contrib/inkscape_tools/README.md [new file with mode: 0644]
contrib/inkscape_tools/src/svg_to_png_and_nit.nit [new file with mode: 0644]
contrib/inkscape_tools/tests/app/Makefile [new file with mode: 0644]
contrib/inkscape_tools/tests/app/art/drawing.svg [new file with mode: 0644]
contrib/inkscape_tools/tests/app/src/s2pn.nit [new file with mode: 0644]
contrib/inkscape_tools/tests/app/src/s2pn_android.nit [new file with mode: 0644]
contrib/inkscape_tools/tests/app/src/s2pn_linux.nit [new file with mode: 0644]

diff --git a/contrib/inkscape_tools/Makefile b/contrib/inkscape_tools/Makefile
new file mode 100644 (file)
index 0000000..895656c
--- /dev/null
@@ -0,0 +1,14 @@
+bin/svg_to_png_and_nit:
+       mkdir -p bin
+       ../../bin/nitg -o bin/svg_to_png_and_nit src/svg_to_png_and_nit.nit
+
+tests: test-dino
+
+test-app: bin/svg_to_png_and_nit
+       make -C tests/app
+
+test-dino: bin/svg_to_png_and_nit
+       mkdir -p tests/dino/images
+       bin/svg_to_png_and_nit --assets tests/dino/ --src tests/dino/ ../../examples/mnit_dino/art/drawing.svg
+
+.PHONY: bin/svg_to_png_and_nit
diff --git a/contrib/inkscape_tools/README.md b/contrib/inkscape_tools/README.md
new file mode 100644 (file)
index 0000000..017ec86
--- /dev/null
@@ -0,0 +1,28 @@
+# SVG to PNG and Nit
+
+This tool is used in combination with Inkscape to simplify assets creation for _mnit_ apps. It uses Inkscape to extract a PNG file from a SVG file. It will also create a Nit source file to create _mnit_ images for each objects with an id beginning by 0.
+
+# Features
+
+* Creates a sinlge PNG file per SVG source file
+* Creates subimages for objects with an id beginning by 0.
+* If the id ends with 0 to 9, will instead create an array of subimages.
+
+# Usage
+
+1. Create a new Inkscape document.
+2. Create objects and set their ids to begin with 0
+3. Save the document (ex: to `drawing.svg`) the name of the file is important
+4. Execute `bin/svg_to_png_and_nit drawing.svg`
+5. From your code, import the generated source file (at  src/drawing.nit`)
+6. Use the class `DrawingImages` and its attributes.
+
+# Examples
+
+The minimal test in  tests/app/  shows the basic usage of this tool.
+
+The Dino example `../../../../examples/mnit_dino` also uses this tool and is a more complete and practical example.
+
+# Authors
+
+Alexis Laferrière <alexis.laf@xymus.net>
diff --git a/contrib/inkscape_tools/src/svg_to_png_and_nit.nit b/contrib/inkscape_tools/src/svg_to_png_and_nit.nit
new file mode 100644 (file)
index 0000000..f1de678
--- /dev/null
@@ -0,0 +1,270 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script extracts pngs from a single svg for all objects with ids
+# beginning by 0. Requires Inkscape.
+module svg_to_png_and_nit
+
+import opts
+import template
+
+class Image
+       var name: String
+       var x: Int
+       var y: Int
+       var w: Int
+       var h: Int
+       fun right: Int do return x+w
+       fun bottom: Int do return y+h
+
+       redef fun to_s do return name
+end
+
+# The Nit source file to retreive all images
+class ImageSetSrc
+       super Template
+
+       var name: String
+       init(name: String) do self.name = name
+
+       var attributes = new Array[String]
+       var load_exprs = new Array[String]
+
+       redef fun rendering
+       do
+               add """
+# file generated by svg_to_png, do not modify, redef instead
+
+import generated_image_set
+
+class {{{name}}}
+       super ImageSet
+
+"""
+               add_all attributes
+               add """
+
+       redef fun load_all(app: App)
+       do
+"""
+               add_all load_exprs
+               add """
+       end
+end
+"""
+       end
+end
+
+redef class String
+       fun to_i_strip_e: Int
+       do
+               if has_substring("e-", 0) then return 0
+               return to_i
+       end
+end
+
+redef class Int
+       fun adapt(d: Int, scale: Float): Int
+       do
+               var corrected = self-d
+               return (corrected.to_f*scale).to_i
+       end
+
+       fun next_pow2: Int
+       do
+               var p = 2
+               while p < self do p = p*2
+               return p
+       end
+end
+
+var opt_out_src = new OptionString("Path to output source file", "--src", "-s")
+var opt_assets = new OptionString("Path to assert dir where to put PNG files", "--assets", "-a")
+var opt_scale = new OptionFloat("Apply scaling to exported images (defaut at 1.0 of 90dpi)", 1.0, "--scale", "-x")
+var opt_help = new OptionBool("Print this help message", "--help", "-h")
+
+var opt_context = new OptionContext
+opt_context.add_option(opt_out_src, opt_assets, opt_scale, opt_help)
+
+opt_context.parse(args)
+var rest = opt_context.rest
+var errors = opt_context.errors
+if rest.is_empty and not opt_help.value then errors.add "You must specify at least one source drawing file"
+if not errors.is_empty or opt_help.value then
+       print errors.join("\n")
+       print "Usage: svg_to_png_and_nit [Options] drawing.svg [Other files]"
+       print "Options:"
+       opt_context.usage
+       exit 1
+end
+
+var drawings = rest
+for drawing in drawings do
+       if not drawing.file_exists then
+               stderr.write "Source drawing file '{drawing}' does not exist."
+               exit 1
+       end
+end
+
+var assets_path = opt_assets.value
+if assets_path == null then assets_path = "assets"
+if not assets_path.file_exists then
+       stderr.write "Assets dir '{assets_path}' does not exist (use --assets)\n"
+       exit 1
+end
+
+var src_path = opt_out_src.value
+if src_path == null then src_path = "src"
+if not src_path.file_exists then
+       stderr.write "Source dir '{src_path}' does not exist (use --src)\n"
+       exit 1
+end
+
+var scale = opt_scale.value
+
+var arrays_of_images = new Array[String]
+
+for drawing in drawings do
+       var drawing_name = drawing.basename(".svg")
+
+       # Get the page dimensions
+       # Inkscape doesn't give us this information
+       var page_width = -1
+       var page_height = -1
+       var svg_file = new IFStream.open(drawing)
+       while not svg_file.eof do
+               var line = svg_file.read_line
+
+               if page_width == -1 and line.search("width") != null then
+                       var words = line.split("=")
+                       var n = words[1]
+                       n = n.substring(1, n.length-2) # remove ""
+                       page_width = n.to_i_strip_e
+               else if page_height == -1 and line.search("height") != null then
+                       var words = line.split("=")
+                       var n = words[1]
+                       n = n.substring(1, n.length-2) # remove ""
+                       page_height = n.to_i_strip_e
+               end
+       end
+       svg_file.close
+
+       assert page_width != -1
+       assert page_height != -1
+
+       # Query Inkscape
+       var prog = "inkscape"
+       var proc = new IProcess.from_a(prog, ["--without-gui", "--query-all", drawing])
+
+       var min_x = 1000000
+       var min_y = 1000000
+       var max_x = -1
+       var max_y = -1
+       var images = new Array[Image]
+
+       # Gather all images beginning with 0
+       # also get the bounding box of all images
+       while not proc.eof do
+               var line = proc.read_line
+               var words = line.split(",")
+               
+               if words.length == 5 then
+                       var id = words[0]
+
+                       var x = words[1].to_i_strip_e
+                       var y = words[2].to_i_strip_e
+                       var w = words[3].to_i_strip_e
+                       var h = words[4].to_i_strip_e
+
+                       if id.has_prefix("0") then
+                               var nit_name = id.substring_from(1)
+                               nit_name = nit_name.replace('-', "_")
+
+                               var image = new Image(nit_name, x, y, w, h)
+                               min_x = min_x.min(x)
+                               min_y = min_y.min(y)
+                               max_x = max_x.max(image.right)
+                               max_y = max_y.max(image.bottom)
+
+                               images.add image
+                       end
+               end
+       end
+       proc.close
+
+       # Nit class
+       var nit_class_name = drawing_name.chars.first.to_s.to_upper + drawing_name.substring_from(1) + "Images"
+       var nit_src = new ImageSetSrc(nit_class_name)
+       nit_src.attributes.add "\tprivate var main_image: Image\n"
+       nit_src.load_exprs.add "\t\tmain_image = app.load_image(\"images/{drawing_name}.png\")\n"
+
+       # Sort images by name, it prevents Array errors and looks better
+       alpha_comparator.sort(images)
+
+       # Add images to Nit source file
+       for image in images do
+               # Adapt coordinates to new top left and scale
+               var x = image.x.adapt(min_x, scale)
+               var y = image.y.adapt(min_y, scale)
+               var w = (image.w.to_f*scale).to_i
+               var h = (image.h.to_f*scale).to_i
+
+               var nit_name = image.name
+               var last_char = nit_name.chars.last
+               if last_char.to_s.is_numeric then
+                       # Array of images
+                       # TODO support more than 10 images in an array
+
+                       nit_name = nit_name.substring(0, nit_name.length-1)
+                       if not arrays_of_images.has(nit_name) then
+                               # Create class attribute to store Array
+                               arrays_of_images.add(nit_name)
+                               nit_src.attributes.add "\tvar {nit_name} = new Array[Image]\n"
+                       end
+                       nit_src.load_exprs.add "\t\t{nit_name}.add(main_image.subimage({x}, {y}, {w}, {h}))\n"
+               else
+                       # Single image
+                       nit_src.attributes.add "\tvar {nit_name}: Image\n"
+                       nit_src.load_exprs.add "\t\t{nit_name} = main_image.subimage({x}, {y}, {w}, {h})\n"
+               end
+       end
+
+       # Output source file
+       var src_file = new OFStream.open("{src_path}/{drawing_name}.nit")
+       nit_src.write_to(src_file)
+       src_file.close
+
+       # Find closest power of 2
+       var dx = max_x - min_x
+       max_x = dx.next_pow2 + min_x
+
+       var dy = max_y - min_y
+       max_y = dy.next_pow2 + min_y
+
+       # Inkscape's --export-area inverts the Y axis. It uses the lower left corner of
+       # the drawing area where as queries return coordinates from the top left.
+       var y0 = page_height - max_y
+       var y1 = page_height - min_y
+
+       # Output png file to assets
+       var png_path = "{assets_path}/images/{drawing_name}.png"
+       var proc2 = new Process.from_a(prog, [drawing, "--without-gui",
+               "--export-dpi={(90.0*scale).to_i}",
+               "--export-png={png_path}",
+               "--export-area={min_x}:{y0}:{max_x}:{y1}",
+               "--export-background=#000000", "--export-background-opacity=0.0"])
+       proc2.wait
+end
diff --git a/contrib/inkscape_tools/tests/app/Makefile b/contrib/inkscape_tools/tests/app/Makefile
new file mode 100644 (file)
index 0000000..649f023
--- /dev/null
@@ -0,0 +1,13 @@
+all: images linux
+
+linux:
+       mkdir -p bin
+       ../../../../bin/nitg -o bin/s2pn src/s2pn_linux.nit
+
+android:
+       mkdir -p bin
+       ../../../../bin/nitg -o bin/s2pn.apk src/s2pn_android.nit
+
+images:
+       mkdir -p assets/images
+       ../../bin/svg_to_png_and_nit art/drawing.svg --assets assets --src src
diff --git a/contrib/inkscape_tools/tests/app/art/drawing.svg b/contrib/inkscape_tools/tests/app/art/drawing.svg
new file mode 100644 (file)
index 0000000..bbcb7a2
--- /dev/null
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="drawing.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.7"
+     inkscape:cx="121.41831"
+     inkscape:cy="436.77983"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1962"
+     inkscape:window-height="1240"
+     inkscape:window-x="2718"
+     inkscape:window-y="1285"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="fill:#0caf0c;fill-opacity:1;stroke:#3b3b3b;stroke-width:1.58335221;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="0square"
+       width="98.416649"
+       height="98.416649"
+       x="83.827385"
+       y="181.54672"
+       inkscape:label="#rect2985" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#ff7900;fill-opacity:1;stroke:#3b3b3b;stroke-width:2.50000000000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="0circle"
+       sodipodi:cx="556.42859"
+       sodipodi:cy="290.21933"
+       sodipodi:rx="85"
+       sodipodi:ry="85"
+       d="m 641.42859,290.21933 a 85,85 0 1 1 -170,0 85,85 0 1 1 170,0 z"
+       transform="translate(-124.28571,-85.714286)"
+       inkscape:label="#path2987" />
+    <path
+       transform="translate(-405.71428,218.57143)"
+       d="m 641.42859,290.21933 a 85,85 0 1 1 -170,0 85,85 0 1 1 170,0 z"
+       sodipodi:ry="85"
+       sodipodi:rx="85"
+       sodipodi:cy="290.21933"
+       sodipodi:cx="556.42859"
+       id="0circles0"
+       style="fill:#0095ff;fill-opacity:1;stroke:#3b3b3b;stroke-width:2.50000000000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       sodipodi:type="arc"
+       inkscape:label="#path2989" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#0095ff;fill-opacity:1;stroke:#3b3b3b;stroke-width:3.33612041999999986;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;opacity:0.71604937999999996"
+       id="0circles1"
+       sodipodi:cx="556.42859"
+       sodipodi:cy="290.21933"
+       sodipodi:rx="85"
+       sodipodi:ry="85"
+       d="m 641.42859,290.21933 a 85,85 0 1 1 -170,0 85,85 0 1 1 170,0 z"
+       transform="matrix(0.74937343,0,0,0.74937343,-104.82992,291.3081)"
+       inkscape:label="#path2991" />
+    <path
+       transform="matrix(0.39849624,0,0,0.39849624,214.6939,393.13944)"
+       d="m 641.42859,290.21933 a 85,85 0 1 1 -170,0 85,85 0 1 1 170,0 z"
+       sodipodi:ry="85"
+       sodipodi:rx="85"
+       sodipodi:cy="290.21933"
+       sodipodi:cx="556.42859"
+       id="0circles2"
+       style="fill:#0095ff;fill-opacity:1;stroke:#3b3b3b;stroke-width:6.27358492000000023;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;opacity:0.28806584000000002"
+       sodipodi:type="arc"
+       inkscape:label="#path2993" />
+    <g
+       id="g3767">
+      <text
+         sodipodi:linespacing="125%"
+         id="text3763"
+         y="912.59924"
+         x="83.155045"
+         style="font-size:54.63201141px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="912.59924"
+           x="83.155045"
+           id="tspan3765"
+           sodipodi:role="line">Not to extract</tspan></text>
+    </g>
+  </g>
+</svg>
diff --git a/contrib/inkscape_tools/tests/app/src/s2pn.nit b/contrib/inkscape_tools/tests/app/src/s2pn.nit
new file mode 100644 (file)
index 0000000..822e419
--- /dev/null
@@ -0,0 +1,57 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import drawing
+
+class S2pnApp
+       super App
+
+       var img_set: DrawingImages
+
+       init do super
+
+       redef fun init_window
+       do
+               super
+
+               img_set = new DrawingImages
+               img_set.load_all(self)
+       end
+
+       redef fun frame_core( display )
+       do
+               display.clear(0.2, 0.2, 0.2)
+
+               display.blit(img_set.square, 0, 0 )
+               display.blit(img_set.circle, 100, 100 )
+               display.blit(img_set.circles[0], 0, 100 )
+               display.blit(img_set.circles[1], 0, 200 )
+               display.blit(img_set.circles[2], 0, 300 )
+       end
+
+       redef fun input( ie )
+       do
+               if ie isa QuitEvent then
+                       quit = true
+                       return true
+               end
+
+               return false
+       end
+end
+
+var app = new S2pnApp
+app.main_loop
diff --git a/contrib/inkscape_tools/tests/app/src/s2pn_android.nit b/contrib/inkscape_tools/tests/app/src/s2pn_android.nit
new file mode 100644 (file)
index 0000000..d91ade5
--- /dev/null
@@ -0,0 +1,18 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import s2pn
+import mnit_android
diff --git a/contrib/inkscape_tools/tests/app/src/s2pn_linux.nit b/contrib/inkscape_tools/tests/app/src/s2pn_linux.nit
new file mode 100644 (file)
index 0000000..c7ce636
--- /dev/null
@@ -0,0 +1,18 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import s2pn
+import mnit_linux