Saturday, January 21, 2012

Stock Market Investment for Geeks: A Crash Course

The ink is mostly faded now, but my Mad Money bull is signed by Jim Cramer.
Screenwriter William Goldman (best known for Butch Cassidy & The Sundance Kid) once famously said of Hollywood: "Nobody knows anything."

I'm here to tell you that the same is true, only more so, for Wall Street. Don't be misled.

There are only two people I trust when it comes to investment advice: Jim Cramer, and myself. And there are times when I don't trust either one.

How's that for paranoia?

Here's the truth about investing. No matter what you invest in, knowing what you're doing means understanding markets. And almost nobody understands markets, because to understand markets you have to be able to wrap your head around why people do the weird things they do when they're in the grip of greed, fear, and desperation.

And so, a good "first book" in investing would be Charles Mackay's 1841 classic, Extraordinary Popular Delusions and the Madness of Crowds. (PDF copy available free here.)

The second book you should read (for pure entertainment value, if nothing else) is Jim Cramer's autobiographical Confessions of a Street Addict. If you're a writer or journalism student, be sure to read this book. Cramer started out as a journalist, covering the crime beat for the Tallahassee Democrat, where he covered the Ted Bundy murders. He later worked for the derelict and decrepit Los Angeles Herald-Examiner (before it went under), where he eventually got tired of being poor (he was living out of his car after being robbed twice) and thus applied to (and got into) Harvard Law. The rest of Cramer's incredible story, you'll have to read for yourself. If you read only one of Cramer's books, though, make sure it's this one.

Cramer is far from infallible. But he's the smartest guy on Wall Street, and the one guy I'd seldom want to bet against. I've learned a ton from him.

I've also learned a ton from practical experience. Over the years, I've traded gold futures, cotton futures, lumber futures. I've traded stocks and options. I've wandered into markets I had no business being in. In 2007, I did something like $1.3 million in trades on a $60K account (yes, lots and lots of small trades), largely because I was on a continuous dopamine high induced by a Yale-trained quack who had me taking a cocktail of pharmaceuticals that made me as manic as Richard Simmons on crystal meth.

I've made money, I've lost money, I've done all kinds of stupid things. And I'll do more stupid things. Just not the same ones as before.

The reason I'm writing this is because I actually think I'm a pretty decent investor at this point, and I think I can make you one, too, in about 15 minutes. Or at the very least, I can keep you from making some very awful mistakes. So listen up.

The first thing you need to do to educate yourself about markets is learn to distrust every market "expert" you come across (myself included) and just spend some time watching markets in action. Turn on CNBC and mute the volume. Watch the ticker for a few days, do a lot of chart-watching on Google Finance, get a feel for what kind of mass hysteria is popular at the moment.

If you have one of those lousy company-sponsored 401K plans that only lets you choose between this or that mutual fund, roll that thing into an IRA and begin to manage it yourself. Do some actual investing. Mutual funds are crap, your 401K plan administrator is making tons of money off them (so is your employer), you need to get out of them. Buy actual stocks yourself.

Let me cut the suspense and get right to my investing strategy, which really comes down to just a few very simple rules.

Rule No. 1: Buy low, sell high. (Don't laugh.) That's your ultimate goal. That's your guiding principle. That's Job One. Always keep it in mind.

How to put that into action? Very simple:

Rule No. 2: On market up-days, look for opportunities to sell. On market down-days, look for opportunities to buy. Buy on down days, sell on up days. (How else were you planning to buy low, sell high?) Most people are bad at following this rule. People see stocks going up day after day during a rally, and they figure "Why not jump in for the ride?" Wrong, wrong, wrong. Don't buy when prices are going up. Chances are, you're near a top. When you turn on CNBC in the morning and you see that the markets are going to open to the up-side, you should be thinking: "Hmm, I wonder what I should sell today?" Not: "What should I buy today?"

Rule No. 3: Diversify. Own at least 5 stocks, in 5 entirely different sectors. Don't own more than 10 stocks. If you do, you're just running your own mutual fund. You might as well trade the major-average indexes.

Rule No. 4: Own some gold. Every portfolio, without exception, needs to have some gold in it. Gold is the one thing that will be up when the rest of the market is down (or when war breaks out with Iran, or whatever). Don't buy futures and don't buy mining companies. Buy the actual metal. I recommend you just take a position in GLD. That's the simplest, safest way to get in and out of gold. Buy on down days. Hold forever.

Rule No. 5: Go for quality. Don't go crazy buying crappy speculative bullshit flash-in-the-pan stocks. I agree with Cramer that every investor should probably own at least one speculative stock, at some point. (At the moment, Cramer seems to think highly of HEK. I think he's nuts.) But on the whole, you shouldn't buy stocks that have crazy out-of-whack P/E ratios or that you have faint misgivings about, etc. You want to be able to sleep at night. That means owning rock-solid companies, preferably ones that pay a dividend. GE, IBM, Boeing, McDonalds, whatever. They don't have to be Dow stocks. They just have to be quality stocks, companies with a good longterm story that are profitable, have been around a while, and will be around for a good while longer.

Rule No. 6: Always, always, always buy with conviction. If you can't look at the stock you're about to buy and say to yourself "I have absolute, utter conviction in the quality and longterm outlook for this stock," don't buy it. You should have so much conviction in what you're buying that if it immediately goes down, right after you bought it, you simply buy more (because you're now able to get it at a bargain price). If you felt Boeing was a good buy at $75, consider it an even better buy if it suddenly goes to $70! (And remember, you'll feel much better about that "down stock" if it's paying you a dividend while you wait out the up side.)

Rule No. 7: Don't fight market momentum. There will be days when the entire market seems like it's acting crazy (because it is). Markets are sometimes pathological. Things go up that shouldn't go up. Things go down that shouldn't go down. Be ready for it, and accept it as part of the game. (That's a lot easier to do if you stick to Rule No. 2, above.)

Rule No. 8: Decide in advance how long you're willing to stay with a position (and approximately at what price points you're willing to get out). For example, I happen to believe IBM will go to 200 sometime in the next year. If I buy it, I have to be willing to sit on it for a year to see if I'm right. (I don't currently own any IBM, by the way.)

