Showing posts with label phantomjs. Show all posts
Showing posts with label phantomjs. Show all posts

Monday, April 18, 2016

Better Static Images for Amphibian.com

The new, improved version of the image for a recent comic.
As I mentioned last week, I have been working to close Issue #7 on my webcomic. Basically, Amphibian.com is not a normal webcomic. It's not an image, which is great until someone wants to share a link to a comic on social media. I auto-generate static images of the comic just for sharing. But the comics which contain embedded games and stuff haven't looked right in the static captures. I wanted to at least put a warning in the image that explains why they often look weird, but as an added bonus I actually made them look better (most of the time).

As you can see in the picture on the right, I was able to add some warning text to the top of dynamic comics. I generally share this version on TopWebcomics.com but you may see it other places as well. When people see comics like this in the future, they'll know that they should view it on Amphibian.com to get the full effect.

Also, when this comic was originally published, the third frame was black. The game was not captured as part of the image. This was due to two things - the URL of the page used for the image capture was such that the game's image assets could not be loaded, and the fact that the capture was taken before the game initialized asynchronously. I fixed the URL issue by changing the static image comic path from /basic/[comic-number] to /[comic-number]&b=1. There wasn't really a generic way to ensure the comic will be rendered before the capture in all cases, but I added a 500 millisecond delay which should be good enough the majority of the time.

Finally, I also locked-down the ability to see the basic versions of the comic. It was the case that you could view a future comic if you asked for the basic rendering. I changed the rules so that only authenticated users or requests coming from the server itself can see them. I use PhantomJS to perform the image capture and it runs on the server, so that was a simpler fix than I initially thought that it would be.

It always feels good to resolve an issue. It also feels good to read one of my comics. Here's the link so you can feel good too.

Amphibian.com comic for 18 April 2016

Monday, January 25, 2016

Run Your Own Comic

Mechanical frogs, for repeatable
 installations of Amphibian.com
As you may have heard, the East Coast got hit by a major snow storm this weekend. It started snowing Friday night and snowed most of the day Saturday. Since I couldn't go anywhere I was able to get a lot done - making some new comics and enhancing the platformer game that I've been working on.

But before I did any of that, I finished checking in files and instructions for repeatable installations of the Amphibian.com webcomic application. I started doing this over a year ago, but never finished it. The goal was to have all of the files and complete keystroke-level instructions necessary to set up Amphibian.com on a new Ubuntu instance.

Why do I want this? Currently, the comic is running on an Ubuntu virtual machine hosted by Digital Ocean. Digital Ocean is great and I hope they're around for a long time, but what if they aren't? Or what if another service comes along for a better price? Or what if I just want to set up another instance somewhere for test purposes?

The complete installation includes:


I sat down in the morning with VirtualBox, the Ubuntu 14.04.3 server ISO image, and got to work. I created a fresh virtual machine with a clean install of Ubuntu and documented all the steps I had to perform to get the comic running. When I thought I had all the commands documented, I started from scratch with a new virtual machine and did it all again by just copying and pasting commands. Of course, I missed a few. I updated my document and tried again with a third virtual machine. That time, everything went well and I had Amphibian.com running in under 10 minutes.

The things that I was really missing were database setup scripts (to make the database, user, and tables), the site configuration files for Nginx, and a script to start and stop the application. I added those to the repository along with my complete setup instructions and now I feel a lot better. Not only does it allow me to have the confidence that I can recreate my application if I ever need to, but it allows other people to easily set up and run my application as well. Maybe you'd like to try it, and make your own comic?

Here is the link directly to the INSTALL.txt file in the repo on GitHub, so you can check it out.

What's next? Maybe complete automation of the setup using something like Puppet. That way I could take advantage of an auto-scaling service like AWS offers, so when my comic gets insanely popular I won't have to worry about long response times. It could happen.





As a side note, I found out while I was doing this that a new major version of PhantomJS was recently released. This 2.0.0 has some major differences and actually fixes some bugs that I've encountered while using it to generate the static images of my comics. Unfortunately, there is no binary distribution available for Linux yet - so my installation instructions for Amphibian.com actually include building PhantomJS 2.0.0 from source. That takes a long time, so I'm keeping my binary around in case I need it again before the official ones are available. If you would like it (built for Ubuntu 14.04.3 64-bit) just let me know and I'll give you a link.



If you're done setting up your own copy of Amphibian.com now, click the picture below and view my Amphibian.com comic for today!

Amphibian.com comic for 25 January 2016

Friday, September 25, 2015

Faux Bold is Better Than No Bold

Today I'm going to forego another in-depth discussion of a new ES6 language feature that recently became available in Node 4. Instead, I'd like to make a brief mention of what I learned this last week concerning "faux" font styles and PhantomJS.

First of all, what are faux font styles? As I recently learned, when you use a web font and not all the special styles are available (such as bold and italic), the browser will just alter the normal version of the font to create the faux styles. As a side note, I also learned that faux has only been used in English by itself to mean "fake" since the 1980's. I am older than this usage.

