dW

Sep 15th 2011

Quantizing PNG Color Channels

I’ve covered color quantization before when I wrote about PNG optimization and quantization. That article only covered quantization of 8-bit images, but color reduction of higher bit depth images which use direct color is quite possible. PNG images can take up quite a bit of space because while the format is definitely compressed the image format isn’t lossy. Adding an alpha channel only creates even larger files. PNG quantization usually is used to turn 32-bit PNG images (24-bit color, 8-bit alpha channel) into 16-bit PNG images (8-bit color, 8-bit alpha channel). Sometimes images aren’t well-suited for that much color reduction, so many of these large 32-bit PNG images wind up being used when quantizing the color channels themselves can yield smaller sizes without sacrificing too much of the image’s appearance in the process. I’m focusing on PNG images here, and while these processes definitely work for JPEG images the results are more widely mixed because of what the lossy compression does to the image; it doesn’t mean attempting to do this on JPEG images is a waste of time, after all.

Direct Color

Images that are higher than 8-bit typically use a direct color system which means the colors are directly encoded instead of mapped to a palette. These images are made up of separate channels containing tints for each primary color: red, green, and blue. When combined the colors are mixed to produce the image itself. In a 24-bit image each channel contains 256 colors, signifying they’re each 8-bit. 8×3=24. To produce a 12-bit image each channel would have to contain 16 colors (4-bit). 4×3=12. 16-bit is a bit different because the red and blue channels each contain 32 colors (5-bit) while the green channel contains 64 colors (6-bit). 5+6+5=16. The green channel contains more colors because humans are more visually sensitive to green light. Given a good raster graphics application like Adobe Photoshop, it’s possible to manually edit these channels separately.

Indexing Channels

Crop of Blimey!: first half 24-bit, second half 16-bit dithered

The image above is a cropping of my digital painting, Blimey!. The first half of the image above is 24-bit and the second half is 16-bit; The 16-bit half was created by copying each channel to a separate file, converting each to an indexed image with the colors necessary for each channel using error diffusion while preserving colors, and then lastly copying the image data to the channels. On most images this process only creates larger files because it makes PNG’s predictive compression algorithm less effective; adding additional noise only increases the file size. The original image as a 1900x1200 24-bit PNG is around 3.85 MB. As a 16-bit PNG at the same resolution using this process just outlined is around 3.94 MB — around a 2% increase in size.

Crop of Blimey!: first half 24-bit, second half 16-bit no dithering

The first half of the image above is 24-bit and the second half is 16-bit as before, but when quantizing the color channels diffusion was turned off. This simple change produces an image which is around 3.83 MB — almost a half a percentage point smaller in file size. This looks to be splitting hairs, but I’m mentioning it because not all images are alike; one method which produces good results on one image might not for another, and this method given another file might produce superior results.

There are other indexing methods available such as pattern dithering, and there are others not available in Photoshop which are explained in great detail in Wikipedia’s article on Dithering which may be available in other applications if someone else is feeling experimental or indeed peckish.

Posterizing Channels

Crop of Blimey!: first half 24-bit, second half 16-bit posterized

There’s yet another method which is available for quantizing direct color images, and that’s to posterize each channel separately. Like before the first half of the image is 24-bit and the other half is 16-bit except with each channel posterized instead of indexed. Posterizing lends itself well to PNG’s compression algorithm because it tends to create larger areas of flat color. The file yielded was around 2.53 MB — an astounding 34% decrease in size. Color preservation is thrown out the window using this process because of how it works, so when color needs to match up with something else it’d be a good idea to check to see if the composite image after channel posterization is acceptable. Additionally, because of the fact that bit depths like 16-bit don’t have the same bit depth per channel greys are affected by being skewed in one color direction; in the case of 16-bit greys are bent purple. To get around this problem the channels can be posterized to a level bit depth like 15-bit (5-bit per channel) or 18-bit (6-bit per channel) as an equal posterization on each channel would prevent the color’s warping. In the example above, however, this process is perfectly suited; the original is hardly noticeable from the quantized one.

Quantization Photoshop Actions

Opera Mac OS X

These processes can be quite monotonous, so they naturally lend themselves well to automation. I’ve created some Photoshop actions which make these processes effortless. My prebuilt actions don’t cover every possible bit depth and method for quantizing color channels, but they’re easily adaptable. They can also in fact be mixed and matched to produce different results.

Download

I’m offering this up for download without any warranty at all, and I’m not going to accept any responsibility for any damages it might cause to your computer. Installing Photoshop actions isn’t to my knowledge capable of causing harm to a computer. The compressed archive contains a license which allows the user to do whatever he/she wishes to do with it as long as attribution and a copy of the license is provided with it. This software requires Adobe Photoshop and a computer capable of running it to install.

Quantization Photoshop Actions (2.19 KB) — After unarchiving double-click on the quantization.atn file to install.

May 29th 2011

Ch-ch-ch-changes

It’s been over two years since I’ve done any significant code changes to this website, and I thought it was about time to implement some of the things that I’ve had in my head for some time. I’ve considered doing a redesign, but brainstorming a design — especially for yourself — takes an enormous amount of time. Thinking up things to program doesn’t take nearly as much time, so I thought I’d just work on some of them for the time being.

The last time I wrote about programming changes to this weblog it was about the CSS, and this time there’s additional changes to my venerable stylesheet. Then I had several stylesheets which were loaded based upon media attributes. That works, but ideally there should be one stylesheet for everything; that’s what I have now. Media queries are used extensively throughout to feed styles to different media types and different criteria. About a month ago I wrote some styles for Opera 11’s new Speed Dial enhancements. Using media queries and the view-mode media feature it’s possible to feed styles designed specifically for the thumbnail-like form factor of the Speed Dial items. I’ve got it working to my liking on all pages of this weblog instead of just the ones that contained articles as before. In addition I’m feeding styles designed for small-screened, yet capable devices. TV’s and projectors are also helped along with styles which make things easier to read from a distance. Lastly there’s print styles, and the entire site should be well-optimized for printing where only the articles and logo are printed; everything else is deemed unnecessary when printing. Also when printed, anchors and abbreviations are expanded — showing the URL and meaning in parentheses and brackets respectively. Unfortunately, all this fancy stuff causes Internet Explorer 8 and below to implode. I’m hoping to get it working better than it does presently in Internet Explorer 8 at the very least, but I’m not going to let it hold off making my changes live especially when Internet Explorer 9 has been released. The website is perfectly usable regardless. This weblog is nothing if experimental, anyway.

Visually, the biggest changes appear at the bottom of the page. I’ve replaced every use I had of Flickr on all of my personal pages because Flickr’s become a hostile environment where the maintainers of the website have become increasingly unprofessional; they don’t even bother testing new features in multiple browsers to check for bugs and then become irate when users describe the problems they experience. I was paying for the service, mostly out of support for what the place stood for and not because I necessarily required the additional features the “pro” account provided. Today it’s a mere shadow of what it once was — just like everything Yahoo! has. I began my transition away from Flickr by removing all hyperlinks from my personal pages to there, and I completed that a few months ago. This batch of changes removes my use of their API to display my latest images here. I’ve replaced it with Dribbble, an invite-only community for designers where crops of their current work are shown to fellow designers for perusal and commenting. I’ve been a member for nearly a year now, and it’s a website I visit on a daily basis. It’s actually more appropriate for me to have it displayed here as I’ve never been much of a photographer.