Rule No. 9: Don't forget the big picture. It's a global market. You have to factor global business conditions (and currency fluctuations) into your strategy. For example, some people think agriculture is a major longterm play (because of all the hungry people in the world) and some people think a tractor company like Deere is a good way to play it. That may well be, but if you believe (as I do) that the dollar will be strong for the first half of this year, you may well want to consider Kubota instead of Deere, since the yen is weak and the world's tractor-buyers may well want to pay for tractors using a comparatively much stronger currency.

Rule No. 10: Do your homework but don't fixate on it. In other words, yes, you should know something about a company before you buy its stock, and the more you know, the better. But don't kid yourself into thinking that markets are all about metrics and stats and knowledge and information. They're largely about fear and greed and stupidity and venality. The best-researched investment will often turn sour for no good reason. If you don't believe it, just look at the performance of professional mutual-fund managers, which tends to suck bigtime.

Would I say that 2012 is going to be a good year for buying stocks? Yes. In general, I think this will be a stellar year for U.S. stocks. But you have to be picky. Some obvious standouts include Boeing (with its huge order backlog) and Apple (which is headed for $500 within the next 12 months). I don't currently own any Boeing or Apple, because they seem a tad overbought right now. I'm waiting for a decent pullback before investing in such "obvious" winners.

Last month, I did well with Oracle (which I bought on December 21 when it took a huge fall based on a disappointing earnings announcement, then sold last week after a 10% runup). I also did well with IBM, which I bought around $181, then again around $179, and sold the minute they released good earnings results and shot up $5 a share. I didn't do so well with WebMD, which I bought the day of its calamitous decline (when the CEO left), thinking it would go up quickly. I got impatient and closed out that position at a $0.75/share loss (a bit prematurely).

If AAPL falls after its Jan. 24 earnings announcement, I'll probably buy some at that point. Right now, $420 still seems a bit pricey. It's probably the best stock there is (or ever was), though. I feel like a damned fool for not owning any.

Boo-hoo-ya.

Monday, January 16, 2012

The New Era of Corporate Gigantism

While we're talking economics, we might as well talk about how competition has fared in pre- and post-Internet economies. The pre-Internet part should be broken up, historically, into pre- and post-mass-media eras. Before the advent of mass media, markets were essentially ecosystems, characterized by diversity and competition. Massive scalability was difficult to achieve. Henry Ford achieved massive scalability by deconstructing complex assembly tasks into smaller tasks that could be accomplished on massive scale by secialized workers, whose output could then be coordinated to produce complex machinery on a massive scale. In a sense, he leveraged the well-known (today) principles of modularity and Separation of Concerns. In doing so, he achieved unprecedented economies of scale, which were hitherto not possible.

Vertical integration was another popular technique pioneered by Ford and other industrial giants of his time. Ford's rubber plantation in the Amazon was not a success, but many other of his "verticalization" attempts were.

It should be mentioned in passing that the reason Ford cars could only be obtained with black paint is not because it was the cheapest color, but because black paint dried the fastest and therefore didn't slow down production. Prior to the advent of the assembly line, you could obtain a Ford car in any number of colors.

We often forget that Ford was a champion for fair wages and utterly flabbergasted the world in 1914 by proactively offering $5-per-day wages (more than double the average wage at the time in America) at his factory, which caused skilled workers to flock to the factory and vastly reduced both turnover and training costs. He also advocated the 48-hour work week (a vast improvement for most workers). Later in his life he would adamantly fight labor unions, however.

Scaling a business, prior to the advent of mass media, mostly meant owning more storefronts (or more factories, or more oilfields) than your competitors. This was also something Ford excelled at. He established a franchise system that put dealerships throughout most of North America and in major cities on six continents.

Enabling scalability of businesses was the major contribution of mass media. In Ford's day, "mass media" meant newspapers. One could argue that the chief contribution of newspapers to society (in years past, and now) is the widespread availability of advertising. The Internet largely moots this purpose and (to an extent) explains the dwindling importance of newspapers today.

The franchise system pioneered by Ford and others is basically a cartelization mechanism. Instead of having businesses that would normally compete against one another, franchising allowed businesses to carve up territories and have no competition within a given territory.

The advent of mass media (newspapers, magazines, radio, and eventually TV) enabled the emergence of highly recognizable national and international brands. Branding is (arguably) yet another form of cartelism. Brand ubiquity diminishes competition by marginalizing little-recognized competitors.

The years leading up to the appearance of the Internet saw the widespread application of franchising and brand-building as cartelization techniques, to the point where "ma-and-pa" entrants in a given market (whether for fast food, books, clothing, banking, gasoline, hardware, tires, home goods, groceries, or you-name-it) were all but eliminated. By 1980, you could pass down any Main Street in America and see the same names: McDonalds, Burger King, Walmart, Target, Best Buy, etc., with hardly a non-franchised name in sight. The familiar megabrands of today have put literally millions of small business owners out of business for good.

The new sweatshops of today are owned by the likes of Amazon.The Amazons of the world have ushered in a new era of capitalism, in which physical storefronts are made obsolete (and competition along with it). As I noted in yesterday's post, Internet-scale businesses permit one, and only one, major winner per sector. Names like Facebook, Amazon, Google, Groupon, Twitter, Salesforce.com, etc., are the new Ford Motor Companies and Standard Oils―the new trusts, the new monopolies―of today. In Internet markets, there can only ever be one category winner.

President Obama likes to talk about how education will lead the way out of our economic doldrums. If only we had more engineering graduates, more science graduates. But in fact, those are the very jobs that are going overseas in record numbers.

If Obama (or any president) truly wanted to encourage small-business ownership, he would break up the Internet monopolies and do away with franchises (which are simply brand-cartels). Of course, to do so would be seen as un-American (and is politically impossible at this point, in any case). But the alternative is a world of monolithic, anticompetitive corporate gigantism on a scale never imaged. It's a world that's just beginning.

Saturday, January 14, 2012

How the Internet is Changing Economics

I'm not an economist by training, but I've spent a long time observing markets, and I've often wondered what the great economic thinkers of centuries past (Marx, Smith, Keynes, others) would say if they were alive today and could witness the transformations that have been caused by mass media (radio, TV, print) and the Internet. The Internet, in particular, has had a transformative effect that will doubtless be studied for hundreds of years to come.