So when a font has its own bold and italic styles available, you should probably use them. Typically, this is accomplished by specifying multiple @font-face directives in your CSS. Each should define font-weight and font-style appropriately.

@font-face {
    font-family: 'Whatnot';
    src: url('/css/Whatnot-Regular.ttf');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'Whatnot';
    src: url('/css/Whatnot-Bold.ttf');
    font-weight: bold;
    font-style: normal;
}

@font-face {
    font-family: 'Whatnot';
    src: url('/css/Whatnot-Italic.ttf');
    font-weight: normal;
    font-style: italic;
}

Like the above example, if you have multiple versions of the font you should use them. They'll probably look better than the faux versions made by the browser.

I use Sniglet as my standard font for the comics and it doesn't have an italic version. I have no choice but to settle for the faux version. It's not the end of the world, but I noticed a problem. When I made a comic that actually relied on bold italic text, the text was showing up as neither bold nor italic in the .png image generated by PhantomJS. Why?

After a bit of investigation, it would appear that PhantomJS can't make faux font styles out of SVG fonts. Any attempt to show bold or italic Sniglet was being ignored for as long as I've been making these comics. The solution? Switch to the True Type version of the font. It doesn't look that great when PhantomJS renders it but it's better than nothing.

Amphibian.com comic for 25 September 2015

Monday, August 25, 2014

PhantomJS for Image Capture

I don't know if you've ever noticed, but when you share one of my comics on Twitter, Facebook, or Pinterest (hint: you should be sharing my comic) it is able to grab a preview of the comic that is a static image. This meta tags on the page tell the social media sites which image to use:

<meta property="og:image" content="http://amphibian.com/cell/25.png">

Dynamically Created Image of a Comic
Since the comics are actually SVGs positioned inside <div> tags, it is necessary to create these images somehow. Early on, I created some manually but I really needed an automated method that was integrated with the online editor. Every time I create or update a comic it needs to automatically generate new images.

The solution I came up with was integrating PhantomJS with my web application. You can do a lot of cool stuff with PhantomJS, like automated testing of web applications, but I am just using it for its image capture capabilities right now.

To make an image out of a web page, you just need to make a JavaScript file to control PhantomJS. Here is an example similar to what I use.

var page = require('webpage').create();
page.viewportSize = { width: 1202, height: 5000 };
page.open('http://amphibian.com/basic/25/1', function() {
    page.clipRect = page.evaluate(function() {
        var areaRect = document.getElementById('comicArea').getBoundingClientRect();
        var cellRect = document.getElementById('cell-0').getBoundingClientRect();
        var r = JSON.parse(JSON.stringify(areaRect));
        r.height += (r.top * 2);
        r.top = 0;
        r.left = cellRect.left - 102;
        r.right = cellRect.right + 102;
        r.width = cellRect.width + 204;
        return r;
    });
    page.render('demo.png');
    phantom.exit();
});

Line 1 is just pretty standard, to make a page object. You can read more about the different modules in the PhantomJS documentation.

One line 2, I set the viewport size. This controls how wide my "virtual" web browser client will be. Remember that my comics resize themselves based on the client, so I want to use the maximum size in order to make the rendered image very large. The social media sites almost always shrink the images down and starting with the largest size will result in the cleanest picture in the end.

Line 3 is where I make the call to open the page. When the page is opened, the callback function is called. That's where the good stuff happens.

When you want to make an image out of a web page you can either do the whole thing or just part of the page. If you want to do a partial page, you need to set the clipRect property of the page object before calling render. That's what I'm doing starting at line 4, with the call to page.evaluate.

When calling evaluate on the page object, you pass in a function that should be executed as JavaScript in the context of the page. It would be the same as if the page had JavaScript in a script block.

To make the clip rectangle that I want, I need to get the dimensions of 2 parts of the page - the comicArea div and the div of the first cell. I get those on lines 5 and 6, by simply getting the elements by id and then calling their getBoundingClientRect methods. However, the rectangle I want to return is neither of these exactly. I need to make some adjustments.

On line 7 I do something that you might find a bit strange. I create a new variable r by parsing the JSON returned from a stringifying the areaRect object. Why am I doing this? Because the rectangles returned from the calls to getBoundingClientRect are immutable and I want to make changes. By dumping the object to a JSON string and then reading that string into a new object, I get a mutable copy.

Lines 8 through 12 are the adjustments to the clip rectangle I want to return. I change the top and height of the rectangle around the comicArea and then adjust the left, right, and width values using the corresponding values from the cell as a starting point.

Try it yourself and you should get an image of a single cell of one of my comics. Or change some stuff around and try getting images of other pages.

The complete solution in my web application is slightly more complicated, because I have to dynamically generate the JavaScript for PhantomJS, call PhantomJS from Node, read the resulting image into the database, and then cleanup the temporary files. But we can talk about that some other time...

Amphibian.com comic for August 25, 2014