In addition to my Dribbble shots’ being displayed in the footer I’ve fixed my playlist. It should now display the most recent albums I’ve been listening to automatically. It accomplishes this through use of a custom AppleScript I wrote to grab recent unique albums’ metadata which returns JSON that is picked up by another script that sends it off to the server using XML-RPC. I wrote the AppleScript ages ago, but my method of getting it to the server was horribly bad. I didn’t feel like fiddling with it for so long because I really hate AppleScript. My beef with AppleScript is for another article, though.

This brings me to my API. I’ve only mentioned how I manage this weblog once, and it was rather vague — just commenting that I used TextMate for it. I manage my website remotely by a custom bundle for TextMate which sends commands via XML-RPC using a brilliant PHP library my friend Jeff King wrote to an API I created. It’s very similar to what many would be familiar with when using the MetaWeblog API and others like it except the commands for mine are proprietary to my weblog. I’m able to practically administer this weblog remotely from TextMate. Most of the work spent this time around was on improving this API.

I get asked quite often why I go through this trouble when I could just use a ready-made free CMS. I could, but I wouldn’t learn anything from just using someone else’s code. Besides, I find most of them to be quite inefficient. They’re overly bloated so that they could support multitudes of uses. Instead of being a small, useful Swiss Army knife they become a bunch of tools that are all individually inadequate to do what they’re supposed to do. It reminds me of something Montgomery Scott so eloquently stated in Star Trek III:

The more they overthink the plumbing, the easier it is to stop up the drain.

I also get told I’m just reinventing the wheel. I sure am, but the only way you can learn to make a wheel is to actually make one. I write this entire weblog in an effort to learn, and every time I modify this weblog’s code I’m learning something new. That’s what’s wrong with Web designers these days; they ignorantly grab a slew of ready-made libraries and slap them together to make visually acute, yet mind-numbingly sluggish websites. I’m not like that, and I don’t want to be like that. I want to know what I’m doing, and I want to assemble something that is as optimal as I can muster for the task at hand. In short, I’ll probably quit programming this website from the ground up when I quit learning from the experience.

There’s likely to be some bugs that remain from this round of changes. I found a few trying to attempt to submit this to my API for publication, in fact. I’ll work on them as soon as I discover them myself and as diligently as time allots me.

Apr 28th 2011

Highlighting

I personally don’t know what I’d do without TextMate. I use it as my primary development environment. I even use it to publish on this weblog. It has some faults especially when handling gargantuan files, and its newer version is looking to be the Duke Nukem Forever of desktop software; but, it’s still the best text editor out there in my honest opinion. The current version gets occasional updates when they’re needed, but as a whole there’s no real active development on the application. It presents a problem because now some of the bundles are outdated, especially CSS. I don’t use all the autocompletion and snippets that are in the CSS bundle. I’m just sick of writing CSS3 code and having everything half-assed highlighted.

I’ve looked high and low for a pre-made CSS3 syntax highlighting language grammar file for TextMate. Unfortunately, none of them are really worth a damn. Usually they would fall apart simply due to the way I write my code which does appear to be somewhat unusual, would contain only Webkit proprietary crap, or would just be missing entire usable parts of CSS3. It’s not possible to syntax highlight everything in CSS3 because parts of it still are in flux, but there are quite a few modules that have syntaxes that are somewhat stable. My only option was to create my own CSS3 language grammar file.

TextMate CSS3 Language Grammar

To put it simply TextMate assigns scope selectors to text which has been selected by regular expressions. Since the scope selectors are language agnostic text can be colored and styled for all languages using a single theme. Since TextMate’s release many applications have copied the syntax highlighting — some nearly verbatim.

Without an actual CSS tokenizer it’s quite difficult to really accurately syntax highlight the language. Problems are going to be encountered no matter what is done, and there’s a limitation in the way TextMate uses regular expressions that makes it a bit more difficult: regular expressions are only matched against single lines of text. That means that a multi-line pattern cannot be used. This sounds like a stupid limitation at first, but it actually makes perfect sense. The document needs to be updated in real-time, and if a multiple line regular expression is used it will severely slow down the parser because it’d have to refresh the entire document upon every keystroke. Fortunately, there’s a mechanism where two single-line regexes are used to grab their matching lines and everything in-between them. Still, it’s nigh-impossible to cover every possible case in CSS. My document is no exception.

My goal with this was to properly syntax highlight CSS3, including paged media and SVG features. I’ve even included support for Opera’s proposed @viewport at-rule. The most recent update to the language grammar for CSS in TextMate contains in my opinion many superfluous scope selectors such as separate scopes for each vendor-specific property among others. I’ve simplified things where I deemed it necessary while adding support for many more features of CSS3.