One of the things that can be said about pre-Internet markets is that most markets permit the emergence of two or three major winners (a primary winner, a secondary winner, a third-place winner), and a rich ecosystem (a long tail) of also-rans. The major winners control the bulk of the market. Seldom does the long tail make up the bulk of sales or unit volume. Think of any market, and think of the winners. You can usually think of two, sometimes three.

In carbonated drinks, there is Coca-Cola (primary winner, with 40% of the market), Pepsi (secondary winner, with 30%), Dr Pepper Snapple (with 16%), and then a long tail of hundreds of also-rans. [reference]

In beer, you have 90% of the market controlled by just three companies: Anheuser-Busch, Coors Molson, and SABMiller. [reference]

In fast food, there's McDonald's, then Subway (which actually claims to have more stores, now, than McDonalds), and Burger King. McDonald's is far and away the sales leader, selling three times as much product as its nearest rival. [reference]

To recap: The nature of offline businesses is that there are usually a few big winners, and a long tail of also-rans. Quite often, the top one or two winners garner the majority of industry sales.

Internet-based businesses (businesses that get all of their revenue from online activities) show a qualitatively different trend.

In the online world, it's essentially a winner-take-all model. There is one major winner in a given category, and the long tail of also-rans who fight over a small residual volume of sales.

Again, it's not hard to think of examples. In search, Google is the principal winner. In online book sales, there's Amazon and then there's everyone else. In social networking, there's Facebook (which gets 40 times the traffic of Twitter). In digital video streaming, there's Netflix. In chips, there's Intel (with 80% of the market). And so on.

All of the great new monopolies of the last 30 years are tech companies, which should tell you something.

For whatever reason, the winners in the online world tend to be fewer in number and have much greater market dominance, compared to the winners in the offline world.

What this tells me is that in markets that are transitioning from offline to online (or that seek a mixed mode of operation, straddling both the online and offline worlds), big shakeups are bound to happen, because the online model leads to rather dramatic, irreversible marginalization of otherwise-big competitors. We see examples of this already happening. Sale of physical books, for example, once demanded that you have brick-and-mortar stores located in or near shopping centers. Prior to the Internet, there was room in the market for a few big winners (Barnes and Noble, Borders, Books-a-Million) and a long tail of popular one-off booksellers. The book industry is transitioning to a different model, however, in which the majority of books will be sold online, with only a small fraction of books being sold in physical facilities. This tells me there will be huge consolidation (and outright bankruptcy, in some cases) of brick-and-mortar stores, with only one big winner online, in the end: Amazon.

Another industry that is in transition is the newspaper business. We don't yet know who the Big Winner will be online (maybe the New York Times, maybe Bloomberg or Murdoch), but it seems certain that when the transition is complete, there will be fewer big players left standing in the newspaper business, and only one dominant online winner. That's the pattern.

