 Alright, good afternoon everybody. Talk is entitled Code of R2, this is kind of a follow up to a talk I gave at RubyConf in November. If you didn't see that one, it's not essential knowledge, I'm going to kind of fly through some of the basics at the front end. Everything, slides, code, source files, all on GitHub, so feel free to check out if you're interested. So this is what we're going to do, briefly talk about what processing is. We're going to create some sample content. We're then going to automate that content and talk about what next steps are if you want to kind of pursue this as a project. So first, what is processing? It is a library built on top of Java. It's kind of a simplified programming language, particularly built for people interested in creating art. So that's 2D, 3D, music, video, interactive, all these kinds of things. It's built on top of Java. Every processing takes processing and wraps it in a nice Ruby shell. This guy, Jeremy Ashkanus, has kind of put it together pretty much as a one man show. It's an extremely small community. And part of the reason I'm doing this talk, like my RubyConf talk, is to encourage people to come check this out because I think it's really neat. These are some of the things I showed off at RubyConf that you can do with processing. So top left, I've got a little abstract art piece, 2D. This is 3D balls chasing the mouse pointer using OpenGL. Down here is real time video processing where I call this the witness protection program video. It pixelizes your video. And lastly, a pong game that doesn't even have a scoreboard, so everyone wins. Okay, Ruby processing, the good and the not so good. First off, it really supports creativity. There's an amazing gallery on processing.org of commercial and open source projects that have been built with processing. This is one I found pretty awesome. It's a map of Manhattan where I think there are nine block squares are cross-referenced with AT&T international phone records and then colorized based on what countries they were calling. There's a lot, there's a big push in the processing community about data visualization. So if you're like a Tufti fan, processing is for you. Easy as pie to develop. It is just, it's fun. It's really, honestly, the most fun I've had developing in a long time. It really encourages you to goof around and you can get really cool things done quickly. That side, not performant, okay? It's especially when you're running Ruby processing. Now you're running a wrapper around the JVM, around with the processing library. There are a lot of pieces in the mix. This is not going to be, you're not going to write Quake 16 in Ruby processing, okay? If you, it's great for developing things quickly and prototyping and low load or asynchronous solutions, but if you need hard real-time open GL, prototype with processing and then when you're ready to achieve 90 frames per second, you're going to be writing some C. Extremely large data can also be a struggle, partly due to the way the memory is managed. It's pretty easy to overflow the Java heap. You can increase the heap and so forth, but it works best with smaller data. Those things aren't that bad, okay? A lot of those complaints are levied against both Ruby and Rails themselves, but as we're all here, we know that pretty awesome things can be created with them nonetheless. So I think the same can be done with Ruby processing. This is really the story of, I almost want to say a dream, you know, Greg talked about launching into a project that he thought was going to be complex and made it complex. This is the story of launching into a project I thought was going to be trivial and turned out to be kind of complex. So this is how I got here. I hate this icon. It makes my, like I'm always force quitting Excel, the RAM is nuts. I don't understand why there's two layouts, one called normal and one called like something else and normal's not the default. If it's normal, it should be normal. Anyway, Excel, I hate you Excel, but I had feature envy, okay? I work with a lot of academic data. I've spent 26 of my 29 years either attending school, teaching high school, or as a middle school vice principal, and I work with academic data all the time. This is very common, right? We have very long table headers and narrow table data. If we have horizontal text, then this ends up being, these cells end up being 90% blank space, right? It's unusable, especially in a web context. You can only fit five columns before you scroll across the page. All I wanted to do was implement this for my Rails app. This part blew my mind. I've never wanted to do something that only worked in Internet Explorer. If you use IE, even IE6, there's a simple TBRL CSS property that will rotate text works great. CSS3 transforms. This is awesome because it kind of works, except for that it measures the column before it does the rotation. You end up with this unreadable, rotated text written over your other table cells. If you do it on a span, it doesn't even rotate, div, and sell. I couldn't believe that this was like, I just looked at the Google results, JavaScript, CSS, SVG, Canvas, especially in the latter two, there are ways to do it, but they were too complicated for me. I just looked at them and was like, I don't really get this. Why is this so hard? My conclusion was that I needed to dynamically create images based on the data in my Rails app. That's what I said all to do. That's what we're going to run through about 12 iterations of a program and fit in in 45 minutes. Here we go. Creating images with Ruby processing. As I said, Ruby processing, great for 2D, 3D, we're going to do very simple things with just 2D for right now. This is what a Ruby processing program looks like. It's called a sketch. When you write processing, they're sketches. There are two basic methods. The setup is run one time when it initializes, and draw is by default run infinitely looped as fast as it can. Unless you set a frame rate or something like that to intentionally slow it down, or your computer slow, it will run as fast as it can until it hits an exit. This specific example is written to run this code once, and it would run this over and over except that it has an exit. You can see here I'm just selecting the setup. Size is how we set the window size, and then in the draw I'm setting a white background. I'm picking a fill color that's random. The style of processing is kind of declarative, so you say background, and then you don't say window.background. It's not really an object-oriented syntax, so you say background, then you set the fill color, then you oval draws a shape, save, just saves the file. It's very intelligent, it's a little bit magical, some might say, about the file name. If you name it .png, it outputs a .png. If you name it .gif, it uses gif, tiff, jpeg, whatever. Once it hits exit, it stops its execution. This draw is actually going to only get run one time. I call this proof of concept because I just wanted to make sure that it was possible to output files from Ruby processing. This is the same sketch I just showed you. When you run Ruby processing, when you install the gem, most gems are maybe a 50K, 200K, this is an 18 megabyte gem, because it wraps an entire JRuby instance into that gem, so you're running an encapsulated JRuby, even if you already have JRuby installed, whatever, it uses its own JRuby. You get an RP5 executable, and RP5 has several run methods. You can do RP5 unpack samples, we'll output a group of about 16 samples that are actually included in the gem, great for if you're starting out, want to see some example code. There's RP5 run, which would execute code in the way you would expect, just runs it one time. More importantly, there's RP5 watch, which makes for a great development cycle. It's kind of like auto-test, in that it watches that file, as soon as you make changes, it reloads the code. I'm going to use RP5 watch today, just in case I'm making mistakes. I'm going to RP5 watch that sample.RB, it'll take a few seconds, you'll see it just flash up here, Ruby processing, and it's done. I go to my file system, and this is the graphic generator, so it's output a file. Just to show you that I'm not making this up, I can save the file, give it a different file name, get a second file. If you really wanted to, you could close our save. I'm going to take out the XN save. Now this is going to put it into a loop, ooh, colors. So concept proven, as far as I'm concerned. I can output a file. This is the goal for the project. Provided a string like week six quiz, the name of an assignment. I want to create an image that looks like this. It's vertically oriented, reads bottom to top, tilt your head taco style, and you can read it week six quiz. It's more or less sized by the text length, so I don't want to automatically have a 200 by 200 image. I want it properly sized to the length of the text. I'm going to use the silkscreen font, which if you haven't seen it before, it's from cocky.org. It's a font built for low sizes, so it's readable down to like five pixels or six pixels, which is even much smaller than like six point. So if you need really small stuff, your disclaimers, put them in silkscreen. And I was just going to use black text on white. This is how the algorithm is going to work. Infrastructure, you know, my setup and my draw methods. I'm going to set some options, some default options that could be overridden. I'm then going to create the window in Ruby processing or in processing, I should say, the window like this, the origin zero zero is at the top left. I would need to move the origin down to the bottom of the window and a little bit to the right because text, so if it was outputting this line of text, the origin of this text is the bottom left corner because the baseline of how text works. So if I move this origin straight down here, it will write the text off the edge here. Does that make sense? So the baseline is on this corner. So I have to move it not only down but also one, the height of the letters to the right. So that way my text will end up right here along the edge. Once the origin is down there, I've got to rotate the plane. So now that text gets written in this direction. I've got to actually write the text and output the file. So here's the demo. That's the finished demo. All right. I've got my algorithm and steps up here. I'm going to actually implement it in a slightly different order than the algorithms because it's easier this way for you to see how it's working. So I start out with a setup, a draw, and I added this load parameters method. This is just Ruby, so you can access other objects. You can create your own methods, include modules, whatever you want to do. So I call load parameters to load up my default parameters. I set the size of the window and then do my draw. So so far all draw does is set the background to this specified background color. Again, I'm going to use RP5 watch here. So then as I make changes, you'll see them automatically show up up here. Oh, I didn't save it. Right window. Very impressive. Next. I'm going to add in a few more parameters. My second step, well, really step four here, is going to be to write the text. So I'm doing this part a little out of order. I'm going to load a font. Processing uses this VLW font format. You can take any open type, true type font and create a VLW. It's basically a rasterized set of bitmap images. So you say, here's the open type font and I want it at size eight pixels and processing will output that file for you. Has advantages and disadvantages for that, but anyway, this is how you load up the font. I set that my font color is going to be black and set some default text. Then down in my draw, I actually write the text. So I set my fill color to the font color. This font just actually utilizes this parameter to set the font. And then finally output the text at X position zero, Y position 10. So again, if I did zero, zero, it would start at the corner here and it would write the text up above the frame of the window. So I put it at 10 so we could see it. If I hit save, okay. You see the text there? Great. Next. My next step is to move the origin. So in processing the command to do that is translate, I just added this line to translate the origin down to the line height and minus two fonts have a little built-in padding. And so I found after a little experimentation, I just wanted to take two pixels off the bottom to make the crop a little bit tighter and the height. And then change this from zero, 10 to zero, zero. Hit save. So the default text is getting there, right? It's down here at the bottom left. Step six is going to be to rotate the plane. So processing uses radians, which you probably haven't thought of since like geometry class or something. This looks a little confusing to me because minus 90 radians is like 12 complete circles, but the radians helper converts degrees to radians for you. So I wanted it rotated minus 90 degrees, so that's counterclockwise and convert that to radians so that comes out to like 1.5, blah, blah, blah, blah, blah, blah. Then down here, I just call rotate to rotate the plane. So now any, because of the declarative style, anything I draw or text I write after this rotate will now be vertically oriented. Hit save. The text pops over there, all right? Almost done. Finally, I want to output the file. So I give it an amazing name of default and a file name is PNG. And then down here, save just takes a string. So I'm just concatenating these pieces together with a dot to output the actual file. So now, and I add an exit because I don't want it saving the file over and over and over and over, thrashing my hard drive, just hit exit. So now once I hit save, the window will actually close because it runs that one draw. Right now if I look in my file system, default, so it worked, okay? You can applaud. Don't worry. Save it, save it, save it. Not yet. All right. Parameters from our Rails app. Not very good. So far, obviously infeasible for me to type in all the parameters manually. My first implementation of this was extremely bootleg where I created CSV files and then had my Ruby processing sketch watch a folder for a CSV file to pop up and then bring it. It was ugly. I wanted to figure out a more robust, a more adult way to pass messages from Rails to Ruby processing. And this is where I started. I was excited when I saw this book came out a few months ago. I saw Dave Thomas speak about a year ago and somebody asked him a good question which was what's your favorite thing in Ruby that nobody uses? And his answer was Rinda and then he took about 10 minutes and wrote a distributed application using Rinda. So if you're not familiar with Rinda, it uses like a chalkboard model where you throw up jobs and then workers come along, look at the board, look for jobs that they know how to do, pull off the jobs, do them, post back the results. There are about two tutorials to use Rinda in English and it's like 2005 English. So of course, things have changed and if you're using Rinda, okay, you probably never heard of Rinda. But if now you want to use Rinda, there's not much out there except this book. It's quite good. It goes through Rinda, other background processors including Starling and the author's own library. So I highly recommend it except I started working through it and realized it was a little more complex than I was ready to tackle right now, partly because I was going to be talking between two interpreters that my Rails app was going to be running in MRI and my sketch was running in JRuby and marshaling the objects back and forth made me uncomfortable. So I went to what I knew, which was Beanstalk. If you haven't used Beanstalk for background processing before, this quote is true. It's simple and fast, okay. It's awesome. You can install it, macOS. If you don't use homebrew, check it out. I didn't get the hype until I tried it and I was like, oh, this is awesome. Okay, so try homebrew. Forget mac ports. Fedora, you can yum. Otherwise, Debian, Ubuntu, there's no app package. You can do it from source. If you're this person, you're psycho. It's like network ports and piping and so forth in Windows. Forget about it. This is how you run Beanstalk. You say Beanstalk D and that's it. By default, it listens on the local host, port 11,300. It runs as the current user and stays attached. You can pass in flags to daemonize to run as a low-privilege user. You can run it on different ports. You can run multiple Beanstalks at once. Very flexible. So the catch, as I mentioned, with Ruby processing is that you don't have access to your same gems and objects that you do from your native Ruby interpreter. So that's not what I'm just going to slide. Different slide. Sorry. Beanstalk client is how you interact with Beanstalk from Ruby. So all you need to do is install Beanstalk client and require it in an IRB session. This is how you find the Beanstalk. You just say Beanstalk pool new. Give it the address in the port. It gives you back the Beanstalk. To put a job on the queue, all you do is say put and then a string. A job is just the string. There's nothing complicated about it. It's basically an implementation of the memcache protocol. And so there's no intelligence. You just put strings and you take strings off. It returns a job number, which is 28 in this instance. When I was beginning to say no, here's how you pull jobs from the queue. On the other side, you again require Beanstalk client. You do the same code to find the Beanstalk and you say bs.reserve. bs.reserve goes to the Beanstalk, finds a job, gives it back to you. The string that was put in with that job is its body. So you can just ask job for its body. When you, it's called reserve a job because the Beanstalk gives you the job, but it has not removed it necessarily from the queue. It's marked it as reserved, and so there's a timeout. If your worker fails, I think the default is 300 seconds. It'll give the job to another worker. So here I just print out the body and then once I've done whatever magic I want to do, I say job.delete and that tells the Beanstalk, I'm done with this job, remove it. If there's no job, bs.reserve blocks. So it's kind of a built-in wait mechanism and it's low utilization. It just sits there and waits for a job to show up. This is how we're going to do the Beanstalk solution. We're going to start with the Rails app. Rails app is going to post a job which is made up of text parameters to the Beanstalk. There's going to be a Ruby processing sketch which is looking for jobs, asking the Beanstalk for jobs. Once a job pops up, those text parameters are going to get sent to Ruby processing. It's going to do its magic, create the image, and put that image directly into the public folder of the Rails app. From the Rails side, all I'm going to do is, so this model after Gradebook where you've got students and assignments and grades, and the assignment model, the assignments are going to be the columns on my table. So all I need to do is when an assignment is saved, find the Beanstalk, create a job, and push on the file name and the text. From the worker's side, wait for a job, load those parameters, create the image, remove the job. That's it, simple. So as I said, Beanstalk works with plain strings. We'll need to do some kind of serialization, unserialization. I chose to use YAML because I like it and it's easy. You could do JSON or whatever other plain text you like. And here's how I implement it. First, in the assignment model, I do an after save, and just say I'm going to generate this header image, and then I define generate header image as find the Beanstalk, push on these parameters. So give it a file name, which is defined here. Give it the text, the name of the assignment itself, convert it to YAML, and then just put that on the Beanstalk. Obviously, it would probably be more efficient to put this Beanstalk finding process somewhere else because I really just need a singleton Beanstalk, but this works. So Ruby processing can't easily use gems. This is probably one of the biggest challenges when you first start with Ruby processing and you start thinking, oh, I'm going to hook this into so many awesome libraries and my Sinatra apps and all these other things. You can get tripped up because you love gems. You're using JRuby, so now you've got two challenges. Number one, JRuby only uses, can't use gems with native code because it's running in the JVM. Challenge number two, it's Ruby processing's own JVM and it's difficult slash impossible to install gems into that JVM. So you can do some complex tricks with your load path and point it to your local MRI or whatever interpreter you have to find its gems, but practically speaking, if you have non-native code gems, just unpack them into your sketch and then you can require them right there. So that's all I did here was create a vendor, create a gems, go in there, unpack Beanstalk client and now we're going to do some work. For this process, it's actually not going to work during each iteration, so I'm just going to show you the steps, there are no, I can't run the sketch each time, so to start out, I'm going to set my load path and requires, this is basically starting with the app where we left it after the last demo. I'm going to modify the load path so it finds the gem that I unpacked and then I'm going to require Beanstalk client and require YAML, YAML's in core Ruby so it's in that JRuby so you don't have to unpack it or anything. Okay, that's all changed so far, everything else is the same as our previous demo. Next, I need to modify the setup. So first, I'm going to set a default window size and it turns out that there's, I'm not expert in the windowing toolkit that JRuby uses, I assume it's swing but it can draw a window and then the canvas within that window that your drawings are put on can be a different size than the window itself. So I'm creating a 200, 200 window but my canvas might end up being 800, 100 and it doesn't really matter. When we're processing saves the file, it'll use the canvas, not the window. Second, find the Beanstalk, same code we used before to find the Beanstalk. Okay. Then third, I want to find the job and load it so I'm going to do that in my load parameters method here. I'm just going to say Beanstalk reserve and so if there is no job pending, this is where my sketch is going to block, it's just going to wait forever until a job shows up. Once it finally finds a job, it's going to take that job, ask it for its body which is the yamalize string, right? I'm going to yamalode it which seems a little backwards to me some tactically like you're unyamaling it really but whatever, this output's an actual hash. Then I'm going to merge those options over my defaults and I'm going to go ahead and remove the job. If you wanted a more robust solution, right? You should probably wait to delete the job until you actually output the image. Okay, next step. I want to calculate the image size. This is a little bit messy but not too bad. I added in a character width parameter. I thought silkscreen was fixed with and it's not. This process would be easier if you use a proper fixed width font so I approximated that characters are about five pixels wide and then I created this, some might say hacky formula to figure out how tall my image should be so that's the length of the text that's getting passed in. It takes that text, finds its length, adds one, multiplies by the character width and then there's a little scaling factor 1.2. This could be better implemented if you looked at the specific letters and said okay there's six M's and M's are six pixels wide and there's four W's and W's are seven pixels wide and so forth but it works well enough for a prototype. And then finally returning those values. In step five, I need to now load the parameters once and size the window each draw. So there are kind of two models you could go with here. You could each job spin up your whole Ruby processing environment in the sketch or keep it up once, let it block and then just use draw once for each job that gets posted. So I think the later is much more reasonable as long as the Ruby processing doesn't like memory leak, then this should run pretty well. So what I have to move then is the loading of those parameters has to come inside the draw since that's the part that's getting repeated. So now I do parameters, call my load parameters here instead of up here where it was in the setup originally the one time run. And then set the size dynamically. This is what I was talking about as far as the canvas having a different size than the window. So this will change the canvas size. We don't care about the window size. Then lastly, I'm gonna set the target directory. So I made a constant as a hack here but you could obviously output your images to S3 or set an alias in your file system or environment variable, whatever you like. This is how to get into my Rails app. It's called tabler. I created a folder name generated where I store my automatically generated images. And then down here I just tack that on to the front of the target directory. And with that, all my steps are checked off. Okay, so I'm ready to go. This is the same file. There are a lot more pieces now though. I've got a sketch, I've got Beanstalk, I've got a Rails server and I gotta actually make some requests. So here's my Rails server. It's already running. Beanstalk, as I said, all I have to do to start it is Beanstalk B. There's no particular output. It's just waiting there silently to do my bidding. Here I'm gonna use Watch again in case I made a typo, but in production you'd probably just use Run. So RP text generator, run that. One consequence when you're writing Ruby processing sketches is the stack trace. Some people complain about the Rails stack trace. Ruby processing stack traces get crazy because there's this Java exception and that caused the Ruby exception and then that caused another Java exception and it's like 12 pages long. But usually the line error detection is pretty reasonable so then you can figure out what's really going wrong. What do we see here? It's booted up Ruby processing. It's got my 200 by 200 window and nothing, just waiting. It's blocking, waiting for that job to hit the queue. So if I go to my Rails app, this is what it looks like. I could show you briefly. This is called tablers. My sample demo all really does is output that table in the view. I just output an image tag and ask the assignment for its image path, image path being defined on the model. So again, probably something you wanna put in environment configuration, but it works. All right, so these are all, if I refresh this, no images, there are no images. It's looking here in table or public generated, images generated, right, which is blank. So this hook is gonna get called as soon as we edit or create an assignment. So I hit edit, I change the name, week six quiz. Submit, view all, boom, text, image is here, namespace by the ID number, so the unique. Go through, okay, I can do a second one. More images, okay, I can test creating a new assignment. Okay, works, yes. Thank you, thank you, thank you, thank you, thank you. So what have we done? We built a distributed message passing framework. We used Ruby Processing to power the worker and we rolled it all into a Rails app. So now we're coordinating two independent Ruby interpreters, including external Java libraries and making it all work together. Now what, right, you were promised video in the brief for this talk, mentions video. Ruby Processing makes this too easy. Rendering an actual video is quite slow, so I'm gonna show you some live video. I call it video tagger. My model here was to take a video with the idea that your Rails app might take uploaded videos like your uploading screencasts or your users are uploading sample videos and you wanna mark them, you know, you wanna say property of my top secret website. This is what you have to do in Processing to make video work. You load a couple libraries. These are from Java QuickTime. You set the window size, smaller it is, the faster frame rate you'll get, but you can go pretty big. Then you say video capture new. This is the line of code it takes to turn on my eyesight and start Processing video, okay? I was amazed that it was that easy. I follow a similar pattern as I did. Otherwise, I have this load parameter, load parameters method, set my default. I'm gonna write some text that's secret property of Mountain West RubyConf. In my draw, there's this if video available, weights for a frame to come in from the camera, so if draw is running faster than 30 FPS, it just waits, waits. And then image actually takes that frame of video and outputs it to the screen. This bar, I'm gonna draw a black bar across the bottom that's black because it's zero. Direct mode sets which kind of coordinate I'm gonna give it for the rectangle, so I'm gonna specify the bottom left corner. It's gonna be at X position zero, and then it's gonna be three line heights tall and go the whole width of the window. Then finally, I'm gonna output some text that's in my specified font color with the same font, and here's my text, and then these are just positioning, the X and Y position. I wanted to slam it against the bottom right corner, but I wanted to scale based on how long the whole line was. All that to say, you go here and that one. You'll see that, oh man, you can't see anything. Well, down here it's tagged. Yeah, we'll make it more beautiful. Hello. Okay, so if you wanna be creeped out while you're programming, watch yourself. Goodbye. You can capture frames, you can do things to individual frames. You know, if you wanna insert a watermark every 30 seconds, it's relatively easy to do that, all these things. So video processing, easy. We did that. These are the next steps. So going from here, I considered this really a prototyping project. What happens next? On the server side, this is nice when it's running on my laptop. I wanted to run this on my actual server. You're gonna run into a problem that processing demands a windowing toolkit. It will try and start rendering, and you know, I was on a Ubuntu host and it was trying to access X windows and start spitting out windows, and if it can't find X running, it will crash. Thankfully, like most times in the Unix world, someone has had this issue before, God knows why, and they created a thing called XVFB, X virtual frame buffer. So this basically just lies to your programs, tells them that X is running, takes their windowing instructions and just dumps them, doesn't do anything. And for our purposes, that's great because we don't actually care about the display of the window, we just want that file to get saved. Secondly, I use God for process monitoring to make sure that JRuby instance doesn't get out of control, crash, close it if the heap gets too big, or if it crashes, restart it. Then, to go beyond prototypes. So where would we go from here? The process is good. Even if processing is too slow, the model here is good, as far as taking your Rails app, spitting out beanstalk jobs, and then having a worker look for those jobs. So if this was running too slowly and I've got 1,000 users and it's taking a few seconds, obviously you could spin up extra Ruby processing workers if that's worth the ramp to you. Or if you're really hitting scale and Ruby processing is just not gonna work out for you anymore, you could re-implement your workers like this. You could first spend two weeks installing ImageMagic and then hire someone for the next six weeks to figure out how to use it. If you're doing audio and video, FFMpeg can do everything that you've never imagined and it's all command line arguments that your command, it's like 32 command line arguments, but make it work first in processing, then you know your goal is good then implement FFMpeg. You can do raw OpenGL. Processing does OpenGL, it wraps OpenGL for you, but if you really want the highest performance, you're gonna write some C and some native OpenGL. Panda is also a great video handling library. All right, wrapping it up. These are all the links. I strongly recommend if you're interested in processing, there's a book called Learning Processing. It's written for non-programmers. So as programmers, you can tear through it quickly. It's great. More importantly, Jeremy and a few other contributors have taken all the examples from this book and re-implemented them with Ruby processing. So you can read the book, see how it works, then look at the Ruby processing example, see how it works in Ruby processing. That's awesome. Silk screen if you want that font is here. Lastly, I teach classes. These are my classes. Most interesting to you is this one down here about professional Rails practices, pair programming, using Pivotal Tracker, all these kinds of things. Talk to me about it if you're interested. And that's it. Do I have time for questions? I'm done. Okay. Are there any questions? Yeah, ask it. With process, you can do whatever you want. There are some alternative implementations of processing. I know there's like a processing.js that tries to implement it all in JavaScript and can be used in the browser, but it only does a small subset of processing. There are other implementations, but I think that would be a decently sized project on its own just because processing relies on a lot of the magic of the existing Java libraries. For instance, the Java QuickTime library and so forth. And obviously those libraries exist on the Mac, but it's not gonna be a small port. It's gonna be some work. Anything else? All right, so go create some dynamic images. Thanks.