Merge: Inkscape tool revamp and gamnit support
authorJean Privat <jean@pryen.org>
Wed, 22 Apr 2015 04:18:34 +0000 (11:18 +0700)
committerJean Privat <jean@pryen.org>
Wed, 22 Apr 2015 04:18:34 +0000 (11:18 +0700)
* Clean up old style inits, add missing doc and refactor main code into classes and methods.
* Adds the gamnit target option, the generated code is much more simple, but the generation a bit more complex.
* Allow to specify the full path to the Nit source file to generate.
* Make rounding to the next power of 2 optional, it will no more be required for mnit programs on Android with #1284.

Pull-Request: #1285
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

contrib/inkscape_tools/README.md
contrib/inkscape_tools/src/svg_to_png_and_nit.nit

index 017ec86..c338656 100644 (file)
@@ -1,25 +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.
+This tool uses Inkscape to prepare assets for Nit applications from a single SVG file.
+It selects objects to extract from the SVG file when their id begins with `0`.
 
-# Features
+It will produce two files:
 
-* 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.
+* A Nit source file that declares a single class with an attribute for each selected object.
+  The attribute usually holds a single texture, except if the the id ends with a digit, then it will be an array.
 
-# Usage
+* A single PNG image file that contains all the selected objects.
+  With the option `--pow2`, the image size is rounded to the next of 2.
+
+## 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
+2. Create objects and set their ids to begin with `0`.
+3. Save the document to `drawing.svg` (for this example), the name of the file is used to name the Nit class.
 4. Execute `bin/svg_to_png_and_nit drawing.svg`