Some businesses, by their nature, don't permit a full transition to an online model. That's true of most businesses, of course. But (here's the point) those that do allow for significant online development will see radical disruption of the hitherto-normal ecosystem. Most online markets don't have room for six or ten (or even two or three) big winners. There's room for one, and only one, big winner. When the music stops, there's only one chair, and everyone else gets to fall on the floor.

The last big operating-system war took place in the 1980s and saw the emergence of a dominant winner (Microsoft) who reaped billions of dollars, even while producing inferior goods. The next big operating-system war (for mobile devices) is underway now. There are those who think Android will win, and there are many more who think iOS will win. If my theory is correct, there is room only for one big winner. The also-rans will end up with a puny, insiginificant share of the market. As it stands now, Microsoft is way too late to the party to have any effect. Android appeared to have the early lead, but recently Apple's iOS and Android have switched adoption numbers, and this has caused many observers to begin to question Google's supposed future preeminence in mobile computing. (GOOG's share price was recently as high as 670, but is now down to around 625, largely because investors are starting to doubt the validity of the $100+ "Android premium" baked into the share price. AAPL, meanwhile, continues to set new all-time highs, as more and more investors come to believe that Apple will be the last one standing in the mobile-OS race.)

I'd love to be able to say, of course, that the advent of the Internet has created new opportunities for small-time operators and will lead to economic ecosystems of unexpected richness and diversity. But that's not what we've seen so far. Quite the opposite. Internet-scale businesses are a winner-take-all event. Any diversity that happens to develop is strictly accidental.

Saturday, January 07, 2012

Some Thoughts on Writer's Block

Having made a living as a writer for more years than I'd like to admit, I've devoted quite a bit of time and thought to writer's block over the years, and it strikes me that a quick brain-dump on the subject might help others of my kind out there who occasionally struggle with the process of committing words to paper (or electrons to æther, as the case may be), especially since (as I look at what others have written about writer's block) it seems I come at the question of writer's block from a bit of a different direction than others do. Which may or may not be helpful.

Some people go so far as to say writer's block is not real. Which is total nonsense, of course. Writer's block is as real as tooth decay. What makes it fascinating is that "writer's block" has no parallel at the level of other seemingly ordinary daily activities. Eaters don't experience Eater's Block. Walkers don't experience Walker's Block. Why, then, is "writer's block" such a problem?

It seems to me there are two broad categories of reasons why anyone runs into "writer's block" (let's not dignify it by capitalizing it). There are external reasons (e.g., I'm worried about XYZ, which is distracting me from writing), and internal reasons (e.g., I can't think of an opening sentence for this section of my paper). In the discussion that follows, I'm not going to deal with external blockers. That's something for you and your psychiatrist. It's the internal blockers that interest me, because those are the hardest, and the most intrinsic to the process.

The vast majority of instances of writer's block can be traced to just two problems. One is the fear of committing verbal crimes against nature (aka perfectionism). The second has to do with the inherent difficulty of the writing process (which is worth elaborating on). Maybe there's a third Main Problem, which is: Your understanding of whatever it is you intend to write about is so fluid, such a moving target, that you just can't see how you can possibly start capturing it in words. In other words, you're still in the process of making sense of whatever it is you're writing about. You feel nowhere near ready to reduce it to (static, frozen, immutable) text. That's a legitimate concern, but it has to do with the second issue, the inherent difficulty of the writing process.

When you begin to realize how difficult the writing process actually is, you will (or should) come to the realization that it's silly to beat yourself up. You absolutely should not beat yourself up when you're blocked. Instead, have great sympathy for yourself. Why? Because what you're attempting to do is, indeed, in a sense, heroic.

Here's the thing you have to realize about writing. The world of thought, of analysis, of understanding, of comprehension, is multidimensional. More than that, it is inherently reentrant, recursive, and iterative, which (therefore) also means it is nonlinear and ultimately chaotic (in the profoundest mathematical sense envisioned by Gleick in his classic book, Chaos). In your procrastination time, I strongly recommend that you read Gleick's Chaos book, because it will give you an unforgettable introduction to the nature of nonlinear systems. That's important, because nothing is more prototypically nonlinear than the thinking process.

When you're trying to understand something, you come at the subject from multiple angles. Invariably, there are many entry points to understanding. As you gain understanding, you add new ideas, then rethink/reprocess everything. The ripple effects are often far-reaching. But the salient point is: Your path to understanding is not a straight line. It's often a random walk in a multidimensional wonderland.

Now consider what the written form is. It's words arranged one after another in a linear chain. You're trying to reduce a multidimensional process to a linear form. This is why writing is difficult (heroic, even). It's a Quixotic quest to reduce the multidimensional to the unidimensional. Really, what could be more absurd?

Unlike the thinking process, a piece of writing has a definite starting point and a definite endpoint. Which is completely unreasonable. It is a sublimely unreasonable demand to say: "Take this complex multidimensional subject and reduce it to a string of words arranged linearly, one after the other." It's unlikely that whatever it is you're writing about has a definite beginning, middle, or end, or a naturally hierarchical top-down structure. Most things worth writing about are not neatly structured.

This, then, is the paradox of writing. The process of writing is a process of projecting multidimensional phenomena (concepts, thoughts, lines of reasoning) onto a one-dimensional wordspace, where words follow one upon the other in a linear string.

Totally unreasonable.

But there is hope, because once you understand the built-in disjunction between wordspace and thoughtspace, you can approach the writing process differently than you might otherwise have.

First of all, you can give yourself permission to enter the verbal space from any of a multitude of different directions. Your journalism teacher (or your seventh-grade writing teacher) may have told you that you have to begin at the beginning, and structure a discussion hierarchically, from the top down. But really, that's not true at all. That's not the way thinking works. Why should it be the way writing works?

Give yourself permission to enter a subject sideways, with an anecdote or a seemingly incidental aside. Don't think of your subject area (whatever it is) as a linear body of knowledge, because it isn't. Think of it as a giant collage. Your understanding of it is collage-like. Your job, as a writer, is to render the collage on paper (in wordspace), in a way that will ultimately be comprehensible to the reader. Chances are, the reader will not assail you for not taking a linear approach. Quite the contrary. He or she will thank you, most likely, for rendering a complex subject as a pastiche of digestible pieces: pieces of knowledge that, in the aggregate, make sense (because you have drawn the appropriate connections between the pieces). It's not important that you put "first things first" unless the subject demands it (in which case your job is easy, actually). It's more important that you lay down the major pieces of the collage intact, in a way that will make sense once the overall job is done.

So first of all: Give yourself permission to take unusual entry points into the discussion. Give yourself permission to start in the middle, or at the end. Sometimes, you can get good traction by simply listing your concluding points. What are the essential takeaways that you want to be able to deliver? Write them down quickly. Entry points will suggest themselves.

Another good heuristic is: Find the parts of your discussion that mean the most to you emotionally. Begin working on those first, if need be. That will give you good momentum. If you can write with conviction, if you can find aspects of the subject that resonate with you on an emotional level, by all means harness that.

The most important advice of all when you're blocked? Always, always, give yourself permission to write crap. You always need to give yourself license to write pure shit. When you're blocked, writing utter crap is better than sitting there staring at the blank page. So write some junk. You can come back to it later (or not) and pretty it up, make it acceptable. Right now, you just need to get moving.

From time to time, you may hear inner voices saying cruel things. "You're useless." "What you're writing is hopelessly bad." "You'll never make a dent in this, it's futile." What you should do is ask yourself: Is this your authentic adult self talking, or is it your child-self? If it's your inner child (which it almost always is), deal with it as you would deal with a petulent child. Firmly and politely say "That's enough now. I heard you. That's enough." If need be, give the child a time-out. ("Go sit in the corner for five minutes.") Don't let your inner child boss you around. It's not right.

And when you do start to make progress (as you inevitably will), take frequent opportunities to congratulate yourself. You're doing the impossible, after all, the heroic: You're committing thoughts to paper (or words to wordspace). You're reducing the multidimensional to the linear. That's something to celebrate. So celebrate it. And keep moving toward the shitstorm.

Monday, January 02, 2012

Turbulence in HTML5 Canvas

This is a screenshot of the shader UI showing what basic turbulence looks like.
As an update to my earlier post on procedural texturing in HTML5 canvas, I wanted to post an improved (faster) version of the earlier code that also incorporates a turbulence function, which is probably more properly called fractal noise.

First, hat's off to Ryan Sturgell for pointing out that by moving the big array initialization outside of the this.noise = function() call, it's possible to speed up the Perlin noise() function several-fold. Which should have been obvious, but I missed it on the first go-round. #doh

In any case, the noise() function in the improved code (below) now can process ~400K pixels per second, which is a significant improvement indeed.

Turbulence
The turbulence function adds Perlin noise of various frequencies together, like so:

function turbulence( x,y,z, octaves ) {

 var t = 0;
 var f = 1;
 var n = 0;

 for (var i = 0; i < octaves; i++, f *= 2) {
  n += PerlinNoise.noise(x * f, y * f, z)/f;
  t += 1/f;
 }
 return n / t;  // rescale back to 0..1
}

This has the net result of giving noise that is much more realistic for things like cloud textures or smoke. For example:


x /= w; y /= h; size = 5;
y = 1 - bias(y,.4);
n = turbulence(size*x,1.8*size*y,1-y,3);
y = Math.sqrt(y);
r = bias(y,.68) * n * 255; 
g = r/1.22;

This code uses turbulence to generate the cloud pattern and bias() to stretch the sky a bit at the top (and stretch the red value vertically as well).

Without further ado, here's the complete code for the shader page. Copy and paste all of the following code into a text file and give it a name that ends with .html. Then open it in Chrome, Firefox, or any HTML5-capable browser.

<html>
    <head>
          <script>

 // A canvas demo by Kas Thomas.
 // http://asserttrue.blogspot.com
 // Use as you will, at your own risk.

  context = null; 
  canvas = null;  
              
            window.onload = function(){

                canvas = document.getElementById("myCanvas");
      canvas.addEventListener('mousemove', handleMousemove, false);
                context = canvas.getContext("2d");
                loadHiddenText();
            }

  function loadHiddenText( ) {

   var options = document.getElementsByTagName( "option" );
   var spans = document.getElementsByTagName( "span" );

   for (var i = 0; i < options.length; i++) 
    options[i].value = spans[i].innerHTML;
  }

     // should probably be called resetCanvas()
  function clearImage( ) {

   canvas.width = canvas.width;
  }  
    
            function drawViaCallback( ) {

   var w = canvas.width;
   var h = canvas.height;
      
   var canvasData = context.getImageData(0,0,w,h);

   for (var idx, x = 0; x < w; x++) {
        for (var y = 0; y < h; y++) {
            // Index of the pixel in the array
            idx = (x + y * w) * 4;    
      

            // The RGB values
            var r = canvasData.data[idx + 0];
            var g = canvasData.data[idx + 1];
            var b = canvasData.data[idx + 2];
 
     var pixel = callback( [r,g,b], x,y,w,h);

            canvasData.data[idx + 0] = pixel[0];
     canvasData.data[idx + 1] = pixel[1];
     canvasData.data[idx + 2] = pixel[2];
        }
   }
   context.putImageData( canvasData, 0,0 );
  }

    function fillCanvas( color ) {
 
   context.fillStyle = color; 
   context.fillRect(0,0,canvas.width,canvas.height);
  }

  function doPixelLoop() {

   var code = document.getElementById("code").value;
   var f = "callback = function( pixel,x,y,w,h )" +
                      " { var r=pixel[0];var g=pixel[1]; var b=pixel[2];" +
       code + " return [r,g,b]; }";
                  
   try {
    eval(f);
    fillCanvas( "#FFFFFF" );
         drawViaCallback( );
   }
   catch(e) { alert("Error: " + e.toString()); }
  }


  
  function handleMousemove (ev) {

     var x, y;

     // Get the mouse position relative to the canvas element.
     if (ev.layerX || ev.layerX == 0) { // Firefox
        x = ev.layerX;
        y = ev.layerY;
     } else if (ev.offsetX || ev.offsetX == 0) { // Opera
        x = ev.offsetX;
        y = ev.offsetY;
     }
   
   document.getElementById("myCanvas").title = x + ", " + y;   
  }
  
 // This is a port of Ken Perlin's Java code.
PerlinNoise = new function() {

   var p = new Array(512)
   var permutation = [ 151,160,137,91,90,15,
   131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
   190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
   88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
   77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
   102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
   135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
   5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
   223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
   129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
   251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
   49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
   138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
   ];
   for (var i=0; i < 256 ; i++) 
 p[256+i] = p[i] = permutation[i]; 

this.noise = function(x, y, z) {



      var X = Math.floor(x) & 255,                  // FIND UNIT CUBE THAT
          Y = Math.floor(y) & 255,                  // CONTAINS POINT.
          Z = Math.floor(z) & 255;
      x -= Math.floor(x);                                // FIND RELATIVE X,Y,Z
      y -= Math.floor(y);                                // OF POINT IN CUBE.
      z -= Math.floor(z);
      var    u = fade(x),                                // COMPUTE FADE CURVES
             v = fade(y),                                // FOR EACH OF X,Y,Z.
             w = fade(z);
      var A = p[X  ]+Y, AA = p[A]+Z, AB = p[A+1]+Z,      // HASH COORDINATES OF
          B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z;      // THE 8 CUBE CORNERS,

      return scale(lerp(w, lerp(v, lerp(u, grad(p[AA  ], x  , y  , z   ),  // AND ADD
                                     grad(p[BA  ], x-1, y  , z   )), // BLENDED
                             lerp(u, grad(p[AB  ], x  , y-1, z   ),  // RESULTS
                                     grad(p[BB  ], x-1, y-1, z   ))),// FROM  8
                     lerp(v, lerp(u, grad(p[AA+1], x  , y  , z-1 ),  // CORNERS
                                     grad(p[BA+1], x-1, y  , z-1 )), // OF CUBE
                             lerp(u, grad(p[AB+1], x  , y-1, z-1 ),
                                     grad(p[BB+1], x-1, y-1, z-1 )))));
   }
   function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
   function lerp( t, a, b) { return a + t * (b - a); }
   function grad(hash, x, y, z) {
      var h = hash & 15;                      // CONVERT LO 4 BITS OF HASH CODE
      var u = h<8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
             v = h<4 ? y : h==12||h==14 ? x : z;
      return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
   } 
   function scale(n) { return (1 + n)/2; }
}

function turbulence( x,y,z, octaves ) {

 var t = 0;
 var f = 1;
 var n = 0;

 for (var i = 0; i < octaves; i++, f *= 2) {
  n += PerlinNoise.noise(x * f, y * f, z)/f;
  t += 1/f;
 }
 return n / t;  // rescale back to 0..1
}

// Perlin's bias function
function bias( a, b)  {
 return Math.pow(a, Math.log(b) / Math.log(0.5));
}

    </script>
    </head>

    <body>
        <canvas id="myCanvas" width="300" height="300">
        </canvas><br/>
        
   <input type="button" value=" Erase " 
     onclick="clearImage(); "/>

   <select onchange=
   "document.getElementById('code').innerHTML = this.value;">
  <option>Choose something, then click Execute</option>
  <option>Basic Perlin Noise</option>
  <option>Basic Turbulence</option>
  <option>Waterfall</option>
  <option>Spherical Nebula</option>
  <option>Green Fibre Bundle</option>
  <option>Orange-Blue Marble</option>
  <option>Blood Maze</option>
  <option>Yellow Lightning</option>
            <option>Downward Rainbow Wipe</option>
  <option>Noisy Rainbow</option>
  <option>Burning Cross</option>
  <option>Fair Skies</option>
   </select>
   
   <br/>  
        <textarea id="code" type="textarea" cols="37" rows="7">/* Enter code here. Globals: r,g,b,x,y,w,h,PerlinNoise.noise(a,b,c) */</textarea>
   <br/>

        <input type="button" value=" Execute " 
     onclick="doPixelLoop();"  />
   <input type="button" value="Open as PNG" 
     onclick="window.open(canvas.toDataURL('image/png'))"/>


<!-- BEGIN HIDDEN TEXT -->
<div hidden="true">
<span>
// you can enter your own code here!
</span>

<span>
x /= w; y /= h;
size = 10;
n = PerlinNoise.noise(size*x,size*y,.8);
r = g = b = 255 * n;
</span>

<span>
x /= w; y /= h;
size = 10;
n = turbulence(size*x,size*y,.8,4);
r = g = b = 255 * n;
</span>

<span>
x/= 30; y/=3 * (y+x)/w;
n = PerlinNoise.noise(x,y,.18);
b = Math.round(255*n);
g = b - 255; r = 0;
</span>

<span>
centerx = w/2; centery = h/2;
dx = x - centerx; dy = y - centery;
dist = (dx*dx + dy*dy)/6000;
n = PerlinNoise.noise(x/5,y/5,.18);
r = 255 - dist*Math.round(255*n);
g = r - 255; b = 0;
</span>

<span>
x/=w;y/=h;sizex=3;sizey=66;
n=PerlinNoise.noise(sizex*x,sizey*y,.1);
x=(1+Math.sin(3.14*x))/2;
y=(1+Math.sin(n*8*y))/2;
b=n*y*x*255; r = y*b;
g=y*255;
</span>

<span>
centerx = w/2; centery = h/2;
dx = x - centerx; dy = y - centery;
dist = 1.2*Math.sqrt(dx*dx + dy*dy);
n = PerlinNoise.noise(x/30,y/110,.28);
dterm = (dist/88)*Math.round(255*n);
r = dist < 150 ? dterm : 255;
b = dist < 150 ? 255-r   : 255; 
g = dist < 151 ? dterm/1.5 : 255;
</span>

<span>
n = PerlinNoise.noise(x/45,y/120, .74);
n = Math.cos( n * 85);
r = Math.round(n * 255);
b = 255 - r; 
g = r - 255 ;
</span>

<span>
x /= w; y /= h; sizex = 1.5; sizey=10;
n=PerlinNoise.noise(sizex*x,sizey*y,.4);
x = (1+Math.cos(n+2*Math.PI*x-.5));
x = Math.sqrt(x); y *= y;
r= 255-x*255; g=255-n*x*255; b=y*255;
</span>

<span>
// This uses no Perlin noise.
x/=w; y/=h;
b = 255 - y*255*(1 + Math.sin(6.3*x))/2;
g = 255 - y*255*(1 + Math.cos(6.3*x))/2;
r = 255 - y*255*(1 - Math.sin(6.3*x))/2;
</span>

<span>
x/=w;y/=h; 
size = 20;
n = PerlinNoise.noise(size*x,size*y,.9);
b = 255 - 255*(1+Math.sin(n+6.3*x))/2;
g = 255 - 255*(1+Math.cos(n+6.3*x))/2;
r = 255 - 255*(1-Math.sin(n+6.3*x))/2;
</span>

<span>
x /= w; y /= h; size = 19;
n = PerlinNoise.noise(size*x,size*y,.9);
x = (1+Math.cos(n+2*Math.PI*x-.5));
y = (1+Math.cos(2*Math.PI*y));
//x = Math.sqrt(x); y = Math.sqrt(y);
r= 255-y*x*n*255; g = r;b=255-r;
</span>

<span>
x /= w; y /= h; size = 5;
y = 1 - bias(y,.4);
n = turbulence(size*x,1.8*size*y,1-y,3);
y = Math.sqrt(y);
r = bias(y,.68) * n * 255; 
g = r/1.22;
b = 255 - r/2;
</span>
</div>
<!-- END HIDDEN TEXT -->
   
    </body>

</html>


Incidentally, I did find a halfway-decent discussion of noise() and turbulence() online, written by Ken Perlin himself, at http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html. Read it and reap!

Sunday, January 01, 2012

Procedural Textures in HTML5 Canvas

This is a screenshot of the user interface for the procedural texture app.
I've been playing around with the canvas API again, and this time I decided to create a simple HTML page that exposes an interface for creating procedural textures. Behind the scenes, I've included Perlin's famous noise function (see yesterday's post for details). The result is a tool that's as powerful (and fast) as it is fun to play with. (And the best part is, you don't need to host any files on a server: You can run the app straight from disk, with no security restrictions, in Chrome, Firefox, or any HTML5/canvas-capable browser.)

