<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;CEQERXs4eSp7ImA9WhRUEUs.&quot;"><id>tag:blogger.com,1999:blog-21557504</id><updated>2012-01-21T11:11:44.531-05:00</updated><category term="Vista" /><category term="jcr" /><category term="http://www.blogger.com/post-create.g?blogID=21557504" /><category term="Sobel" /><category term="programming" /><category term="RAID" /><category term="graphics" /><category term="transformation" /><category term="sling" /><category term="DCT" /><category term="Java" /><category term="imaging" /><category term="3D JPEG" /><category term="Flash" /><category term="fractal" /><category term="patent" /><category term="JAI" /><category term="convolution" /><category term="2D" /><category term="kernel" /><category term="FFT" /><category term="xpath" /><category term="performance" /><category term="DFT" /><category term="json" /><title>assertTrue( )</title><subtitle type="html">Techno-cruft from around the Web.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://asserttrue.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>397</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/assertTrue" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="asserttrue" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CEQERXs_eCp7ImA9WhRUEUs.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-1346184955511269389</id><published>2012-01-21T10:21:00.000-05:00</published><updated>2012-01-21T11:11:44.540-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-21T11:11:44.540-05:00</app:edited><title>Stock Market Investment for Geeks: A Crash Course</title><content type="html">&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-gWZdbZALPoA/TxrBJc4fi7I/AAAAAAAAA2s/0NrcTO6tb8g/s1600/MadMoneyBull.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="240" src="http://2.bp.blogspot.com/-gWZdbZALPoA/TxrBJc4fi7I/AAAAAAAAA2s/0NrcTO6tb8g/s320/MadMoneyBull.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The ink is mostly faded now, but my Mad Money bull is signed by Jim Cramer.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Screenwriter &lt;a href="http://en.wikipedia.org/wiki/William_Goldman" target="_blank"&gt;William Goldman&lt;/a&gt; (best known for &lt;i&gt;Butch Cassidy &amp;amp; The Sundance Kid&lt;/i&gt;) once famously said of Hollywood: "Nobody knows anything."&lt;br /&gt;
&lt;br /&gt;
I'm here to tell you that the same is true, only more so, for Wall Street. Don't be misled.&lt;br /&gt;
&lt;br /&gt;
There are only two people I trust when it comes to investment advice: &lt;a href="http://en.wikipedia.org/wiki/Jim_Cramer" target="_blank"&gt;Jim Cramer&lt;/a&gt;, and myself. And there are times when I don't trust either one.&lt;br /&gt;
&lt;br /&gt;
How's &lt;i&gt;that &lt;/i&gt;for paranoia?&lt;br /&gt;
&lt;br /&gt;
Here's the truth about investing. No matter what you invest in, knowing what you're doing means understanding &lt;i&gt;markets&lt;/i&gt;. 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.&lt;br /&gt;
&lt;br /&gt;
And so, a good "first book" in investing would be Charles Mackay's 1841 classic, &lt;a href="http://en.wikipedia.org/wiki/Extraordinary_Popular_Delusions_and_the_Madness_of_Crowds" target="_blank"&gt;&lt;i&gt;Extraordinary Popular Delusions and the Madness of Crowds&lt;/i&gt;&lt;/a&gt;. (PDF copy available free &lt;a href="http://www.cmi-gold-silver.com/pdf/mackaych2451824518-8.pdf" target="_blank"&gt;here&lt;/a&gt;.)&lt;br /&gt;
&lt;br /&gt;
The second book you should read (for pure entertainment value, if nothing else) is Jim Cramer's autobiographical &lt;a href="http://www.thestreet.com/tsc/book.html" target="_blank"&gt;&lt;i&gt;Confessions of a Street Addict&lt;/i&gt;&lt;/a&gt;. 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 &lt;i&gt;&lt;a href="http://en.wikipedia.org/wiki/Tallahassee_Democrat" title="Tallahassee Democrat"&gt;Tallahassee Democrat&lt;/a&gt;&lt;/i&gt;, where he covered the Ted Bundy murders. He later worked for the derelict and decrepit &lt;a href="http://en.wikipedia.org/wiki/Los_Angeles_Herald-Examiner" target="_blank"&gt;&lt;i&gt;Los Angeles Herald-Examiner&lt;/i&gt;&lt;/a&gt; (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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-2vU5pO_4aBc/TxrL6oq-MhI/AAAAAAAAA20/bypjzlj-1eg/s1600/513XETFT9SL.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="320" src="http://2.bp.blogspot.com/-2vU5pO_4aBc/TxrL6oq-MhI/AAAAAAAAA20/bypjzlj-1eg/s320/513XETFT9SL.jpg" width="209" /&gt;&lt;/a&gt;&lt;/div&gt;
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 &lt;a href="http://www.richardsimmonsdvds.com/0_61_320_Simmons2.jpg" target="_blank"&gt;Richard Simmons&lt;/a&gt; on crystal meth.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://www.google.com/finance" target="_blank"&gt;Google Finance&lt;/a&gt;, get a feel for what kind of mass hysteria is popular at the moment.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Let me cut the suspense and get right to my investing strategy, which really comes down to just a few very simple rules.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 1: Buy low, sell high.&lt;/b&gt; (Don't laugh.) That's your ultimate goal. That's your guiding principle. That's Job One. Always keep it in mind. &lt;br /&gt;
&lt;br /&gt;
How to put that into action? Very simple:&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 2: On market up-days, look for opportunities to sell. On market down-days, look for opportunities to buy.&lt;/b&gt; &lt;i&gt;Buy on down days, sell on up days.&lt;/i&gt; (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. &lt;i&gt;Don't buy when prices are going up.&lt;/i&gt; 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?"&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 3: Diversify.&lt;/b&gt; Own at least 5 stocks, &lt;i&gt;in 5 entirely different sectors&lt;/i&gt;. 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.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 4: Own some gold.&lt;/b&gt; Every portfolio, &lt;i&gt;without exception&lt;/i&gt;, 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 &lt;a href="http://www.google.com/finance?q=gld" target="_blank"&gt;GLD&lt;/a&gt;. That's the simplest, safest way to get in and out of gold. Buy on down days. Hold forever.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 5: Go for quality.&lt;/b&gt; 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 &lt;i&gt;one &lt;/i&gt;speculative stock, at some point. (At the moment, Cramer seems to think highly of &lt;a href="http://www.google.com/finance?q=hek" target="_blank"&gt;HEK&lt;/a&gt;. 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 &lt;i&gt;pay a dividend&lt;/i&gt;. 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.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 6: Always, always, always buy with conviction. &lt;/b&gt;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 &lt;i&gt;paying you a dividend&lt;/i&gt; while you wait out the up side.)&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 7: Don't fight market momentum. &lt;/b&gt;There will be days when the entire market seems like it's acting crazy (because it is). &lt;i&gt;Markets are sometimes pathological.&lt;/i&gt; Things go up that &lt;i&gt;shouldn't&lt;/i&gt; go up. Things go down that &lt;i&gt;shouldn't &lt;/i&gt;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.) &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 8: Decide in advance how long you're willing to stay with a position&lt;/b&gt; (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, &lt;i&gt;I have to be willing to sit on it for a year&lt;/i&gt; to see if I'm right. (I don't currently own any IBM, by the way.)&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 9: Don't forget the big picture.&lt;/b&gt; It's a global market. You have to factor &lt;i&gt;global &lt;/i&gt;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. &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Rule No. 10: Do your homework but don't fixate on it. &lt;/b&gt;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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Boo-hoo-ya.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-1346184955511269389?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1346184955511269389?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1346184955511269389?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2012/01/stock-market-investment-for-geeks-crash.html" title="Stock Market Investment for Geeks: A Crash Course" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-gWZdbZALPoA/TxrBJc4fi7I/AAAAAAAAA2s/0NrcTO6tb8g/s72-c/MadMoneyBull.jpg" height="72" width="72" /></entry><entry gd:etag="W/&quot;DUYMSH46fip7ImA9WhRVF08.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-1371795075319123065</id><published>2012-01-16T09:45:00.000-05:00</published><updated>2012-01-16T10:19:49.016-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-16T10:19:49.016-05:00</app:edited><title>The New Era of Corporate Gigantism</title><content type="html">&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-3OXC3uargf8/TxQrTL7GhOI/AAAAAAAAA2Q/Zfz4r8Eu2OQ/s1600/Ford_f520.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="320" src="http://2.bp.blogspot.com/-3OXC3uargf8/TxQrTL7GhOI/AAAAAAAAA2Q/Zfz4r8Eu2OQ/s320/Ford_f520.jpg" width="311" /&gt;&lt;/a&gt;&lt;/div&gt;
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 &lt;a href="http://en.wikipedia.org/wiki/Separation_of_concerns" target="_blank"&gt;Separation of Concerns&lt;/a&gt;. In doing so, he achieved unprecedented &lt;i&gt;economies of scale&lt;/i&gt;, which were hitherto not possible.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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 (&lt;i&gt;more than double&lt;/i&gt; 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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://en.wikipedia.org/wiki/Franchising" title="Franchising"&gt;franchise&lt;/a&gt; system that put dealerships throughout most of North America and in major cities on six continents.&lt;br /&gt;
&lt;br /&gt;
Enabling &lt;i&gt;scalability &lt;/i&gt;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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The advent of mass media (newspapers, magazines, radio, and eventually TV) enabled the emergence of highly recognizable national and international &lt;i&gt;brands&lt;/i&gt;. Branding is (arguably) yet another form of cartelism. Brand ubiquity diminishes competition by marginalizing little-recognized competitors.&lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;millions &lt;/i&gt;of small business owners out of business for good.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-82gUrMMBMYk/TxQ1HqRc_-I/AAAAAAAAA2g/dHHyla8XgJw/s1600/amazon-workers-460_1210880c.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="236" src="http://2.bp.blogspot.com/-82gUrMMBMYk/TxQ1HqRc_-I/AAAAAAAAA2g/dHHyla8XgJw/s320/amazon-workers-460_1210880c.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
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 &lt;a href="http://asserttrue.blogspot.com/2012/01/how-internet-is-changing-economics.html" target="_blank"&gt;yesterday's post&lt;/a&gt;, 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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-1371795075319123065?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1371795075319123065?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1371795075319123065?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2012/01/new-era-of-corporate-gigantism.html" title="The New Era of Corporate Gigantism" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-3OXC3uargf8/TxQrTL7GhOI/AAAAAAAAA2Q/Zfz4r8Eu2OQ/s72-c/Ford_f520.jpg" height="72" width="72" /></entry><entry gd:etag="W/&quot;CUcNQHgyeCp7ImA9WhRVFUg.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-6049842821470694043</id><published>2012-01-14T09:36:00.004-05:00</published><updated>2012-01-14T09:58:11.690-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-14T09:58:11.690-05:00</app:edited><title>How the Internet is Changing Economics</title><content type="html">&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-aEuKIShvw2E/TxGToPAKzMI/AAAAAAAAA2I/7v9MdJVZExY/s1600/coca-cola-ad-for-china-2008.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="222" src="http://4.bp.blogspot.com/-aEuKIShvw2E/TxGToPAKzMI/AAAAAAAAA2I/7v9MdJVZExY/s320/coca-cola-ad-for-china-2008.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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. &lt;a href="http://www.cnbc.com/id/36016032/Soft_Drink_Sales_Fall_but_Dr_Pepper_Gains_Share" target="_blank"&gt;[reference]&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
In beer, you have 90% of the market controlled by just three companies: Anheuser-Busch, Coors Molson, and SABMiller. &lt;a href="http://www.nku.edu/%7Efordmw/mgt490projectbeer.pdf" target="_blank"&gt;[reference]&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
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. &lt;a href="http://www.sogoodblog.com/2009/08/06/top-ten-fast-food-chains/" target="_blank"&gt;[reference]&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
To recap: The nature of &lt;i&gt;offline &lt;/i&gt;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.&lt;br /&gt;
&lt;br /&gt;
Internet-based businesses (businesses that get all of their revenue from online activities) show a qualitatively different trend.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://www.marketingcharts.com/interactive/top-10-social-networking-websites-forums-december-2011-20683/" target="_blank"&gt;40 times the traffic of Twitter&lt;/a&gt;). In digital video streaming, there's Netflix. In chips, there's Intel (with 80% of the market). And so on.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://247wallst.com/2011/03/22/the-new-generation-of-american-monopolies/" target="_blank"&gt;All of the great new monopolies of the last 30 years are tech companies&lt;/a&gt;, which should tell you something. &lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Another industry that is in transition is the newspaper business. We don't yet know who the Big Winner will be online (maybe the &lt;i&gt;New York Times&lt;/i&gt;, 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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;do &lt;/i&gt;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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://www.pcmag.com/article2/0,2817,2398695,00.asp" target="_blank"&gt;have switched adoption numbers&lt;/a&gt;, 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.)&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-6049842821470694043?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6049842821470694043?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6049842821470694043?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2012/01/how-internet-is-changing-economics.html" title="How the Internet is Changing Economics" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-aEuKIShvw2E/TxGToPAKzMI/AAAAAAAAA2I/7v9MdJVZExY/s72-c/coca-cola-ad-for-china-2008.jpg" height="72" width="72" /></entry><entry gd:etag="W/&quot;DEYEQnszcSp7ImA9WhRWGUg.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-8867795966001739581</id><published>2012-01-07T12:04:00.000-05:00</published><updated>2012-01-07T12:08:23.589-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-07T12:08:23.589-05:00</app:edited><title>Some Thoughts on Writer's Block</title><content type="html">&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-k-Fg0gT3IO0/TwhxaeAHKUI/AAAAAAAAA2A/jRsASVa7dEY/s1600/Einstein.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="240" src="http://1.bp.blogspot.com/-k-Fg0gT3IO0/TwhxaeAHKUI/AAAAAAAAA2A/jRsASVa7dEY/s320/Einstein.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
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 &lt;a href="http://en.wikipedia.org/wiki/Aether_%28classical_element%29" target="_blank"&gt;æther&lt;/a&gt;, 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. &lt;br /&gt;
&lt;br /&gt;
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?&lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;external &lt;/i&gt;reasons (e.g., I'm worried about XYZ, which is distracting me from writing), and &lt;i&gt;internal &lt;/i&gt;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 &lt;i&gt;internal &lt;/i&gt;blockers that interest me, because those are the hardest, and the most intrinsic to the process.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;not &lt;/i&gt;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. &lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://en.wikipedia.org/wiki/Reentrant_%28subroutine%29" target="_blank"&gt;reentrant&lt;/a&gt;, recursive, and iterative, which (therefore) also means it is &lt;a href="http://en.wikipedia.org/wiki/Nonlinear" target="_blank"&gt;nonlinear&lt;/a&gt; and ultimately &lt;a href="http://en.wikipedia.org/wiki/Chaos_theory" target="_blank"&gt;chaotic&lt;/a&gt; (in the profoundest mathematical sense envisioned by Gleick in his classic book, &lt;a href="http://www.around.com/chaos.html" target="_blank"&gt;Chaos&lt;/a&gt;). In your procrastination time, I strongly recommend that you read Gleick's &lt;i&gt;Chaos&lt;/i&gt; 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.&lt;br /&gt;
&lt;br /&gt;
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:&lt;i&gt; Your path to understanding is not a straight line.&lt;/i&gt; It's often a random walk in a multidimensional wonderland.&lt;br /&gt;
&lt;br /&gt;
Now consider what the written form is. It's words arranged one after another in a linear chain. &lt;i&gt;You're trying to reduce a multidimensional process to a linear form.&lt;/i&gt; 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?&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Totally unreasonable.&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;thinking &lt;/i&gt;works. Why should it be the way writing works? &lt;br /&gt;
&lt;br /&gt;
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, &lt;i&gt;because it isn't. &lt;/i&gt;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, &lt;i&gt;in the aggregate&lt;/i&gt;, 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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The most important advice of all when you're blocked? &lt;i&gt;Always, always, give yourself permission to write crap.&lt;/i&gt; 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.&lt;br /&gt;
&lt;br /&gt;
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: &lt;i&gt;Is this your authentic adult self talking, or is it your child-self?&lt;/i&gt; 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.&lt;br /&gt;
&lt;br /&gt;
And when you &lt;i&gt;do &lt;/i&gt;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. &lt;i&gt;So celebrate it. &lt;/i&gt;And keep moving toward the shitstorm.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-8867795966001739581?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8867795966001739581?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8867795966001739581?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2012/01/some-thoughts-on-writers-block.html" title="Some Thoughts on Writer's Block" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-k-Fg0gT3IO0/TwhxaeAHKUI/AAAAAAAAA2A/jRsASVa7dEY/s72-c/Einstein.jpg" height="72" width="72" /></entry><entry gd:etag="W/&quot;D0cFRHY8cCp7ImA9WhRWFUw.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-7790062523247362876</id><published>2012-01-02T09:36:00.001-05:00</published><updated>2012-01-02T09:36:55.878-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-02T09:36:55.878-05:00</app:edited><title>Turbulence in HTML5 Canvas</title><content type="html">&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-jTscTILAx8A/TwG_r1xqlII/AAAAAAAAA14/PpvxK_9NMzc/s1600/turb.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-jTscTILAx8A/TwG_r1xqlII/AAAAAAAAA14/PpvxK_9NMzc/s1600/turb.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;span style="font-size: xx-small;"&gt;&lt;i&gt;This is a screenshot of the shader UI showing what basic turbulence looks like.&lt;/i&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
As an update to my &lt;a href="http://asserttrue.blogspot.com/2012/01/procedural-textures-in-html5-canvas.html" target="_blank"&gt;earlier post&lt;/a&gt; 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.&lt;br /&gt;
&lt;br /&gt;
First, hat's off to &lt;a href="https://plus.google.com/102963672610224993454/posts" target="_blank"&gt;Ryan Sturgell&lt;/a&gt; for pointing out that by moving the big array initialization outside of the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;this.noise = function() &lt;/span&gt;call, it's possible to speed up the Perlin &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; function several-fold. Which should have been obvious, but I missed it on the first go-round. &lt;i&gt;#doh&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
In any case, the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise() &lt;/span&gt;function in the improved code (below) now can process ~400K pixels per second, which is a significant improvement indeed.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Turbulence &lt;/b&gt;&lt;br /&gt;
The turbulence function adds Perlin noise of various frequencies together, like so:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;function turbulence( x,y,z, octaves ) {

 var t = 0;
 var f = 1;
 var n = 0;

 for (var i = 0; i &amp;lt; octaves; i++, f *= 2) {
  n += PerlinNoise.noise(x * f, y * f, z)/f;
  t += 1/f;
 }
 return n / t;  // rescale back to 0..1
}
&lt;/pre&gt;
&lt;br /&gt;
This has the net result of giving noise that is much more realistic for things like cloud textures or smoke. For example:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-zx68_IxYKJU/TwG-hpMPm0I/AAAAAAAAA1s/I_ZWKOenlUM/s1600/sky.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-zx68_IxYKJU/TwG-hpMPm0I/AAAAAAAAA1s/I_ZWKOenlUM/s1600/sky.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;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;&lt;/pre&gt;
&lt;br /&gt;
This code uses turbulence to generate the cloud pattern and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;bias()&lt;/span&gt; to stretch the sky a bit at the top (and stretch the red value vertically as well).&lt;br /&gt;
&lt;br /&gt;
Without further ado, here's the complete code for the shader page. &lt;b&gt;Copy and paste all of the following code into a text file&lt;/b&gt; and give it a name that ends with &lt;b&gt;.html&lt;/b&gt;. Then open it in Chrome, Firefox, or any HTML5-capable browser.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
          &amp;lt;script&amp;gt;

 // 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 &amp;lt; 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 &amp;lt; w; x++) {
        for (var y = 0; y &amp;lt; 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 &amp;lt; 256 ; i++) 
 p[256+i] = p[i] = permutation[i]; 

this.noise = function(x, y, z) {



      var X = Math.floor(x) &amp;amp; 255,                  // FIND UNIT CUBE THAT
          Y = Math.floor(y) &amp;amp; 255,                  // CONTAINS POINT.
          Z = Math.floor(z) &amp;amp; 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 &amp;amp; 15;                      // CONVERT LO 4 BITS OF HASH CODE
      var u = h&amp;lt;8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
             v = h&amp;lt;4 ? y : h==12||h==14 ? x : z;
      return ((h&amp;amp;1) == 0 ? u : -u) + ((h&amp;amp;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 &amp;lt; 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));
}

    &amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;

    &amp;lt;body&amp;gt;
        &amp;lt;canvas id="myCanvas" width="300" height="300"&amp;gt;
        &amp;lt;/canvas&amp;gt;&amp;lt;br/&amp;gt;
        
   &amp;lt;input type="button" value=" Erase " 
     onclick="clearImage(); "/&amp;gt;

   &amp;lt;select onchange=
   "document.getElementById('code').innerHTML = this.value;"&amp;gt;
  &amp;lt;option&amp;gt;Choose something, then click Execute&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Basic Perlin Noise&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Basic Turbulence&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Waterfall&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Spherical Nebula&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Green Fibre Bundle&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Orange-Blue Marble&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Blood Maze&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Yellow Lightning&amp;lt;/option&amp;gt;
            &amp;lt;option&amp;gt;Downward Rainbow Wipe&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Noisy Rainbow&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Burning Cross&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Fair Skies&amp;lt;/option&amp;gt;
   &amp;lt;/select&amp;gt;
   
   &amp;lt;br/&amp;gt;  
        &amp;lt;textarea id="code" type="textarea" cols="37" rows="7"&amp;gt;/* Enter code here. Globals: r,g,b,x,y,w,h,PerlinNoise.noise(a,b,c) */&amp;lt;/textarea&amp;gt;
   &amp;lt;br/&amp;gt;

        &amp;lt;input type="button" value=" Execute " 
     onclick="doPixelLoop();"  /&amp;gt;
   &amp;lt;input type="button" value="Open as PNG" 
     onclick="window.open(canvas.toDataURL('image/png'))"/&amp;gt;


&amp;lt;!-- BEGIN HIDDEN TEXT --&amp;gt;
&amp;lt;div hidden="true"&amp;gt;
&amp;lt;span&amp;gt;
// you can enter your own code here!
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
x /= w; y /= h;
size = 10;
n = PerlinNoise.noise(size*x,size*y,.8);
r = g = b = 255 * n;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
x /= w; y /= h;
size = 10;
n = turbulence(size*x,size*y,.8,4);
r = g = b = 255 * n;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
x/= 30; y/=3 * (y+x)/w;
n = PerlinNoise.noise(x,y,.18);
b = Math.round(255*n);
g = b - 255; r = 0;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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 &amp;lt; 150 ? dterm : 255;
b = dist &amp;lt; 150 ? 255-r   : 255; 
g = dist &amp;lt; 151 ? dterm/1.5 : 255;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
n = PerlinNoise.noise(x/45,y/120, .74);
n = Math.cos( n * 85);
r = Math.round(n * 255);
b = 255 - r; 
g = r - 255 ;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
// 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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;!-- END HIDDEN TEXT --&amp;gt;
   
    &amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;

&lt;/pre&gt;
&lt;br /&gt;
Incidentally, I did find a halfway-decent discussion of &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;turbulence()&lt;/span&gt; online, written by Ken Perlin himself, at &lt;a href="http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html"&gt;http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html&lt;/a&gt;. Read it and reap!&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-7790062523247362876?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7790062523247362876?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7790062523247362876?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2012/01/turbulence-in-html5-canvas.html" title="Turbulence in HTML5 Canvas" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-jTscTILAx8A/TwG_r1xqlII/AAAAAAAAA14/PpvxK_9NMzc/s72-c/turb.png" height="72" width="72" /></entry><entry gd:etag="W/&quot;AkUCQHk8eCp7ImA9WhRWE0Q.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-8612589817231866160</id><published>2012-01-01T01:11:00.000-05:00</published><updated>2012-01-01T01:11:01.770-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-01T01:11:01.770-05:00</app:edited><title>Procedural Textures in HTML5 Canvas</title><content type="html">&lt;div class="separator" style="clear: both; text-align: none;"&gt;
&lt;/div&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-XERAFVXwp5w/Tv8MHnDmo5I/AAAAAAAAA0Y/Jlp-5Znl3lQ/s1600/GreenFibre.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-XERAFVXwp5w/Tv8MHnDmo5I/AAAAAAAAA0Y/Jlp-5Znl3lQ/s1600/GreenFibre.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;span style="font-size: x-small;"&gt;&lt;i&gt;This is a screenshot of the user interface for the procedural texture app.&lt;/i&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
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 &lt;a href="http://asserttrue.blogspot.com/2011/12/perlin-noise-in-javascript_31.html" target="_blank"&gt;yesterday's post&lt;/a&gt; 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.)&lt;br /&gt;
&lt;br /&gt;
The interface is simple. There's a text box where you can type some code (see illustration at right). &lt;i&gt;Whatever you type there will be executed against every pixel of the 2D canvas.&lt;/i&gt; Exposed globals include:&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;x&lt;/i&gt; -- the x coordinate of the current pixel&lt;br /&gt;
&lt;i&gt;y&lt;/i&gt; -- the y coord of the current pixel&lt;br /&gt;
&lt;i&gt;w&lt;/i&gt; -- the width of the canvas, in pixels&lt;br /&gt;
&lt;i&gt;h&lt;/i&gt; -- the height of the canvas&lt;br /&gt;
&lt;i&gt;r&lt;/i&gt; -- the red channel of the current pixel&lt;br /&gt;
&lt;i&gt;g&lt;/i&gt; -- the green value of the current pixel&lt;br /&gt;
&lt;i&gt;b&lt;/i&gt; -- the blue value of the current pixel&lt;br /&gt;
&lt;i&gt;PerlinNoise.noise( u,v,w )&lt;/i&gt; -- Perlin's 3D noise function&lt;br /&gt;
&lt;br /&gt;
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 &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise() &lt;/span&gt;function in your loop, that'll slow you down to ~120K pixels per second. But that's still pretty good.&lt;br /&gt;
&lt;br /&gt;
The versatility of the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; function is truly amazing. The key to using it effectively is to understand how to scale it. By appropriately scaling the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;x&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;y&lt;/span&gt; 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 &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;r&lt;/span&gt;, &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;g&lt;/span&gt;, and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;b&lt;/span&gt; channels. For example:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-k5CPtGeV84c/Tv8WeOgk6iI/AAAAAAAAA0k/EeFJnq_3YPM/s1600/blood.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-k5CPtGeV84c/Tv8WeOgk6iI/AAAAAAAAA0k/EeFJnq_3YPM/s1600/blood.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
This texture was achieved with the following shader code:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;n = PerlinNoise.noise(x/45,y/120, .89);
n = Math.cos( n * 85);
r = Math.round(n * 255);
b = 255 - r; 
g = r - 255 ;
&lt;/pre&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
By normalizing the texture space in various ways, you can end up with surprising effects. For example, consider:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-MOJg0uiUlVU/Tv8YoBvl-9I/AAAAAAAAA0w/q6ClvCWLgFw/s1600/nebula.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-MOJg0uiUlVU/Tv8YoBvl-9I/AAAAAAAAA0w/q6ClvCWLgFw/s1600/nebula.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;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;
&lt;/pre&gt;
&lt;br /&gt;
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 &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; have been scaled to give a relatively fine-grain noise. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
A dramatically different effect can be obtained by normalizing x and y and applying trig functions creatively:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-KqOzvPCukHw/Tv8asWnHh1I/AAAAAAAAA08/SDxMzMTTjSo/s1600/lightning.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-KqOzvPCukHw/Tv8asWnHh1I/AAAAAAAAA08/SDxMzMTTjSo/s1600/lightning.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;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;
&lt;/pre&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-FKhvufdEfAg/Tv8cqKVldAI/AAAAAAAAA1g/3zzGi1E6Gts/s1600/rainbow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-FKhvufdEfAg/Tv8cqKVldAI/AAAAAAAAA1g/3zzGi1E6Gts/s1600/rainbow.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;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;
&lt;/pre&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
          &amp;lt;script&amp;gt;

 // 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 &amp;lt; 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 &amp;lt; w; x++) {
        for (var y = 0; y &amp;lt; 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 &amp;lt; 256 ; i++) 
 p[256+i] = p[i] = permutation[i]; 

      var X = Math.floor(x) &amp;amp; 255,                  // FIND UNIT CUBE THAT
          Y = Math.floor(y) &amp;amp; 255,                  // CONTAINS POINT.
          Z = Math.floor(z) &amp;amp; 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 &amp;amp; 15;                      // CONVERT LO 4 BITS OF HASH CODE
      var u = h&amp;lt;8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
             v = h&amp;lt;4 ? y : h==12||h==14 ? x : z;
      return ((h&amp;amp;1) == 0 ? u : -u) + ((h&amp;amp;2) == 0 ? v : -v);
   } 
   function scale(n) { return (1 + n)/2; }
}

    &amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;

    &amp;lt;body&amp;gt;
        &amp;lt;canvas id="myCanvas" width="300" height="300"&amp;gt;
        &amp;lt;/canvas&amp;gt;&amp;lt;br/&amp;gt;
        
   &amp;lt;input type="button" value=" Erase " 
     onclick="clearImage(); "/&amp;gt;

   &amp;lt;select onchange=
   "document.getElementById('code').innerHTML = this.value;"&amp;gt;
  &amp;lt;option&amp;gt;Choose something, then click Execute&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Basic Perlin Noise&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Waterfall&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Spherical Nebula&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Green Fibre Bundle&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Orange-Blue Marble&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Blood Maze&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Yellow Lightning&amp;lt;/option&amp;gt;
            &amp;lt;option&amp;gt;Downward Rainbow Wipe&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Noisy Rainbow&amp;lt;/option&amp;gt;
  &amp;lt;option&amp;gt;Burning Cross&amp;lt;/option&amp;gt;
   &amp;lt;/select&amp;gt;
   
   &amp;lt;br/&amp;gt;  
        &amp;lt;textarea id="code" type="textarea" cols="40" rows="7"&amp;gt;/* Enter code here. */&amp;lt;/textarea&amp;gt;
   &amp;lt;br/&amp;gt;

        &amp;lt;input type="button" value=" Execute " 
     onclick="doPixelLoop();"  /&amp;gt;
   &amp;lt;input type="button" value="Open as PNG" 
     onclick="window.open(canvas.toDataURL('image/png'))"/&amp;gt;


&amp;lt;!-- BEGIN HIDDEN TEXT --&amp;gt;
&amp;lt;div hidden="true"&amp;gt;
&amp;lt;span&amp;gt;
// you can enter your own code here!
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
x /= w; y /= h;
size = 10;
n = PerlinNoise.noise(size*x,size*y,.8);
r = g = b = 255 * n;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
x/= 30; y/=3 * (y+x)/w;
n = PerlinNoise.noise(x,y,.18);
b = Math.round(255*n);
g = b - 255; r = 0;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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 &amp;lt; 150 ? dterm : 255;
b = dist &amp;lt; 150 ? 255-r   : 255; 
g = dist &amp;lt; 151 ? dterm/1.5 : 255;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
n = PerlinNoise.noise(x/45,y/120, .74);
n = Math.cos( n * 85);
r = Math.round(n * 255);
b = 255 - r; 
g = r - 255 ;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
// 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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;

&amp;lt;span&amp;gt;
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;
&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;!-- END HIDDEN TEXT --&amp;gt;
   
    &amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;

&lt;/pre&gt;
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 &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;loadHiddenText()&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
The Perlin &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; function may look intimidating, but it's not, really. It's a port of Ken Perlin's Java-based reference implementation of &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt;. See &lt;a href="http://asserttrue.blogspot.com/2011/12/perlin-noise-in-javascript_31.html" target="_blank"&gt;yesterday's post&lt;/a&gt; for more information. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-8612589817231866160?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8612589817231866160?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8612589817231866160?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2012/01/procedural-textures-in-html5-canvas.html" title="Procedural Textures in HTML5 Canvas" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-XERAFVXwp5w/Tv8MHnDmo5I/AAAAAAAAA0Y/Jlp-5Znl3lQ/s72-c/GreenFibre.png" height="72" width="72" /></entry><entry gd:etag="W/&quot;DUQEQncyeip7ImA9WhRWE04.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-7792391842773304545</id><published>2011-12-31T08:14:00.001-05:00</published><updated>2011-12-31T08:15:03.992-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-31T08:15:03.992-05:00</app:edited><title>Perlin Noise in JavaScript</title><content type="html">&lt;table align="left" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-r6yuiIo5Q1s/TvpKu-chp6I/AAAAAAAAAzg/FQb7QpSChHQ/s1600/perlin.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-r6yuiIo5Q1s/TvpKu-chp6I/AAAAAAAAAzg/FQb7QpSChHQ/s1600/perlin.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;span style="font-size: x-small;"&gt;&lt;i&gt;Perlin noise in two dimensions, generated using the code below.&lt;/i&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
I've been working on an HTML5 canvas-based procedural texture demo (which I'll blog about tomorrow), for which I did a JavaScript port of Ken Perlin's &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; routine (which is in Java). Ahead of tomorrow's blog, I thought I'd briefly discuss Perlin Noise.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span style="font-size: small;"&gt;Perlin Noise&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
If you've worked with 3D graphics programs, you're already well familiar with Ken Perlin's famous noise function (which gives rise to so-called &lt;a href="http://en.wikipedia.org/wiki/Perlin_noise" target="_blank"&gt;Perlin noise&lt;/a&gt;). The code for it looks a little scary, but intuitively it's an easy function to understand. Let's take the 2D case (although you can generate Perlin noise for any number of dimensions). Imagine that you have a 256-pixel-square image (blank, all white). Now, imagine that I come along and tell you to mark the canvas off into 32 rows and 32 columns of 8x8-pixel squares. Further imagine that I ask you to assign a random grey value to each square. You've now got a kind of checkerboard pattern of random greys.&lt;br /&gt;
&lt;br /&gt;
What differentiates Perlin noise from random checkboard noise is that in Perlin's case, the color values are interpolated smoothly from the center of each tile outward, in such a way that you don't see an obvious gridlike pattern. In other words, when you cross a tile boundary, you want the slope of the pixel intensity to be constant (no discontinuities). You can visualize the end result if you took the 32x32 random checkboard pattern and passed it through a Gaussian blur a few times. Pretty soon, you wouldn't even be able to tell that gridlines ever existed in the first place. That's the idea with Perlin noise. You want to interpolate colors from one block to the next in such a way that there are no discontinuities at the cell boundaries. It turns out this requirement can be met in quite a variety of ways (by using cubic splines, quartics, or even sine- or cosine-based interpolation between squares, for example; or by using Perlin's &lt;a href="http://asserttrue.blogspot.com/2010/01/fast-contrast-adjustment-using-perlins.html" target="_blank"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;gain()&lt;/span&gt;&lt;/a&gt; function). There's no one "correct" way to do it.&lt;br /&gt;
&lt;br /&gt;
I'd love to be able to link to a good Perlin noise tutorial on the Web, but so far I haven't found one that doesn't try to conflate fractal noise, turbulence, and other topics with Perlin noise. The best treatment I've come across, frankly, is (not surprisingly) in Perlin's own &lt;a href="http://www.amazon.com/Texturing-Modeling-Third-Procedural-Approach/dp/1558608486/ref=dp_ob_title_bk" target="_blank"&gt;Texturing and Modeling&lt;/a&gt; book (which is truly a first-rate book, "must reading" for graphics programmers).&lt;br /&gt;
&lt;br /&gt;
Fortunately, Ken Perlin has done all the hard work for us in writing the necessary interpolation (and other) code for &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt;, and he has kindly provided a &lt;a href="http://cs.nyu.edu/%7Eperlin/noise/" target="_blank"&gt;3D reference implementation of the noise() function&lt;/a&gt; in highly optimized Java. I ported his code to JavaScript (see below) and I'm happy to say it works very well in a canvas environment (as we'll see in tomorrow's post, right here). It's reasonably fast, too. In fact, it's so fast that there's no need to fall back to a 2D version for better speed. This is good, because the 3D version gives you added versatility in case you decide you want to animate your noise in the time domain.&lt;br /&gt;
&lt;br /&gt;
Usage of Perlin's function is very straightforward. It takes 3 arguments (in Java, these are double-precision floating point numbers -- which is fine, because in JavaScript all numbers are IEEE-754 double-precision floating point numbers, under the covers). The way the function is usually used, the first two arguments correspond to the &lt;i&gt;x&lt;/i&gt; and &lt;i&gt;y&lt;/i&gt; coordinate values of a pixel in 2-space. If you're working in 3-space, the third argument is the &lt;i&gt;z&lt;/i&gt;-value. In 2-space, you can call the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise() &lt;/span&gt;function with the third argument set to whatever you like. If you're doing a 2D animation and want the texture to animate in real time, you can link the third argument (that &lt;i&gt;z&lt;/i&gt;-value) to a time-based index, and the texture will animate smoothly, because you are, in effect, sampling closely spaced slices of a 3D noise space.&lt;br /&gt;
&lt;br /&gt;
The return value from &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; is a double-precision floating point number in the range &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;0..1&lt;/span&gt;. Actually, in Perlin's original code, the return value can range from -1 to 1.0, but in my JavaScript port (below), I clamp the return to &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;0..1&lt;/span&gt;. Here's the code:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;// This is a port of Ken Perlin's Java code. The
// original Java code is at http://cs.nyu.edu/%7Eperlin/noise/.
// Note that in this version, a number from 0 to 1 is returned.
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 &amp;lt; 256 ; i++) 
 p[256+i] = p[i] = permutation[i]; 

      var X = Math.floor(x) &amp;amp; 255,                  // FIND UNIT CUBE THAT
          Y = Math.floor(y) &amp;amp; 255,                  // CONTAINS POINT.
          Z = Math.floor(z) &amp;amp; 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 &amp;amp; 15;                      // CONVERT LO 4 BITS OF HASH CODE
      var u = h&amp;lt;8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
             v = h&amp;lt;4 ? y : h==12||h==14 ? x : z;
      return ((h&amp;amp;1) == 0 ? u : -u) + ((h&amp;amp;2) == 0 ? v : -v);
   } 
   function scale(n) { return (1 + n)/2; }
}
&lt;/pre&gt;
&lt;br /&gt;
So let's say you have a function that marches through all the pixel values in an image, and you want to use this code. You need the &lt;i&gt;x&lt;/i&gt; and &lt;i&gt;y&lt;/i&gt; coordinates of the pixel, the width of the image (as &lt;i&gt;w&lt;/i&gt;), and the height (as &lt;i&gt;h&lt;/i&gt;). Then you could do something like:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;x /= w; y /= h; // normalize
size = 10;  // pick a scaling value
n = PerlinNoise.noise( size*x, size*y, .8 );
r = g = b = Math.round( 255 * n );
&lt;/pre&gt;
&lt;br /&gt;
Here, the &lt;i&gt;z&lt;/i&gt;-argument is arbitrarily set to .8, but it could just as well be set to zero or whatever you like. You can fiddle with &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;size &lt;/span&gt;to get a result that's visually pleasing (it will vary considerably, depending on the effect that you're trying to achieve). If you're animating the texture, the next time-step might set the &lt;i&gt;z&lt;/i&gt;-arg to 0.9, say, instead of 0.8.&lt;br /&gt;
&lt;br /&gt;
In the example given above, we're setting &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;r = g = b&lt;/span&gt;, which of course gives a grey pixel. The overall result looks like the picture at the top of this post. In fact, that image was generated using the code shown above.&lt;br /&gt;
&lt;br /&gt;
Perlin's justly famous noise function is enormously versatile (and a ton of fun to play with). As I say, the most authoritative, in-depth discussion of it occurs in Perlin's &lt;a href="http://www.amazon.com/Texturing-Modeling-Third-Procedural-Approach/dp/1558608486/ref=dp_ob_title_bk" target="_blank"&gt;Texturing and Modeling&lt;/a&gt; book. We'll see more colorful uses of the &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;noise()&lt;/span&gt; function in tomorrow's blog. Don't miss it!&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-7792391842773304545?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7792391842773304545?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7792391842773304545?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/perlin-noise-in-javascript_31.html" title="Perlin Noise in JavaScript" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-r6yuiIo5Q1s/TvpKu-chp6I/AAAAAAAAAzg/FQb7QpSChHQ/s72-c/perlin.png" height="72" width="72" /></entry><entry gd:etag="W/&quot;DEQCRHszeyp7ImA9WhRWEkg.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-7131283938702734038</id><published>2011-12-30T09:46:00.000-05:00</published><updated>2011-12-30T09:46:05.583-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-30T09:46:05.583-05:00</app:edited><title>Convolution Kernels in HTML5 Canvas</title><content type="html">&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
Convolution is a straightforward mathematical process that is fundamental to many image processing effects. If you've played around with the &lt;b&gt;Filter &amp;gt; Other &amp;gt; Custom&lt;/b&gt; dialog in Photoshop, you're already familiar with what convolutions can do.&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-jxhVH6P-e_0/Tv3MIg3QGBI/AAAAAAAAAzs/jrl87y5I33o/s1600/sharpen.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-jxhVH6P-e_0/Tv3MIg3QGBI/AAAAAAAAAzs/jrl87y5I33o/s320/sharpen.png" width="250" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;i&gt;A sharpening convolution applied to Lena.&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
A convolution applies a matrix (often called a kernel) against each pixel in an image. For any given pixel in the image, a new pixel value is calculated by multiplying the various values in the kernel by corresponding (underlying) pixel values, then summing the result (and rescaling to the applicable pixel bandwidth, usually &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;0..255&lt;/span&gt;). If you imagine a 3x3 kernel in which all values are equal to one, applying this as a convolution is the same as multiplying the center pixel and its eight nearest neighbors by one, then adding them all up (and dividing by 9 to rescale the pixel). In other words, it's tantamount to averaging 9 pixel values, which essentially blurs the image slightly if you do this to every pixel, in turn.&lt;br /&gt;
&lt;br /&gt;
The application of convolutions to an HTML5 canvas image is straightforward. I've created an example Chrome extension that is active whenever you visit a URL ending in ".jpg" or ".png" (from &lt;i&gt;any &lt;/i&gt;website). The extension provides a 3x3 convolution kernel (as text fields). You can enter any values you want (positive or negative) in the kernel columns and rows. Behind the scenes, the kernel will be normalized for you automatically. (That simply means each value is divided by the sum of all the values, except in the case where the values sum to zero, in which instance the normalization step is skipped.)&lt;br /&gt;
&lt;br /&gt;
Some convolutions, such as the Sobel kernel, have kernel values that add up to zero. In this case, you end up with a mostly dark image that you'll probably want to invert. My Chrome extension provides an Invert Image button, for just that occasion.&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-iRUkA7-GjPQ/Tv3MgSdb9kI/AAAAAAAAA0A/mQQhPmTF0A8/s1600/sobel.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-iRUkA7-GjPQ/Tv3MgSdb9kI/AAAAAAAAA0A/mQQhPmTF0A8/s320/sobel.png" width="250" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;i&gt;A modified Sobel kernel, plus image inversion.&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
The UI also includes a Reset button (which reloads the original image and sets the kernel to an identity kernel) and a button that opens the newly modified image in a new window as a PNG that can be saved to disk.&lt;br /&gt;
&lt;br /&gt;
The code for the Chrome extension is shown below. To use it, do this:&lt;br /&gt;
&lt;br /&gt;
1. Copy and paste all of the code into a new file. Call it &lt;b&gt;Kernel.user.js&lt;/b&gt; (or whatever you want, but be sure the name ends with &lt;b&gt;.user.js&lt;/b&gt;).&lt;br /&gt;
2. Save the file (text-only) to any convenient folder.&lt;br /&gt;
3. Launch Chrome. Use &lt;b&gt;Control-O&lt;/b&gt; to bring up the file-open dialog. Navigate to the file you just saved. Open it.&lt;br /&gt;
4. Notice at the very bottom of the Chrome window, there'll be a status warning (saying that extensions can harm your health, etc.) with two buttons, Continue and Discard. Click &lt;b&gt;Continue&lt;/b&gt;.&lt;br /&gt;
5. In the Confirm Installation dialog that pops up, click the &lt;b&gt;Install &lt;/b&gt;button. After you do this, the extension is installed and running. Test it by navigating to any convenient URL that ends in ".jpg" or ".png" (but do note, the extension may fail due to security restrictions if you are loading images from disk, via a "file:" scheme). For best results, navigate to an image on the web using &lt;i&gt;http&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;// @name           KernelTool
// @namespace      ktKernelTool
// @description    Canvas Image Kernel Tool
// @include        *
// ==/UserScript==



