From: Jean Privat Date: Wed, 22 Apr 2015 04:18:34 +0000 (+0700) Subject: Merge: Inkscape tool revamp and gamnit support X-Git-Tag: v0.7.4~12 X-Git-Url: http://nitlanguage.org?hp=a77ea294448fa7de0f592cadbd3bceb1d2e09ca2 Merge: Inkscape tool revamp and gamnit support * 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 Reviewed-by: Alexandre Terrasa --- diff --git a/contrib/inkscape_tools/README.md b/contrib/inkscape_tools/README.md index 017ec86..c338656 100644 --- a/contrib/inkscape_tools/README.md +++ b/contrib/inkscape_tools/README.md @@ -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. diff --git a/contrib/inkscape_tools/src/svg_to_png_and_nit.nit b/contrib/inkscape_tools/src/svg_to_png_and_nit.nit index b24cbf5..f9e690b 100644 --- a/contrib/inkscape_tools/src/svg_to_png_and_nit.nit +++ b/contrib/inkscape_tools/src/svg_to_png_and_nit.nit @@ -1,6 +1,6 @@ # This file is part of NIT ( http://www.nitlanguage.org ). # -# Copyright 2012-2014 Alexis Laferrière +# Copyright 2012-2015 Alexis Laferrière # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,51 +14,140 @@ # 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.