{   scopeName = 'source.css';
    comment = '';
    fileTypes = ( 'css' );
    foldingStartMarker = '/\*\*(?!\*)|\{\s*($|/\*(?!.*?\*/.*\S))';
    foldingStopMarker = '(?<!\*)\*\*/|^\s*\}';
    patterns = (
        {   name = 'meta.at-rule.media.css';
            begin = '\s*(/\*.*?\*/)?\s*((@)media)';
            end = '\s*(?=</style)|\s*\{';
            captures = {
                1 = { name = 'comment.block.css'; };
                2 = { name = 'keyword.control.at-rule.css'; };
                3 = { name = 'punctuation.definition.keyword.css'; };
            };
            patterns = (
                {   include = '#comment-block'; },
                {   include = '#media-feature'; },
                {   include = '#media-token'; },
                {   include = '#media-constant'; },
                {   include = '#media-and'; },
            );
        },
        {   name = 'meta.at-rule.page.css';
            begin = '\s*(/\*.*?\*/)?\s*((@)page)';
            end = '\s*(?=</style)|\s*(?=\{)';
            captures = {
                1 = { name = 'comment.block.css';}; 
                2 = { name = 'keyword.control.at-rule.css'; }; 
                3 = { name = 'punctuation.definition.keyword.css'; };
            };
            patterns = (
                {   include = '#comment-block'; },
                {   match = '\s*([\w\d]+)?((:)\b(first|last|left|right))?';
                    captures = { 
                        1 = { name = 'constant.other.page-name.css'; }; 
                        2 = { name = 'entity.other.attribute-name.pseudo-class.css';};
                        3 = { name = 'punctuation.definition.entity.css'; }; 
                    };
                },
            );
        },

        {   name = 'meta.at-rule.import.css';
            begin = '\s*(/\*.*?\*/)?\s*((@)import)';
            end = '\s*(?=</style)|\s*(?=;)';
            captures = {
                1 = { name = 'comment.block.css';}; 
                2 = { name = 'keyword.control.at-rule.import.css'; }; 
                3 = { name = 'punctuation.definition.keyword.css'; }; 
            };
            patterns = (
                {   begin = '(url)\s*(\()\s*';
                    end = '\s*(?=</style)|\s*(\))\s*';
                    beginCaptures = {
                        1 = { name = 'support.function.css'; };
                        2 = { name = 'punctuation.section.function.css'; };
                    };
                    endCaptures = { 1 = { name = 'punctuation.section.function.css'; }; };
                    patterns = (
                        {   include = '#string-single'; },
                        {   include = '#string-double'; },
                        {   include = '#string-unquoted'; },
                    );
                },
                    {   include = '#media-feature'; },
                    {   include = '#media-token'; },
                    {   include = '#media-constant'; },
                    {   include = '#media-and'; }
            );
        },
        {   name = 'meta.at-rule.charset.css';
            begin = '\s*(/\*.*?\*/)?\s*((@)charset)';
            end = '\s*(?=</style)|\s*(?=;)';
            captures = {
                1 = { name = 'comment.block.css';}; 
                2 = { name = 'keyword.control.at-rule.charset.css'; }; 
                3 = { name = 'punctuation.definition.keyword.css'; }; 
            };
            patterns = (
                {   include = '#string-single'; },
                {   include = '#string-double'; },
            );
        },
        {   name = 'meta.at-rule.namespace.css';
            begin = '\s*(/\*.*?\*/)?\s*((@)namespace)(\s+([\w]+)\s+)?';
            end = '\s*(?=</style)|\s*(?=;)';
            captures = {
                1 = { name = 'comment.block.css';}; 
                2 = { name = 'keyword.control.at-rule.namespace.css'; }; 
                3 = { name = 'punctuation.definition.keyword.css'; };
                5 = { name = 'constant.namespace.prefix.css';};
            };
            patterns = (
                {   include = '#comment-block'; },
                {   include = '#string-single'; },
                {   include = '#string-double'; },
            );
        },
        {   name = 'meta.at-rule.viewport.css';
            begin = '\s*(/\*.*?\*/)?\s*((@)(-o-)?viewport)';
            end = '\s*(?=</style)|\s*(?=\{)';
            captures = {
                1 = { name = 'comment.block.css';}; 
                2 = { name = 'keyword.control.at-rule.viewport.css'; }; 
                3 = { name = 'punctuation.definition.keyword.css'; };
            };
            patterns = (
                {   include = '#comment-block'; },
            );
        },
        {   name = 'meta.at-rule.font-face.css';
            begin = '\s*(/\*.*?\*/)?\s*((@)font-face)';
            end = '\s*(?=</style)|\s*(?=\{)';
            captures = {
                1 = { name = 'comment.block.css';}; 
                2 = { name = 'keyword.control.at-rule.font-face.css'; }; 
                3 = { name = 'punctuation.definition.keyword.css'; };
            };
            patterns = (
                {   include = '#comment-block'; },
            );
        },
        {   name = 'meta.property-list.css';
            begin = '\{';
            end = '\s*(?=</style)|\}';
            patterns = (
                {   include = '#comment-block'; },
                {   name = 'meta.property-name.css';
                    begin = '(?<![-_*a-z:])(?=[-_*a-z])';
                    end = '\s*(?=</style)|\s*(:)?(?![-_*a-z])';
                    endCaptures = { 1 = { name = 'punctuation.separator.key-value.css'; }; };
                    patterns = (
                        {   include = '#comment-block'; },
                        {   name = 'meta.at-rule.page.context.css';
                            comment = 'This is stupid, but there''s no other way to do it...';
                            begin = '((@)(bottom-center|bottom-left-corner|bottom-left|bottom-right-corner|bottom-right|left-middle|left-top|right-bottom|right-middle|right-top|top-center|top-left-corner|top-left|top-right-corner|top-right))';
                            end = '\s*(?=</style)|\s*(?=\{)';
                            captures = {
                                1 = { name = 'keyword.control.at-rule.css'; }; 
                                2 = { name = 'punctuation.definition.keyword.css'; };
                            };
                            patterns = (
                                {   include = '#comment-block'; },
                            );
                        },
                        {   name = 'support.type.property-name.vendor.css';
                            match = '(mso|-(ah|atsc|hp|khtml|moz|ms|ro|o|prince|rim|tc|wap|webkit|[-\w]+))-[-\w]+';
                        },
                        {   name = 'support.type.property-name.svg.css';
                            match = '\b(alignment-baseline|baseline-shift|clip-path|clip-rule|color-interpolation-filters|color-interpolation|color-profile|color-rendering|dominant-baseline|enable-background|fill-opacity|fill-rule|fill|filter|flood-color|flood-opacity|glyph-orientation-horizontal|glyph-orientation-vertical|image-rendering|kerning|lighting-color|marker-end|marker-mid|marker-start|marker|mask|pointer-events|shape-rendering|stop-color|stop-opacity|stroke-dasharray|stroke-dashoffset|stroke-linecap|stroke-linejoin|stroke-miterlimit|stroke-opacity|stroke-width|stroke|text-anchor|text-rendering|writing-mode)\b';
                        },
                        {   name = 'support.type.property-name.css';
                            match = '\b(animation-delay|animation-direction|animation-duration|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|animation|azimuth|backface-visibility|background-attachment|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|background|bikeshedding|bleed|bookmark-label|bookmark-level|bookmark-state|bookmark-target|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-bottom|border-collapse|border-color|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-image|border-left-color|border-left-style|border-left-width|border-left|border-radius|border-right-color|border-right-style|border-right-width|border-right|border-spacing|border-style|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-top|border-width|border|bottom|box-decoration-break|box-shadow|box-sizing|break-after|break-before|break-inside|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule-color|column-rule-style|column-rule-width|column-rule|column-span|column-width|columns|content|counter-increment|counter-reset|cue-after|cue-before|cue|cursor|display|elevation|empty-cells|flex-align|flex-direction|direction|flex-order|flex-pack|float-offset|float|font-family|font-feature-settings|font-kerning|font-language-override|font-size-adjust|font-size|font-stretch|font-style|font-synthesis|font-variant-alternates|font-variant-caps|font-variant-east-asian|font-variant-ligatures|font-variant-numeric|font-variant|font-weight|font|grid-columns|grid-rows|hanging-punctuation|height|hyphenate-after|hyphenate-before|hyphenate-character|hyphenate-limit-chars|hyphenate-limit-last|hyphenate-limit-lines|hyphenate-limit-zone|hyphenate-lines|hyphenate-resource|hyphens|image-resolution|left|letter-spacing|line-break|line-height|list-style-image|list-style-position|list-style-type|list-style|margin-bottom|margin-left|margin-right|margin-top|margin|marks|marquee-direction|marquee-loop|marquee-speed|marquee-style|max-height|max-width|min-height|min-width|opacity|orphans|outline-color|outline-style|outline-width|outline|overflow-style|overflow-x|overflow-y|overflow|padding-bottom|padding-left|padding-right|padding-top|padding|page-break-after|page-break-before|page-break-inside|pause-after|pause-before|pause|perspective-origin|perspective|phonemes|pitch-range|pitch|play-during|position|quotes|rest-after|rest-before|rest|richness|right|rotation-point|rotation|ruby-align|ruby-overhang|ruby-position|ruby-span|speak-header|speak-numeral|speak-punctuation|speakability|speak|speech-rate|src|stress|string-set|tab-size|table-layout|text-align-last|text-align|text-autospace|text-decoration-color|text-decoration-line|text-decoration-skip|text-decoration-style|text-decoration|text-emphasis-color|text-emphasis-position|text-emphasis-style|text-emphasis|text-indent|text-justify|text-outline|text-shadow|text-transform|text-trim|text-underline-position|text-wrap|top|transform-origin|transform-style|transform|transition-delay|transition-duration|transition-property|transition-timing-function|transition|unicode-bidi|vertical-align|vertical-position|visibility|voice-balance|voice-duration|voice-family|voice-pitch-range|voice-pitch|voice-rate|voice-stress|voice-volume|volume|white-space|widows|width|word-break|word-spacing|word-wrap|z-index)\b';
                        },
                    );
                },
                {   name = 'meta.property-value.css';
                    begin = '(?<=:)\s*';
                    end = '\s*(?=</style)|\s*(?=;)|(?=\})';
                    patterns = (
                        {   include = '#comment-block'; },
                        {   name = 'meta.function.attr.css';
                            begin = '(attr)\s*(\()\s*';
                            end = '\s*(\))\s*';
                            beginCaptures = {
                                1 = { name = 'support.function.css'; };
                                2 = { name = 'punctuation.section.function.css'; };
                            };

                        },
                        {   name = 'meta.function.counter.css';
                            begin = '(counter)\s*(\()\s*';
                            end = '\s*(?=</style)|\s*(\))\s*';
                            beginCaptures = {
                                1 = { name = 'support.function.css'; };
                                2 = { name = 'punctuation.section.function.css'; };
                            };
                            endCaptures = { 1 = { name = 'punctuation.section.function.css'; }; };
                            patterns = (
                                {   name = 'constant.other.counter.css';
                                    match = '[-\w\d]+';
                                },
                            );
                        },
                        {   begin = '(cubic-bezier|rgb(a)?|hsl(a)?)\s*(\()\s*';
                            end = '\s*(?=</style)|\s*(\))\s*';
                            beginCaptures = {
                                1 = { name = 'support.function.css'; };
                                2 = { name = 'punctuation.section.function.css'; };
                            };
                            endCaptures = { 1 = { name = 'punctuation.section.function.css'; }; };
                            patterns = (
                                {   name = 'punctuation.separator.argument.function.css';
                                    match = ',';
                                },
                                {   include = '#number'; },
                            );
                        },
                        {   begin = '(local|url)\s*(\()\s*';
                            end = '\s*(?=</style)|\s*(\))\s*';
                            beginCaptures = {
                                1 = { name = 'support.function.css'; };
                                2 = { name = 'punctuation.section.function.css'; };
                            };
                            endCaptures = { 1 = { name = 'punctuation.section.function.css'; }; };
                            patterns = (
                                {   include = '#string-single'; },
                                {   include = '#string-double'; },
                                {   include = '#string-unquoted'; },
                            );
                        },
                        {   name = 'support.constant.property-value.svg.css';
                            match = '\b(accumulate|after-edge|before-edge|bevel|butt|central|crispEdges|currentColor|end|evenodd|geometricPrecision|ideographic|linearRGB|lr-tb|lr|mathematical|middle|miter|no-change|nonzero|optimizeQuality|optimizeSpeed|painted|reset-size|rl-tb|rl|sRGB|small-caption|start|status-bar|stroke|sub|super|tb-rl|tb|text-after-edge|text-before-edge|use-script|visibleFill|visiblePainted|visibleStroke)\b';
                        },
                        {   name = 'support.constant.property-value.css';
                            match = '\b(A3|A4|A5|B4|B5|above|absolute|afar|after|alias|all-scroll|allow-end|all|alphabetic|alternate|always|amharic-abegede|amharic|annotation|arabic-indic|armenian|asterisks|auto|avoid-column|avoid-page|avoid|back|balance|baseline|before|behind|below|bengali|binary|blink|block-reverse|block|bolder|bold|border-box|both|bottom|box|break-all|break-word|bt|cambodian|cancel-all|cancel-line-through|cancel-overline|cancel-underline|capitalize|caption|cell|center|character-variant|check|circled-decimal|circled-lower-latin|circled-upper-latin|circle|cjk-earthly-branch|cjk-heavenly-stem|cjk-ideographic|clone|closed|col-resize|collapse|column|compact|condensed|consume-after|consume-before|contain|content-box|context-menu|copy|cover|crop|crosshair|cross|current|dashed|decimal-leading-zero|decimal|default|devanagari|diamond|digits|discard|disc|distribute-letter|distribute-space|distribute|dotted-decimal|dotted|dot|double-circled-decimal|double-circle|double|e-resize|each-line|ease-in-out|ease-in|ease-out|ease|end|ethiopic-abegede-am-et|ethiopic-abegede-gez|ethiopic-abegede-ti-er|ethiopic-abegede-ti-et|ethiopic-abegede|ethiopic-halehame-aa-er|ethiopic-halehame-aa-et|ethiopic-halehame-am-et|ethiopic-halehame-gez|ethiopic-halehame-om-et|ethiopic-halehame-sid-et|ethiopic-halehame-ti-er|ethiopic-halehame-ti-et|ethiopic-halehame-tig|ethiopic-numeric|ethiopic|ew-resize|extra-condensed|extra-expanded|expanded|fast|filed-circled-decimal|filled|fill|first|fixed|flat|flexbox|footnotes|force-end|force-start|forward|from-image|front|fullsize-kana|fullwidth|georgian|groove|gujarati|gurmukhi|hanging|hangul-consonant|hangul|hebrew|help|here|hidden|high|hiragana-iroha|hiragana|historical-forms|horizontal|hyphenate|hyphen|icon|ideograph-alpha|ideograph-numeric|infinite|inherit|ink|inline-block|inline-flexbox|inline-reverse|inline-table|inline|inset|inside|inter-cluster|inter-ideograph|inter-word|italic|japanese-formal|japanese-informal|justify|kannada|kashida|katakana-iroha|katakana|keep-all|keep-end|khmer|lao|last|ledger|leftwards|left|legal|letter|lighter|line-edge|line-through|linear|list-item|literal-punctuation|local|loose|loud|lower-alpha|lower-armenian|lower-greek|lower-hexadecimal|lower-latin|lower-norwegian|lower-roman|lowercase|low|lr|malayalam|manual|match-parent|medium|meet|menu|message-box|modal|moderate|mongolian|move|myanmar|n-resize|ne-resize|nesw-resize|new|no-content|no-display|no-drop|no-justify|no-limit|no-punctuation|no-repeat|none|normal|not-allowed|nowrap|ns-resize|nw-resize|nwse-resize|objects|oblique|octal|open|ordinal|oriya|ornament|oromo|outset|outside|overline|padding-box|page|parenthesised-decimal|parenthesised-lower-latin|parent|paused|persian|pointer|pre-line|pre-wrap|preserve-3d|preserve-breaks|preserve|pre|progress|punctuation|reduced|relative|repeat-x|repeat-y|repeat|reverse|ridge|rightwards|right|rl|root|round|row-resize|ruby-base-group|ruby-base|ruby-text-group|ruby-text|ruby|run-in|running|s-resize|scoll|scroll|se-resize|semi-condensed|semi-expanded|sesame|sidama|silent|simp-chinese-formal|simp-chinese-informal|slashed-zero|slice|slide|slow|small-caption|smaller|soft|solid|spaces|space|spell-out|spread|square|start|status-bar|stretch|strict|strong|style|stylistic|subscript|superscript|sw-resize|swash|syriac|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tab|tamil|tb|telugu|text|thai|tibetan|tigre|tigrinya-er-abegede|tigrinya-er|tigrinya-et-abegede|tigrinya-et|top|trad-chinese-formal|trad-chinese-informal|transparent|triangle|trim-inner|ultra-condensed|ultra-expanded|underline|upper-alpha|upper-armenian|upper-greek|upper-hexadecimal|upper-latin|upper-norwegian|upper-roman|uppercase|urdu|vertical-text|vertical|visible|w-resize|wait|wavy|weak|weight|window|x-fast|x-high|x-loud|x-low|x-slow|x-soft|x-strong|x-weak)\b';
                        },
                        {   name = 'support.constant.font-name.css';
                            match = '(\b((?i:arial|cambria|century|comic|consolas|constantia|corbel|cordia|courier|garamond|georgia|helvetica|impact|inconsolata|lucida|menlo|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings)|cursive|fantasy|monospace|sans-serif|serif)\b)';
                        },
                        {   include = '#string-single'; },
                        {   include = '#string-double'; },
                        {   name = 'support.constant.color.css';
                            match = '\b(?i:aliceblue|antiquewhite|aquamarine|aqua|azure|beige|bisque|black|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|darkcyan|cyan|darkblue|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|blue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|goldenrod|gold|lightgray|gray|greenyellow|green|honeydew|hotpink|indianred|indigo|ivory|khaki|lavenderblush|lavender|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgreen|lightgrey|grey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|lime|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olivedrab|olive|orangered|orange|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|whitesmoke|white|yellowgreen|yellow)\b';
                        },
                        {   name = 'constant.other.color.rgb-value.css';
                            match = '(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b';
                            captures = { 1 = { name = 'punctuation.definition.constant.css'; }; };
                        },
                        {   include = '#number'; },
                        {   include = '#units'; },
                        {   name = 'keyword.other.important.css';
                            match = '\!\s*important';
                        },
                    );
                },
            );
        }, 
        {   name = 'meta.selector.css';
            begin = '((?<=\})|^)\s*(/\*.*?\*/)?\s*(?=\s*[\|\[:.*#a-zA-Z])';
            end = '\s*(?=</style)|(?=\{)';
            beginCaptures = { 1 = { name = 'comment.block.css'; }; };
            patterns = (
                {   include = '#comment-block'; },
                {   begin = '([\w\d]+)\s*(?=(\|))';
                    end = '(\|)';
                    beginCaptures = { 1 = { name = 'constant.namespace.prefix.css'; }; };
                    endCaptures = { 1 = { name = 'punctuation.definition.namespace.css'; }; };
                },
                {   begin = '(\[)\s*([\w\d]+)\s*((\~|\^|\$|\*|\|)?\=)?\s*';
                    end = '\s*(?=</style)|(\])';
                    beginCaptures = {
                        1 = { name = 'punctuation.definition.attribute-selector.begin.css'; };
                        2 = { name = 'entity.other.attribute-name.css'; };
                        3 = { name = 'keyword.operator.attribute-selector.css'; };
                    };
                    endCaptures = { 1 = { name = 'punctuation.definition.attribute-selector.end.css'; }; };
                    patterns = (
                        {   include = '#string-single'; },
                        {   include = '#string-double'; },
                    );
                },
                {   include = '#string-single'; },
                {   include = '#string-double'; },
                {   name = 'entity.name.tag.css';
                    match = '\b(abbr|acronym|address|applet|area|article|aside|audio|basefont|base|a|bdo|big|blockquote|body|br|button|b|canvas|caption|center|cite|code|colgroup|col|command|datalist|dd|defs|del|desc|details|dfn|dir|div|dl|dt|embed|em|fieldset|figcaption|figure|font|footer|form|frameset|frame|h1|h2|h3|h4|h5|h6|header|head|hgroup|hr|html|iframe|image|img|input|ins|isindex|i|kbd|keygen|label|legend|g|link|listing|li|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|param|plaintext|pre|progress|q|rp|rt|ruby|samp|script|p|section|select|small|source|span|strike|strong|style|sub|summary|sup|svg|switch|symbol|s|table|tbody|td|textarea|tfoot|thead|th|time|title|track|tr|tt|ul|use|u|var|video|wbr|xmp)\b';
                },
                {   name = 'entity.other.attribute-name.class.css';
                    match = '(\.)[a-zA-Z0-9_-]+';
                    captures = { 1 = { name = 'punctuation.definition.entity.css'; }; };
                },
                {   name = 'entity.other.attribute-name.id.css';
                    match = '(#)[a-zA-Z][a-zA-Z0-9_-]*';
                    captures = { 1 = { name = 'punctuation.definition.entity.css'; }; };
                },
                {   name = 'entity.name.tag.wildcard.css';
                    match = '\*';
                },
                {   name = 'entity.other.attribute-name.pseudo-element.css';
                    match = '(:{1,2})\b(after|before|first-child|first-letter|first-line|last-child|marker|only-child|selection)\b';
                    captures = { 1 = { name = 'punctuation.definition.entity.css'; }; };
                },
                {   name = 'entity.other.attribute-name.pseudo-class.css';
                    match = '(:)\b(active|disabled|enabled|focus|hover|indeterminate|invalid|link|required|root|target|valid|visited)\b';
                    captures = { 1 = { name = 'punctuation.definition.entity.css'; }; };
                },
                {   begin = '(:)\b(nth(-last)?(-(child|of-type)))\s*\(\s*';
                    end = '\s*(?=</style)|\s*\)\s*';
                    captures = {
                        1 = { name = 'punctuation.definition.entity.css'; };
                        2 = { name = 'entity.other.attribute-name.pseudo-class.css'; };
                    };
                    patterns = (
                        {   name = 'constant.numeric.css';
                            match = '[^''") \t]+';
                        },
                    );
                },
                {   begin = '(:)\b(lang|not)\s*\(\s*';
                    end = '\s*(?=</style)|\s*\)\s*';
                    captures = {
                        1 = { name = 'punctuation.definition.entity.css'; };
                        2 = { name = 'entity.other.attribute-name.pseudo-class.css'; };
                    };
                    patterns = ( { include = '#string-unquoted'; } );
                },
            );
        },
        {   include = '#comment-block'; },
    );
    repository = {
        comment-block = {
            name = 'comment.block.css';
            begin = '/\*';
            end = '\*/';
            captures = { 0 = { name = 'punctuation.definition.comment.css'; }; };
        };
        media-and = {
            name = 'keyword.and.media.css';
            match = '\s+and\s+';
        };
        media-constant = {
            name = 'support.constant.media.css';
            match = 'all|braille|handheld|print|projection|screen|tty|tv';
        };
        media-feature = {
            name = 'meta.feature.media.css';
            begin = '(?<!url)(\()\s*';
            end = '\s*(?=</style)|\s*(\))';
            beginCaptures = { 1 = { name = 'punctuation.definition.feature.media.begin.css'; }; };
            endCaptures = { 1 = { name = 'punctuation.definition.feature.media.begin.css'; }; };
            patterns = (
                {   name = 'support.type.property-name.media.css';
                    match = '(((min|max)-)?(device-)?(width|height|aspect-ratio)|orientation|((min|max)-)?color(-index)?|((min|max)-)?(monochrome|resolution)|scan|grid|view-mode)';
                },
                {   name = 'meta.property-value.media.css';
                    begin = '(?<=:)';
                    end = '\s*(?=</style)|(?=(\s*\)))';
                    patterns = (
                        {   include = '#number'; },
                        {   include = '#units'; },
                        {   name = 'support.constant.property-value.css';
                            match = '\b(portrait|landscape|progressive|interlace|windowed|floating|fullscreen|maximized|minimized)\b';
                        },
                        {   name = 'punctuation.separator.ratio.css';
                            match = '/';
                        },
                        {   name = 'punctuation.separator.key-value.css';
                            match = ':';
                        },
                    );
                },
            );
        };
        media-token = {
            name = 'constant.token.media.css';
            match = '\s+(only|not)\s+';
        };
        number = {
            name = 'constant.numeric.css';
            match = '(-|\+)?\s*[0-9]+(\.[0-9]+)?';
        };
        string-double = {
            name = 'string.quoted.double.css';
            begin = '"';
            end = '"';
            beginCaptures = { 0 = { name = 'punctuation.definition.string.begin.css'; }; };
            endCaptures = { 0 = { name = 'punctuation.definition.string.end.css'; }; };
            patterns = (
                {   name = 'constant.character.escape.css';
                    match = '\\.';
                },
            );
        };
        string-single = {
            name = 'string.quoted.single.css';
            begin = "'";
            end = "'";
            beginCaptures = { 0 = { name = 'punctuation.definition.string.begin.css'; }; };
            endCaptures = { 0 = { name = 'punctuation.definition.string.end.css'; }; };
            patterns = (
                {   name = 'constant.character.escape.css';
                    match = '\\.';
                },
            );
        };
        string-unquoted = {
            name = 'string.unquoted.css';
            match = '[^''") \t]+';
        };
        units = {
            name = 'keyword.other.unit.css';
            match = '(?<=[\d])(deg|dpi|dpcm|px|pt|cm|mm|in|em|ex|pc|rad|s|ms)\b|%';
        };
    };
}