The interface is simple. There's a text box where you can type some code (see illustration at right). Whatever you type there will be executed against every pixel of the 2D canvas. Exposed globals include:

x -- the x coordinate of the current pixel
y -- the y coord of the current pixel
w -- the width of the canvas, in pixels
h -- the height of the canvas
r -- the red channel of the current pixel
g -- the green value of the current pixel
b -- the blue value of the current pixel
PerlinNoise.noise( u,v,w ) -- Perlin's 3D noise function

Offhand, you wouldn't think a loop that calls a callback for every pixel of a canvas image would be fast, but in reality the procedural shader can "call out" at a rate of over a million pixels per second. If you make calls to the Perlin noise() function in your loop, that'll slow you down to ~120K pixels per second. But that's still pretty good.

The versatility of the noise() function is truly amazing. The key to using it effectively is to understand how to scale it. By appropriately scaling the x and y parameters, you can stretch the noise space to any degree you want. You can achieve very colorful results, of course, by applying the result in creative ways to the r, g, and b channels. For example:


This texture was achieved with the following shader code:

n = PerlinNoise.noise(x/45,y/120, .89);
n = Math.cos( n * 85);
r = Math.round(n * 255);
b = 255 - r; 
g = r - 255 ;

In this instance, the noise is scaled differently in x and y and then "reflected back on itself" (so to speak) using the cosine function, then the color channels are fiddled in such a way that whatever isn't red is blue.

