Welcome to Atalasoft Community Sign in | Join | Help

Non-Rectangular Masks on the Web: Part 1

Over the past 15 or so years, I've edited and created a thousands of images.  I almost always use a mask for something, and it's very rarely only rectangular. I have a need for doing this on the web, natively in the browser, and I might not be the only one.

With JavaScript and the DOM, you can create a series rectangles that represent masks, but as the complexity of these masks go up, the number of DOM objects that need to be created go up.  This can make it slow to render, and slow to update.

Most of the browsers used today show PNG images with an alpha channel(transparency) correctly.  PNG images with an alpha channel can be used to mimic a client side mask by placing them on top of the object that you want to mask.  The image to the right is one of these PNG images, with a JPG image behind it. There are three things you need to do to create a masking PNG image:

  1. Create an image with a background color of your choice, at the size of the object you are masking.
  2. Make the areas you want selected completely transparent on the alpha channel (Black).
  3. Make the rest of the image half transparent on the alpha channel (Gray).

The remaining parts that you will need will be some JavaScript mouse events that allow you to drag a box on the object (something like a selection marquee), and a server side method that will create the PNG image described above.

Since I have access to DotImage, I can use the WebAnnotationViewer and RemoteInvoke to do everything.  In my case I decided to use a ReferencedImage annotation to hold the alpha PNG.  Here's a basic run down of the concept, using DotImage:

  1. Use the WebAnnotationViewer's Selection box to select an area on the image, and attach a JavaScript event to the Selection.Changed.  (MaskSelection In this example)
  2. In the Selection.Changed JavaScript event, use RemoteInvoke to run a Page level server side method that creates the masked PNG image from the selection box. (Remote_MaskSelection in this example)
  3. The server side method keeps two images in the disk cache.  One for the masked areas drawn, and one for the transparent PNG that represents those masked areas.
  4. Every time the server side method is called it overwrites these images with the new data from selection rectangle of the WebAnnotationViewer, effectively appending what was selected to the new mask.
  5. The mask image is an 8-bit grayscale which starts out completely gray (color index 127) to indicate half transparent.  Areas that are selected are drawn onto this image as black (index 0).
  6. The alpha PNG image is a 32-bit RGBA image with a background color (red in this case), and a SetAlphaFromMaskCommand is used to put the mask image into the alpha channel of this new PNG
  7. The alpha PNG is then added to an annotation on the server side, and the client side JavaScript is notified that it needs to update the annotation.

Here is the code I used to create the mask image and the alpha PNG:

 private void SynchronizeMaskAnnotation(Size size)
 {
   // This annotation was created on PageLoad, and is the only one on layer 0
   AnnotationUI ann = this.WebAnnotationViewer1.Annotations.GetAnnotation(0, -1, 0);
   
   ReferencedImageAnnotation refAnn = ann as ReferencedImageAnnotation;
   if (refAnn != null)
   {
     refAnn.Size = size;

     ReferencedImageData refAnnData = refAnn.Data as ReferencedImageData;
     if (refAnnData != null)
     {
       if (refAnnData.ImageObject != null)
       {
         ((Bitmap)refAnnData.ImageObject).Dispose();
         GC.Collect(); // This is done because the .NET Bitmap class doesn't free the file in time for us to write over it
       }
     }
   }

   this.WebAnnotationViewer1.UpdateAnnotations();
 }

 private void AppendMask()
 {
   string _pathToCache = System.Configuration.ConfigurationManager.AppSettings["AtalasoftWebControls_Cache"];
   string _prefix = Page.Session.SessionID + "_";
   string _pathToMask = this._pathToCache + _prefix + "Mask.png";
   string _pathToMaskAnn = this._pathToCache + _prefix + "MaskAnn.png";

   AtalaImage maskImage = null;
   AtalaImage alphaPNG = null;

   try
   {
     if (File.Exists(Page.MapPath(_pathToMask)))
       maskImage = new AtalaImage(Page.MapPath(_pathToMask));
     else
       maskImage = new AtalaImage(this.WebAnnotationViewer1.Image.Size.Width, this.WebAnnotationViewer1.Image.Size.Height, PixelFormat.Pixel8bppGrayscale, Color.Gray);

     alphaPNG = new AtalaImage(maskImage.Width, maskImage.Height, PixelFormat.Pixel32bppBgra, Color.Red);

     Canvas maskCanvas = new Canvas(maskImage);
     maskCanvas.DrawRectangle(this.WebAnnotationViewer1.Selection.Rectangle, new SolidFill(Color.Black));

     SetAlphaFromMaskCommand alpha = new SetAlphaFromMaskCommand(maskImage, false, AlphaMergeType.Replace);
     ImageResults results = alpha.Apply(alphaPNG);
     if (alphaPNG != results.Image)
     {
       alphaPNG.Dispose();
       alphaPNG = results.Image;
     }

     SynchronizeMaskAnnotation(alphaPNG.Size);

     maskImage.Save(Page.MapPath(_pathToMask), new PngEncoder(), null);
     alphaPNG.Save(Page.MapPath(_pathToMaskAnn), new PngEncoder(), null);
   }
   finally
   {
     if (maskImage != null)
       maskImage.Dispose();
     if (alphaPNG != null)
       alphaPNG.Dispose();
   }
 }
Published Friday, April 04, 2008 3:45 PM by David Cilley
Filed under: , ,

Comments

Sunday, April 06, 2008 12:15 AM by DotNetKicks.com

# Non-Rectangular Masks on the Web: Part 1

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Anonymous comments are disabled