Caveats

As I mentioned before there are certain problems because of the difficulty in accurately highlighting CSS. The good news is that I’ve really only found one:

Example of a CSS Syntax Highlighting Error

As can be seen in the image the selectors in that first media query aren’t being highlighted properly. This is because of the left curly bracket in front of them. If the selectors start a line or are preceded by a right curly bracket they will highlight properly. I’ve yet to figure out a way around this; perhaps someone else will. Of course, there’s probably other caveats that I haven’t discovered yet. I’ll attempt to fix them as they’re discovered.

Install

Open TextMate and follow these directions to install:

  1. In the main menus go to Bundles → Bundle Editor → Show Bundle Editor.
  2. On the top on the left will be a select box. Click on it and go to “Languages”.
  3. In the listing on the left click on the dropdown arrow to the left of CSS and click on the language grammar item that has been made visible.
  4. In the textarea to the right paste what you see above into the box.
  5. Click the “Test” button and close the Bundle Editor.

I’ve been playing around with and using this language grammar document for a bit now, and I find it at least stable enough for everyday usage. If there’s problems don’t hesitate to contact me. However, I release this to the public without implying that I have any responsibility for any damage it does to your computer. I offer no warranty for this software. If you don’t understand much of what’s written here it’d be best to not attempt installation at all.

