cparnot / ASCIImage

Create UIImage / NSImage instances with NSString and ASCII art
MIT License
1.52k stars 76 forks source link

Implementation-independent context/path metadata? #2

Open Oblomov opened 9 years ago

Oblomov commented 9 years ago

The aim of this issue is to get the discussion started on a point that, I think we all agree, would be pretty useful: finding some implementation-independent method to define context/path metadata (i.e. both default and per-path overrides of fill color, stroke color and stroke width, aliasing/anti-aliasing, and open/closed path choices).

A possibility would be a JSON or YAML snippet holding a simple map having keys default, element0, ... elementN (or something similar), and with values maps of the properties to their values.

This does have the downside that more sophisticated selections (e.g. "if index is less than 2 or higher than 16") are not possible; a solution would be to allow keys in the hash to be arrays or ranges or something similar.

cparnot commented 9 years ago

@mz2 suggested YAML as well.

Shape indexes would be keys, and values for these would be dictionaries with the 'context' keys defined in ASCIImage. Colors could be preset (black, red, white, etc.) as well as based on css conventions.

One problem with the current setup is that default values are not great, with nil / NO assumed by default. That's a problem for anti-aliased as it should be YES by default. I will change that in the implementation. We should define the default values.

I think the shape should come first, then any line after that that does not have the right number of characters is considered the start of some YAML stuff.

In addition, support for layers as proposed by @mz2 could be incorporated. I'll let @mz2 comment here about his proposed syntax. I would suggest layers should come in the order in which they are drawn, so first layer in the list would end up as the bottom layer when drawn. But either way is fine with me.

This does have the downside that more sophisticated selections (e.g. "if index is less than 2 or higher than 16") are not possible; a solution would be to allow keys in the hash to be arrays or ranges or something similar.

Arrays and ranges seem reasonable indeed.

[0..2]: { stroke-color: black; line-width: 1.0 } 3 : { fill-color: white }

(please excuse any YAML syntax error!)

Please help me finalize the "specs" here. I really don't have time to work on this much :-)

Oblomov commented 9 years ago

Ok, I think we have an agreement on YAML.

Concerning the separation of data from metadata, and layers between them, I would propose the following:

So, for example:

1.
23

.1
23

---
default:
 fill: red
0:
 fill: yellow

(where 'default' would override the format defaults, and apply to all picture elements unless further overridden). Also, YAML doesn't have "native" range support, so we'd have to think something up ourselves.

Question: what about metadata for different layers? e.g. element 0 in layer 0 vs element 0 in layer 1. Should elements be numbered consecutively across layers? or should each layer have its own metadata?

cparnot commented 9 years ago

I think layers could be separated by an empty line (empty = nothing, not even whitespace, just two consecutive newlines)

I like that very much. @mz2 has a different syntax in the editor, but it will be easy to switch to that instead.

I don't have a preference about layers order (top to bottom vs bottom to top);

Keeping the order in which things are drawn make more sense IMO, so let's settle on this.

what about multiple images? should this be conflated in layers, or we could maybe use two empty lines to mark the end of an image and the beginning of the next? or is this too much?

Yeah, one image at a time seems like the right level. If we ever want multiple images per file, we could use another '---' or maybe '...' before the next ASCII art.

(where 'default' would override the format defaults, and apply to all picture elements unless further overridden). Also, YAML doesn't have "native" range support, so we'd have to think something up ourselves.

Is YAML flexible in allowing any string as keys? If that's the case, we can use Ruby ranges and arrays. Otherwise, let's just drop it for now.

Question: what about metadata for different layers? e.g. element 0 in layer 0 vs element 0 in layer 1. Should elements be numbered consecutively across layers? or should each layer have its own metadata?

Yes, I would number consecutively across layers. This makes it much simpler to read and interpret, in particular if we have arrays and ranges.

mz2 commented 9 years ago

Sorry for the delay. YAML would be my preference indeed too. I would prefer to keep the metadata and the image close together though -- some of the readability goes away IMO if the metadata follows at the end. Given that YAML supports multiline strings, how about something as follows:

0:
  fill:red 
  image: |
    - 1 -
    1 - 1
    - 1 -
