{"id":36440240,"url":"https://github.com/smackem/ylang","last_synced_at":"2026-01-11T21:53:38.806Z","repository":{"id":52523527,"uuid":"165493578","full_name":"smackem/ylang","owner":"smackem","description":"An image manipulation language","archived":false,"fork":false,"pushed_at":"2020-08-25T15:06:01.000Z","size":14792,"stargazers_count":3,"open_issues_count":8,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-14T19:40:03.819Z","etag":null,"topics":["compiler","golang","image","image-analysis","image-manipulation","image-processing","vscode-extension"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smackem.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-13T10:44:39.000Z","updated_at":"2023-01-29T03:57:07.000Z","dependencies_parsed_at":"2022-09-19T00:00:38.657Z","dependency_job_id":null,"html_url":"https://github.com/smackem/ylang","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/smackem/ylang","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smackem%2Fylang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smackem%2Fylang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smackem%2Fylang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smackem%2Fylang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smackem","download_url":"https://codeload.github.com/smackem/ylang/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smackem%2Fylang/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28324404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T18:42:50.174Z","status":"ssl_error","status_checked_at":"2026-01-11T18:39:13.842Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","golang","image","image-analysis","image-manipulation","image-processing","vscode-extension"],"created_at":"2026-01-11T21:53:38.339Z","updated_at":"2026-01-11T21:53:38.799Z","avatar_url":"https://github.com/smackem.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ylang\nAn image manipulation language\n\nylang is able to easily express these algorithms:\n* Convolution\n* Median\n* Color distribution\n* Edge detection\n* Hough transform\n\n## Usage\n```\n./ylang -code script.ylang -image image.jpg -out out.png\n```\n* `script.ylang` contains the ylang code to execute against the passed image\n* `image.jpg` is the input image\n* `out.png` is the output image\n\n## Samples\n\nThis is the original image:\n\n![original](https://raw.githubusercontent.com/smackem/ylang/master/doc/fish.jpg \"Original\")\n\n### Invert\n\n```\nfor point in Bounds {\n    color := @point\n    @point = -color\n}\n```\nThis is a complete ylang script. It iterates over all points contained in the rectangle `Bounds`, where `Bounds` is a constant that holds the dimensions of the input image.\n\n`color := @point` assigns the color at point `point` to the new variable `color`.\n\n`@point = -color` creates an rgb color with all three channels inverted, then sets the pixel at position `point` to this color.\n\n![invert](https://raw.githubusercontent.com/smackem/ylang/master/doc/invert.png \"Image\")\n\n### Monochrome\n\n```\nfor p in Bounds {\n    @p = @p.intensity01 * #4080ff\n}\n```\n\nThe single statement that is executed for each pixel in the source image is `@p = @p.i01 * #4080ff`.\n\nIt takes the color at point p, calculates the intensity (normalized to 0 .. 1) and multiplies the color `#4080ff` with the intensity. The result of this multiplication is a color with all color channels (r, g, b) multiplied by the intensity value.\n\nNote that using white (`#ffffff`) instead of `#4080ff` yields a greyscale image.\n\n![monochrome](https://raw.githubusercontent.com/smackem/ylang/master/doc/monochrome.png \"Image\")\n\n### Saturate\n\n```\nfor p in Bounds {\n    @p = @p | hsv($) | hsv($.h, $.s * 1.5, $.v) | rgb($)\n}\n```\n\nLike bash, powershell or F#, ylang supports pipelining.\n`n := 1 | $ + 5 | $ * 2` reads like \"take the value 1, add 5, multiply with 2 and finally store the result in the newly declared variable n.\n\nThe statement is equivalent to `n := (1 + 5) * 2`.\n\nThe sample takes the input pixel color, converts it to hsv, creates a new hsv color with 50% more saturation, converts it to rgb and stores it in the target image.\n\n![saturate](https://raw.githubusercontent.com/smackem/ylang/master/doc/saturate.png \"Image\")\n\n### Convolution\n\nConvolution with a HPF (high-pass filter) kernel emphasizes edges:\n\n```\nKernel := |-1 -1 -1\n           -1  8 -1\n           -1 -1 -1|\n\nfor p in Bounds {\n    @p = convolute(p, Kernel)\n}\n```\n\nylang supports kernel (a two-dimensional array of numbers) as a data type with concise and natural syntax. The built-in function `convolute` takes a point and a kernel as input and returns the resulting color.\n\nThe convolution is applied to all color channels (r, g, b).\n\nTo find out more about convolution and kernels, visit\nhttps://en.wikipedia.org/wiki/Kernel_(image_processing)\n\n![edges](https://raw.githubusercontent.com/smackem/ylang/master/doc/edges.png \"Image\")\n\nRunning the same program with a LPF (low-pass filter) kernel blurs the image:\n\n```\nKernel := |0  1  2  1  0\n           1  2  4  2  1\n           2  4  8  4  2\n           1  2  4  2  1\n           0  1  2  1  0|\n\nfor p in Bounds {\n    @p = convolute(p, Kernel)\n}\n```\n\n![blur](https://raw.githubusercontent.com/smackem/ylang/master/doc/blur.png \"Image\")\n\n### Slightly More Complex Sample\n\n```\n// create a 5x5 kernel with all elements set to 1\nMedian := kernel(5, 5, 1)\nCenter := 2;2\n\n// iterate over the image:\n// fetch the red channel values of the 5x5 area around p, sort the resulting kernel\n// and get the median value (center of kernel).\n// then do the same for the green and blue channel.\nfor p in Bounds {\n    r := fetchRed(p, Median) | sort($)[Center]\n    g := fetchGreen(p, Median) | sort($)[Center]\n    b := fetchBlue(p, Median) | sort($)[Center]\n    @p = rgb(r, g, b)\n}\n\n// swap target and source image, committing all changes\nflip()\n\nSobelX := |-1  0  1\n           -2  0  2\n           -1  0  1|\nSobelY := | 1  2  1\n            0  0  0\n           -1 -2 -1|\n\n// iterate over the image:\n// apply the sobel operator in both directions and calculate the gradient's magnitude.\n// hypot is the same as writing sqrt(gx*gx + gy*gy).\n// then calculate the gradient's angle, add 90 deg to get the edge's angle,\n// normalize the magnitude to 0..1 and the angle to 0..360 and write the resulting\n// hsv color to the target image.\nfor p in Bounds {\n    gx := convolute(p, SobelX).i\n    gy := convolute(p, SobelY).i\n    mag := hypot(gx, gy)\n    angle := gx == 0 ? 0 : atan(gy / gx) * Rad2Deg + 90\n    mag = mag / 255\n    @p := round(angle * 2) | hsv($, mag, mag) | rgb($)\n}\n```\n\nThis sample first applies the median filter to the source image, then applies the sobel operator to the resulting image. The sobel operator yields gradient magnitude and gradient angle of edges, both of which are encoded into a the target pixel - the angle as the hue and the magnitude as value and saturation.\n\n![complex](https://raw.githubusercontent.com/smackem/ylang/master/doc/complex.png \"Image\")\n\n## Walkthrough\n\nylang is a dynamic script language featuring built-in types like points, kernels and colors - all of which are needed for image processing.\n\nylang's syntax is inspired by Go, JavaScript and Bash.\n\n### Basics - Primitive Types\n\n* All numbers in ylang are in 32 bit floating point format. The basic mathematical operations are supported on numbers. Constants can be written as `123` or `51.25`.\n* Booleans have either the value `true` or `false`. All comparison operators like `==` or `\u003c` return a boolean value.\n* Strings are usually only used as hash map keys or for logging. String constants are written like this: `\"Hello, world!\"`.\n\n### Basics - Variables and Constants\n\n* Variables are declared with the `:=` operator, like in Go:\n  ```\n  num := 1\n  str := \"hello\"\n  ```\n* Variables can be mutated with the `=` operator:\n  ```\n  num := 1\n  log(num) // prints 1\n  num = 2\n  log(num) // prints 2\n  ```\n  Because of the dynamic type system, variables can also change type:\n  ```\n  v := 100 // initially a number\n  log(v)\n  v = \"hello\" // now a string\n  log(v)\n  ```\n* Identifiers that start with a capital letter can be assigned only once:\n  ```\n  Ratio := 0.5\n  Ratio = 1 // compilation error - Ratio is constant\n  ```\n* The following built-in constants are available:\n  * `Black` - the color black #000000\n  * `White` - the color white #ffffff\n  * `Transparent` - transparent white #ffffff:00\n  * `Pi` - the mathematical constant pi in 32 bit resolution\n  * `Rad2Deg` - the factor to convert radians into degrees\n  * `Deg2Rad` - the factor to convert degrees to radians\n  * `Bounds` - a rectangle containing the bounds of the input image\n\n### Basics - Control Flow\n\n* ylang's `if else` statement is used like this:\n  ```\n  if @p.alpha \u003e 100 {\n      @p = #ff0000\n  } else {\n      @p = #00ff00\n  }\n  ```\n* A shorthand for the previous `if else` statement is the ternary operator:\n  ```\n  @p = @p.alpha \u003e 100 ? #ff0000 : #00ff00\n  ```\n* the `if else` statement can include any number of cases:\n  ```\n  a := @p.alpha\n  if a \u003e 200 {\n      @p = #ff0000\n  } else if a \u003e 150 {\n      @p = #800000\n  } else if a \u003e 100 {\n      @p = #00ff00\n  } else if a \u003e 50 {\n      @p = #008000\n  } else {\n      @p = #0000ff\n  }\n  ```\n* The for loop iterates over ranges of numbers:\n  ```\n  // draw the top line blue\n  for x in 0 .. Bounds.width {\n      @(x;0) = #0000ff\n  }\n  ```\n  or:\n  ```\n  // draw every second pixel in the top line blue\n  for x in 0 .. 2 .. Bounds.width {\n      @(x;0) = #0000ff\n  }\n  ```\n* As seen before, the for loop can also iterate over iterable objects, like lists, kernels or geometrical shapes:\n  ```\n  for n in [\"c\", 0, 0, \"l\"] {\n      log(n)\n  }\n  ```\n* Another, much less common kind of loop is the while loop:\n  ```\n  x := 100\n  y := 1\n  while x \u003e y {\n      x = x / 2\n      y = y * 2\n  }\n  ```\n* To exit from a script or to return a value from a function, you can use the `return` statement:\n  ```\n  for p in Bounds {\n      if p.y \u003e 100 {\n          return nil\n      }\n      @p = #00ff00\n  }\n  ```\n  When breaking execution of the script, you can only return the empty value `nil`.\n\n### Colors\n\nColor literals are written as in HTML: `#ff0000` is the color red. You can append the alpha channel like this: `#00ff00:80` is half-transparent green.\n\nYou can also create color values with the functions `rgb`, `rgba`, `rgb01` or `rgba01`:\n```\ngold := rgb(255, 190, 0)\ngrey := rgb(128)\nhalfOpaqueBlue := rgba(0, 0, 255, 127)\nwhite := rgb01(1, 1, 1)\nhalfOpaqueRed := rgba01(1, 0, 0, 0.5)\n```\n\nThe color type defines these properties:\n```\ncolor := rgba(255, 128, 64, 32)\nred := color.red // or color.r -- red is 255\ngreen := color.green // or color.g -- green is 128\nblue := color.blue // or color.b -- blue is 64\nalpha := color.alpha // or color.a -- alpha is 32\nr01 := color.red01 // or color.r01 -- r01 is 1\ng01 := color.green01 // or color.g01 -- g01 is 0.5\nb01 := color.blue01 // or color.b01 -- b01 is 0.25\na01 := color.alpha01 // or color.a01 -- a01 is 0.125\ni := color.intensity // or color.i -- the intensity (brightness) of the color\ni01 := color.intensity01 // or color.i01 -- the intensity normalized to 0..1\n```\n\nThe color type supports basic arithmetic operations, which are applied per channel:\n```\nrgb(100, 110, 120) + rgb(10, 20, 30) // = rgb(110, 130, 150)\n#ff0080 * 0.5 // = #800040\nrgb(100, 200, 300) * 2 // = rgb(200, 400, 600)\n```\n\nColors can take any value and are only clamped to 0..255 when necessary, e.g. when writing the color to the target image.\n\n### Point\n\n`x;y` denotes a point. `x` and `y` are implicitly converted to integer values.\n\nPoints have the following properties:\n```\np := 100;120\nx := p.x // = 100\ny := p.y // = 120\nmag := p.mag // magnitude of the point interpreted as a vector\n```\n\n### Kernel\n\nKernels can be created as literals:\n```\nk := |0 1 0\n      1 2 1\n      0 1 0|\n```\n\nKernel literals need to be quadratic: width and height must be equal. To create non-quadratic kernels, use the `kernel` function:\n```\nk := kernel(3, 3, 1) // 3x3 with all elements set to 1\nk := kernel(4, 2, fn(x, y) -\u003e x + y)\n// = |0 1 2 3\n//    1 2 3 4|\n```\n\nKernels can be indexed with numbers or points:\n```\nk := |0 1 0\n      1 2 1\n      0 1 0|\nn := k[4] // = 2\nm := k[1;1] // = 2\n```\n\nKernels have these properties:\n```\nk := kernel(2, 3, 0)\nwidth := k.width // = 2\nheight := k.height // = 3\ncount := k.count // = 6 -- number of elements\n```\n\nKernels can be iterated over:\n```\nmaximum := 0\nfor n in |1 5 3 4| {\n    maximum = max(maximum, n)\n}\n// maximum is 5\n```\n\nNote that getting the maximum value of a kernel can be expressed much easier:\n`max(|1 5 3 5|) // = 5`.\n\n### Rectangle\n\nCreate rectangles by passing x, y, width and height to the function `rect`:\n```\nrectangle := rect(100, 100, 20, 50)\n```\n\nRectangles have these properties:\n```\nx := rectangle.x // or rectangle.left\ny := rectangle.y // or rectangle.top\nw := rectangle.width // or rectangle.w\nh := rectangle.height // or rectangle.h\nr := rectangle.right\nb := rectangle.bottom\n```\n\nLike all geometrical shape types in ylang, rectangles can be iterated over. The iteration yields all points within the bounds of the shape.\nThe most common rectangle constant is `Bounds`, which contains the bounds of the input image.\n\n### Line\n\nCreate lines y passing the two endpoints of the line to the function `line`:\n```\nln := line(100;100, 200;250)\n```\n\nLines have these properties:\n```\np1 := ln.p1 // or ln.point1\np2 := ln.p2 // or ln.point2\ndx := ln.dx // the difference between x1 and x2\ndy := ln.dy // the difference between y1 and y2\nlen := ln.len // the length of the line (distance between p1 and p2)\n```\n\nLike all geometrical shape types in ylang, lines can be iterated over. The iteration yields all points on the line.\n\n### Polygon\n\nCreate polygons by passing either an arbitrary number of points or a list of points to the function `polygon`:\n```\npoly := polygon(100;100, 300;200, 150;300)\npoly2 := polygon([100;100, 300;200, 150;300])\n```\nThe last point does not have to be the same as the first, polygons are automatically closed.\n\nPolygons have these properties:\n```\nbounds := poly.bounds // the bounding rectangle around the polygon\nvertices := poly.vertices // the list of vertices (corner points) that make up the polygon\n```\n\nLike all geometrical shape types in ylang, polygons can be iterated over. The iteration yields all points within the shape.\n\n### Circle\n\nCreate circles by passing the center point and the radius to the function `center`:\n```\ncirc := circle(100;100, 50)\n```\n\nCircles have these properties:\n```\ncenter := circ.center\nradius := circ.radius\nbounds := circ.bounds\n```\n\nYou can iterate over circles like over all geometrical shapes.\n\nPlotting a red circle:\n```\nfor p in circle(100;100, 50) {\n    @p = #ff0000\n}\n```\n\nThis can also be achieved more easily with function `plot`:\n```\nplot(circle(100;100, 50), #ff0000)\n```\n\n### Working with images\n\nA ylang script is always executed against two images: a source image and a target image. All read operations are executed against the source image, all write operations against the target image.\n\nReading and writing single pixels can both be achieved with the `@` operator:\n```\n@(0;0) = @(100;100) // copy the source pixel at 100;100 to 0;0 in the target image\n```\n\nInitially, the target image has the same dimensions as the input image and all pixels are transparent black (`#000000:00`).\n\nThis loop copies the source image to the target image:\n```\nfor p in Bounds {\n    @p = @p\n}\n```\n\nThe `blt` function is a much faster way to do this:\n```\nblt(Bounds)\n```\n\nA ylang script can only write to one target image at a time. To apply multiple operations that build upon each other (e.g. blur, then edge detect), use the `flip` function:\n```\nGauss := // LPF kernel...\nLaplace := // HPF kernel...\nfor p in Bounds {\n    @p = convolute(p, Gauss)\n}\nflip()\nfor p in Bounds {\n    @p = convolute(p, Laplace)\n}\n```\n\nTo recall a flipped source image, use the `recall` function:\n```\n// mutate target image...\nOriginalImage := flip()\n// mutate flipped image...\nrecall(OriginalImage)\n// now, the source image is restored to the initial source image\n// do more things...\n```\n\nTo resize the output image, use the `resize` function:\n```\noutBounds := resize(Bounds.width * 2, Bounds.height * 2)\n```\n\n### Math Functions\n\nThe following basic math functions on numbers are available:\n* sin(angle)\n* cos(angle)\n* tan(angle)\n* asin(n)\n* acos(n)\n* atan(n)\n* atan2(dy, dx)\n* sqrt(n)\n* pow(base, exponent)\n* abs(n)\n* round(n)\n* floor(n)\n* ceil(n)\n* hypot(x, y)\n* random(lower, upper)\n* min(n...)\n* max(n...)\n\nAll trigonometric functions work with angles in radians. Use the constants `Deg2Rad` and `Rad2Deg` to convert between degrees and radians.\nSee the functions documentation for details.\n\n\n### Alpha Channel\n\nThe alpha channel does not take part in color arithmetics: `#ffffff:ff / 2` equals `#808080:ff`. All operations to manipulate the alpha channel must be executed explicitly:\n```\nold := #ff0080:ff\nnew := rgba(old, old.alpha / 2)\n```\n\nThe alpha channel is also ignored by convolution. The color returned by the `convolute` function has the alpha value of the center pixel.\nTo convolute the alpha channel, use the `fetchAlpha` function:\n```\nk := |-1 0\n       0 1|\nalpha := fetchAlpha(p, k) | sum($)\n```\nor for kernels with a non-zero sum:\n```\nk := |0 1 0\n      1 2 1\n      0 1 0|\nalpha := fetchAlpha(p, k) | sum($) / sum(k)\n```\n\nylang features the function `compose` for alpha composition:\n```\ngrey := compose(#000000, #ffffff:80) // paint half-opaque white on black - the result is #808080\n```\n\n### Lists\n\nLists in ylang can be written like this:\n```\nls := [1, 2, 3]\n```\nor with the `list` function:\n```\nls := list(100, 0) // a list of 100 zeroes\n```\n\nYou can append to lists with the `::` operator:\n```\nls := [1, 2, 3] :: 4\nls = ls :: 5\n// ls is now [1, 2, 3, 4, 5]\n```\n\nThanks to ylang's dynamic nature, you can mix types in lists:\n```\nls := [1, \"B\", 100;200] // list containing a number, a string and a point\n```\n\nTo retrieve individual values from a list, use the index operator with a numeric index value:\n```\nls := [1, 2, 3]\nfirst := ls[0] // = 1\nsecond := ls[1] // = 2\nthird := ls[2] // = 3\nlast := ls[-1] // = 3\n```\n\nYou can also retrieve sub-lists (slices) from lists:\n```\nls := [1, 2, 3, 4, 5]\nfirstTwo := ls[0 .. 1] // = [1, 2]\nlastTwo := ls[-2 .. -1] // = [4, 5]\ntail := ls[1 .. -1] // = [2, 3, 4, 5]\n```\n\nYou can iterate over lists:\n```\nfor elem in [1, 2, 3] {\n    ...\n}\n```\n\n### Hash Maps and Object Syntax\n\nLike in JavaScript, ylang uses hash maps as objects. Create object literals like so:\n```\npixel := {\n    point: 100;200,\n    color: #ff00ff,\n}\n```\n\nTo create hashmap the \"traditional\" way, use this syntax:\n```\npixel := {}\npixel[\"point\"] = 100;200\npixel[\"color\"] = #ff00ff\n```\n\nEither way, the object properties can be accessed like this:\n```\npt := pixel.point\n```\nor like this:\n```\npt := pixel[\"point\"]\n```\n\n### Functions and Lambdas\n\nylang supports functions as first-class objects, using the keyword `fn`:\n\n```\npow2 := fn(n) {\n    return n * n\n}\n\nresult := pow2(10) // = 100\n```\n\nYou can also define functions with lambda syntax, if the function body only consists returning a single expression:\n```\npow2 := fn(n) -\u003e n * n\n```\n\nYou can pass functions as arguments of functions:\n```\nfilter := fn(ls, predicate) {\n    result := []\n    for elem in ls {\n        if predicate(elem) {\n            result = result :: elem\n        }\n    }\n    return result\n}\nnumbers := [1, 2, 3, 4]\nevenNumbers := filter(numbers, fn(n) -\u003e n % 2 == 0) // [2, 4]\n```\n\n## Roadmap\n\n* Web interface with monaco as editor\n* Compile to intermediate language -\u003e ByteCode\n* Compile to JavaScript, maybe WASM?\n* Implement canny\n  https://www.codeproject.com/kb/cs/canny_edge_detection.aspx\n* Implement Rectange detection through hough transform\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmackem%2Fylang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmackem%2Fylang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmackem%2Fylang/lists"}