By normalizing the texture space in various ways, you can end up with surprising effects. For example, consider:


centerx = w/2; centery = h/2;
dx = x - centerx; dy = y - centery;
dist = (dx*dx + dy*dy)/6000;
n = PerlinNoise.noise(x/5,y/5,.18);
r = 255 - dist*Math.round(255*n);
g = r - 255; b = 0;

In this case, we calculate the pseudo-distance from the center of the image as dx*dx + dy*dy (scaled by 6000) and fiddle with the colors to make the result red on a black background. The parameters to noise() have been scaled to give a relatively fine-grain noise.

If you download the code for the procedural-shader page (given further below), you can play with this "texture" yourself. Try substituting larger or smaller values for the scaling numbers to see what happens.

A dramatically different effect can be obtained by normalizing x and y and applying trig functions creatively:


x /= w; y /= h; sizex = 1.5; sizey=10;
n=PerlinNoise.noise(sizex*x,sizey*y,.4);
x = (1+Math.cos(n+2*Math.PI*x-.5));
x = Math.sqrt(x); y *= y;
r= 255-x*255; g=255-n*x*255; b=y*255;

Again, if you decide to download the code yourself, try playing with the various sizing parameters to see what the effect on the image is. That's the best way to get a feel for what's going on.

As you know if you've played with procedural textures before, you get a lot of mileage by normalizing x and y first (to keep them in the range of 0..1) and then using functions on them that are also normalized to produce output in the range 0..1. (Sine and cosine can, of course, easily be normalized to stay in the range 0..1.) It goes without saying that once a number is in the range 0..1 it can be squared (or squared-rooted) and still fall in the range 0..1. When you're ready to apply the number to a color channel, then of course you should multiply by 255 so that the result is in the range 0..255.