1:
  fill:blue
  image: |
    1 - 2
    - - -
    2 - 1

The top level keys would need to be numerical and express the layer index. On top of these, a default key to describe drawing hints applying to all layers sounds like a good idea. This definition would have a few benefits:

That is, we'd wrap asciimage drawing syntax to YAML under the key <layer index>.image. Plain asciimage markup should remain possible to draw too, this YAML markup would be necessary only if you want to include drawing hints or layers (it's easy enough to see if the data looks like asciimage.yaml or plain asciimage :)

What do you guys think?

cparnot commented 9 years ago

@mz2 It's indeed nice that YAML supports multiline strings. Having the metadata next to each layer is also nice actually.

One issue here is that you also need to index the different shapes in each layer that may have different color/line/etc What would you propose? Sub-dictionaries? Or indexed key, e.g. 'fill[0]', with 'fill' applying to all, and also support for ranges and lists, e.g. 'fill[1..3]' and 'fill[0,2]'?

The exemple from the blog post really is nice as being all in one layer:

  @"· · · · 1 1 1 · · · ·",
  @"· · 1 · · · · · 1 · ·",
  @"· 1 · · · · · · · 1 ·",
  @"1 · · 2 · · · 3 · · 1",
  @"1 · · · # · # · · · 1",
  @"1 · · · · # · · · · 1",
  @"1 · · · # · # · · · 1",
  @"1 · · 3 · · · 2 · · 1",
  @"· 1 · · · · · · · 1 ·",
  @"· · 1 · · · · · 1 · ·",
  @"· · · 1 1 1 1 1 · · ·",
mz2 commented 9 years ago

How about:

0:
  fill:
    all:red 
  image: |
    - 1 -
    1 - 1
    - 1 -
1:
  stroke:
    1:green
    2:yellow
  image: |
    1 - 2
    - - -
    2 - 1

So, the child to items that can refer to all shapes, an individual shape, or a range. I would in fact not allow the child item of for instance "fill" to be a scalar or a dictionary, but only a dictionary (so, the all would need to be explicit -- again ease of parsing correctly and readability in mind).

For ranges I would propose expressing those as keys of the form x..y where x and y are non-negative integers and x < y. The range would be inclusive of both x and y.

Oblomov commented 9 years ago

I like the idea of embedding the ASCIImage data into a standard YAML document a lot. It keeps the ASCIImage format itself extremely simple, and makes parsing of the data plus metadata much simpler, as it's a trivial YAML document load.

I do have a suggestion on how to make things simpler, and particularly to avoid having too deep nesting, which is honestly a little annoying to write and read. I would instead exploit the possibily to have multiple YAML "documents" in the same file to put each layer in a separate document. Additionally, it would be possible to put global metadata (e.g. defaults shared by all layers, as well as whatever else might be applicable, such as image title, author and whatnot) in an initial document without an image: key.

Having less indentation also makes it easier to specify fill/stroke/whatever, IMO, by going the other way around: the values of fille/stroke/whatever are always scalar, but they are nested inside the index of the path they refer to if they are path-specific.

Example:

---
title: Sample document
author: Oblomov
fill: red
stroke: yellow
width: 2
---
fill: blue
image: |
 - 1 -
 1 - 1
  - 1 -
---
stroke: white
1:
 stroke: black
image: |
 1 - 2
 - - -
 2 - 1

It would also be simpler to add layer-metadata (e.g. layer name) this way, and more readable.