Feb 15th 2011

Opera’s Interface, Part 2

About six months ago I wrote a long article showcasing my suggestions for improvement to Opera’s interface. I mentioned in my review of Opera I would be writing more on the interface, and here I go. I mentioned in said previous article about the interface I had no clue what was coming in Opera 11, and Opera 11 has indeed changed a few things. Like before I’m going to show an image or series of images followed by ordered lists of points which correspond to numbers placed in the images themselves. Also, like before clicking on the image will provide a larger unadorned image for perusal. First, something different, though.

Opera Mac OS X
Opera Windows 7
Opera Ubuntu Linux

Above are three images which showcase Opera on three separate platforms: Mac OS X, Windows 7, and lastly Ubuntu Linux respectively. Last time I touched upon this subject I only fleetingly mentioned how my suggestions could apply to other platforms. The images above show a bit of that. Unfortunately I don’t have the free time to mock these things up for three separate platforms, and this is as far as I’ll go this time to show how my ideas apply to platforms other than Mac OS X. What it does show clearly is that everything here can very well apply to multiple platforms while maintaining each platforms’ differences.

Diagram of a Typical Opera Toolbar Button

Before returning to the format present in my last interface-centric article I need to discuss possibly the biggest change to the UI that Opera hasn’t even addressed yet — the extensions’ icon size and its effect on everything else.

