The Requested Gradient Cannot Be Found: Use the HTML5 Canvas

Some days ago I found that someone was looking for a D3.js expert. To prove one is an expert one has to pass a test, and one of the tasks on this test is to create a 3D color picker with RGB for axes.
enter image description here
The cube faces cannot be filled with a bi-dimensional linear gradients because such gradients are not supported. So, you have to explicitly write a loop to add the pixels.
Using SVG to add the pixels is a bad idea: SVG is an XML language, and uses a DOM tree. Using SVG will use a lot of memory and will slow down your computer. Use a canvas instead. Drawing on a canvas is done by simple Javascript commands, that add lines and shapes.
Following is a little code snippet that fills a cube face:

    for (i=x1; i<=x2;i++){
      for (j=y1; j<=y2; j++){
        rgb_arr[d.rgb_variable[0]]=i;
        rgb_arr[d.rgb_variable[1]]=j;
        ctx.fillStyle=d3.rgb(rgb_arr[0],rgb_arr[1],rgb_arr[2]);
        ctx.fillRect(i,j,1,1);
      }
    }

Now, to get the color where the mouse points, first get the position using the mouse event’s offsetX and offsetY. These properties will hold the correct value even if the canvas is rotated.
Then you can get the RGBA values of the pixel using method getImageData of the canvas’ context. The method returns the data of a rectangle defined by 4 arguments: x,t,width and height.
Following is an example:

   canvas.on('click', function(evt){
     var ctx=d3.event.target.getContext('2d');
     var pixelData = ctx.getImageData(d3.event.offsetX,d3.event.offsetY,1,1).data;
     alert(pixelData);
  });
Advertisements

How To Develop Firefox Extensions – Files And Directories (Actually Coding It)

In the previous post, How To Develop Firefox Extensions – Intro, we discussed the Chrome document’s DOM. In this post, we’ll learn how to create an add-on that can be installed.  In this post, we’ll learn how to access the Chrome document’s nodes, and to organize an XPI (Cross Platform Installer) file.

XPI files are ZIP files

A file having an ‘.xpi’ suffix, is a compressed file in ‘.zip’ format. If we use Firefox as the opening program for those files, it will install an extension. We can refer to a zipped file as a root directory, containing files and sub-directories.

Following is a recommended directory structure:

  • /
    • install.rdf
    • chrome.manifest
    • content/
      • <filename>.xul
      • <filename>.js
    • locale/
      • <language code>/
        • <filename>.dtd
      • <language code>/
        • <filename>.dtd
      • .
      • .
      • .
    • skin/
      • <filename>.css

The XUL File

The XUL file is located in the content directory. XUL is an acronym for *XML User-interface Language’. The XUL file is a data file that makes the development of Mozilla extensions easier. The following example is an Overlay XUL file, i.e. an XUL used for adding a component to the browser’s Chrome, the great UI container.

<?xml version="1.0"?>
<!DOCTYPE overlay SYSTEM
  "chrome://imageinnewtab_extension/locale/browserOverlay.dtd">

<overlay id="imageinnewtabextensionOverlay"
         xmlns:html="http://www.w3.org/1999/xhtml"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

 <script src="imageinnewtab_extension.js">
 </script>

    <menupopup id="contentAreaContextMenu"  >
        <menuitem id="context-imageinnewtab"
                  label="&phpandmoreimageinnewtab.image-new-tab.label;"
                  oncommand="ImageInNewTab.BrowserOverlay.openInNewTab(event)"
                  insertafter="context-viewimage"
                  hidden="true"
                 />
    </menupopup>

</overlay>

This overlay contains the required data for adding an “Open Image In New Tab” option to the context-menu that pops up when you right-click in the HTML document area. This file contains a link to a script and instructions to add a new menu item to the popup menu with the unique id ‘contentAreaContextMenu’.

Note that the value of the label attributes begins with an ampersand(‘&’) and ends with a seni-colons; this means that the value is variable. The actual value is translated according to the definitions found in the DTD file specified in the ‘!DOCTYPE’ tag.

The ‘oncommand’ attribute defines how Firefox should response to command events, i.e. the function called when the menu item is selected either by a mouse click or by a keyboard shortcut.

Learn more about XUL here.

The Javascript file

