1 /**
  2  * The Render Engine
  3  * RenderUtil
  4  *
  5  * @fileoverview A static class with helper methods for rendering screen shots, partial images, and some effects.
  6  *
  7  * @author: Brett Fattori (brettf@renderengine.com)
  8  * @author: $Author: bfattori@gmail.com $
  9  * @version: $Revision: 1557 $
 10  *
 11  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 12  *
 13  * Permission is hereby granted, free of charge, to any person obtaining a copy
 14  * of this software and associated documentation files (the "Software"), to deal
 15  * in the Software without restriction, including without limitation the rights
 16  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 17  * copies of the Software, and to permit persons to whom the Software is
 18  * furnished to do so, subject to the following conditions:
 19  *
 20  * The above copyright notice and this permission notice shall be included in
 21  * all copies or substantial portions of the Software.
 22  *
 23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 29  * THE SOFTWARE.
 30  *
 31  */
 32 
 33 // The class this file defines and its required classes
 34 R.Engine.define({
 35     "class":"R.util.RenderUtil",
 36     "requires":[
 37         "R.rendercontexts.CanvasContext",
 38         "R.math.Point2D",
 39         "R.math.Rectangle2D"
 40     ]
 41 });
 42 
 43 /**
 44  * @class A static class of rendering utilities.  Most of the methods are intended
 45  *     for bitmap contexts, such as canvas, but may apply to others.  The methods
 46  *     have been designed with canvas in mind.
 47  *
 48  * @static
 49  */
 50 R.util.RenderUtil = /** @scope R.util.RenderUtil.prototype */ {
 51 
 52     // Private cache of temporary contexts
 53     tempContexts:{},
 54 
 55     /**
 56      * Get a temporary context to render into.  Only one context will ever be created for the type
 57      * specified, and cached for repeated use.  It will be cleaned up when the engine is shut down.
 58      * @param type {R.rendercontexts.RenderContext2D} The context class to mimic
 59      * @param width {Number} The width of the temporary context
 60      * @param height {Number} The height of the temporary context
 61      * @return {R.rendercontexts.RenderContext2D}
 62      */
 63     getTempContext:function (type, width, height) {
 64         if (R.util.RenderUtil.tempContexts[type.getClassName()] == null) {
 65             // Create the temporary context to render to
 66             R.util.RenderUtil.tempContexts[type.getClassName()] = type.create("tempCtx", 800, 800);
 67 
 68             // When the engine shuts down, clean up the contexts
 69             R.Engine.onShutdown(function () {
 70                 for (var c in R.util.RenderUtil.tempContexts) {
 71                     R.util.RenderUtil.tempContexts[c].destroy();
 72                     R.util.RenderUtil.tempContexts = {};
 73                 }
 74             })
 75         }
 76 
 77         // Prepare the temporary context
 78         var ctx = R.util.RenderUtil.tempContexts[type.getClassName()];
 79         ctx.getElement().width = width;
 80         ctx.getElement().height = height;
 81         ctx.reset();
 82 
 83         return ctx;
 84     },
 85 
 86     /**
 87      * Perform a single execution of a rendering component.
 88      * @param contextType {R.rendercontexts.RenderContext2D} The type of context to render to
 89      * @param renderComponent {R.components.Render} The component to render
 90      * @param width {Number} The width of the temporary context
 91      * @param height {Number} The height of the temporary context
 92      * @param time {Number} The time in milliseconds, or <code>null</code> to use the current engine time
 93      * @param offset {R.math.Point2D} The offset for the rendering position
 94      * @return {String} The data URL of the rendered image
 95      */
 96     renderComponentToImage:function (contextType, renderComponent, width, height, time, offset) {
 97         // Get the temporary context
 98         var ctx = R.util.RenderUtil.getTempContext(contextType, width, height);
 99 
100         time = time || R.Engine.worldTime;
101 
102         // The position to render to in the context
103         offset = offset || Point2D.ZERO;
104 
105         // Render the component
106         var p = R.math.Point2D.create(0, 0);
107         p.add(offset);
108         ctx.setPosition(p);
109         p.destroy();
110         renderComponent.execute(ctx, time, 1);
111 
112         // Extract the rendered image
113         return ctx.getDataURL();
114     },
115 
116     /**
117      * Takes a screen shot of the context provided, optionally cropped to specific dimensions.
118      * @param renderContext {R.rendercontexts.RenderContext2D} The context to get a screenshot of
119      * @param [cropRect] {R.math.Rectangle2D} Optional rectangle to crop to, or <code>null</code> for the
120      *     entire context.
121      * @return {String} The data URL of the screen shot
122      */
123     screenShot:function (renderContext, cropRect) {
124         cropRect = cropRect || renderContext.getViewport();
125 
126         // Render the screenshot to the temp context
127         var ctx = R.util.RenderUtil.getTempContext(renderContext.constructor, renderContext.getViewport().w, renderContext.getViewport().h);
128         ctx.drawImage(renderContext.getViewport(), renderContext.getSurface(), cropRect);
129 
130         // Return the image data
131         return ctx.getDataURL();
132     },
133 
134     /**
135      * Extract the image data URL from the provided image.  The image can either be an HTML <img> element,
136      * or it can be another render context.  This method currently only works with the canvas context.
137      * @param image {Object} Image or context
138      * @param [cropRect] {R.math.Rectangle2D} A rectangle to crop to, or <code>null</code> to use the entire image
139      * @param [contextType] {R.rendercontexts.RenderContext2D} Optional render context class, or <code>null</code> to
140      *     assume a canvas context.
141      * @return {String} A data URL for the extracted image
142      */
143     extractDataURL:function (image, cropRect, contextType) {
144         contextType = contextType || R.rendercontexts.CanvasContext;
145         var img = $(image), imgRect = R.math.Rectangle2D.create(0, 0, img.attr("width"), img.attr("height"));
146 
147         if (cropRect) {
148             imgRect.sub(cropRect.getTopLeft());
149         }
150 
151         // Get the temporary context
152         var w = cropRect ? cropRect.w : imgRect.w, h = cropRect ? cropRect.h : imgRect.h,
153             ctx = R.util.RenderUtil.getTempContext(contextType, w, h);
154 
155         ctx.drawImage(imgRect, image);
156 
157         // Return the image data from the temp context
158         return ctx.getDataURL("image/png");
159     },
160 
161     /**
162      * Extract the image data from the provided image.  The image can either be an HTML <img> element,
163      * or it can be another render context.  This method currently only works with the canvas context.
164      * @param image {Object} Image or context
165      * @param [cropRect] {R.math.Rectangle2D} A rectangle to crop to, or <code>null</code> to use the entire image
166      * @param [contextType] {R.rendercontexts.RenderContext2D} Optional render context class, or <code>null</code> to
167      *     assume a canvas context.
168      * @return {Object} Image data object with "width", "height", and an Array of each pixel, represented as
169      *     RGBA data where each element is represented by an integer 0-255.
170      */
171     extractImageData:function (image, cropRect, contextType) {
172         contextType = contextType || R.rendercontexts.CanvasContext;
173         var img = $(image), w = img.attr("width"), h = img.attr("height");
174         var imgRect = R.math.Rectangle2D.create(0, 0, w, h);
175 
176         // Get the temporary context
177         var ctx = R.util.RenderUtil.getTempContext(contextType, w, h);
178         ctx.drawImage(imgRect, image);
179 
180         // Return the image data from the temp context
181         return ctx.getImage(cropRect);
182     },
183 
184     blurCtx:null,
185 
186     /**
187      * Blur the contents of the <tt>renderContext</tt> using the number of passes specified.
188      * The blur operation is fairly quick and is an approximation of a blur, not an actual
189      * blur filter.  However, this method is slow and shouldn't be called per frame due to
190      * passing the render context on the stack.
191      *
192      * @param renderContext {R.rendercontexts.RenderContext2D} The context to blur
193      * @param [passes] {Number} Optional number of passes to apply (default: 1)
194      */
195     blur:function (renderContext, passes) {
196         var i, x, y, rect = R.math.Rectangle2D.create(renderContext.getViewport()),
197             ctx = R.util.RenderUtil.getTempContext(renderContext.constructor,
198                 renderContext.getViewport().w,
199                 renderContext.getViewport().h);
200 
201         // Extract the context's current image
202         ctx.drawImage(rect, renderContext.getSurface());
203 
204         // Run the blur
205         passes = passes || 1;
206         renderContext.get2DContext().globalAlpha = 0.125;
207         for (i = 1; i <= passes; i++) {
208             for (y = -1; y < 2; y++) {
209                 for (x = -1; x < 2; x++) {
210                     rect.x = x;
211                     rect.y = y;
212                     renderContext.drawImage(rect, ctx.getSurface());
213                 }
214             }
215         }
216         renderContext.get2DContext().globalAlpha = 1.0;
217     },
218 
219     /**
220      * Get a mask for the provided image.  The mask is a simple pixel, no-pixel image which
221      * exactly mimics the provided image.
222      *
223      * @param image {Object} Image or context
224      * @param [cropRect] {R.math.Rectangle2D} A rectangle to crop to, or <code>null</code> to use the entire image
225      * @param [contextType] {R.rendercontexts.RenderContext2D} Optional render context class, or <code>null</code> to
226      *     assume a canvas context.
227      * @return {String} A data URL for the image mask
228      */
229     getMaskImage:function (image, cropRect, contextType) {
230         var imgData = R.util.RenderUtil.extractImageData(image, cropRect, contextType);
231 
232         // Modify the image data so each pixel is either fully on or off
233         for (var pix = 0; pix < imgData.data.length; pix += 4) {
234             if (imgData.data[pix + 3] != 0) {
235                 imgData.data[pix] = 255;
236                 imgData.data[pix + 1] = 255;
237                 imgData.data[pix + 2] = 255;
238                 imgData.data[pix + 3] = 255;
239             } else {
240                 imgData.data[pix] = 0;
241                 imgData.data[pix + 1] = 0;
242                 imgData.data[pix + 2] = 0;
243                 imgData.data[pix + 3] = 0;
244             }
245         }
246 
247         // Recreate the temp context and draw the new image so we can extract the data URL
248         var img = $(image), w = img.attr("width"), h = img.attr("height");
249         var imgRect = R.math.Rectangle2D.create(0, 0, w, h);
250 
251         // Get the temporary context
252         var ctx = R.util.RenderUtil.getTempContext(contextType, w, h);
253         ctx.putImage(imgData, R.math.Point2D.ZERO);
254 
255         // Extract the data URL
256         return ctx.getDataURL("image/png");
257     }
258 };