{"id":15405705,"url":"https://github.com/zverok/drosterize","last_synced_at":"2025-04-17T02:42:22.838Z","repository":{"id":34714778,"uuid":"38691586","full_name":"zverok/drosterize","owner":"zverok","description":"Self-replicating images with Ruby \u0026 RMagick","archived":false,"fork":false,"pushed_at":"2015-07-07T14:06:32.000Z","size":1775,"stargazers_count":26,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T05:51:17.959Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zverok.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-07-07T14:05:23.000Z","updated_at":"2022-12-01T02:49:24.000Z","dependencies_parsed_at":"2022-09-14T18:40:39.272Z","dependency_job_id":null,"html_url":"https://github.com/zverok/drosterize","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fdrosterize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fdrosterize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fdrosterize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fdrosterize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zverok","download_url":"https://codeload.github.com/zverok/drosterize/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249304803,"owners_count":21247926,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-10-01T16:18:19.058Z","updated_at":"2025-04-17T02:42:22.822Z","avatar_url":"https://github.com/zverok.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Drosterize\n\n**Drosterize** is yet another experiment on translating image-processing\ncode from Wolfram Language to Ruby. Previous was\n[xkcdize](https://github.com/zverok/xkcdize), take a look.\n\nSource of current experiment was an article by\n[Jon McLoone](http://blog.wolfram.com/author/jon-mcloone/), named\n[Droste Effect with Mathematica](http://blog.wolfram.com/2009/04/24/droste-effect-with-mathematica/).\n\nDrosterize does \"[Droste effect](https://en.wikipedia.org/wiki/Droste_effect)\"\n(self-including recursive images).\n\nHere's some examles, and after them is algorithm description and some\nreflections on its implementation.\n\nSource:\n\n\u003cimg src=\"https://raw.github.com/zverok/drosterize/master/examples/tardis.jpg\" width=\"420\" height=\"262\"\u003e\n([Image credits](http://the-hunger-games-users.wikia.com/wiki/File:Tardis-david-tennant-doctor-who-tenth-doctor-HD-Wallpapers.jpg))\n\nSimplest self-replication (l-t-r-b is left-top-right-bottom of white plate on Tardis' door):\n\n```\n./bin/drosterize -f examples/tardis.jpg -l 409 -t 439 -r 635 -b 710 --spirals 0 -o examples/tardis-copies.jpg\n```\n\n\u003cimg src=\"https://raw.github.com/zverok/drosterize/master/examples/tardis-copies.jpg\" width=\"396\" height=\"476\"\u003e\n\nThe same effect with spiral:\n\n```\n./bin/drosterize -f examples/tardis.jpg -l 409 -t 439 -r 635 -b 710 --spirals 1 -o examples/tardis-copies.jpg\n```\n\n\u003cimg src=\"https://raw.github.com/zverok/drosterize/master/examples/tardis-spiral.jpg\" width=\"396\" height=\"476\"\u003e\n\nOr with two spirals:\n\n```\n./bin/drosterize -f examples/tardis.jpg -l 409 -t 439 -r 635 -b 710 --spirals 2 -o examples/tardis-copies.jpg\n```\n\n\u003cimg src=\"https://raw.github.com/zverok/drosterize/master/examples/tardis-2spirals.jpg\" width=\"396\" height=\"476\"\u003e\n\n## Algorithm description\n\n**NB**: ALL algorithm credits belong to Jon McLoone, author of original\narticle.\n\n(It took two days for me to reverse-engineer it from code. Sometimes I\neven think the Wolfram Language is _intetionally_ obscure... Don't know)\n\n1. User provides source image and coordinates (topleft and bottomright)\n  of rectangle, which will contain replications. Typically, selected\n  rectangle should be in some frame or borders, for smoother outlook.\n2. This algorithm works only when image and frame have same centers and\n  same aspect ratios. So, next thing to do is image cropping.\n3. For each (x, y) of output image we calculate which point of input\n  image it should be copied from:\n  * if there's no spiralling (see first example above), the logic is\n    simple: if (x,y) is inside frame, they are scaled by frame-to-image\n    coefficient and taken from that point, otherwise just copied from\n    source image untouched\n  * spirals are complicated! they are calculated by treating (x,y) as\n    a complex number `x + iy` and then doint very nasty things with powers\n    and logarithms; and then above logic (scaling if inside frame) is\n    applied\n4. All math should be done in \"symmetrical\" coordinates space (with `0,0`\n  at center of the image), so, there is a need to convert each coordinate\n  pair\n\n## Implementation highlights\n\n* RMagick is still the only Ruby image manipulation library which can be\n  \"just used\". Though, for this case I've used almost none of its features, just\n  \"get pixel color\" and \"set pixel color\" -- so, maybe some simpler library\n  could do the task as well;\n* I've tried to keep Ruby code as clean and Rubyish as possible; so, I've\n  monkey-patched some RMagick classes, refined `Numeric`, utilized\n  `OpenStruct`-wrapped options and so on;\n* Two methods have prooved they extreme usability: `Image#pixel_color_f` and\n  `Image#transform`;\n* `#pixel_color_f` I've done for [xkcdize](https://github.com/zverok/xkcdize),\n  it just takes pixel color by _non-integer_ coordinates, interpolating\n  surrounding pixels;\n* `#transform` also can be seen in xkcdize (where it\n  has a dumb name `ImageList#map_to_image`), and it is common concept\n  can be seen in [Wolfram](https://reference.wolfram.com/language/ref/ImageTransformation.html)\n  as well as in ImageMagick `convert` interface (where it's called\n  [fx](http://www.imagemagick.org/script/fx.php)). The idea is to transform\n  some image to another point-by-point, where user provides definition\n  of such a transformation (via Ruby block in our case);\n* On parallelism: to be head-on-head with Jon's code, I've tried to use\n  [parallel](https://github.com/grosser/parallel) gem, which seems a good\n  idea on 4-core notebook... Yet the problem is the task not only computational-\n  heavy, it's also data-heavy. It means that transferring data to child\n  processes (which parallel do by `Marshal`-ing them) adds so much\n  overhead that it is killing the entire idea. And multithread (instead of\n  multiprocess) attempt was just as slow as single-thread -- maybe it\n  is because of the GIL or ... Don't know. I left `Drosterize#parallel_drosterize`\n  in `lib/drosterize.rb`, so you can take a look and experiment with;\n* I have not implemented all the options from Jon's article (in fact,\n  I've copied the formula, but then commented it out: it's still there)\n\n## Lessons learned\n\n* Wolfram code not always easy to read! Or its just me... Also, math\n  is hard, yet powerful;\n* There is several things which can be added to Ruby image processing\n  (think RMagick) to make it more suitable for everyday experiment with\n  images;\n* Ruby IS slow, at least for a large amount of simple computations;\n  profiling report for drosterize show most time (tens of seconds!) is\n  taken by things like `Float#abs`, `Complex#**` and so on. Okay, there\n  are millions of them, but it still doesn't look really cool... Still\n  thinking on potential solutions (like using NMatrix and so on)\n* Simple abstractions can be really pricey. At first, I've used `Point`\n  and `Rectangle` classes from [geometry](https://github.com/bfoz/geometry)\n  gem; code was pretty yet overhead (shown by profiling) was really awful.\n  Finally I've dropped the gem completely and used just `[x,y]` for pairs\n  of coordinates and very simple custom Rectangle implementation; also,\n  I've dropped almost all smart refinements and monkey-patches to core\n  classes, despite my love to them;\n* Low-level abstactions are vague. There's dozens of implementations for\n  things like `Point` or `Rectangle` (in fact, RMagick also\n  [have them](https://rmagick.github.io/struct.html#Rectangle)\n  as a very simple utility structs), but when you're really start needing\n  them, there's no confidence of whether some gem should be used, or\n  you need to implement them from scratch, and what functionality should\n  go to those \"basic\" classes (like `Point#inside?(rect)` or `Rect#cover?(point)`),\n  and how to \"teach\" other gems to work with your abstractions, so, you\n  eventually find yourself with bare pairs of coordinates. It is not the\n  Ruby way we like\n* There is no point in using non-Ruby metaphors in Ruby code. While\n  translating code from Wolfram, I was fascinated with FixedPoint concept:\n  `fixed_point(10){|x| do_something(x)}` will call `do_something` on results\n  of previous function... Until \"fixed point\" (value not changing)\n  will be reached (or max iterations pass). The port was pretty straightforward,\n  yet as a basic construct in code it looked confusing -- as if \"I don't\n  want to read this guy's code\". (You can look at `Drosterize#ensure_replication`\n  to see what I've done without this function. For me, the intention and\n  code is pretty clear without that \"cool\" feature.)\n  \n## Credits \u0026 license\n\nIt's not a gem, just an experiment, so, lets think of it as a public\ndomain for a greater good. Written by Victor Shepelev, in train on his\nway to the lovely Odessa city.\n\nAll credits for original algo and many thanks for inspiration are going\nto [Jon McLoone](http://blog.wolfram.com/author/jon-mcloone/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzverok%2Fdrosterize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzverok%2Fdrosterize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzverok%2Fdrosterize/lists"}