In this example, ‘content/imageinnewtab_extension.js’, the Javascript file, defines an object that response to the ‘command’ event. The object is initialized as a response to the “load” event, i.e. after the UI components has completed loading. When the Chrome document has completed loading, the new menu item is defined. The ‘init’ member is a function that defines what callback function to call when the context menu pops up.

Following is the code:

if ("undefined" == typeof(ImageInNewTab)) {
  var ImageInNewTab = {};
};

ImageInNewTab.BrowserOverlay = {
    hideIfNoImage: function(event){
      var element=document.popupNode;
      var whatWasClicked = document.getElementById("context-imageinnewtab");
      whatWasClicked.hidden = !(element instanceof Components.interfaces.nsIImageLoadingContent &&
                 element.currentURI);
    },

    init: function (event){
        var contextMenu = document.getElementById("contentAreaContextMenu");
        contextMenu.addEventListener("popupshowing", ImageInNewTab.BrowserOverlay.hideIfNoImage, false);
    },

    openInNewTab: function (event){
      gBrowser.addTab(document.popupNode.src);
    }

}

addEventListener("load",
                 ImageInNewTab.BrowserOverlay.init,false);

The Install RDF file

The ‘install.rdf’ file includes information for the browser. The information includes information shown to the installing end-uder, and for the browser to know if the extension supports the current browser version.

Following is an example:

<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
    <Description about="urn:mozilla:install-manifest">
        <em:id>imageinnewtab_extension@phpandmore.net</em:id>
        <em:name>Image in New Tab</em:name>
        <em:version>0.1</em:version>
        <em:description>Add a new Menu Item in the context menu to view an image in a new tab.</em:description>
        <em:creator>Amit Yaron</em:creator>
        <em:homepageURL>https://phpandmore.net/</em:homepageURL>
        <em:type>2</em:type> <!-- type=extension --> 

        <em:targetApplication>
           <!-- Firefox -->
           <Description>
                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                <em:minVersion>3.5</em:minVersion>
                <!-- Since Firefox's numbering scheme is unpredictable, users
                are encouraged to edit this field to the current max value.
                -->
                <em:maxVersion>999.0.*</em:maxVersion>
           </Description>
        </em:targetApplication>
    </Description>
</RDF>

Note: don’t change the ’em:id’ tag value.

Learn more about the install.rdf here.

The Chrome Manifest