// A demo script by Kas Thomas.
// Use as you will, at your own risk.


// The stuff under loadCode() will be injected
// into a &amp;lt;script&amp;gt;
 element in the page.

function loadCode() {


window.KERNEL_SIZE = 3; // 3 x 3 square kernel

window.transformImage = function( x1,y1,w,h ) {
      
 var canvasData = context.getImageData(x1,y1,w,h);

 var kernel = getKernelValues( );
 normalizeKernel( kernel );
  
 for (var x = 1; x &amp;lt; w-1; x++) {
      for (var y = 1; y &amp;lt; h-1; y++) {

   // get the real estate around this pixel
   // (using the offscreen image)
   var area = 
    context.getImageData(x-1,y-1,
     KERNEL_SIZE,KERNEL_SIZE);
   
          // Index of the current pixel in the array
          var idx = (x + y * w) * 4;

   // apply kernel to current index
   var rgb = applyKernel( kernel, area, canvasData, idx );

   canvasData.data[ idx ] = rgb[0];
   canvasData.data[idx+1] = rgb[1];
     canvasData.data[idx+2] = rgb[2];
       }
 } 

 // inner function that applies the kernel
 function applyKernel( k, localData, imageData, pixelIndex ) {

  var sumR = 0; var sumG = 0; var sumB = 0;
   var n = 0;

  for ( var i = 0; i &amp;lt; k.length; i++,n+=4 ) {
   sumR += localData.data[n]  *  k[i];
   sumG += localData.data[n+1] * k[i];
   sumB += localData.data[n+2] * k[i];
  }

  if (sumR &amp;lt; 0)  sumR *= -1; 
  if (sumG &amp;lt; 0)  sumG *= -1; 
  if (sumB &amp;lt; 0)  sumB *= -1; 

  return [Math.round( sumR ),Math.round( sumG ),Math.round( sumB )]; 
 }

 context.putImageData( canvasData,x1,y1 );
};

window.invertImage = function( ) {

  var w = canvas.width;
  var h = canvas.height;
  var canvasData = 
   context.getImageData(0,0,w,h);
  for (var i = 0; i &amp;lt; w*h*4; i+=4)  {
   canvasData.data[i] = 255 - canvasData.data[i];
   canvasData.data[i+1] = 255 - canvasData.data[i+1];
   canvasData.data[i+2] = 255 - canvasData.data[i+2];  
  }
  context.putImageData( canvasData,0,0 ); 
 }

// get an offscreen drawing context for the image
window.getOffscreenContext = function( w,h ) {
   
 var offscreenCanvas = document.createElement("canvas");
 offscreenCanvas.width = w;
 offscreenCanvas.height = h;
 return offscreenCanvas.getContext("2d");
};

window.getKernelValues = function( ) {

 var kernel = document.getElementsByClassName("kernel");
 var kernelValues = new Array(9);
 for (var i = 0; i &amp;lt; kernelValues.length; i++)
  kernelValues[i] = 1. * kernel[i].value;
 return kernelValues;
}

window.setKernelValues = function( values ) {

 var kernel = document.getElementsByClassName("kernel");
 for (var i = 0; i &amp;lt; kernel.length; i++)
  kernel[i].value = values[i];
}

window.normalizeKernel = function( k ) {
 
 var sum = 0;

 for (var i = 0; i &amp;lt; k.length; i++)
  sum += k[i];

 if (sum &amp;gt; 0)
  for (var i = 0; i &amp;lt; k.length; i++)
   k[i] /= sum;
}

window.setupGlobals = function() {

 window.canvas = document.getElementById("myCanvas");
 window.context = canvas.getContext("2d");
 var imageData = context.getImageData(0,0,canvas.width,canvas.height);
 window.offscreenContext = getOffscreenContext( canvas.width,canvas.height );
 window.offscreenContext.putImageData( imageData,0,0 );
};

setupGlobals();  // actually call it

// enable the buttons now that code is loaded
document.getElementById("reset").disabled = false;
document.getElementById("invert").disabled = false;
document.getElementById("PNG").disabled = false;

} // end loadCode()