-5. From your code, import the generated source file (at  src/drawing.nit`)
+5. From your code, import the generated source file at `src/drawing.nit`.
 6. Use the class `DrawingImages` and its attributes.
 
-# Examples
+## Examples
 
-The minimal test in  tests/app/  shows the basic usage of this tool.
+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.
 
index b24cbf5..f9e690b 100644 (file)
@@ -1,6 +1,6 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
-# Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2012-2015 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.
 # 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.
+# Extract images of objects from an SVG file using Inkscape
 module svg_to_png_and_nit
 
 import opts
 import template
 
+# Image information extracted from the SVG file
 class Image
+       # Name extracted from the object ID minus the `0` prefix and Nit safe
        var name: String
+
+       # Left border
        var x: Int
+
+       # Top border
        var y: Int
+
+       # Image width
        var w: Int
+
+       # Image height
        var h: Int
+
+       # Right border
        fun right: Int do return x+w
+
+       # Bottom border
        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
+# Document being processed, concerns both the source and the target
+class Document
+       # Name of the source file
+       var drawing_name: String
+
+       # Name of the class to generate
+       var nit_class_name: String = drawing_name.capitalized + "Images" is lazy
+
+       # Scaling to apply to the exported image
+       var scale: Float
+
+       # Source minimum X
+       var min_x: Int
+
+       # Source maximum X
+       var max_x: Int
+
+       # Source minimum Y
+       var min_y: Int
+
+       # Source maximum Y
+       var max_y: Int
+
+       # Get the coordinates for `image` as `"x, y, w, h"`
+       fun coordinates(image: Image): String
+       do
+               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
+
+               return "{x}, {y}, {w}, {h}"
+       end
+end
+
+# Nit module with a single class to retrieve to access the extracted images
+abstract class ImageSetSrc
        super Template
 
-       var name: String
-       init(name: String) do self.name = name
+       # Target document
+       var document: Document
 
-       var attributes = new Array[String]
-       var load_exprs = new Array[String]
+       # Images found in the source document
+       var images: Array[Image]
+end
+
+# Nit module targeting the MNit framework
+class MnitImageSetSrc
+       super ImageSetSrc
 
        redef fun rendering
        do
+               # Known array of images
+               var arrays_of_images = new Array[String]
+
+               # Attributes of the generated class
+               var attributes = new Array[String]
+
+               # Statements for the generated `load_all` method
+               var load_exprs = new Array[String]
+
+               # Add images to Nit source file
+               for image in images do
+                       # Adapt coordinates to new top left and scale
+                       var coordinates = document.coordinates(image)
+
+                       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)
+                                       attributes.add "\tvar {nit_name} = new Array[Image]\n"
+                               end
+                               load_exprs.add "\t\t{nit_name}.add(main_image.subimage({coordinates}))\n"
+                       else
+                               # Single image
+                               attributes.add "\tvar {nit_name}: Image is noinit\n"
+                               load_exprs.add "\t\t{nit_name} = main_image.subimage({coordinates})\n"
+                       end
+               end
+
                add """
-# file generated by svg_to_png, do not modify, redef instead
+# File generated by svg_to_png_and_nit, do not modify, redef instead
 
 import mnit::image_set
 
-class {{{name}}}
+class {{{document.nit_class_name}}}
        super ImageSet
 
+       private var main_image: Image is noinit
 """
                add_all attributes
                add """
 
        redef fun load_all(app: App)
        do
+               main_image = app.load_image(\"images/{{{document.drawing_name}}}.png\")
 """
                add_all load_exprs
                add """
@@ -68,13 +157,94 @@ end
        end
 end
 
+# Nit module targeting the Gamnit framework
+#
+# Gamnit's `Texture` already manage the lazy loading, no need to do it here.
+class GamnitImageSetSrc
+       super ImageSetSrc
+
+       private fun attributes: Array[String]
+       do
+               # Separate the images from the arrays of images
+               var single_images = new Array[Image]
+               var arrays_of_images = new HashMap[String, Array[Image]]
+
+               for image in images do
+                       var nit_name = image.name
+                       var last_char = nit_name.chars.last
+                       if last_char.to_s.is_numeric then
+
+                               # Is an array
+                               nit_name = nit_name.substring(0, nit_name.length-1)
+                               if not arrays_of_images.keys.has(nit_name) then
+                                       # Create a new array
+                                       var array = new Array[Image]
+                                       arrays_of_images[nit_name] = array
+                               end
+
+                               arrays_of_images[nit_name].add image
+                       else
+                               # Is a single image
+                               single_images.add image
+                       end
+               end
+
+               # Attributes of the class
+               var attributes = new Array[String]
+               attributes.add "\tprivate var main_image = new Texture(\"images/{document.drawing_name}.png\")\n"
+
+               # Add single images to Nit source file
+               for image in single_images do
+                       # Adapt coordinates to new top left and scale
+                       var coordinates = document.coordinates(image)
+
+                       attributes.add "\tvar {image.name}: Texture = main_image.subtexture({coordinates})\n"
+               end
+
+               # Add array of images too
+               for name, images in arrays_of_images do
+
+                       var lines = new Array[String]
+                       for image in images do
+                               var coordinates = document.coordinates(image)
+                               lines.add "\t\tmain_image.subtexture({coordinates})"
+                       end
+
+                       attributes.add """
+       var {{{name}}} = new Array[Texture].with_items(
+{{{lines.join(",\n")}}})
+"""
+               end
+
+               return attributes
+       end
+
+       redef fun rendering
+       do
+               add """
+# File generated by svg_to_png_and_nit, do not modify, redef instead
+
+import gamnit::display
+
+class {{{document.nit_class_name}}}
+
+"""
+               add_all attributes
+               add """
+end
+"""
+       end
+end
+
 redef class Int
-       fun adapt(d: Int, scale: Float): Int
+       # Magic adaption of this coordinates to the given `margin` and `scale`
+       fun adapt(margin: Int, scale: Float): Int
        do
-               var corrected = self-d
+               var corrected = self-margin
                return (corrected.to_f*scale).to_i
        end
 
+       # The first power of to equal or greater than `self`
        fun next_pow2: Int
        do
                var p = 2
@@ -83,13 +253,15 @@ redef class Int
        end
 end
 
-var opt_out_src = new OptionString("Path to output source file", "--src", "-s")
+var opt_out_src = new OptionString("Path to output source file (folder or 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_scale = new OptionFloat("Apply scaling to exported images (default at 1.0 of 90dpi)", 1.0, "--scale", "-x")
+var opt_gamnit = new OptionBool("Target the Gamnit framework (by default it targets Mnit)", "--gamnit", "-g")
+var opt_pow2 = new OptionBool("Round the image size to the next power of 2", "--pow2")
 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.add_option(opt_out_src, opt_assets, opt_scale, opt_gamnit, opt_pow2, opt_help)
 
 opt_context.parse(args)
 var rest = opt_context.rest
@@ -125,15 +297,13 @@ end
 
 var src_path = opt_out_src.value
 if src_path == null then src_path = "src"
-if not src_path.file_exists then
+if not src_path.file_exists and src_path.file_extension != "nit" 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")
 
@@ -177,7 +347,7 @@ for drawing in drawings do
        while not proc.eof do
                var line = proc.read_line
                var words = line.split(",")
-               
+
                if words.length == 5 then
                        var id = words[0]
 
@@ -202,54 +372,37 @@ for drawing in drawings do
        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 is noinit\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 document = new Document(drawing_name, scale, min_x, max_x, min_y, max_y)
 
-               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 is noinit\n"
-                       nit_src.load_exprs.add "\t\t{nit_name} = main_image.subimage({x}, {y}, {w}, {h})\n"
-               end
+       # Nit class
+       var nit_src: ImageSetSrc
+       if opt_gamnit.value then
+               nit_src = new GamnitImageSetSrc(document, images)
+       else
+               nit_src = new MnitImageSetSrc(document, images)
+       end
+
+       if not src_path.file_extension == "nit" then
+               src_path = src_path/drawing_name+".nit"
        end
 
        # Output source file
-       var src_file = new FileWriter.open("{src_path}/{drawing_name}.nit")
+       var src_file = new FileWriter.open(src_path)
        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
+       # Find next power of 2
+       if opt_pow2.value then
+               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
+               var dy = max_y - min_y
+               max_y = dy.next_pow2 + min_y
+       end
 
        # 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.