The ‘chrrome.manifest’ file is a simple text files that contains instructions on how to access Chrome URL addresses (beginning with ‘chrome://’.

Following is an example manifest:

content imageinnewtab_extension content/
skin imageinnewtab_extension classic/1.0 skin/
locale imageinnewtab_extension en-US locale/en-US/
locale imageinnewtab_extension he-IL locale/he-IL/

overlay chrome://browser/content/browser.xul chrome://imageinnewtab_extension/content/imageinnewtab_extension_overlay.xul
  • The ‘content ‘ instruction tells Firefox where to find ‘chrome://imageinnewtab_extension/content’, where the XUL and code are found.
  • The ‘skin ‘ instruction tells Firefox where to find ‘chrome://imageinnewtab_extension/skin’, where the CSS files are found.
  • The ‘locale ‘ instruction tells Firefox where to find ‘chrome://imageinnewtab_extension/locale’ according to the ‘LANG’ environment variable, This is where the DTD file, used for label translation, is found.
  • The ‘overlay’ instruction tells Firefox the role of the overlay XUL file, which extends the great UI container.

Learn more about the manifest here.

The Example Add-on

The example add-on, that opens an image in a new tab, can be downloaded here.

JSON, The New XML

When you write for the web, you may want to send data to another server or to a client. A common way to transfer that data is in XML format. The data will then be processed using SAX, DOM or XPath. Every language support it.

If what you want is to define a variable, an object or an array in Javascript, you can use the JSON extension. JSON is an acronym for ‘Java Script Object Notation’. In Javascript you can use it as follows:

var myObject=<?php echo json_encode($php_object); >;

Here no parsers are required.

Here’s an example of using it in PHP:

<?
class my_class {
  public $prop1;
  public $prop2;

  function __construct(){
    $this->prop1='a';
    $this->prop2=400;
  }
}

$obj=new my_class();
echo json_encode($obj);
?>

The output looks like:
{“prop1″:”a”,”prop2″:400}

In addition to encoding, a JSON string can be decoded into an object in a language other than Javascript. Thus, you can pass data in the JSON format to any program supporting JSON, and, as you can see in www.json.org, most languages used today support it.

The ability to encode varibles into JSON and decode it back in any language is not the only reason why JSON can replace XML. If you go to www.json.org, you can see links in the bottom referring to other sites. For example, JSONPath, that allows you to access a member just like XPath. JSONPath is available in PHP and Javascript.

ImageMagick Workaround for SVG Support

I have installed the PECL package IMagick in my computer. Its current version 7:6.6.2.6 -1 does a great job converting SVG files into raster images. In my hosting account the Imagemagick package is not the most recent: I have an SVG file that contained elemants repeated using the Inkscape’s spray can, but when I convert it using a test program in my host it only print one of the repeated stars.

How can I resolve this?

Well, as you probably know, SVG is an XML format, so I can read it using a text editor. Doing this I found that there’s an element named ‘use’ with an attribute named ‘href’. “href” we know from HTML, and the element’s named “use” makes sense when using an existing element. So I commented out all the “use” elements, and viewing the SVG file as an image I saw the star at the exact location as in the  erroneous raster image, but with other elements not printed there. Probably the SAX engine stopped after the first unknown element. Commented out all elements from the first “use” elements until the end of file, and BINGO! This reproduced the problem. Well, the “use” elements should be replaced by the original ones with changes to their attributes. The following code does it:

/**
* I found that the "use" tags are not supported, so I'm replacing them
* by something else.
*
* The function returns the content of the file, the returned value can be the 1st argument of 
* Imagick::readImageBlob
*/
function svg_workaround($filename){
  $mimeType = mime_content_type($filename);

  if ($mimeType != 'text/xml' && mimeType!='image/svg+xml'){
  // At home the mime type is "Image/svg+xml" in the hosting account "text/xml"
    return file_get_contents($filename);
  }

  $contents = file_get_contents($filename);
  $docRoot = new DOMDocument(); // For random access and fast location of referenced objects
                                                     // you would use 'DOM'.

  if (!$docRoot->loadXML($contents))
    return FALSE;

  $xlinkNs = getXLinkNs($docRoot); // For the "href" attribute.

  $svgElems = $docRoot->getElementsByTagName('svg');
  if (!$svgElems->length)
    return $contents;

  $nodeList = $docRoot->getElementsByTagName('use');

  // Because the node lists changes when elements are replaces, we keep the elements in an array.
  $nodes = array();
  for ($i=0; $ilength; $i++)
    $nodes[]=$nodeList->item($i);

  foreach ($nodes as $node)
    if (!replace_use_tag($node, $docRoot, $xlinkNs))
      return false;

  return $docRoot->saveXML();
}

// The function that replaces the "use" elements
function replace_use_tag($node, $doc, $xlinkNs){
  $href=$node->getAttribute($xlinkNs . ':href');
  if (!$href)
    $href=$node->getAttribute('href');

  if (!$href)
    return FALSE;

  $id=substr($href, 1); // Removing the '#' from the href.
  if (!$id)
    return FALSE;
  // We use 'xpath' to locate a node with a given id because, unlike HTML, the use of getElementById
  // in XML DOM is not trivial.
  $xpath=new DOMXPath($doc);
  $elems=$xpath->query('//*[@id=\'' . $id . '\']');
  $orig=$elems->item(0);

  if (!$orig)
    return FALSE;

  $newNode=$orig->cloneNode(TRUE); // TRUE for deep cloning; When cloning a composite such as "g",
                                                             // its child elements should be copied as well.

  // Copy attributes from the 'use' node to the new node, not including the 'href'
  $attributes=$node->attributes;

  $length=$attributes->length;

  for ($i=0; $iitem($i); $i++){
    $attrName=$item->name;
    $attrValue=$item->value;
    if ($attrName=='href' || $attrName==$xlinkNs . ':href')
      continue;
    $newNode->setAttribute($attrName, $attrValue);
  }

  $parent=$node->parentNode;
  $parent->replaceChild($newNode, $node);
  return TRUE;
}

// Get the namespace for XLink.
function getXLinkNs($doc){
  return $doc->lookupPrefix('http://www.w3.org/1999/xlink');
}