/* * * * * * * * * main() * * * * * * * * */

(function main( ) {

 // are we really on an image URL?
 var ext = location.href.split(".").pop();
 if (ext.match(/jpg|jpeg|png/) == null )
    return;

 // ditch the original image
 img = document.getElementsByTagName("img")[0];
 img.parentNode.removeChild(img); 

 // put scripts into the page scope in 
 // a &amp;lt;script&amp;gt; elem with id = "myCode"
 // (we will eval() it in an event later...)
 var code = document.createElement("script");
 code.setAttribute("id","myCode");
 document.body.appendChild(code);  
 code.innerHTML += loadCode.toString() + "\n";

 // set up canvas
 canvas = document.createElement("canvas");
 canvas.setAttribute("id","myCanvas");
 document.body.appendChild( canvas );

 context = canvas.getContext("2d");

 image = new Image();

 image.onload = function() {

  canvas.width = image.width;
  canvas.height = image.height;
       context.drawImage(image,0, 0,canvas.width,canvas.height ); 
 };

 // This line must come after, not before, onload!
 image.src = location.href;

 createKernelUI( );
      createApplyButton( );
 createResetButton( );  
 createInvertImageButton( );
 createPNGButton( ); // create UI for Save As PNG

 function createPNGButton( ) {

  var button = document.createElement("input");
  button.setAttribute("type","button");
  button.setAttribute("value","Open as PNG...");
  button.setAttribute("id","PNG");
  button.setAttribute("disabled","true");
  button.setAttribute("onclick",
   "window.open(canvas.toDataURL('image/png'))" );
  document.body.appendChild( button );
 }

 function createInvertImageButton( ) {

  var button = document.createElement("input");
  button.setAttribute("type","button");
  button.setAttribute("value","Invert Image");
  button.setAttribute("id","invert");
  button.setAttribute("disabled","true");
  button.setAttribute("onclick",
   "invertImage()" );
  document.body.appendChild( button );
 }

 function createResetButton( ) {

  var button = document.createElement("input");
  button.setAttribute("type","button");
  button.setAttribute("value","Reset");
  button.setAttribute("id","reset");
  button.setAttribute("disabled","true");
  button.setAttribute("onclick",
   "var data = offscreenContext.getImageData(0,0,canvas.width,canvas.height);" +
   "context.putImageData(data,0, 0 );" + 
   "setKernelValues([0,0,0,0,1,0,0,0,0]);" );
  document.body.appendChild( button );
 }

 // This will load code if it hasn't been loaded yet.
 function createApplyButton( ) {

  var button = document.createElement("input");
  button.setAttribute("type","button");
  button.setAttribute("value","Apply");
  button.setAttribute("onclick","if (typeof codeLoaded == 'undefined')" +
   "{ codeLoaded=1; " + 
   "code=document.getElementById(\"myCode\").innerHTML;" +
   "eval(code); loadCode(); }" +
   "transformImage(0,0,canvas.width,canvas.height);" );
  document.body.appendChild( button );
 }

 function createKernelUI( ) { 

  var kdiv = document.createElement("div");
  var elem = new Array(9);

  for ( var i = 0; i &amp;lt; 9; i++ ) {
   elem[i] = document.createElement("input");
   elem[i].setAttribute("type","text");
   elem[i].setAttribute("value","1");
   elem[i].setAttribute("class","kernel");
   elem[i].setAttribute("style","width:24px");
   elem[i].setAttribute("id","k" + i);
  }
  for ( var i = 0; i &amp;lt; 9; i++ ) {
   kdiv.appendChild( elem[i] );
   if (i == 2 || i == 5 || i == 8)
    kdiv.innerHTML += "&amp;lt;br/&amp;gt;";
  }

  document.body.appendChild( kdiv );
 } 
})();
&lt;/pre&gt;
&lt;br /&gt;
It can be fun and educational to experiment with new kernel values (and to apply more than one convolution &lt;i&gt;sequentially &lt;/i&gt;to achieve new effects). With the right choice of values, you can easily achieve blurring, sharpening, embossing, and edge detection/enhancement, among other effects.&amp;nbsp;&lt;br /&gt;
&lt;br /&gt;
Incidentally, for more information about the Lena test image (in case you're not familiar with the interesting backstory), check out &lt;a href="http://en.wikipedia.org/wiki/Lenna"&gt;http://en.wikipedia.org/wiki/Lenna&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-7131283938702734038?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7131283938702734038?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7131283938702734038?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/convolution-kernels-in-html5-canvas.html" title="Convolution Kernels in HTML5 Canvas" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-jxhVH6P-e_0/Tv3MIg3QGBI/AAAAAAAAAzs/jrl87y5I33o/s72-c/sharpen.png" height="72" width="72" /></entry><entry gd:etag="W/&quot;D04NRXs7fyp7ImA9WhRXGE8.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-5071970491959119352</id><published>2011-12-25T08:18:00.000-05:00</published><updated>2011-12-25T10:13:14.507-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-25T10:13:14.507-05:00</app:edited><title>How to Save a Canvas as PNG</title><content type="html">In &lt;a href="http://asserttrue.blogspot.com/2011/12/gamma-adjustment-in-html5-canvas.html" target="_blank"&gt;yesterday's post&lt;/a&gt;, I showed how to render an image into an HTML5 canvas element (and then operate on it with canvas API calls). When you've made changes to a canvas image, the time may come when you want to save the canvas as a regular PNG image. As it turns out, doing that isn't hard at all.&lt;br /&gt;
&lt;br /&gt;
The key is to use &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;canvas.toDataURL('image/png')&lt;/span&gt; to serialize the image as a &lt;a href="http://en.wikipedia.org/wiki/Data_URI_scheme" target="_blank"&gt;data URI&lt;/a&gt;, which you can (of course) open in a new window with &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;window.open( uri )&lt;/span&gt;. Once the image is open in a new window (note: you may have to instruct your browser to allow popups), you can right-click on the image to get the browser's &lt;b&gt;Save Image As...&lt;/b&gt; command in a context menu. From there, you just save the image as you normally would.&lt;br /&gt;
&lt;br /&gt;
The following code can be added to yesterday's example in order to create a button on the page called &lt;b&gt;Open as PNG...&lt;/b&gt; &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;function createPNGButton( ) {

 var button = document.createElement("input");
 button.setAttribute("type","button");
 button.setAttribute("value","Open as PNG...");
 button.setAttribute("onclick",
  "window.open(canvas.toDataURL('image/png'))" );
 document.body.appendChild( button );
}&lt;/pre&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;&lt;/pre&gt;
As you can see, there's no rocket science involved. Just a little HTML5 magic.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-5071970491959119352?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/5071970491959119352?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/5071970491959119352?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/in-yesterdays-post-i-showed-how-to.html" title="How to Save a Canvas as PNG" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;CU4DRX06eSp7ImA9WhRXF0g.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-1267556627805929357</id><published>2011-12-24T12:36:00.000-05:00</published><updated>2011-12-24T14:12:54.311-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-24T14:12:54.311-05:00</app:edited><title>Gamma Adjustment in an HTML5 Canvas</title><content type="html">&lt;a href="http://1.bp.blogspot.com/-fRVmsOSZ_1Y/TvYYVsB2dlI/AAAAAAAAAw4/7Z5QdBXJGt0/s1600/penguins.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="246" src="http://1.bp.blogspot.com/-fRVmsOSZ_1Y/TvYYVsB2dlI/AAAAAAAAAw4/7Z5QdBXJGt0/s320/penguins.png" width="320" /&gt;&lt;/a&gt;I came up with kind of a neat trick I'd like to share. If you're an HTML canvas programmer, listen up. You just might get a kick out of this.&lt;br /&gt;
&lt;br /&gt;
You know how, when you're loading images in canvas (and then fiddling with the pixels using canvas API code), you have to load your scripts and images from the same server? (For security reasons.) That's no problem for the hard-core geeks among us, of course. Many web developers keep a local instance of Apache or other web server running in the background for just such occasions. (As an Adobe employee, I'm fortunate to be able to run &lt;a href="http://www.adobe.com/solutions/customer-experience/web-experience-management.html" target="_blank"&gt;Adobe WEM&lt;/a&gt;, aka Day CQ, on my machine.) But overall, it sucks. What I'd like to be able to do is fiddle with any image, taken from any website I choose, any time I want, without having to run a web server on my local machine.&lt;br /&gt;
&lt;br /&gt;
So, what I've done is create a Chrome extension that comes into action whenever my browser is pointed at any URL that ends in ".png" or ".jpg" or ".jpeg". The instant the image in question loads, my extension puts it into a canvas element, re-renders it, and exposes its 2D context for scripts to work against.&lt;br /&gt;
&lt;br /&gt;
For demo purposes, I've included some code for making gamma adjustments to the image via canvas-API calls (which I'll talk more about later).&lt;br /&gt;
&lt;br /&gt;
The code for the Chrome extension is shown below. To use it, do this:&lt;br /&gt;
&lt;br /&gt;
1. Copy and paste all of the code into a new file. Call it &lt;b&gt;BrightnessTest.user.js&lt;/b&gt;. Actually, call it whatever you want, but be sure the name ends with &lt;b&gt;.user.js&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
2. Save the file (text-only) to any convenient folder.&lt;br /&gt;
&lt;br /&gt;
3. Launch Chrome. (I did all my testing in Chrome. The extension should be Greasemonkey-compatible, but I have not tested it in Firefox.) Use &lt;b&gt;Control-O&lt;/b&gt; to bring up the file-open dialog. Navigate to the file you just saved. Open it.&lt;br /&gt;
&lt;br /&gt;
4. Notice at the very bottom of the Chrome window, there'll be a status warning (saying that extensions can harm your loved ones, etc.) with two buttons, Continue and Discard. Click &lt;b&gt;Continue&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
5. In the Confirm Installation dialog that pops up, click the &lt;b&gt;Install &lt;/b&gt;button. After you do this, the extension is installed and running.&lt;br /&gt;
&lt;br /&gt;
Test the extension by navigating to &lt;a href="http://goo.gl/UQpRA"&gt;http://goo.gl/UQpRA&lt;/a&gt; (the penguin image shown in the above screenshots). Please try a small image (like the penguin) first, for performance reasons. Note: Due to security restrictions, you can't load images from disk (no&lt;i&gt; file:&lt;/i&gt; scheme in the URL; only &lt;i&gt;http:&lt;/i&gt; and/or &lt;i&gt;https:&lt;/i&gt; are allowed). Any PNG or JPG on the web should work.&lt;br /&gt;
&lt;br /&gt;
When the image loads, you should see a small slider underneath it. This is an HTML5 input element. If you are using an obsolete version of Chrome, you might see a text box instead.&lt;br /&gt;
&lt;br /&gt;
If you move the slider to the right, you'll (in effect) do a gamma adjustment on the image, tending to make the image lighter. Move the slider to the &lt;i&gt;left&lt;/i&gt;, and you'll &lt;i&gt;darken &lt;/i&gt;the image. No actual image processing takes place until you lift your finger off the mouse (the slider just slides around until a mouseup occurs, &lt;i&gt;then &lt;/i&gt;the program logic kicks in). After the image repaints, you should see a gamma curve appear under the slider, as in the examples above.&lt;br /&gt;
&lt;br /&gt;
Here's the code for the Chrome extension:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size: 9pt;"&gt;// ==UserScript==
// @name           ImageBrightnessTool
// @namespace      ktBrightnessTool
// @description    Canvas Image Brightness Tool
// @include        *
// ==/UserScript==