Extensions have the ability to add an additional button to the interface, currently restricted to reside on the right side of the Address Bar. Opera, in an extremely odd move, made the icon size 18x18. A standard small icon size is 16x16, and this is the general size of toolbar icons in Mac OS X — at least icons which reside in push buttons. Since Opera has chosen this oddball size for extensions it needs to stick with it for the rest of the application.

At present toolbar button icons are all sorts of sizes in Opera. There’s no consistency. This can easily be seen by simply adding a Print button or the Author Mode/User mode button to the Address Bar. The icons are far too big for the buttons they reside in. One of Opera’s best features is being able to add buttons practically anywhere, so it’s more necessary for consistency in Opera than most applications.

My proposal is something like the diagram above.1 All toolbar icons need to fit within an 18×18 container with the exception of panel selector icons. I’ll have an opportunity to explain just why later on.

Opera with Webpage Open and Close Button on Active Tab Hovered Over
Opera with Webpage Open and Open Panel Hovered Over
  1. Opera 11 changed a lot of things, and its tab stacking made my attached tabs used in my previous set of mockups impractical. These are more similar to what is used now in Opera. I didn’t illustrate tab stacks here. My idea for them is quite similar to what is available now anyway.
  2. One major difference on the tabs would be the close button. Presently the close button in Opera resembles Safari’s. I find that a mistake. Safari’s present close button first appeared in the Safari 4 beta where Apple experimented with putting the tabs on the top of the window. In their compressed state the square close button made sense because it followed the contours of the tab. It doesn’t today in Safari and neither does it in Opera; it’s a fish out of water. My proposal is to make it a simple “x” and a circular close button very similar to the Mac OS X standard close button when hovered over. It has the added benefit of being similar to what is used in the standard skin; it adds consistency between the platforms.
  3. Here I show the hover status of the Open Panel button. This is one of the few examples of a deviation of the strictly buttoned appearance I’m trying to impose, but I believe it is necessary. It looks like it belongs there. The icon still fits within the 18×18 bounds, so if it was placed by the user on another toolbar which uses the push button appearance it wouldn’t break layout.
Opera with open Mail Panel and Webpage Open
Opera with open Mail Panel and Unread Items Open
  1. The Panel Selector Bar as it is today simulates a triangle tab’s sticking out from the left side of the panel. This can only visually work if the pixels immediately to the right of the triangular tab is the same as what is contained within itself. This illusion is broken already when the first panel item is clicked on. The triangle isn’t the same color as the toolbar’s gradient. In addition there’s this unsightly vertical padding on the top of the bar that’s only there because of this need to preserve this false illusion. What’s best here is to make it point from the panel selector towards the panel.
  2. My icons are also much smaller than what is there now — a maximum of 16×16. They are that size because of Web panels. Today if a user were to create a Web panel the icon would be smaller than the rest of the panel icons, and the layout would break. If all the icons were the same size like they are here then the layout wouldn’t break. The default Web panel icon here is also shown in a style similar to the other panels’ icons. The consistency in design leaves Web panel authors open to design favicons for their panels which also uses the common style like what I’ve shown here with a CSS panel.
  3. Like the Panel Selector Bar the panel itself is BLUE. Sidebars in Mac OS X are blue, not grey. I chose to mock up the mail panel first because I believe what’s there now is complete garbage. It needs to change badly. As can be seen here section headers are capitalized and devoid of an icon. Everything doesn’t need an icon, and everything’s having an icon creates visual overload and hinders information hierarchy. In my mockup each section collapses and expands, and count badges only appear on section headers when the section is collapsed. Section headers should not be fixed positioned and the scrollbar should remain the Mac OS X standard scrollbar. The simplified scrollbar used now is useful in some situations (unfortunately I don’t have any of these mocked up this time), but it is out of place here. I’ve used the regular size scrollbar, but a mini scrollbar would be appropriate as well in my opinion.
  4. Typically in Mac OS X a sidebar’s toolbar is at the bottom of the window. In Opera I think it should be as well. Also, in keeping up with the design of the Address Bar buttons on this toolbar will have a push button-like appearance, albeit with a blue shade to them. Mac OS X buttons of this type typically do not have colored icons within them, but in Opera’s case it is necessary due to the sheer amount of commands Opera can perform; it would be foolish to make them all in a simplistic monochromatic style — indeed it would be monotonous. It’s a problem I see in some Mac apps today even with a small set of icons; they become difficult to differentiate upon first glance. An added benefit to using the push button style is that monochromatic icons like what is used for navigation in the Address Bar don’t feel out of place sitting next to full color icons like they do now in Opera.
  5. Selected items in sidebars on Mac OS X have this colored gradient bar behind inverted content. Opera should do this as well, and the selection should honor the user’s Appearance setting: Aqua or graphite.
  6. I’m demonstrating here that Opera’s current behavior of showing a menu icon on section headers upon hover is maintained in my vision for Opera’s mail panel.
  7. The Mail Toolbar shares the design of the Address Bar, complete with full color icons which have a maximum vertical height of 18 pixels. The mail search input hugs the right side of the toolbar and sticks with the OS native appearance and behavior of the search input on the Address Bar unlike what is there now.
  8. The list headers do not have a 1 pixel margin and do not have double borders between each header. This is a very basic thing in Mac OS X which shouldn’t have to be shown and demonstrated for correction, but here’s my demonstration for correction. In my mockup I have the received column’s being used for content sorting with the newest item at the bottom. This should either be the default or there should be a setting so the user can make it the default behavior. This is one of the major annoyances of the mail and feeds client.
  9. Rows in the list view should be zebra-striped, and the stripes should continue down the entire length of the view as demonstrated here. They also should honor the user’s Appearance setting. So if the user has the Aqua appearance selected there should be alternating rows of blue and white. I use Graphite so they’re grey in my mockup. It’s quite stupid to have a feed as a contact, so this ability should be removed. When feeds are viewed with mail the icon next to the name of the author should be a feed icon. This different icon can be used to give the user an instantaneous visual clue that the particular item is a feed and not mail.
  10. The Mail Header Bar here is mocked up to be a light grey to white bar. I feel as if what’s there now is too dark and too monotonous. This here — while very similar — is easier to read than what is in Opera today.
  11. The Quick Reply Bar has long been an eyesore. It’s one of the first things I disable because it is indeed so ugly. What I have made here is still expandable and has all the features the current one has. Everything is flush with the interface, and the button is iconographic rather than textual as it is presently in Opera. It’s still quite obvious what the button is supposed to do.