I've included a number of "presets" in the procedural-texture page (including code for the foregoing images). Here's another one that I like:


x/=w;y/=h; 
size = 20;
n = PerlinNoise.noise(size*x,size*y,.9);
b = 255 - 255*(1+Math.sin(n+6.3*x))/2;
g = 255 - 255*(1+Math.cos(n+6.3*x))/2;
r = 255 - 255*(1-Math.sin(n+6.3*x))/2;

I call this the "noisy rainbow." Without the noise term, it simply paints a rainbow across the image space, but a little added noise gives the effect shown here.

The code includes a few more examples (that aren't shown here). I encourage you to download it and play with it. Simply copy and paste all of the code below into a text file and give it a name that ends in ".html". Then open it in Chrome, Firefox, or any canvas-capable browser.
<html>
    <head>
          <script>

 // A canvas demo by Kas Thomas.
 // http://asserttrue.blogspot.com
 // Use as you will, at your own risk.

  context = null; 
  canvas = null;  
              
            window.onload = function(){

                canvas = document.getElementById("myCanvas");
      canvas.addEventListener('mousemove', handleMousemove, false);
                context = canvas.getContext("2d");
                loadHiddenText();
            }

  function loadHiddenText( ) {

   var options = document.getElementsByTagName( "option" );
   var spans = document.getElementsByTagName( "span" );

   for (var i = 0; i < options.length; i++) 
    options[i].value = spans[i].innerHTML;
  }

     // should probably be called resetCanvas()
  function clearImage( ) {

   canvas.width = canvas.width;
  }  
    
            function drawViaCallback( ) {
    
   var w = canvas.width;
   var h = canvas.height;
      
   var canvasData = context.getImageData(0,0,w,h);

   for (var idx, x = 0; x < w; x++) {
        for (var y = 0; y < h; y++) {
            // Index of the pixel in the array
            idx = (x + y * w) * 4;    
      

            // The RGB values
            var r = canvasData.data[idx + 0];
            var g = canvasData.data[idx + 1];
            var b = canvasData.data[idx + 2];
 
     var pixel = callback( [r,g,b], x,y,w,h);

            canvasData.data[idx + 0] = pixel[0];
     canvasData.data[idx + 1] = pixel[1];
     canvasData.data[idx + 2] = pixel[2];
        }
   }
   context.putImageData( canvasData, 0,0 );
  }

    function fillCanvas( color ) {
 
   context.fillStyle = color; 
   context.fillRect(0,0,canvas.width,canvas.height);
  }

  function doPixelLoop() {

   var code = document.getElementById("code").value;
   var f = "callback = function( pixel,x,y,w,h )" +
                      " { var r=pixel[0];var g=pixel[1]; var b=pixel[2];" +
       code + " return [r,g,b]; }";
                  
   try {
    eval(f);
    fillCanvas( "#FFFFFF" );
         drawViaCallback( );
   }
   catch(e) { alert("Error: " + e.toString()); }
  }


  
  function handleMousemove (ev) {

     var x, y;

     // Get the mouse position relative to the canvas element.
     if (ev.layerX || ev.layerX == 0) { // Firefox
        x = ev.layerX;
        y = ev.layerY;
     } else if (ev.offsetX || ev.offsetX == 0) { // Opera
        x = ev.offsetX;
        y = ev.offsetY;
     }
   
   document.getElementById("myCanvas").title = x + ", " + y;   
  }
  
 // This is a port of Ken Perlin's Java code.
PerlinNoise = new function() {

this.noise = function(x, y, z) {

   var p = new Array(512)
   var permutation = [ 151,160,137,91,90,15,
   131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
   190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
   88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
   77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
   102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
   135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
   5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
   223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
   129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
   251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
   49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
   138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
   ];
   for (var i=0; i < 256 ; i++) 
 p[256+i] = p[i] = permutation[i]; 

      var X = Math.floor(x) & 255,                  // FIND UNIT CUBE THAT
          Y = Math.floor(y) & 255,                  // CONTAINS POINT.
          Z = Math.floor(z) & 255;
      x -= Math.floor(x);                                // FIND RELATIVE X,Y,Z
      y -= Math.floor(y);                                // OF POINT IN CUBE.
      z -= Math.floor(z);
      var    u = fade(x),                                // COMPUTE FADE CURVES
             v = fade(y),                                // FOR EACH OF X,Y,Z.
             w = fade(z);
      var A = p[X  ]+Y, AA = p[A]+Z, AB = p[A+1]+Z,      // HASH COORDINATES OF
          B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z;      // THE 8 CUBE CORNERS,

      return scale(lerp(w, lerp(v, lerp(u, grad(p[AA  ], x  , y  , z   ),  // AND ADD
                                     grad(p[BA  ], x-1, y  , z   )), // BLENDED
                             lerp(u, grad(p[AB  ], x  , y-1, z   ),  // RESULTS
                                     grad(p[BB  ], x-1, y-1, z   ))),// FROM  8
                     lerp(v, lerp(u, grad(p[AA+1], x  , y  , z-1 ),  // CORNERS
                                     grad(p[BA+1], x-1, y  , z-1 )), // OF CUBE
                             lerp(u, grad(p[AB+1], x  , y-1, z-1 ),
                                     grad(p[BB+1], x-1, y-1, z-1 )))));
   }
   function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
   function lerp( t, a, b) { return a + t * (b - a); }
   function grad(hash, x, y, z) {
      var h = hash & 15;                      // CONVERT LO 4 BITS OF HASH CODE
      var u = h<8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
             v = h<4 ? y : h==12||h==14 ? x : z;
      return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
   } 
   function scale(n) { return (1 + n)/2; }
}

    </script>
    </head>

    <body>
        <canvas id="myCanvas" width="300" height="300">
        </canvas><br/>
        
   <input type="button" value=" Erase " 
     onclick="clearImage(); "/>

   <select onchange=
   "document.getElementById('code').innerHTML = this.value;">
  <option>Choose something, then click Execute</option>
  <option>Basic Perlin Noise</option>
  <option>Waterfall</option>
  <option>Spherical Nebula</option>
  <option>Green Fibre Bundle</option>
  <option>Orange-Blue Marble</option>
  <option>Blood Maze</option>
  <option>Yellow Lightning</option>
            <option>Downward Rainbow Wipe</option>
  <option>Noisy Rainbow</option>
  <option>Burning Cross</option>
   </select>
   
   <br/>  
        <textarea id="code" type="textarea" cols="40" rows="7">/* Enter code here. */</textarea>
   <br/>

        <input type="button" value=" Execute " 
     onclick="doPixelLoop();"  />
   <input type="button" value="Open as PNG" 
     onclick="window.open(canvas.toDataURL('image/png'))"/>


<!-- BEGIN HIDDEN TEXT -->
<div hidden="true">
<span>
// you can enter your own code here!
</span>

<span>
x /= w; y /= h;
size = 10;
n = PerlinNoise.noise(size*x,size*y,.8);
r = g = b = 255 * n;
</span>

<span>
x/= 30; y/=3 * (y+x)/w;
n = PerlinNoise.noise(x,y,.18);
b = Math.round(255*n);
g = b - 255; r = 0;
</span>

<span>
centerx = w/2; centery = h/2;
dx = x - centerx; dy = y - centery;
dist = (dx*dx + dy*dy)/6000;
n = PerlinNoise.noise(x/5,y/5,.18);
r = 255 - dist*Math.round(255*n);
g = r - 255; b = 0;
</span>

<span>
x/=w;y/=h;sizex=3;sizey=66;
n=PerlinNoise.noise(sizex*x,sizey*y,.1);
x=(1+Math.sin(3.14*x))/2;
y=(1+Math.sin(n*8*y))/2;
b=n*y*x*255; r = y*b;
g=y*255;
</span>

<span>
centerx = w/2; centery = h/2;
dx = x - centerx; dy = y - centery;
dist = 1.2*Math.sqrt(dx*dx + dy*dy);
n = PerlinNoise.noise(x/30,y/110,.28);
dterm = (dist/88)*Math.round(255*n);
r = dist < 150 ? dterm : 255;
b = dist < 150 ? 255-r   : 255; 
g = dist < 151 ? dterm/1.5 : 255;
</span>

<span>
n = PerlinNoise.noise(x/45,y/120, .74);
n = Math.cos( n * 85);
r = Math.round(n * 255);
b = 255 - r; 
g = r - 255 ;
</span>

<span>
x /= w; y /= h; sizex = 1.5; sizey=10;
n=PerlinNoise.noise(sizex*x,sizey*y,.4);
x = (1+Math.cos(n+2*Math.PI*x-.5));
x = Math.sqrt(x); y *= y;
r= 255-x*255; g=255-n*x*255; b=y*255;
</span>

<span>
// This uses no Perlin noise.
x/=w; y/=h;
b = 255 - y*255*(1 + Math.sin(6.3*x))/2;
g = 255 - y*255*(1 + Math.cos(6.3*x))/2;
r = 255 - y*255*(1 - Math.sin(6.3*x))/2;
</span>

<span>
x/=w;y/=h; 
size = 20;
n = PerlinNoise.noise(size*x,size*y,.9);
b = 255 - 255*(1+Math.sin(n+6.3*x))/2;
g = 255 - 255*(1+Math.cos(n+6.3*x))/2;
r = 255 - 255*(1-Math.sin(n+6.3*x))/2;
</span>

<span>
x /= w; y /= h; size = 19;
n = PerlinNoise.noise(size*x,size*y,.9);
x = (1+Math.cos(n+2*Math.PI*x-.5));
y = (1+Math.cos(2*Math.PI*y));
//x = Math.sqrt(x); y = Math.sqrt(y);
r= 255-y*x*n*255; g = r;b=255-r;
</span>
</div>
<!-- END HIDDEN TEXT -->
   
    </body>

</html>

The texture presets have been placed in a hidden div containing a bunch of span elements, and then at runtime the HTML dropdown menu is populated by loadHiddenText().

The Perlin noise() function may look intimidating, but it's not, really. It's a port of Ken Perlin's Java-based reference implementation of noise(). See yesterday's post for more information.

In the meantime, I encourage you to use this demo to explore the possibilities of procedural texture creation in HTML5 canvas. I hope you agree with me, it's a lot of fun, and educational as well.