// A demo script by Kas Thomas.
// Use as you will, at your own risk.


// The stuff under loadCode() will be injected
// into a &amp;lt;script&amp;gt; element in the page.

function loadCode() {

window.LUT = null;

// Ken Perlin's bias function
window.bias = function( a, b)  {
 return Math.pow(a, Math.log(b) / Math.log(0.5));
};

window.createLUT = function( biasValue ) {
 // create global lookup table for colors
 LUT = createBiasColorTable( biasValue );
};

window.createBiasColorTable = function( b ) {

 var table = new Array(256);
 for (var i = 0; i &amp;lt; 256; i++)
  table[i] = applyBias( i, b );
 return table;
};

window.applyBias = function( colorValue, b ) {
   
 var normalizedColorValue = colorValue/255;
 var biasedValue = bias( normalizedColorValue, b );
 return Math.round( biasedValue * 255 );
};

window.transformImage = function( x,y,w,h ) {
      
 var canvasData = offscreenContext.getImageData(x,y,w,h);
 var limit = w*h*4; 
  
 for (i = 0; i &amp;lt; limit; i++)  
  canvasData.data[i] = LUT[ canvasData.data[i] ]; 
  
 context.putImageData( canvasData,x,y );
};


// get an offscreen drawing context for the image
window.getOffscreenContext = function( w,h ) {
   
 var offscreenCanvas = document.createElement("canvas");
 offscreenCanvas.width = w;
 offscreenCanvas.height = h;
 return offscreenCanvas.getContext("2d");
};

window.getChartURL = function() {
 
 var url = "http://chart.apis.google.com/chart?";
 url += "chf=bg,lg,0,EFEFEF,0,BBBBBB,1&amp;amp;chs=100x100&amp;amp;";
 url += "cht=lc&amp;amp;chco=FF0000&amp;amp;&amp;amp;chds=0,255&amp;amp;chd=t:"
 url += LUT.join(",");
 url += "&amp;amp;chls=1&amp;amp;chm=B,EFEFEF,0,0,0";
 return url;
}

setupGlobals = function() {

 window.canvas = document.getElementById("myCanvas");
 window.context = canvas.getContext("2d");
 var imageData = context.getImageData(0,0,canvas.width,canvas.height);
 window.offscreenContext = getOffscreenContext( canvas.width,canvas.height );
 window.offscreenContext.putImageData( imageData,0,0 );
};

setupGlobals();  // actually call it

} // end loadCode()



/* * * * * * * * * main() * * * * * * * * */