Opera with Open Feeds Panel and N+ Feeds Open
  1. With Opera 11, Opera allowed users to have a feeds-only mail panel. I say take it a step further and make it a feeds panel when there’s no mail account configured, complete with its own panel icon and header.
  2. The new feeds-only mail panel in Opera is incomplete. It only shows the feeds section, but users need to be able to filter and label feeds along with viewing deleted feeds. A user can view all of the feeds through a contextual menu item now, but there needs to be a simpler way to access that feature. That’s why I’ve added an “All Feeds” item.
  3. Attachments are pointless in feeds so when viewing a listing that consists only of feeds there shouldn’t be an attachments column. Like discussed earlier having a feed as a contact is equally as pointless, so there’s no contact icon next to the author’s name. In fact there’s no icon at all because the feed icon used when feeds are listed amongst mail isn’t needed when all the items are feeds.
  4. Currently the stylesheet used for feed viewing is less than ideal. A stylesheet with proper leading and with images’ scaling to fit within the viewport would be welcome. Something worth noting is that the Quick Reply Bar is missing. It’s pointless when viewing feeds, and it shouldn’t show up when viewing a feed when it’s listed amongst mail, either.

That concludes everything I have thus far. This is less than I would have liked to have shown as I was planning on showing my ideas for mail composing, the downloads panel, and the downloads view. I looked ahead at my schedule and saw I’d have little time to devote to this task for a while, so I thought I’d go ahead and showcase what I have now. I think it’s enough.


  1. The diagram is an SVG. The image is also a link to a traditional raster image so people who are using browsers which are behind the times can still view the diagram.

Jan 1st 2011

This One Goes To Eleven

A few weeks ago Opera 11 was released. Usually I try to have my reviews of Opera releases a bit closer to the release date, starting work on them usually around the time the first release candidate is seeded. I’ve been a bit occupied.

So, it goes to eleven. That phrase comes from a hilarious Eighties film, This Is Spinal Tap. It’s one of the best films of the decade, and shame to anyone (especially those alive in the 1980’s) who haven’t seen the movie. Opera actually created their own spoof of this scene from the film in their promotional material for the beta. I was disappointed they didn’t follow up with it for the final release; it would have certainly been hilarious. It’s not like they needed the extra hilarity as Opera 11 looks like it was the most downloaded Opera release ever.

As is usual with a new Opera release, there’s quite a bit new. However, what’s important for a long-time user are bug fixes, and Opera 11 has plenty of those for us loyal users. Eleven is not without its problems, though. Nothing’s perfect.

Bugs & Other Annoyances

In my previous Opera review I chose to use video to demonstrate glaring bugs in Opera 10.50. I received a lot of positive feedback about the video, so I shall do it again:

To watch this video you need Adobe Flash.

Yes, Flash sucks ass. I can completely understand if you don’t have Flash installed, can’t, or have it disabled. I just can’t afford to use the HTML 5 methods because my host still has byzantine restrictions on bandwidth, and no third party website will allow me to serve standards-based video. I absolutely abhor Flash, but it’s a necessary evil.

Of everything discussed in the video the few things worth repeating are the asinine behaviors of the mail panel, inline print preview, and the state of HTML5 form elements. What Opera should do is keep the count badges and return the mail panel to what it was prior to all the weird experimental scrolling behavior. Like I said inline print preview is quite important to me personally. I even devoted a single post to begging Opera to fix it and not disregard it for Mac users. It looks as if my plea so far has fell on deaf ears. It’s a shame as it’s a feature unique to Opera and infinitely useful for developers. HTML5 is fast becoming really important, and the way users interact with the Web is going to change for the better. However, if the primary methods users are expected to interact with a webpage — form elements — are half-assed then it does nothing but hurt the user; the color picker is especially heinous and altogether useless.

I plan on tackling the interface more thoroughly in a later post very similar to the one I’ve already done, and I expressed my distaste for a couple of things from that category in the video already. The ensuing post will showcase in detail my suggestions for improvement. My intent with doing these reviews is to provide a thorough review of the application, but if I write in detail about the interface I’ll just be repeating the same thing again in more detail. I just find it’s best to keep that information all in one place.

Extensions

My Extensions Manager Page

Out of all the new features in Opera 11 extensions are the most notable. For a very long time Opera has refrained from implementing extensions in its browser while by now nearly all of its competition has embraced them. Opera’s lack of extensions was always a feature in my opinion. Firefox’s extensions have the tendency to thoroughly slow down the browser, and speed isn’t Firefox’s strong point to begin with if it even has one. Most extensions available were either built-in features in Opera or could be accomplished by utilizing built-in features.

Opera’s implementation is similar to Safari and Chrome’s, but they’ve adapted their standards-based widgets for extensions. Opera always practices what it preaches, and extensions are no exception. I had little trouble creating my own when Opera 11 was in its beta stages even with very little documentation (as documentation was still in the process of being written). The way I see it Opera extensions are glorified User JavaScripts — in fact my extension is nothing more than a direct port of my original User JavaScript with some long thought-of improvements. The other two extensions I have installed are also directly-ported User JavaScripts. The extension manager GUI provides a much better way of managing these scripts than was available to us before, and this applies to every single feature extensions have the ability to replace.

I have a feeling as if extensions are sort of separate from everything else in Opera. The way they’re handled gives that perception. Extensions presently can be programmed to be accessed as buttons in the GUI, but the buttons cannot be moved anywhere like long-time users are used to in Opera. They can’t be removed from a toolbar without disabling or uninstalling the extension. The only place they can reside are to the right of the address input on the address bar. Users have been able to add buttons that do extra behavior for ages now in Opera, but the methods to do so were really only reserved for power users. By manually editing a toolbar configuration file using a really esoteric syntax custom buttons could be created; after saving the file the button would show up in the Appearance dialog and could be placed anywhere the user wished. I’ve demonstrated this before in my weblog a couple of years ago when I created a custom button for adding items to an Amazon wishlist. It’d be beneficial to open up this behavior for extensions as well. The only method of accessing extensions that appears to be missing in my opinion would be as a contextual menu item, and that method of access would be of greater interest to me as personally I don’t like littering my interface with superfluous buttons — especially ones with icons which are for the most part horrendously ugly. All my presently installed extensions add no extra bits to the UI.