With this structure, I think it actually makes more sense to identify paths by the in-layer index, honestly, since that makes it easier to "move" layers around, copy them to other images etc. even by hand (just copy-paste the block and you're done!)

mz2 commented 9 years ago

All makes sense. Who wants to build the first implementation? :D

Oblomov commented 9 years ago

I can give it a shot for my Ruby version. So, just to be clear, the full list of options for the paths is:

Did I forget anything?

I also propose common metadata such as title, author, creator, layername, layervisible (might be useful while editing?) and the possiblity to have an imagefile instead of an image key, to link to an external file (relative paths resolve relative to the metadata file) instead of embedding the content.

cparnot commented 9 years ago

I think like the idea of layers separated with '---', which is (somewhat) consistent with YAML and simplifies the indentation issue. We could have an layer-index property if necessary too.

I am happy with most of the names except I would use a dash between words for multi-word keys.

I would have more naturally gone with more verbose name because of my ObjC bias, but I think fill and stroke are good (or else fill-color and stroke-color), and somewhat consistent with css for instance. However, width is very confusing potentially, so I would propose either stroke-width, line-width, line or border.

For path closing, I'd use open-path.

For aliasing, I like the idea of going with false being the default so that anti-aliased is the default. It is perfectly reasonable. But aliased would be my choice, instead of aliasing.

I also like all the other metadata values, except again I would put an extra dash in the keys, e.g. layer-name, layer-visible etc.

About image-file... sure, but that means more things to implement and implies the text is in a file, so might be a bit hard to specify rigourously if you use this in code. Let's call this part an experiment...

There will be a few more properties to add in the future that have to do with join-cap and line-cap styles for instance. Also a scale factor. And then a width and height for the image, together with align-horizontal, align-vertical, top, left, etc. :D But that's for another GitHub issue.

Oblomov commented 9 years ago

At least in Ruby, YAML.load_documents() will return an array of hashes (dictionaries) when passed a multi-document YAML text. If this is the same for whatever YAML loading functions are available for other languages/platforms, the layer-index could be made implicit unless the sequence is not the same as the sequence in the file.

Hyphen to separate multiple words make a lot of sense. I'll follow the names you suggested. I like it because it's also consistent with e.g. CSS. For the same reason, I agree with you that stroke-width makes sense. In fact, for the stroke properties (such as join and line cap) I would suggest keeping the CSS names; it would be easier on anybody that has familiarity with that language.

Concerning scale, width and height, I was actually going to propose something about it (i.e. specify width and height in the global metadata document (the optional first one without image key), and then for each layer specify x and y coordinates, but yes, that's better reserved for another issue.

cparnot commented 9 years ago

At least in Ruby, YAML.load_documents() will return an array of hashes (dictionaries) when passed a multi-document YAML text. If this is the same for whatever YAML loading functions are available for other languages/platforms, the layer-index could be made implicit unless the sequence is not the same as the sequence in the file.

Good point. Indeed, it seems reasonable to assume any YAML library would report the documents in the order in which they are in the text. And I agree layer-index should not have to be explicit.

All good on the naming, then!

Oblomov commented 9 years ago

I've pushed preliminary support for metadata to my asciimage-ruby repository. It only supports single-layer images for the time being, and single-path overrides. If I've done things right, the famous “padlock” image (fixture10) could be assembled from this YAML source:

fill: white
0:
 fill: none
 stroke: black
 stroke-width: 2
2:
 fill: black
image: |
 · · · · · · · · · · · · · · 
 · · · 1 · · · · · · 1 · · · 
 · · · · · · · · · · · · · · 
 · · · · · · · · · · · · · · 
 · · · · · · · · · · · · · · 
 · 3 · 1 · · · · · · 1 · 4 · 
 · · · · · · · · · · · · · · 
 · · · · 2 A · · A 2 · · · · 
 · · · 1 · · · · · · 1 · · · 
 · · · · · · C D · · · · · · 
 · · · · · A · · A · · · · · 
 · · · · · · · · · · · · · · 
 · · · · · · B E · · · · · · 
 · · · · · · · · · · · · · · 
 · 6 · · · · · · · · · · 5 · 

I'm still not entirely bought on ranges, it might be easier to just go with layers to gather multiple paths with the same drawing options, so I'll focus on that next.

mz2 commented 9 years ago

The Famous Padlock :D

I see what you mean about ranges. I think I agree with you, it's technically redundant functionality if there's layers.

cparnot commented 9 years ago

Nitpick warning!

Regarding layers: ASCIImage is supposed to allow you to 'see' the image in your code. To me, layers are supposed to be there to help with that, not to help in grouping paths by color.

I am not saying ranges are necessary (they are potentially just syntactic sugar), but I also don't consider them redundant with layers. Both have different purposes. Layers are for separating images in graphical units that are visually distinct. Indexes are to address different paths within a layer. Ranges are to address multiple paths at once within a layer. 'Visually distinct' will often coincide with groups of paths that have the same fill/stroke/width/etc attributes, so that makes ranges less critical, I agree.

flopp commented 9 years ago

What I find confusing about the discussed formats, is the 0-based indexing of the individual style rules (style 0 corresponds to image's first shape, style 1 to the second shap, and so on). This makes it visually difficult to find the style corresponding to a shape in the image (what's the style number of the "circle shape" created by "A" in the padlock example? You have to count the image's shapes to find that out!). Why not simply use the image's characters as style indexes (the style indexed by "A" would correspond to the circle shape "A" in the padlock example)? Of course there's a problem with polygon shapes consisting of multiple characters, but in this case one could simply use the polygon's first character ("3" is the style index of the polygon made from "3", "4", "5", "6").

cparnot commented 9 years ago

@flopp Excellent point, and something I have been considering as well recently as I used the block-based API a bit more, and struggled with the shapeIndex values. I agree it should allow to use any of the characters from the shape to designate it (in your example of a polygon with 3-4-5-6, any of those would then do). I don't know if YAML allows to distinguish between integer and string values, in which case both types of values could be supported. Or else, we could use "[1]" to distinguish from "1", the former being the shape index, and the latter for the character in the shape (and even allow to use "1" as the shape index if not otherwise used as a character).

By the way, the reason why I am leaning towards something established like YAML is to avoid implementing a custom parser and syntax for this kind of stuff. YAML seemed both flexible enough, meant for human consumption and editing, and thus close in spirit to the rationale for ASCIImage.

Oblomov commented 9 years ago

YAML can differentiate between numeric indices and string indices:

---
0: "this has a numeric index 0"
'0': "this has a string index 0"

so we could do the whole "string index to be matched with any character of the path” thing.

cparnot commented 9 years ago

@Oblomov Good to know, thanks for chiming in!

One more not quite related comment: I am just swamped with other stuff now, and while I like the YAML idea very much, the ObjC implementation will have to wait a bit :-)

lultimouomo commented 9 years ago

It would be usefult to have a way to specify the single path composition mode - typically, you'll want a (partially) transparent path to "cut through" the image, possibly over a solid background. This could be a composition attribute, with values blend and over - other modes can be added, but I think these cover 99% of the uses

cparnot commented 9 years ago

@lultimouomo Good one, and something I had in mind as well. The lock example is a case where having the keyhole transparent could be useful for instance. But indeed, one needs a way to specify a compositing option. blend and over would indeed cover most of the needs.

Memnarch commented 9 years ago

sounds interesting. Just hope the format is still simple at the end. I'D like to simply fire up Notepad++, and write my image(without too much bloated stuff).

cparnot commented 9 years ago

@Memnarch Yeah, my goal is that the extra stuff is completely optional, and human-readable without documentation. Markdown-like simplicity.

Memnarch commented 9 years ago

that's good. currently i load simple textfiles from disk during development. Your fixturex.txt files for example. My AsciiImage implementation even registers a file-extension into the graphicsapi of Delphi(still .aig, need to change it to .aimg and *.AsciiImage as soon as we set them as final names), which allows loading an AsciiImage into a normal TImage at Designtime on a Form(!) :D And from there on all components using this GraphicsAPI to load arbitary images from disk support it! I just need a final file notation.

EDIT: did i mention a PreviewHandler for WindowsExplorer?

cparnot commented 9 years ago

@Memnarch Wow, nice! Regarding the file extension, it's final. Regarding the format, unfortunately, it's not something I am actively working on. The above discussion sets up the YAML basics already pretty well, but a final more formal "spec" will come in the future. The basic text format as described in the README is not going to change, the YAML bits being just an extension of that.

Memnarch commented 8 years ago

mh the only problem i have with YAML at the moment, that there is no parser for Delphi(But json exists) :(

Memnarch commented 7 years ago

Mh sad to see this discussion got lost a bit as it seems?

cparnot commented 7 years ago

Sorry I don't really have time to put into the project :( Happy for anyone to work on it.

Memnarch commented 7 years ago

That's no problem, we could discuss solutions and present options to you when they are finished. But there is not much sense to it if it's just me :(