(function main( ) {

 // are we really on an image URL?
 var ext = location.href.split(".").pop();
 if (ext.match(/jpg|jpeg|png/) == null )
    return;

 // ditch the original image
 img = document.getElementsByTagName("img")[0];
 img.parentNode.removeChild(img); 

 // put scripts into the page scope in 
 // a &amp;lt;script&amp;gt; elem with id = "myCode"
 // (we will eval() it in an event later...)
 var code = document.createElement("script");
 code.setAttribute("id","myCode");
 document.body.appendChild(code);  
 code.innerHTML += loadCode.toString() + "\n";

 // set up canvas
 canvas = document.createElement("canvas");
 canvas.setAttribute("id","myCanvas");
 document.body.appendChild( canvas );

 context = canvas.getContext("2d");

 image = new Image();

 image.onload = function() {

  canvas.width = image.width;
  canvas.height = image.height;
       context.drawImage(image,0, 0,canvas.width,canvas.height );            
 };

 // This line must come after, not before, onload!
 image.src = location.href;

 createSliderUI( );  // create the slider UI
 createGoogleChartUI( );  // create chart UI


 function createGoogleChartUI( ) {
  // set up iframe for Google Chart
  var container = document.createElement("div");
  var iframe = document.createElement("iframe");
  iframe.setAttribute("id","iframe");
  iframe.setAttribute("style","padding-left:14px");
  iframe.setAttribute("frameborder","0");
  iframe.setAttribute("border","0");
  iframe.setAttribute("width","101");
  iframe.setAttribute("height","101");
  container.appendChild(iframe); 
  document.body.appendChild(container);
 }
 

 // Create the HTML5 slider UI
 function createSliderUI( ) {

  var div = document.body.appendChild( document.createElement("div") );
       var slider = document.createElement("input");
  slider.setAttribute("type","range");
  slider.setAttribute("min","0");
  slider.setAttribute("max","100");
  slider.setAttribute("value","50");
  slider.setAttribute("step","1");

  // if code hasn't been loaded already, then load it now
  // (one time only!); update the slider range indicator;
  // create a color lookup table
  var actionCode = "if (typeof codeLoaded == 'undefined')" +
   "{ codeLoaded=1; " + 
   "code=document.getElementById(\"myCode\").innerHTML;" +
   "eval(code); loadCode(); }" +
   "document.getElementById(\"range\").innerHTML=" +
   "String(this.value*.01).substring(0,4);" +
   "createLUT( Number(document.getElementById('range').innerHTML) );" 
   
      
  slider.setAttribute("onchange",actionCode);


  // The following operation is too timeconsuming to attach to
  // the onchange event. We attach it to onmouseup instead.
  slider.setAttribute("onmouseup",
   "document.getElementById('iframe').src=getChartURL();"+
   "transformImage(0,0,canvas.width,canvas.height);");
  
  div.appendChild( slider );
  div.innerHTML += '&amp;lt;span id="range"&amp;gt;0.5&amp;lt;/span&amp;gt;';
  
 }

 
})();
&lt;/pre&gt;
&lt;br /&gt;
This code is a little less elegant in Chrome than it would have been in Firefox (which, unlike Chrome, supports &lt;a href="http://en.wikipedia.org/wiki/ECMAScript_for_XML" target="_blank"&gt;E4X&lt;/a&gt; and exposes a usable &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;unsafeWindow &lt;/span&gt;object). The code does, however, illustrate a number of useful techniques. To wit:&lt;br /&gt;
&lt;br /&gt;
1. How to swap out an &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;lt;img&amp;gt;&lt;/span&gt; for a &lt;i&gt;canvas &lt;/i&gt;image.&lt;br /&gt;
2. How to draw to an offscreen context.&lt;br /&gt;
3. How to inject script code into page scope, from extension (gmonkey) scope.&lt;br /&gt;
4. How to use the HTML5 slider input element.&lt;br /&gt;
5. How to change the gamma (or, colloquially and somewhat incorrectly, "brightness") of an image's pixels via a color lookup table.&lt;br /&gt;
6. How to use &lt;a href="http://cs.nyu.edu/%7Eperlin/" target="_blank"&gt;Ken Perlin&lt;/a&gt;'s&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt; bias( ) &lt;/span&gt;function to remap pixel values in the range &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;0..255&lt;/span&gt;.&lt;br /&gt;
7. How to display the resulting gamma curve (actually, bias curve) in a Google Chart in real time.&lt;br /&gt;
&lt;br /&gt;
That's a fair amount of stuff, actually. Discussing it could take a long time. The code's not long, though, so you should be able to grok most of it from a quick read-through.&lt;br /&gt;
&lt;br /&gt;
The most important concept here, from an image processing standpoint, is the notion of remapping pixel values using a pre-calculated lookup table. The naive (and very slow) approach would simply be to parse pixels and do a separate &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;bias()&lt;/span&gt; call on each red, green, or blue value in the image. But that would mean calling &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;bias() &lt;/span&gt;hundreds of thousands of times (maybe millions of times, in a sizable image). Instead, we create a table (an array of size 256) and remap the values there once, then&lt;i&gt; look up&lt;/i&gt; the appropriate substitution value for each color in each pixel, rather than laboriously calling &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;bias()&lt;/span&gt; on each color in each pixel.&lt;br /&gt;
&lt;br /&gt;
If this is the first time you've encountered Ken Perlin's &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;bias() &lt;/span&gt;function, it's actually a very important class of function to understand. Fundamentally, it remaps the unit interval (that is, real numbers in the range 0..1) to itself. With a bias value of 0.5, all real numbers from 0..1 map to their original values. With a bias value less than 0.5, the remapping is swayed in the manner shown in the screenshot above, on the right. A bias value greater than 0.5 bends the curve in exactly the opposite direction. But in any case, 0 always ends up mapping to zero and 1 always maps to one, no matter what the bias knob is set to. The function is, in that sense, nicely normalized. &lt;br /&gt;
&lt;br /&gt;
Bias is technically quite a bit different from a true "gamma" adjustment. Gamma curves come from a different formula and they don't have the desirable property of mapping onto the unit interval or behaving intuitively with respect to the 0.5 midpoint. Nevertheless, because "gamma" is more familiar to graphic artists, I've (ab)used that word throughout this post, and even in the headline. (Shame on me.)&lt;br /&gt;
&lt;br /&gt;
The performance of the bias code is surprisingly poor in this particular usage (as a Chrome extension). On my Dell laptop, I see processing at a rate of just under 50,000 pixels per second. The same bias-lookup code running in a normal web page (not a Chrome extension that injects it into page scope) goes about ten times faster. Yes, an order of magnitude faster. In a native web page, I can link the image transformation call to an &lt;i&gt;onchange &lt;/i&gt;handler (so that the image -- even a large one -- updates continuously, in real time, as you drag the slider) -- that's how fast the code is in some of my other projects. But in this particular context (as a Chrome extension) it seems to be dreadfully slow, so I've hooked the main processing routine to an &lt;i&gt;onmouseup &lt;/i&gt;handler on the slider. Otherwise the slider sticks.&lt;br /&gt;
&lt;br /&gt;
Anyway, I hope the techniques in this post have whetted your appetite for more HTML5 canvas explorations. There are some &lt;a href="http://www.canvasdemos.com/top-100/" target="_blank"&gt;great canvas demos&lt;/a&gt; out there, and I'll be delving into some more canvas scripting techniques in the not-so-distant future. &lt;br /&gt;
&lt;br /&gt;
Happy pixel-poking!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-1267556627805929357?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1267556627805929357?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1267556627805929357?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/gamma-adjustment-in-html5-canvas.html" title="Gamma Adjustment in an HTML5 Canvas" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-fRVmsOSZ_1Y/TvYYVsB2dlI/AAAAAAAAAw4/7Z5QdBXJGt0/s72-c/penguins.png" height="72" width="72" /></entry><entry gd:etag="W/&quot;DkIFRXg_fSp7ImA9WhRXEEk.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-2563142933902949318</id><published>2011-12-16T08:59:00.004-05:00</published><updated>2011-12-16T09:08:34.645-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-16T09:08:34.645-05:00</app:edited><title>How to tell if a corporation is evil</title><content type="html">I made up this checklist as a quick way to gauge whether a company is (or might be considered) evil. It's not a definitive list, by any means, and there is no right or wrong way to score a company against this list. In the end, &lt;span style="font-style: italic;"&gt;you &lt;/span&gt;have to decide whether you think a given company is evil or not. This list is meant only as a guide, a starting point for discussion.&lt;br /&gt;&lt;br /&gt;1. Does your company value profits over people? This is easy to check. When earnings are threatened, does the company simply lay off workers? Most companies follow this kneejerk policy, since "people costs" (salaries and benefits) constitute the single biggest cost for  most companies.&lt;br /&gt;&lt;br /&gt;2. Does your company engage in &lt;a href="http://en.wikipedia.org/wiki/Anti-competitive"&gt;deceptive, monopolistic, or anti-competitive business practices&lt;/a&gt;?&lt;br /&gt;&lt;br /&gt;3. Does your company routinely &lt;a href="http://en.wikipedia.org/wiki/False_advertising"&gt;lie to customers in its advertising&lt;/a&gt;?&lt;br /&gt;&lt;br /&gt;4. Does your company engage in unfair or deceptive HR practices, such as billing a 50-hr/week job as being 40 hours per week, or hiring two part-time workers (without benefits) in lieu of one full-time worker with benefits, or failing to promote women or minorities? (E.g., are executives mostly men?)&lt;br /&gt;&lt;br /&gt;5. Does your company pay zero taxes? Many large companies (e.g., General Electric) manage to escape paying taxes altogether even in profitable years (&lt;a href="http://www.reuters.com/article/2011/11/03/us-usa-tax-corporate-idUSTRE7A261C20111103"&gt;http://www.reuters.com/article/2011/11/03/us-usa-tax-corporate-idUSTRE7A261C20111103&lt;/a&gt;). Whether this is done legally or illegally doesn't matter: If (as some say) &lt;a href="http://en.wikipedia.org/wiki/Corporate_personhood"&gt;corporations are "persons,"&lt;/a&gt; not paying your fair share in taxes makes you a bad "person" regardless of what the law says.&lt;br /&gt;&lt;br /&gt;6. Does your company accept &lt;a href="http://en.wikipedia.org/wiki/Corporate_welfare"&gt;corporate welfare&lt;/a&gt; (subsidies or bailout money from government)? According to the Cato Institute, the U.S. federal government spent $92 billion on corporate welfare during fiscal year 2006 (a year in which there was no recession). Recipients that year included Boeing, Xerox, IBM, Motorola, Dow Chemical, and General Electric.&lt;br /&gt;&lt;br /&gt;7. Does your company spend money on lobbying? Check your company's lobbying track record at &lt;a href="http://www.opensecrets.org/"&gt;http://www.opensecrets.org/&lt;/a&gt;. Lobbying, by definition, is an attempt to exert influence on lawmakers by circumvention of normal democractic processes. Which is inherently unethical.&lt;br /&gt;&lt;br /&gt;8. Does your company endorse political candidates or contribute to their campaigns? It should be obvious that corporations have no legitimate role in politics. They should not tell workers how to vote and shouldn't spend shareholder money on politicians' (re))election campaigns.&lt;br /&gt;&lt;br /&gt;9. Does your company pollute the environment? (What is your company's green agenda? Does it even have one?) Does your company have overseas subsidiaries or contractors who pollute the environment?&lt;br /&gt;&lt;br /&gt;10. Does your company routinely outsource jobs to countries where labor is cheap and labor laws are lax? Suppose your company hires workers from North America, Europe, and Asia during good times but tends to lay off workers in North America or Europe preferentially during bad times (allowing Asians to remain on the payroll). This is the same as exporting jobs to Asia. It's a very common tactic, and it escapes notice because in good times, the company does not appear to be favoring any one geo.&lt;br /&gt;&lt;br /&gt;11. Does your company use layoffs as a way to tailor HR ratios? This is a very common tactic. For example, in technology, companies often go out of their way to try to obtain more female employees since women traditionally have not been drawn to technology, and the ratio of male to female workers in tech is (consequently) high. Big companies like to beef up their female-to-male percentages as much as possible to avoid lawsuits (so that when a woman is fired, she can't claim sexual discrimination). When layoffs occur, many companies will preferentially lay off male workers to "balance the ratios." I have personally seen this practice in action, at one large company, where layoffs were viewed as an opportunity to balance all kinds of HR ratios. I believe it is one reason (but certainly not the only reason) why the most recent recession was particularly hard on men (see for example &lt;a href="http://www.spiegel.de/international/germany/0,1518,622373,00.html"&gt;http://www.spiegel.de/international/germany/0,1518,622373,00.html&lt;/a&gt;, and &lt;a href="http://mjperry.blogspot.com/2008/12/2008-male-recession-gender-jobs-gap.html"&gt;http://mjperry.blogspot.com/2008/12/2008-male-recession-gender-jobs-gap.html&lt;/a&gt;, and &lt;a href="http://healthland.time.com/2011/03/01/why-the-recession-may-trigger-more-depression-among-men/"&gt;http://healthland.time.com/2011/03/01/why-the-recession-may-trigger-more-depression-among-men/&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;12. Do the company's top people earn more than 20  times the median of all workers? The Economic Policy Institute and the Institute for Policy Studies have both studied CEO pay ratios across a variety of industries in a variety of countries. In most of the industrialized world, the CEO pay ratio is 20 or less. In the U.S., it tends to be well over 100:1 (and in some years it has been 250:1 or higher; see &lt;a href="http://articles.moneycentral.msn.com/Investing/Extra/CEOsNearRecordPayRatios.aspx"&gt;http://articles.moneycentral.msn.com/Investing/Extra/CEOsNearRecordPayRatios.aspx&lt;/a&gt; and &lt;a href="http://www.aflcio.org/corporatewatch/paywatch/"&gt;http://www.aflcio.org/corporatewatch/paywatch/&lt;/a&gt;). There is no reason for any one human being in any one company to make 100 times what another human being makes, and in fact most companies could find talented volunteers to fill CxO positions for far less money than 20:1. Certainly anything in excess of 20:1 is just that -- needless excess. Steve Jobs paid himself a dollar a year at Apple. (He did own a lot of stock, of course.) More CEOs should follow his example.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-2563142933902949318?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/2563142933902949318?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/2563142933902949318?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/how-to-tell-if-corporation-is-evil.html" title="How to tell if a corporation is evil" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;DkcGSHkyfyp7ImA9WhRQGUU.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-8493861932960997047</id><published>2011-12-15T15:46:00.004-05:00</published><updated>2011-12-15T16:20:29.797-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-15T16:20:29.797-05:00</app:edited><title>Firefox adoption just keeps going down</title><content type="html">&lt;img src="http://chart.apis.google.com/chart?chs=440x220&amp;amp;cht=lxy&amp;amp;chco=3072F3,FF0000,FF9900,008000&amp;amp;chds=0,100,0,100,0,100,-5,100,10,100,-20,100,5,100,-10,100&amp;amp;chd=t:-1%7C68,65,62,55,52,48,45,42,43,43,37,38,32%7C-1%7C16,8,10,12,12,9,16,15,20,21,21,16,13%7C-1%7C8,9,9,11,18,26,25,27,25,27,30,34,38%7C-1%7C4,11,11,14,12,11,9,9,9,6,8,7,7&amp;amp;chdl=FF%7CIE%7CCr%7CSafari&amp;amp;chdlp=b&amp;amp;chls=2,4,1%7C1%7C1%7C1&amp;amp;chma=5,5,5,25" alt="" height="220" width="440" /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: arial; font-style: italic;"&gt;Browser usage data from Q4 2008 to Q4 2011, for visitors of this blog.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I was somewhat surprised (as many people were) to learn, earlier this month, that in terms of market share, Google's Chrome browser has &lt;a href="https://www.technologyreview.com/blog/mimssbits/27381/"&gt;recently surpassed Firefox&lt;/a&gt; in overall adoption. I shouldn't have been surprised at all: If I had taken the time to look carefully at my own blog's analytics, I would've seen this very result &lt;span style="font-style: italic;"&gt;nine months ago&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Readers of my blog tend to be developers, techies, and early adopters, and so Internet Explorer usage has never been high for people who visit this blog, whereas Firefox usage has always been high (68% in Q4 of 2008, for example). Trends like Chrome overtaking Firefox tend to show up early in my analytics; my readers are trendsetters. I completely didn't see the Firefox death spiral coming, however.&lt;br /&gt;&lt;br /&gt;I decided, finally, to sit down and sift through my analytics to find out exactly how browser usage has varied over time for visitors to this blog. In the graph above, I've plotted browser statistics quarter-by-quarter for the 13 quarters going back to Q4 2008 (the earliest date for which analytics were available). Note that data for the most recent quarter run only to mid-December (obviously). For the total data set going back to Q4 2008, we're talking slightly more than half a million total visits.&lt;br /&gt;&lt;br /&gt;The vertical scale tops out at 100 percent. Firefox (in blue) starts at 68 percent and ends, most recently, at 32 percent. Chrome starts at 8 percent and finishes at an impressive 38 percent. The curves crossed, for visitors to this blog, around nine months ago (in early 2011).&lt;br /&gt;&lt;br /&gt;What can I say? I don't think these sorts of trends bode well for Firefox. In fact I think the future is looking pretty bad for Firefox (for a variety of reasons), and there's probably not time to reinvent the product at this point.&lt;br /&gt;&lt;br /&gt;In the meantime, if you work for a software (or other) company that currently supports Firefox &lt;span style="font-style: italic;"&gt;but not Chrome&lt;/span&gt;, you've got your priorities backwards! Get to work supporting Chrome, ASAP, lest the market leave you behind.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-8493861932960997047?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8493861932960997047?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8493861932960997047?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/firefox-adoption-just-keeps-going-down.html" title="Firefox adoption just keeps going down" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;CEIFRHc8fip7ImA9WhRQFkw.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-5799149897705383115</id><published>2011-12-11T08:18:00.004-05:00</published><updated>2011-12-11T09:08:35.976-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-11T09:08:35.976-05:00</app:edited><title>6 Tips for Beginning Canvas Programmers</title><content type="html">Lately I've spent some time programming against the &lt;a href="https://developer.mozilla.org/en/HTML/Canvas"&gt;&amp;lt;canvas&amp;gt;&lt;/a&gt; API. Predictably, I encountered all the common beginner's mistakes, and had to work through them. Along the way, I learned a number of useful things about canvas programming, some basic, some not-so-basic. Here's a quick summary:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;1. To avoid security errors, always serve your HTML (and scripts) from the same server as any images you're going to be working with.&lt;/span&gt; (Corollary: Don't "serve" your HTML and images from the local filesystem. That's a sure way to get security errors.) Install a local instance of Apache web server (or some other web server) and serve content to your browser from &lt;span style="font-style: italic;"&gt;localhost&lt;/span&gt;, if need be.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;2. If you're modifying pixels using context.getImageData( ), use putImageData( ) to draw back to the image, and be sure to supply all 3 arguments to putImageData( )!&lt;/span&gt; Here is a common pattern:&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;pre&gt;function doSomething() {&lt;br /&gt;&lt;br /&gt;var canvasData =&lt;br /&gt; context.getImageData(0, 0, imageObj.width, imageObj.height);&lt;br /&gt;&lt;br /&gt; for (var x = 0; x &amp;lt; w; x++) {&lt;br /&gt;   for (var y = 0; y &amp;lt; h; y++) {&lt;br /&gt;       var idx = (x + y * w) * 4;&lt;br /&gt;       var r = canvasData.data[idx + 0];&lt;br /&gt;       var g = canvasData.data[idx + 1];&lt;br /&gt;       var b = canvasData.data[idx + 2];&lt;br /&gt;&lt;br /&gt;      // do something to r,g,b here&lt;br /&gt;&lt;br /&gt;       canvasData.data[idx + 0] = r;&lt;br /&gt;       canvasData.data[idx + 1] = g;&lt;br /&gt;       canvasData.data[idx + 2] = b;&lt;br /&gt;   }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;// draw it back out to the screen:&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;context.putImageData(canvasData, 0, 0);&lt;/span&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/span&gt;&lt;br /&gt;Notice the three arguments to &lt;span style="font-family: courier new;"&gt;putImageData()&lt;/span&gt;. The final two args are the&lt;span style="font-style: italic;"&gt; x&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;y&lt;/span&gt; position at which to draw the image. If you forget those two args, expect errors.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;3. You can draw offscreen&lt;/span&gt; by simply creating a canvas element programmatically. Like this:&lt;br /&gt;&lt;pre&gt;    imageObj = new Image();&lt;br /&gt;   imageObj.src = "http://localhost:4502/content/lena.png";&lt;br /&gt;      &lt;br /&gt;   function getOffscreenContext(imageObj) {&lt;br /&gt;      var offscreenCanvas = document.createElement("canvas");&lt;br /&gt;      offscreenCanvas.width = imageObj.width;&lt;br /&gt;      offscreenCanvas.height = imageObj.height;&lt;br /&gt;      return offscreenCanvas.getContext("2d");&lt;br /&gt;   }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you use this function (or one like it), you can keep an offscreen copy of your image around, which can be extremely handy.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;4. You can save programmatically created/modified images offline.&lt;/span&gt; The trick is to slurp the canvas into a data URL and then open or display that URL in a new frame or window where you can right-click it to get the usual image-save options from the browser. Something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;myImage = canvas.toDataURL("image/png"); &lt;br /&gt;window.open( myImage );  // opens in new window as a PNG&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This serializes the image as a (big, huge) data URL, then opens the image in a new window. The new window contains a PNG image, plain and simple.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;5. Any time you assign a value to canvas.width or canvas.height, you will wipe the canvas clean!&lt;/span&gt; This is both weird and handy. Just doing &lt;span style="font-family: courier new;"&gt;canvas.width = canvas.width&lt;/span&gt; will instantly erase the canvas.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;6. When all else fails, consult the &lt;a href="http://simon.html5.org/dump/html5-canvas-cheat-sheet.html"&gt;HTML 5 Canvas Cheatsheet&lt;/a&gt;. &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-5799149897705383115?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/5799149897705383115?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/5799149897705383115?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/6-tips-for-beginning-canvas-programmers.html" title="6 Tips for Beginning Canvas Programmers" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;Dk8GSXkyfCp7ImA9WhRQEk8.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-3636459332914598818</id><published>2011-12-06T20:25:00.004-05:00</published><updated>2011-12-06T21:27:08.794-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-06T21:27:08.794-05:00</app:edited><title>How Google is quietly killing Firefox</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-0zWSJA7hzo4/Tt7OZfrVEmI/AAAAAAAAAwY/K5LrKhKRXbE/s1600/Mozilla_Crash_Reporter.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 362px; height: 400px;" src="http://3.bp.blogspot.com/-0zWSJA7hzo4/Tt7OZfrVEmI/AAAAAAAAAwY/K5LrKhKRXbE/s400/Mozilla_Crash_Reporter.png" alt="" id="BLOGGER_PHOTO_ID_5683206717183431266" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;After a certain period of time spent programming, you develop a kind of sixth sense about what programs are doing. Surprisingly often, this sixth sense turns out to be right. I don't know if what I'm about to say is right. I do know that my sixth sense is telling me something.&lt;br /&gt;&lt;br /&gt;I've been a Firefox user for years, and I still like Firefox, the way I still like my 1998 Jeep Grand Cherokee (with the cast-iron six-cylinder engine) even though it's not the latest-and-greatest model. Lately, though, Firefox has been freezing and/or quitting unexpectedly with greater and greater frequency, even though my browsing habits haven't changed.&lt;br /&gt;&lt;br /&gt;What's changed over the past couple of years? The Web. Web content has become more and more dynamic, more AJAX-driven, more JavaScript-intensive.&lt;br /&gt;&lt;br /&gt;JavaScript is a great language, but like a lot of languages these days it relies on programmers being careful about how they manage runtime objects. It's &lt;a href="http://www.ibm.com/developerworks/web/library/wa-memleak/"&gt;surprisingly easy&lt;/a&gt;, if you manipulate the DOM a lot, to generate code that leaks memory. See this &lt;a href="http://blog.j15r.com/2005/01/dhtml-leaks-like-sieve.html"&gt;nice writeup&lt;/a&gt; for more info (also see &lt;a href="https://developer.mozilla.org/en/Debugging_memory_leaks"&gt;https://developer.mozilla.org/en/Debugging_memory_leaks&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;My contention is that most AJAX code leaks memory like a sieve, and &lt;span style="font-style: italic;"&gt;this &lt;/span&gt;is why more and more users are seeing their browsers (not just Firefox, but IE, Safari, and Chrome as well) freeze up and die in normal operation these days. The various browsers differ in how they manage memory and how they do garbage collection. Chrome, in particular, has undergone &lt;a href="http://news.techworld.com/applications/3320205/google-updates-chrome-with-improved-garbage-collection/"&gt;significant changes recently&lt;/a&gt; in how it handles garbage collection. This is no accident. It's in response to the greater challenges imposed on &lt;span style="font-style: italic;"&gt;all&lt;/span&gt; browsers today by dynamic web pages.&lt;br /&gt;&lt;br /&gt;When I leave AJAX-intensive web pages open all day in Firefox, I eventually find that Firefox is using 1.3 gigabytes or more of RAM. Usually, by the time it reaches 1.4 gigabytes, the browser freezes (goes white) and then either dies outright, or unfreezes again after 30 or 40 seconds. If it manages to unfreeze, usually about 60 megabytes of RAM have been freed up (according to Task Manager). But then memory usage marches upwards again and it freezes (goes white) again, within seconds. The freeze/unfreeze cycle continues until Firefox unceremoniously crashes.&lt;br /&gt;&lt;br /&gt;My programmer's sixth sense tells me that when memory usage exceeds a certain level, Firefox lacks sufficient headroom to carry out a proper garbage collection cycle. Partway through the cycle, it runs out of memory and initiates another GC cycle. This repeats until the program is in what one might call a GC panic. Uncommanded program termination is the inevitable result.&lt;br /&gt;&lt;br /&gt;Firefox is often roundly criticized for its tendency to "leak memory," but I would caution that it is not really the core program that is leaking memory. It's really the AJAX code running in JavaScript-intensive pages that's causing the memory leakage.&lt;br /&gt;&lt;br /&gt;So ironically, Firefox's reputation is suffering not because of anything Mozilla's programmers are doing, but because of web developers who are using jQuery and tons of other popular libraries indiscriminately, without regard to memory leakage. (Be sure to have a look at &lt;a href="http://blog.linkibol.com/2010/05/07/did-you-know-that-jquery-leaks-memory-like-a-fountain/"&gt;this blog post&lt;/a&gt; about jQuery's role in memory leakage.)&lt;br /&gt;&lt;br /&gt;I haven't done careful testing, but I can tell you (from daily experience) that if I leave Gmail open in Firefox all night, Firefox will run out of memory by morning. If I leave Facebook and Twitter open as well, I can count on running out of memory in just a few hours.&lt;br /&gt;&lt;br /&gt;Now here's where it starts to get &lt;span style="font-style: italic;"&gt;really &lt;/span&gt;troubling.&lt;br /&gt;&lt;br /&gt;Mozilla's greatest revenue source today (accounting for &lt;a href="http://news.yahoo.com/mozilla-survive-without-google-010651745.html"&gt;more than 80 percent of annual income&lt;/a&gt;) is Google. &lt;span style="font-style: italic;"&gt;Mozilla is deeply dependent on Google for operating revenue.&lt;/span&gt; And yet, it is in direct competition with Google for browser market-share. Recently, the Firefox and Chrome &lt;a href="http://www.technologyreview.com/blog/mimssbits/27381/"&gt;adoption curves crossed&lt;/a&gt;, with Firefox now lagging behind Chrome for the first time.&lt;br /&gt;&lt;br /&gt;There's a huge conflict of interest here. If you buy the theory that most people who abandon Firefox do so because it crashes (runs out of memory) unpredictably, it stands to reason that all Google has to do to pick up market share in the browser world is publish AJAX-intensive web pages (Google Search, Gmail, Google Maps, etc.) of a kind that Firefox's garbage-collection algorithms choke on — and in the meantime, improve Chrome's own GC algorithms to better handle just those sorts of pages.&lt;br /&gt;&lt;br /&gt;That's exactly what seems to be going on. Or at least that's what my gut says.&lt;br /&gt;&lt;br /&gt;And my gut is sometimes right.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-3636459332914598818?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/3636459332914598818?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/3636459332914598818?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/12/how-google-is-quietly-killing-firefox.html" title="How Google is quietly killing Firefox" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-0zWSJA7hzo4/Tt7OZfrVEmI/AAAAAAAAAwY/K5LrKhKRXbE/s72-c/Mozilla_Crash_Reporter.png" height="72" width="72" /></entry><entry gd:etag="W/&quot;DUUCSXY8cCp7ImA9WhRTFU4.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-9159224266659327262</id><published>2011-11-05T19:42:00.004-04:00</published><updated>2011-11-05T19:54:28.878-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-05T19:54:28.878-04:00</app:edited><title>This Is What Democracy Looks Like</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-Co_HPrXlQBc/TrXMDzJRmrI/AAAAAAAAAwI/BONOgemwTiE/s1600/crowd2.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 239px;" src="http://4.bp.blogspot.com/-Co_HPrXlQBc/TrXMDzJRmrI/AAAAAAAAAwI/BONOgemwTiE/s320/crowd2.jpg" alt="" id="BLOGGER_PHOTO_ID_5671663671383792306" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Yesterday, I submitted the following op-ed piece to my local newspaper. I don't know yet if it will be published there. But it is published here. ;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    When my 17-year-old son visited me in Jacksonville last month over Columbus Day weekend, I had several "activities" lined up for him on his visit. The first and most important was to accompany me to the inaugural General Assembly of the first Occupy Jacksonville meetup in Hemming Plaza.&lt;br /&gt;&lt;br /&gt;    I was (and am) proud to have had my son by my side that day. I wanted him to see democracy in action. And that's exactly what we experienced, together, along with upwards of 300 other concerned Jacksonville residents.&lt;br /&gt;&lt;br /&gt;    Some might suggest that if I wanted my son to see democracy in action, I should perhaps take him to Washington, DC. But unfortunately, "democracy in action" is not the order of the day any more in Washington (to the extent it ever was). Mostly what happens in Washington these days are closed-door meetings between government officials and their corporate sponsors, the big-dollar lobbyists.&lt;br /&gt;&lt;br /&gt;    To call the Occupy Movement "democracy in action" might seem premature, or even delusional, depending on your political bent. After all, protesters in a public park don't sign laws into existence. Protesters balance no budgets, create no legislation, add no earmarks.&lt;br /&gt;&lt;br /&gt;They do something far more important, though. They change the nature of political discourse in this country, one conversation at a time.&lt;br /&gt;&lt;br /&gt;    Naysayers are fond of criticizing the Occupy Movement as having no demands and (more to the point) being utterly powerless to bring about concrete change in Washington. And yet, on November 1, six U.S. Senators (Tom Udall of New Mexico, Michael Bennett of Colorado, Tom Harkin of Indiana, Dick Durbin of Illinois, Chuck Schumer of New York, Sheldon Whitehouse of Rhode Island, and Jeff Merkely of Oregon) introduced a Constitutional amendment that would effectively overturn the Supreme Court's &lt;a href="http://en.wikipedia.org/wiki/Citizens_United_v._Federal_Election_Commission"&gt;&lt;span style="font-style: italic;"&gt;Citizens United v. Federal Election Commission&lt;/span&gt;&lt;/a&gt; decision, thereby restoring the ability of Congress (and the states) to properly regulate the campaign finance system.&lt;br /&gt;&lt;br /&gt;    Getting the Citizens United decision nullified has been one of the most avidly advocated reforms of the Occupy Movement, not just in New York but in all venues where the movement exists. Placards and signs denouncing the 2010 Supreme Court decision (a decision that effectively opened the door to unlimited corporate and special-interest spending in elections, treating corporations as ordinary citizens) can be found at any Occupy rally, including the rallies that have been occurring in Jacksonville since October 8.&lt;br /&gt;&lt;br /&gt;    Following the Supreme Court's January 21, 2010 decision, there was a national outcry. President Barack Obama himself said that the decision "gives the special interests and their lobbyists even more power in Washington — while undermining the influence of average Americans who make small contributions to support their preferred candidates." Obama would eventually say in his weekly radio address that "this ruling strikes at our democracy itself" and "I can't think of anything more devastating to the public interest."&lt;br /&gt;&lt;br /&gt;    The controversy over the ruling quickly died down, however, and from January 2010 to September 2011, there was no further action on the matter.&lt;br /&gt;&lt;br /&gt;    But then a curious thing happened.&lt;br /&gt;&lt;br /&gt;    Following on the heels of the Arab Spring uprisings, a protest called Occupy Wall Street began in lower Manhattan. With startling alacrity, what began as a modest public gathering in an obscure park in New York spread to become a nationwide phenomenon involving (by some counts) as many as a thousand cities, here and abroad.&lt;br /&gt;&lt;br /&gt;    Much uncertainty lies ahead. What can be said with certainty, however, is that the Occupy movement, for all its supposed disorganization and lack of goals, has (in a few short weeks) fundamentally changed the nature of political debate in the United States. What was previously unthinkable (e.g., a Constitutional amendment overthrowing the Supreme Court's Citizens United decision) is now not only thinkable, but front-and-center in Congress.&lt;br /&gt;&lt;br /&gt;    I'm proud to have brought my son to Occupy Jacksonville. I'm proud of what the Occupy movement has done, and will do. As we're fond of chanting in our marches: &lt;span style="font-style: italic;"&gt;this &lt;/span&gt;is what democracy looks like.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-9159224266659327262?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/9159224266659327262?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/9159224266659327262?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/11/this-is-what-democracy-looks-like.html" title="This Is What Democracy Looks Like" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-Co_HPrXlQBc/TrXMDzJRmrI/AAAAAAAAAwI/BONOgemwTiE/s72-c/crowd2.jpg" height="72" width="72" /></entry><entry gd:etag="W/&quot;AkcNQ3s5fCp7ImA9WhdbFU0.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-7867806289138922606</id><published>2011-10-13T08:04:00.007-04:00</published><updated>2011-10-13T08:14:52.524-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-13T08:14:52.524-04:00</app:edited><title>What does it take to get a job at Google?</title><content type="html">&lt;p&gt;It takes patience, that's for sure. The process, from start to finish, can take six weeks or more. And although the infographic doesn't specifically say so, the candidate can be blackballed by one "no" vote along the way. Hiring decisions have to be unanimous. (Click on graphic for larger version.)&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.jobvine.co.za/what-does-it-take-to-get-a-job-at-google/"&gt;&lt;br /&gt;&lt;img src="http://www.jobvine.co.za/content/images/jobvine-infographic.png" alt="What does it take to get a job at Google" width="500" height="2023" /&gt;&lt;br /&gt;&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Infographic by &lt;a href="http://www.jobvine.co.za/"&gt;Jobvine Recruitment Network&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-7867806289138922606?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7867806289138922606?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7867806289138922606?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/10/what-does-it-take-to-get-job-at-google.html" title="What does it take to get a job at Google?" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;Dk8DQns6fSp7ImA9WhdUF0s.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-7557382278221555367</id><published>2011-10-04T17:37:00.002-04:00</published><updated>2011-10-04T17:47:53.515-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-04T17:47:53.515-04:00</app:edited><title>Occupation spreading</title><content type="html">&lt;img src="http://www.nfglm.com/images/Occupy-the-Nation.png" /&gt;&lt;br /&gt;For CNN's well-paid talking heads (who represent the 1% wealthiest Americans) it seems incomprehensible that a political movement with no clear agenda and no leadership hierarchy is spreading  across the U.S. like wildfire. But it's true. The above map (current as of early October 2011) shows some of the planned locations for Occupation events.&lt;br /&gt;&lt;br /&gt;Meanwhile, reporters like CNN's cutesy-faced Erin Burnett (who worked for several years on Wall St. and has a &lt;a href="http://t.co/yA8lSMNf"&gt;personal net worth of $12 million&lt;/a&gt;) are aghast at the impromptu street protests, wondering empty-headedly "what their agenda is" and snickering at the youthful innocence of the participants, going so far as to ask one of them (on her Oct. 3rd show) "Do you know that the bank bailouts benefitted the American taxpayer?"&lt;br /&gt;&lt;br /&gt;Burnett is obviously not anxious to burn bridges with her Wall Street contacts by acting like a real journalist. Because that would require that she do actual research, ask probing questions, and get at the truth, which is that the people protesting in the streets represent the 99% of Americans who have been defrauded by Wall Street and by their government representatives (who answer to big business). The Erin Burnetts of the world are in the lucky 1%. She doesn't have to care about the 99% who are struggling. She has her cake, and eats it too, while 15% of her viewership lives on food stamps.&lt;br /&gt;&lt;br /&gt;&lt;span style="display: block;" id="formatbar_Buttons"&gt;&lt;span class="" style="display: block;" id="formatbar_CreateLink" title="Link" onmouseover="ButtonHoverOn(this);" onmouseout="ButtonHoverOff(this);" onmouseup="" onmousedown="CheckFormatting(event);FormatbarButton('richeditorframe', this, 8);ButtonMouseDown(this);"&gt;&lt;img src="img/blank.gif" alt="Link" class="gl_link" border="0" /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-7557382278221555367?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7557382278221555367?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7557382278221555367?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/10/occupation-spreading.html" title="Occupation spreading" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;D0MDQX48fCp7ImA9WhdWF08.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-6114149846035837725</id><published>2011-09-11T03:13:00.001-04:00</published><updated>2011-09-11T03:17:50.074-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-11T03:17:50.074-04:00</app:edited><title>Remembering the Fallen</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-9MrdImP7ThA/TmxUc5V42TI/AAAAAAAAAtY/p8pH4w1PVA0/s1600/jumpers_holdinghands.jpg"&gt;&lt;img style="text-align:left;cursor:pointer; cursor:hand;width: 210px; height: 320px;" src="http://2.bp.blogspot.com/-9MrdImP7ThA/TmxUc5V42TI/AAAAAAAAAtY/p8pH4w1PVA0/s320/jumpers_holdinghands.jpg" alt="" id="BLOGGER_PHOTO_ID_5650984487848827186" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://t3.gstatic.com/images?q=tbn:ANd9GcTrLP0OV3B68ArX4KU0CAt7dym0zYUa4z9W9124RYS-k8ki24MOxA"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;It's time to remember the roughly 200 brave individuals (many of them still nameless) who chose to plunge to their deaths on 9/11 rather than face immolation or massive smoke inhalation (or the imminent collapse of the towers themselves). According to a story in the &lt;a href="http://www.dailymail.co.uk/news/article-2035720/9-11-anniversary-America-wants-forget-victims-jumped-Twin-Towers.html?ITO=socialnet-twitter-mailonline"&gt;Daily Mail&lt;/a&gt;, "sometimes the fallers were separated by an interval of just a second. At one point nine people fell in six seconds from five adjacent windows; at another, 13 people fell in two minutes." Twenty minutes after the first building was struck, two people fell simultaneously from the same window on the 95th floor.  Some individuals (above) chose to hold hands with a partner on the way down.&lt;br /&gt;&lt;br /&gt;Most jumpers were in free-fall for about 10 seconds before hitting the ground at between 120 and 200 miles per hour.&lt;br /&gt;&lt;span style="font-size: 1.2em;font-size:78%;" &gt; &lt;/span&gt;&lt;div style="overflow: hidden; color: rgb(0, 0, 0); background-color: transparent; text-align: left; text-decoration: none; border: medium none;"&gt;&lt;br /&gt;Read more: &lt;a style="color: #003399;" href="http://www.dailymail.co.uk/news/article-2035720/9-11-anniversary-America-wants-forget-victims-jumped-Twin-Towers.html#ixzz1XckWlUCT"&gt;http://www.dailymail.co.uk/news/article-2035720/9-11-anniversary-America-wants-forget-victims-jumped-Twin-Towers.html#ixzz1XckWlUCT&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-6114149846035837725?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6114149846035837725?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6114149846035837725?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/09/remembering-fallen.html" title="Remembering the Fallen" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-9MrdImP7ThA/TmxUc5V42TI/AAAAAAAAAtY/p8pH4w1PVA0/s72-c/jumpers_holdinghands.jpg" height="72" width="72" /></entry><entry gd:etag="W/&quot;CE8HQnYzfyp7ImA9WhZbFko.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-7163535897606381709</id><published>2011-06-21T11:23:00.003-04:00</published><updated>2011-06-21T12:13:53.887-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-21T12:13:53.887-04:00</app:edited><title>Oracle endorses Adobe strategy with FatWire purchase</title><content type="html">The Content Management ecoverse is &lt;a href="http://twitter.com/#%21/search/fatwire"&gt;abuzz&lt;/a&gt; today with the news of Oracle's &lt;a href="http://www.oracle.com/us/corporate/Acquisitions/fatwire/customer-letter-418756.html"&gt;decision to acquire&lt;/a&gt; Web Experience Management vendor &lt;a href="http://fatwire.com/"&gt;FatWire&lt;/a&gt; (for an undisclosed sum). While I suspect Oracle didn't have to spend much more than $200 million to acquire the Mineola, NY-based FatWire, it would not surprise me if cash-flush Larry &amp;amp; Co. dumped as much as $400 million (in stock and cash) into the acquisition. Recall that back in 2006 (when the U.S. dollar was actually worth something) Oracle cheerfully coughed up &lt;a href="http://www.cmswire.com/cms/ma/ecm-mergers-continue-oracle-grabs-stellent-000862.php"&gt;$440 million for Stellent&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;What does the FatWire acquisition say about Oracle's previous foray into WCM (via Stellent)? What does it say about the current marketplace?&lt;br /&gt;&lt;br /&gt;The second question is easier. Oracle has clearly bought into the &lt;a href="http://en.wikipedia.org/wiki/Customer_experience#Customer_Experience_Management"&gt;Customer Experience Management&lt;/a&gt; &lt;span style="font-style: italic;"&gt;Weltanschauung&lt;/span&gt;. Oracle's Kumar Vora says: "As more companies rely on their web sites as their most important channel for communication, marketing, customer engagement and commerce, they realize that optimizing the online experience requires best-in-class capabilities delivered on an integrated platform with complete back-office and data integration." This is exactly the message of Adobe with respect to CEM, and I (for one) find it only slightly ironic (and not at all coincidental) that the FatWire acquisition announcement follows Adobe's announcement of &lt;a href="http://www.adobe.com/solutions/customer-experience/enterprise-platform.html"&gt;its new Digital Enterprise Platform&lt;/a&gt; by only a day.&lt;br /&gt;&lt;br /&gt;Bottom line, it's clear from &lt;a href="http://www.oracle.com/us/corporate/acquisitions/fatwire/index.html?origref=http://www.oracle.com/us/corporate/Acquisitions/fatwire/customer-letter-418756.html"&gt;Oracle's own rhetoric&lt;/a&gt; surrounding the purchase of FatWire that the FatWire acquisition is nothing if not a hand-over-fist endorsement of the Adobe CEM vision.&lt;br /&gt;&lt;br /&gt;Does this mean Oracle will be able to compete effectively against Adobe in the CEM-platform space? For insight on this, perhaps we should return to the question I posed earlier about FatWire vs. Stellent. With Stellent, Oracle acquired a capable Web CMS of modest complexity that offered good value for the money. They folded it into the Universal Content Management suite, did a little rearchitecting (but without ditching the proprietary Idoc Script language), and came up with an ECM-flavored document-management suite that could also do WCM. Meanwhile, Oracle invested relatively little R&amp;amp;D in personalization, optimization, campaign management, social collaboration, multi-device output, or any of the other disciplines that companies like Day Software (now Adobe) and FatWire continued to innovate in. In short, Oracle chose not to innovate (at a pace that would ensure continued relevance for UCM). As a result, it is today having to buy (rather than build) its way into the WEM/CEM space.&lt;br /&gt;&lt;br /&gt;It's expected to take another 6 to 9 months for the FatWire deal to close (during which time the two companies, and their respective WCM offerings, will operate separately). After the deal closes, expect another year or so for any meaningful product integration to occur. (In the meantime, you can expect to hear a lot of happy talk about &lt;a href="http://en.wikipedia.org/wiki/Content_Management_Interoperability_Services"&gt;CMIS&lt;/a&gt; being the holy grail of interoperability.) Bottom line, while the well-oiled Oracle sales machine will no doubt have a nice-sounding story in place well before then, I don't think you can expect a credible integration of FatWire technology with the UCM/Fusionware morass in less than 18 months; and by that time, who knows what other technologies Oracle will have to have bought in order to maintain credibility in the WEM/CEM/WCM space?&lt;br /&gt;&lt;br /&gt;My hat's off to &lt;a href="http://www.fatwire.com/company/company/management"&gt;Yogesh Gupta&lt;/a&gt; for doing a fine job of fatting the FatWire calf just in time for slaughter. Had he waited another six months to clinch a buyout deal, things might have looked quite a bit less rosy for the company formerly known as Divine, &lt;span style="font-style: italic;"&gt;nee &lt;/span&gt;OpenMarket (&lt;span style="font-style: italic;"&gt;nee &lt;/span&gt;Future Tense).&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-7163535897606381709?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7163535897606381709?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/7163535897606381709?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/06/oracle-endorses-adobe-strategy-with.html" title="Oracle endorses Adobe strategy with FatWire purchase" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;DEIHQHo4eyp7ImA9WhZVFUU.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-4295283389660480715</id><published>2011-05-28T04:49:00.004-04:00</published><updated>2011-05-28T08:42:11.433-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-28T08:42:11.433-04:00</app:edited><title>A perfect storm for XMP?</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-VouqoRGEBxU/TeDdyeBwGYI/AAAAAAAAAqM/noQ8Y6r2Hto/s1600/Xmp_200px.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 202px; height: 82px;" src="http://4.bp.blogspot.com/-VouqoRGEBxU/TeDdyeBwGYI/AAAAAAAAAqM/noQ8Y6r2Hto/s320/Xmp_200px.png" alt="" id="BLOGGER_PHOTO_ID_5611728994827245954" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Like many others in this biz, I've been following the development of Adobe's &lt;a href="http://www.adobe.com/products/xmp/"&gt;Extensible Metadata Platform (XMP)&lt;/a&gt; for quite some time, and for at least three years I've &lt;a href="http://www.realstorygroup.com/Blog/1295-PDF-now-has-a-standard-home,-but-whither-XMP?"&gt;been saying&lt;/a&gt; that it would be in Adobe's best interest to hand oversight over this ostensibly open standard to a bonafide Standards Body (rather than let adoption languish as people continue to associate XMP with "Adobe-proprietary"). Happily, Adobe is in fact now doing the right thing: XMP is in the process of becoming &lt;a href="http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=57421"&gt;ISO-16684-1&lt;/a&gt;, via an effort led by my colleague &lt;a href="http://www.linkedin.com/in/frankbiederich"&gt;Frank Biederich&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This effort couldn't have come at a better time. The content world is in desperate need of an industry-standard way to represent rich-content metadata, and I strongly believe XMP is the right technology at the right time.&lt;br /&gt;&lt;br /&gt;One can quibble over whether embedding XMP in a host file is the correct thing to do (as opposed to placing it in the file's resource fork, or simply creating XMP as a separate sidecar file and managing it separately). There are good arguments pro and con. But packaging issues aside, there's not much question, in my mind, that nearly every form of content benefits from having easily-parsed metadata associated with it. This is particularly true of enterprise content (content that's managed in some kind of access-controlled repository). The availability of metadata makes a given content bit easier to repurpose, easier to track, easier to search -- easier to work with all the way around.&lt;br /&gt;&lt;br /&gt;At &lt;a href="http://day.com/"&gt;Day Software&lt;/a&gt; (now a part of Adobe), we've long had a saying that "everything is content." I'm fond of saying that once metadata is attached, "everything is an asset."&lt;br /&gt;&lt;br /&gt;I think XMP is poised to become a huge success, comparable to, say, Atom or RSS. First of all, the &lt;a href="http://www.adobe.com/devnet/xmp.html"&gt;specification itself&lt;/a&gt; is short and easily understood (thus easily implemented) -- always a Good Thing where XML standards are concerned. It's also semantically flexible and highly extensible -- again two very good things. The fact that it leverages &lt;a href="http://www.w3.org/RDF/"&gt;RDF&lt;/a&gt; also bodes well for XMP as we trundle ever-closer to the Ontological Web. The social dimensions of an asset, for example, could easily be accommodated by XMP via RDF triples. Let your imagination dwell on that for a minute.&lt;br /&gt;&lt;br /&gt;But I also think the timing for XMP is quite propitious just in terms of where it's at in its lifecycle. I was giving a talk last week at Adobe Research in Basel (on the subject of XMP) in which I mentioned a certain lifecycle theory (whose, I can't remember) that sees all technologies as basically going through three phases, each one lasting about six years. Phase One is Acceptance: It takes around six years for anything truly new to change the way people think about it. (During this period, only alpha geeks will actually adopt the new technology.) Phase Two is Adoption: It takes six years for the (at last understood) new technology to enter the mainstream in earnest. Phase Three is Ubiquity: This is when adoption becomes universal (or as close to that as it's going to get) and the market is saturated.&lt;br /&gt;&lt;br /&gt;Not everything goes through an 18-year cycle, obviously. This is just a rough conceptual model, but I find that it applies in a surprising number of cases. If we look at XMP (which dates to 2001) through this model, we see that it is a little more than halfway through the Adoption phase. I think that the ratification of ISO-16684-1 will kick off a Ubiquity phase in which we see XMP used in more ways and in more places than anyone would ever have thought possible.&lt;br /&gt;&lt;br /&gt;My talk last week in Basel was in front of a roomful of developers. Everyone there was familiar with aspect-oriented programming, so I made the (admittedly imperfect) analogy with XMP, saying "Imagine if &lt;span style="font-style: italic;"&gt;resources &lt;/span&gt;could have aspects. What would that look like? It would look a lot like XMP." The info that gets packaged up into XMP often has to do with crosscutting concerns, like access control, DRM, version history, and what might be called "serving suggestions" (mimetype, compatibility hints). It's not that far different from an advice stack in &lt;a href="http://www.jboss.org/jbossaop"&gt;JBossAOP&lt;/a&gt;. Even the packaging concerns are familiar from AOP. A classic problem in AOP, after all, is where to &lt;span style="font-style: italic;"&gt;put &lt;/span&gt;aspects: in the source code itself (as annotations), or in separate descriptors (as in JBossAOP)? The same concerns (and tradeoffs) arise with XMP.&lt;br /&gt;&lt;br /&gt;In any case, I think the pressing need for more and better metadata (as it pertains to enterprise content in particular) plus the built-in (in many cases) support for XMP in cell-phone cameras, plus the need for ontology-friendly web formats going forward, and many other factors (including the opening up of XMP under ISO-16684), spell a &lt;a href="http://en.wikipedia.org/wiki/Perfect_storm"&gt;perfect storm&lt;/a&gt; for XMP as we hurtle toward Web.Next. All I can say is: It's about time.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-4295283389660480715?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/4295283389660480715?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/4295283389660480715?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/05/perfect-storm-for-xmp.html" title="A perfect storm for XMP?" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-VouqoRGEBxU/TeDdyeBwGYI/AAAAAAAAAqM/noQ8Y6r2Hto/s72-c/Xmp_200px.png" height="72" width="72" /></entry><entry gd:etag="W/&quot;AkcFQHw_eyp7ImA9WhRXF0k.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-5436857128130475701</id><published>2011-04-30T07:03:00.003-04:00</published><updated>2011-12-24T12:33:31.243-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-24T12:33:31.243-05:00</app:edited><title>A script for putting page numbers on PDF pages</title><content type="html">We had a project in the office recently wherein a large amount of web documentation was converted to a (single) PDF file using the built-in Web Capture capability of Acrobat X (otherwise known as Control-Shift-O), and when we were done, we wanted a page number to appear on each page. It turns out, it's fairly easy to apply page numbers to (any) PDF file using a bit of JavaScript.&lt;br /&gt;
&lt;br /&gt;
Here is a script that will add a page number (as a read-only text field)  to the upper left corner of every page of a PDF document. (Obviously, you can adjust the script to place the page number in any position you want, if you don't like the upper left corner.) The best way  to play with this code is to run it in the JS console in Acrobat Pro  (Control-J to make the console appear). Paste the code into the console,  select all of it, then type Control-Enter to execute it.&lt;br /&gt;
&lt;tt&gt;&lt;/tt&gt;&lt;br /&gt;
&lt;pre class="prettyprint"&gt;&lt;tt&gt;
&lt;span style="font-size: 85%;"&gt;
&lt;span style="font-family: courier new;"&gt;var inch = 72;
&lt;/span&gt;&lt;span style="font-family: courier new;"&gt;for (var p = 0; p &amp;lt; this.numPages; p++) { &lt;/span&gt;&lt;span style="font-family: courier new;"&gt;
 // put a rectangle at .5 inch, .5 inch &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  var aRect = this.getPageBox( {nPage: p} ); &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  aRect[0] += .5*inch;// from upper left corner of page &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  aRect[2] = aRect[0]+.5*inch; // Make it .5 inch wide &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  aRect[1] -= .5*inch; &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  aRect[3] = aRect[1] - .5*inch; // and .5 inch high &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  var f = this.addField("p."+p, "text", p, aRect ); &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  f.textSize = 20;  // 20-pt type&lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  f.textColor = color.blue; // use whatever color you want&lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  f.strokeColor = color.white; &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  f.textFont = font.Helv; &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  f.value = String(p+1);  // page numbering is zero-based&lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;  f.readonly = true; &lt;/span&gt;
&lt;span style="font-family: courier new;"&gt;} &lt;/span&gt;&lt;/span&gt;&lt;/tt&gt;&lt;/pre&gt;
&lt;br /&gt;
When you're done, you'll have a page number (as a read-only text field) on each page. If you like, you can Flatten the PDF programmatically using &lt;span style="font-family: courier new;"&gt;flattenPages()&lt;/span&gt; in the console afterwards, to convert the text fields to static objects on the pages (making them no longer editable as text fields).&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-5436857128130475701?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/5436857128130475701?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/5436857128130475701?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/04/script-for-putting-page-numbers-on-pdf.html" title="A script for putting page numbers on PDF pages" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;C0AFQno4eip7ImA9WhZQFE8.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-6962103823777297088</id><published>2011-04-21T17:19:00.002-04:00</published><updated>2011-04-21T17:35:13.432-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-21T17:35:13.432-04:00</app:edited><title>Configurable Web Capture in Acrobat</title><content type="html">Today I put in a feature request for a new feature for the next dot-release of &lt;a href="http://www.adobe.com/products/acrobatsuite.html"&gt;Adobe Acrobat X&lt;/a&gt;. What I requested is a white-list/black-list (of URLs) capability for Web Capture. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You may already know about Acrobat's incredibly useful Control-Shift-O (Open URL) functionality, which does just what you think it should: It captures a web page as a PDF document. The built-in functionality is already plenty powerful. It walks all the links in a web page and captures all linked-to pages (and &lt;i&gt;their &lt;/i&gt;linked-to pages, etc., however many levels deep you want), creating appropriate links and Bookmarks inside the finished PDF document. And you can specify "Stay on the same server" if you want, to be sure the web-capture session doesn't inadvertently pull in content from a partner's (or competitor's) site, say. Which is all pretty neat. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I ran into a situation the other day, though, where I wanted to capture all the web content from a site, but I didn't want to pull down any content from URLs containing /javadoc/. It would have been neat if Acrobat's Ctrl-Shft-O feature had an Advanced Configuration dialog in which I could have specified certain URLs which either MUST always (white list) or MUST NOT (black list) be followed in the course of a traversal. Neater still would be if you could supply white-listed or black-listed URLs as &lt;i&gt;regular expressions&lt;/i&gt;. (Follow this pattern, don't follow that pattern.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I don't hold out much hope that this kind of feature will make it into a dot release, but I figured I would submit it anyway. As they say, no squeaky, no greasy.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-6962103823777297088?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6962103823777297088?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6962103823777297088?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2011/04/configurable-web-capture-in-acrobat.html" title="Configurable Web Capture in Acrobat" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;CEYMQXc7eCp7ImA9Wx9TF0Q.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-1320933485458372171</id><published>2010-11-26T11:19:00.004-05:00</published><updated>2010-11-26T12:23:00.900-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-26T12:23:00.900-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="http://www.blogger.com/post-create.g?blogID=21557504" /><title>Why Microsoft Wants Novell's Patents</title><content type="html">On Monday,  Novell let it be known that it would be &lt;a href="http://galaxystocks.com/2334/stock-alerts/novell-inc-has-decided-to-be-acquired-by-attachmate-corp-nasdaqnovl/"&gt;acquired by Attachmate Corporation&lt;/a&gt; in a deal worth $2.2 billion. Meanwhile, in a &lt;a href="http://www.sec.gov/Archives/edgar/data/758004/000119312510265965/d8k.htm"&gt;Form  8-K&lt;/a&gt; filing with the SEC, Novell stated that it "will sell to CPTN all of Novell's right, title and  interest in 882 patents ... for $450 million in cash." CPTN Holdings LLC is a consortium of technology companies organized by  Microsoft.&lt;br /&gt;&lt;br /&gt;Immediately, people began to speculate that the reason Microsoft would bid such an enormous amount of money to obtain Novell's patent portfolio (which, by the way, comes to only &lt;a href="http://patft.uspto.gov/netacgi/nph-Parser?Sect1=PTO2&amp;amp;Sect2=HITOFF&amp;amp;u=%2Fnetahtml%2FPTO%2Fsearch-adv.htm&amp;amp;r=0&amp;amp;f=S&amp;amp;l=50&amp;amp;d=PTXT&amp;amp;RS=IN%2FKasman+AND+AN%2FNovell&amp;amp;Refine=Refine+Search&amp;amp;Refine=Refine+Search&amp;amp;Query=AN%2FNovell"&gt;462 issued U.S. Patents&lt;/a&gt;; the 882 figure represents &lt;span style="font-style: italic;"&gt;applied-for&lt;/span&gt; patents as well as issued patents) is to get its hands on the intellectual property around UNIX. (Novell acquired UNIX from AT&amp;amp;T in the 1990s.)&lt;br /&gt;&lt;br /&gt;But &lt;a href="http://www.pcworld.idg.com.au/article/369244/attachmate_retain_novell_unix_copyrights/"&gt;it now appears&lt;/a&gt; that Novell will &lt;span style="font-style: italic;"&gt;not &lt;/span&gt;be selling UNIX patents as part of the CPTN deal. So the $450 million question is: What, exactly, is Microsoft (via CPTN) paying all that money for?&lt;br /&gt;&lt;br /&gt;I'll offer my own speculation. (Disclosure: In 2006 and 2007, I was a member of Novell's Inventions Committee -- the company's internal patent-oversight board. I don't maintain "special connections" with the Committee, however, nor do I pretend to speak for Novell.) If you look at Novell's patent portfolio as a whole  -- and in particular, if you look at the bulk of the work done in the past five years -- you can't help but notice that the single largest category of inventions has to do with &lt;span style="font-style: italic;"&gt;security&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;If you go to the USPTO website and so a &lt;a href="http://patft.uspto.gov/netacgi/nph-Parser?Sect1=PTO2&amp;amp;Sect2=HITOFF&amp;amp;u=%2Fnetahtml%2FPTO%2Fsearch-adv.htm&amp;amp;r=0&amp;amp;f=S&amp;amp;l=50&amp;amp;d=PTXT&amp;amp;RS=%28%28ABST%2Fsecurity+OR+ABST%2Fauthentication%29+AND+AN%2FNovell%29&amp;amp;Refine=Refine+Search&amp;amp;Refine=Refine+Search&amp;amp;Query=+%28ABST%2Fsecurity+OR+ABST%2Fauthentication+OR+ABST%2Ftrust%29+AND+AN%2FNovell"&gt;search on patents with "security," "trust," or "authentication" in the Abstract, where Novell is the Assignee&lt;/a&gt;, you'll come up with 60 hits. The search query I used was:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;(((ABST/security OR ABST/authentication) OR ABST/trust) AND  AN/Novell)&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;If you do a search on ABST/encryption, you'll get another 12 hits. That's 72 hits out of 462 granted patents (roughly 16% of the total) having to do with &lt;span style="font-style: italic;"&gt;encryption or security&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Microsoft is &lt;a href="http://www.networkworld.com/community/node/58101"&gt;well aware&lt;/a&gt; of its lagging reputation in matters involving security. And the company well knows that the success of its initiatives in cloud computing, collaboration, and social networking will depend, in large measure, on whether it can present a credible security story to customers. There's a lot at stake (to put it mildly). Compared to the size of the cloud computing, collab, and social markets, $450 million is a pittance.&lt;br /&gt;&lt;br /&gt;How good are Novell's security patents? That's another question. Many (not all) of them &lt;span style="font-style: italic;"&gt;are &lt;/span&gt;genuinely clever. Exactly which ones Microsoft has its eye on, though, is a secret probably only a few people in Redmond know.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-1320933485458372171?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1320933485458372171?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/1320933485458372171?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2010/11/why-microsoft-wants-novells-patents.html" title="Why Microsoft Wants Novell's Patents" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;A0ACSX84eCp7ImA9Wx9TE04.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-8106126066908800348</id><published>2010-11-21T06:14:00.004-05:00</published><updated>2010-11-21T06:42:48.130-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-21T06:42:48.130-05:00</app:edited><title>Getting Started with Adobe AIR</title><content type="html">It seems I'm always late to a good party. Yesterday, I finally did something I've been meaning to do for, oh, at least two years: I compiled and ran my first &lt;a href="http://www.adobe.com/devnet/air.html"&gt;Adobe AIR&lt;/a&gt; application. And in typical masochistic fashion, I decided to do it with Notepad as my code editor and command-line tools for compilation. It's not that I can't afford &lt;a href="http://www.adobe.com/products/dreamweaver/"&gt;Dreamweaver&lt;/a&gt; or &lt;a href="http://www.adobe.com/products/flashbuilder/"&gt;Flash Builder&lt;/a&gt;, mind you (I have both products and recommend them highly); it was more a matter of wanting to get dirt under my fingernails, so to speak. That's just how I am.&lt;br /&gt;&lt;br /&gt;The whole process of downloading the &lt;a href="http://www.adobe.com/go/getairsdk/"&gt;AIR SDK&lt;/a&gt;, reading online code examples, and getting my first example up and running took a little less than an hour from start to finish. There were only a couple of rough spots (both easily resolved). The first was creating my own self-signed security certificate. I did this with the ADT tool that comes with the AIR SDK. The magic command-line incantation that worked for me was:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new; color: rgb(0, 51, 0);"&gt;adt -certificate -cn SelfSign -ou KT -o "Kas Thomas" -c US 2048-RSA cert.p12 password1234&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Naturally, you'll want to change some of the parameters (e.g., the ones with my name and initials, and the password) when you do this yourself. But running this command should produce a certificate named cert.p12 on your local drive, assuming &lt;span style="font-style: italic;"&gt;adt.bat&lt;/span&gt; (Windows) is in your path.&lt;br /&gt;&lt;br /&gt;For example code, I turned to the text editor example described &lt;a href="http://www.adobe.com/devnet/air/ajax/quickstart/articles/building_text_editor.html"&gt;here&lt;/a&gt;. I compiled the code with:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new; color: rgb(0, 51, 0);"&gt;..\bin\adt -package -storetype pkcs12 -keystore ..\cert.p12  TextEditorHTML.air application.xml .&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;(running a command console from a location of C:\AIR\TextEditorHTML, with my certificate stored under C:\AIR). The first time I did this, I got an error of "File C:\AIR\TextEditorHTML\application.xml is not a valid AIRI or AIR file." If you get the "is not a valid AIRI or AIR file" error, it means you left the trailing period off the foregoing command line. (Note carefully the period after "application.xml" at the very end.)&lt;br /&gt;&lt;br /&gt;And that was basically it. My first AIR app: done in under an hour. Now, as &lt;a href="http://www.pcmag.com/article2/0,2817,2372805,00.asp"&gt;Shantanu Narayen says&lt;/a&gt;, "let the games begin!"&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-8106126066908800348?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8106126066908800348?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/8106126066908800348?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2010/11/getting-started-with-adobe-air.html" title="Getting Started with Adobe AIR" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author></entry><entry gd:etag="W/&quot;C0YEQnozcCp7ImA9Wx9TEEQ.&quot;"><id>tag:blogger.com,1999:blog-21557504.post-6163830454742324524</id><published>2010-11-18T08:20:00.005-05:00</published><updated>2010-11-18T09:38:23.488-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-18T09:38:23.488-05:00</app:edited><title>The Strength of Weak Ties</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_6ZIqLRChuQg/TOU3ywghWbI/AAAAAAAAAok/bIBc05IBZlk/s1600/hydrogenbonds.27854958_std.gif"&gt;&lt;img style="cursor: pointer; width: 250px; height: 197px;" src="http://4.bp.blogspot.com/_6ZIqLRChuQg/TOU3ywghWbI/AAAAAAAAAok/bIBc05IBZlk/s400/hydrogenbonds.27854958_std.gif" alt="" id="BLOGGER_PHOTO_ID_5540896261703555506" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-style: italic;"&gt;Hydrogen bonds (dotted lines) are only about&lt;br /&gt;5% as strong as covalent bonds (solid lines).&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Last Saturday, there was a fascinating discussion on Twitter about the power of weak connections. It was a real-time Tweetup held under the banner of &lt;a href="http://wthashtag.com/Ideachat"&gt;#ideachat,&lt;/a&gt; the latter being a monthly Twitter Chat focused on the process of ideation, held every second Saturday of the month at 9:00 a.m. EST. (Ideachat bills itself as "a Salon for Twitter Thinkers About Ideas." It is founded by Angela Dunn, Idea Designer and Digital Consultant, aka &lt;span id="short-description"&gt;&lt;span id="sd" class="sd"&gt;&lt;a href="http://twitter.com/blogbrevity" onclick="linkClicked=true;"&gt;@blogbrevity&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;.)&lt;br /&gt;&lt;br /&gt;The discussion was loosely grounded in the work of Mark S. Granovetter, whose 1973 paper &lt;a href="http://www.stanford.edu/dept/soc/people/mgranovetter/documents/granstrengthweakties.pdf"&gt;"The Strength of Weak Ties"&lt;/a&gt; (&lt;span style="font-style: italic;"&gt;American Journal of Sociology&lt;/span&gt;, May 1973, pp. 1360-1380) is one of the most widely cited papers in sociology. (See also Granovetter's 1983 followup paper in &lt;span style="font-style: italic;"&gt;Sociological Theory&lt;/span&gt;, &lt;a href="http://docs.google.com/viewer?a=v&amp;amp;q=cache:IvsBuxY0j8oJ:citeseerx.ist.psu.edu/viewdoc/download%3Fdoi%3D10.1.1.128.7760%26rep%3Drep1%26type%3Dpdf+the+strength+of+weak+ties.&amp;amp;hl=en&amp;amp;gl=us&amp;amp;pid=bl&amp;amp;srcid=ADGEESixi8waOds0MVD0e9MtZVrYJdvqPV4Oc4tIPkySW_OK-txt2qMCLfjOeh_TBO4KsGUzWuyAfhVkAS7cNs96uYCwtlY73O-JU8W09zZReoPfc6__8jczAEe3HhsEN0R9dnwK35x7&amp;amp;sig=AHIEtbReydSIrZ5Zx-HMUCZwUaQ8ZO7oyg"&gt;"The Strength of Weak Ties: A Network Theory Revisited."&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;I won't try to recap the whole discussion here, since you can read the full transcript &lt;a href="http://wthashtag.com/transcript.php?page_id=18541&amp;amp;start_date=2010-11-13&amp;amp;end_date=2010-11-13&amp;amp;export_type=HTML"&gt;online elsewhere&lt;/a&gt;. Suffice it to say that in little more than an hour, 92 people contributed 695 tweets on the subject of how weak ties contribute to the spread of ideas in social networks. The discussion seemed particularly apropos given that almost none of the discussants knew each other except through the casual, transient contact afforded by Twitter and &lt;a href="http://tweetchat.com/"&gt;TweetChat&lt;/a&gt; (the tool used by most participants in the discussion).&lt;br /&gt;&lt;br /&gt;My main contribution to the discussion was to draw a parallel between weak social ties and the physical chemistry of &lt;a href="http://en.wikipedia.org/wiki/Hydrogen_bond"&gt;hydrogen bonding&lt;/a&gt;. I pointed out that in chemistry, &lt;span style="font-style: italic;"&gt;weak &lt;/span&gt;links (viz., hydrogen bonds) are responsible for much of what makes biomolecule behavior interesting. It's a hard point to try to make in 140 characters or less. But it's worth spending a minute thinking about.&lt;br /&gt;&lt;br /&gt;In chemistry, there are several types of chemical bond. The strongest type is the &lt;a href="http://en.wikipedia.org/wiki/Covalent_bond"&gt;&lt;span style="font-style: italic;"&gt;covalent &lt;/span&gt;bond&lt;/a&gt;: This is the kind of bond that connects the various atoms in a molecule (such as the hydrogens to the oxygen in water). About 5% as strong as the covalent bond is the &lt;a href="http://en.wikipedia.org/wiki/Hydrogen_bond"&gt;&lt;span style="font-style: italic;"&gt;hydrogen bond&lt;/span&gt;&lt;/a&gt;, which represents the weak electrostatic pull between electron-rich atoms and electron-poor atoms of different molecules. About an order of magnitude weaker still is the &lt;a href="http://en.wikipedia.org/wiki/Van_der_Waals_force"&gt;van der Waals force&lt;/a&gt; between atoms. Hydrogen bonds and van der Waals interactions are transient in nature, whereas covalent bonds are (for all intents) permanent, or at least long-lasting.&lt;br /&gt;&lt;br /&gt;It turns out that a &lt;span style="font-style: italic;"&gt;lot&lt;/span&gt; of interesting chemical behavior arises from the short-lasting weak interactions that go under the name of hydrogen bonding. The concept of surface tension arises from it. Protein folding happens the way it does because of hydrogen bonding. The stickiness of adhesives is due to hydrogen bonding. (Epoxy, on the other hand, owes its strength to covalent bonds.)&lt;br /&gt;&lt;br /&gt;At one point in the #ideachat session, I asked (rhetorically) which is more useful, Scotch tape or Krazy-Glue? Someone later suggested a better analogy would have been duct tape, or even PostIt notes (which famously rely on an adhesive that is almost -- but not quite -- ineffective). You can do a lot of useful things with Krazy-Glue (which relies on covalent bonds to get the job done), but I can think of at least &lt;span style="font-style: italic;"&gt;100 times more things&lt;/span&gt; you can do with duct tape. Tape is incredibly more versatile, even though the mechanism by which its adhesive works is fundamentally at least 20 times weaker than the mechanism behind Krazy-Glue.&lt;br /&gt;&lt;br /&gt;In the same way, I tend to think that the weak ties engendered by things like Twitter tend, in the aggregate, to produce effects that are surprisingly far-reaching -- causing many tipping-points to be reached long before they otherwise would be.&lt;br /&gt;&lt;br /&gt;Whether you agree with my physical-chemistry analogies or not, I encourage you to take part in the next #ideachat, which is scheduled to happen on the eleventh of December at 9:00 a.m. Eastern U.S. time. Mark your calendar. I'll see you there.&lt;div class="blogger-post-footer"&gt;The views expressed here are entirely my own, not those of my employer.&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/21557504-6163830454742324524?l=asserttrue.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6163830454742324524?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/21557504/posts/default/6163830454742324524?v=2" /><link rel="alternate" type="text/html" href="http://asserttrue.blogspot.com/2010/11/strength-of-weak-ties.html" title="The Strength of Weak Ties" /><author><name>Kas Thomas</name><uri>http://www.blogger.com/profile/10019988763491638199</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/-jwpU0fLihHQ/TmxUHqlPJuI/AAAAAAAAAs4/ZCDBSd4oUmM/s220/Kas%2Btiny.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_6ZIqLRChuQg/TOU3ywghWbI/AAAAAAAAAok/bIBc05IBZlk/s72-c/hydrogenbonds.27854958_std.gif" height="72" width="72" /></entry></feed>