In adding to the perception that extensions are somewhat separate from everything else if a user has installed an extension which adds a button to the address bar and then copies their toolbar setup to another install the extension’s button will be there in the new install regardless of whether the extension is installed or not. This is obviously a bug, but it does indeed demonstrate that at least with this release extensions were just duct taped onto Opera. I think it’d be safe to predict that later releases would provide better integration.

The Opera Extensions Download Page

The only thing I’m not really happy with is the Opera extensions page. It’s a disaster area. Users aren’t allowed to simply comment on an extension; they’re required to provide a rating. Any particular extension’s page is filled with ratings which are nothing more than users’ asking questions of the developer without a prayer of being answered directly because if the developer himself has used up his one rating for his own extension then he cannot comment again. These shortcomings were mentioned back when Opera 11 was in its alpha stages, and little has changed from then. I’m simply not updating my extension there unless I can provide some support to my users without having to waste my personal time writing a dedicated website for the extension; in fact I’m considering removing it. Unite application developers are provided with a bug tracker and a comment area separate from ratings to provide support. Hardly anyone uses Unite. Why didn’t Opera just appropriate what they already created there for extensions? The URL structure of the page is also quite disturbed. Opera advertises its extensions as “extensions”, yet the base URL of the webpage is addons.opera.com. Why? A casual user thinking up the URL for the webpage might think to type in extensions.opera.com, but that location provides a 404 error. It would be less confusing to choose only one name for extensions; semantics are important.

Tab Stacking

A Collapsed Tab Stack with a Tab Preview Hover Box

There’s some arguments on whether or not Opera actually introduced tabbed browsing as Opera’s initial creation didn’t exactly resemble tabs, but there’s no denying Opera pioneered its use anyway. Tabbed browsing is a feature we take for granted these days, but it doesn’t scale well. The more tabs that are open the harder it is to navigate between them, and when page titles start becoming truncated tabbed navigation becomes almost completely useless. A user can specify to show the extender menu for the tab bar through the Appearance dialog box or they can simply use the Window panel, but those methods only alleviate the problem. Tab stacking is Opera’s attempt to solve the problem completely, but like older features it only really mitigates the problem because once you have your tab bar filled with tab stacks the behavior’s the same; the methods mentioned before that make tab navigation with numerous tabs easier to endure are necessary once again. However, it’s indeed a welcome addition.

Grouping tabs together — essentially what Tab Stacking does — isn’t anything new either. There’s been extensions for Firefox to accomplish it for ages, and Mozilla itself is trying to introduce a feature called Panorama (previously called Tab Candy) which groups tabs as well, although completely different from the way Opera is doing it with Opera 11. It groups them by treating grouped tabs as has been seen with windows in virtual desktop managers like Compiz or Apple’s Spaces; it’s not at all in my opinion that accessible for novice users because its use of a separate dashboard to manage the tab groups adds complexity to the simplicity of tabbed navigation. In that regard it’s vastly inferior to Opera’s Tab Stacking, but Panorama’s implementation does allow for far more scalability.

Tab stacking in Opera works by simply dragging tabs on top of each other, creating a collapsed group with the newly added tab as the one on top of the stack. Expanding and collapsing of the stack can be accomplished by clicking on the arrow to the right of the stack. Hovering over tab stacks shows previews of what the content is within them, just like regular tabs. Through the tab preview users are able to navigate easily between tabs in a stack without having to repeatedly expand and contract a tab stack. It’s really simple and easy.

More Secure Address Field

The Address Field with a Security Dropdown

Security is quite important, and Opera has always taken its users’ security seriously. However, with this new address field I believe they’ve gone a bit too far.

There’s always been a sense of redundancy in the address field because there’s a favicon on both the tab and its corresponding address field. Opera found a way to solve this redundancy in Opera 11. They’ve chosen to attach a security info button to the left side of the address bar where the favicon historically has been. In my opinion it’s a perfect place for it; it’s more accessible and useful in that location. Personally, I don’t like how it expands and contracts horizontally, but it still serves its purpose well.

In the way the application displays the address itself is where I believe Opera goes too far. I’m personally adverse to de-emphasizing everything other than the host, but as it is rather subtle it’s an unobtrusive security feature. Anyhow, completely hiding parts of the URL is just absolutely dumb.

Query strings are an essential part of the modern Web as nearly every Web application under the sun makes use of them internally, and many servers aren’t configured to hide them behind pretty URL’s. In other words, the URL is the page’s address, and information contained within the query string is necessary for identifying the page’s location. Hiding that information is akin to a postman’s looking at a mailing address devoid of a street number; it’d tell him which street to mail it but not which house to deliver it to. Furthermore, a user can visit twenty completely different pages in a particular Web application, all of which are currently using URL’s, yet the user’s address bar will never visibly change.

The Address Field with Show Full URL Toggled

Thankfully nearly all this ridiculous behavior can be removed by changing a single setting: opera:config#userprefs|showfullurl. The security button is thus reduced to a single icon, and the full URL is shown in the field; too bad the playing down of everything but the host remains. Oh well; you can’t win them all.

Visual Mouse Gestures

Visual Mouse Gesture Interface

Opera introduced mouse gestures on April 10, 2001 with the release of Opera 5.10. Until now it hasn’t changed much at all. Opera 11 has introduced a visual guide for users to let them know what exactly they are performing. As the user uses the feature more and becomes more familiar with it the guide will cease to display. This is a good thing in my opinion, but I find the guide overly large. It feels large especially on small screens. I’ve never been much of a user of mouse gestures because I always found them cumbersome. With gestures on I found that I would trigger them accidentally more than intentionally. That’s still the case today, so I’m not really the person to turn to for a thorough review of mouse gestures; I’ll try.

Nevertheless, I did notice that gestures themselves were more difficult to perform in Opera 11 than they were in previous versions. In fact they seem to be rather erratic. Even after adjusting the threshold it requires a lot of effort to get Opera to accept the gesture; this goes against what gestures are for. They should be methods to quickly perform actions, but if you can’t quickly complete a gesture to perform an action what’s the point of them?

Other New Features

Extensions, Tab Stacking, a more secure address field, and Visual Mouse Gestures aren’t the only new features. There’s Google search predictions and quite a lot of improvements and bug fixes to page rendering. Every single change between the last release of Opera and Opera 11 can be viewed in the changelogs:

  1. Mac
  2. Windows
  3. Unix

Assessment

I find Opera 11 to be a decent release. It’s definitely better than Opera 10.5 in my view, but there’s some glaring problems and inconsistencies in it. There’s been no advancement with Macintosh platform integration, and truth be told that aspect has deteriorated. There’s been some focus on the Macintosh skin, but it’s mostly been in the wrong places and in the wrong direction — most notably the new absolutely useless mail panel. It is, however, stable (at least on my platform), and it serves its purpose well despite the blatant problems it has. I’ve seen lots of situations where the Mac team has desired to show the upper echelons of the company that there’s a user base for the Mac so the company would devote more resources to it. That’s a rather backwards way to assess the problem. Opera’s not going to get many Mac users without first devoting resources to them; the same logic applies to all platforms. The last time that was done with much gusto was with Opera 10, and for the most part things have gone downhill from there in regards to Macintosh integration aside from a few minor improvements.