/** * $Id: mxImageShape.js,v 1.67 2012-04-22 10:16:23 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxImageShape * * Extends to implement an image shape. This shape is registered * under in . * * Constructor: mxImageShape * * Constructs a new image shape. * * Parameters: * * bounds - that defines the bounds. This is stored in * . * image - String that specifies the URL of the image. This is stored in * . * fill - String that defines the fill color. This is stored in . * stroke - String that defines the stroke color. This is stored in . * strokewidth - Optional integer that defines the stroke width. Default is * 0. This is stored in . */ function mxImageShape(bounds, image, fill, stroke, strokewidth) { this.bounds = bounds; this.image = (image != null) ? image : ''; this.fill = fill; this.stroke = stroke; this.strokewidth = (strokewidth != null) ? strokewidth : 1; this.isShadow = false; }; /** * Extends mxShape. */ mxImageShape.prototype = new mxShape(); mxImageShape.prototype.constructor = mxImageShape; /** * Variable: crisp * * Disables crisp rendering via attributes. Image quality defines the rendering * quality. Default is false. */ mxImageShape.prototype.crisp = false; /** * Variable: preserveImageAspect * * Switch to preserve image aspect. Default is true. */ mxImageShape.prototype.preserveImageAspect = true; /** * Function: apply * * Overrides to replace the fill and stroke colors with the * respective values from and * . * * Applies the style of the given to the shape. This * implementation assigns the following styles to local fields: * * - => fill * - => stroke * * Parameters: * * state - of the corresponding cell. */ mxImageShape.prototype.apply = function(state) { mxShape.prototype.apply.apply(this, arguments); this.fill = null; this.stroke = null; if (this.style != null) { this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND); this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER); this.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1; this.gradient = null; } }; /** * Function: create * * Override to create HTML regardless of gradient and * rounded property. */ mxImageShape.prototype.create = function() { var node = null; if (this.dialect == mxConstants.DIALECT_SVG) { // Workaround: To avoid control-click on images in Firefox to // open the image in a new window, this image needs to be placed // inside a group with a rectangle in the foreground which has a // fill property but no visibility and absorbs all events. // The image in turn must have all pointer-events disabled. node = this.createSvgGroup('rect'); this.innerNode.setAttribute('visibility', 'hidden'); this.innerNode.setAttribute('pointer-events', 'fill'); this.imageNode = document.createElementNS(mxConstants.NS_SVG, 'image'); this.imageNode.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', this.image); this.imageNode.setAttribute('style', 'pointer-events:none'); this.configureSvgShape(this.imageNode); // Removes invalid attributes on the image node this.imageNode.removeAttribute('stroke'); this.imageNode.removeAttribute('fill'); node.insertBefore(this.imageNode, this.innerNode); // Inserts node for background and border color rendering if ((this.fill != null && this.fill != mxConstants.NONE) || (this.stroke != null && this.stroke != mxConstants.NONE)) { this.bg = document.createElementNS(mxConstants.NS_SVG, 'rect'); node.insertBefore(this.bg, node.firstChild); } // Preserves image aspect as default if (!this.preserveImageAspect) { this.imageNode.setAttribute('preserveAspectRatio', 'none'); } } else { // Uses VML image for all non-embedded images in IE to support better // image flipping quality and avoid workarounds for event redirection var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1; var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1; var img = this.image.toUpperCase(); // Handles non-flipped embedded images in IE6 if (mxClient.IS_IE && !flipH && !flipV && img.substring(0, 6) == 'MHTML:') { // LATER: Check if outer DIV is required or if aspect can be implemented // by adding an offset to the image loading or the background via CSS. this.imageNode = document.createElement('DIV'); this.imageNode.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader ' + '(src=\'' + this.image + '\', sizingMethod=\'scale\')'; node = document.createElement('DIV'); this.configureHtmlShape(node); node.appendChild(this.imageNode); } // Handles all data URL images and HTML images for IE9 with no VML support (in SVG mode) else if (!mxClient.IS_IE || img.substring(0, 5) == 'DATA:' || document.documentMode >= 9) { this.imageNode = document.createElement('img'); this.imageNode.setAttribute('src', this.image); this.imageNode.setAttribute('border', '0'); this.imageNode.style.position = 'absolute'; this.imageNode.style.width = '100%'; this.imageNode.style.height = '100%'; node = document.createElement('DIV'); this.configureHtmlShape(node); node.appendChild(this.imageNode); } else { this.imageNode = document.createElement('v:image'); this.imageNode.style.position = 'absolute'; this.imageNode.src = this.image; // Needed to draw the background and border but known // to cause problems in print preview with https node = document.createElement('DIV'); this.configureHtmlShape(node); // Workaround for cropped images in IE7/8 node.style.overflow = 'visible'; node.appendChild(this.imageNode); } } return node; }; /** * Function: updateAspect * * Updates the aspect of the image for the given image width and height. */ mxImageShape.prototype.updateAspect = function(w, h) { var s = Math.min(this.bounds.width / w, this.bounds.height / h); w = Math.max(0, Math.round(w * s)); h = Math.max(0, Math.round(h * s)); var x0 = Math.max(0, Math.round((this.bounds.width - w) / 2)); var y0 = Math.max(0, Math.round((this.bounds.height - h) / 2)); var st = this.imageNode.style; // Positions the child node relative to the parent node if (this.imageNode.parentNode == this.node) { // Workaround for duplicate offset in VML in IE8 is // to use parent padding instead of left and top this.node.style.paddingLeft = x0 + 'px'; this.node.style.paddingTop = y0 + 'px'; } else { st.left = (Math.round(this.bounds.x) + x0) + 'px'; st.top = (Math.round(this.bounds.y) + y0) + 'px'; } st.width = w + 'px'; st.height = h + 'px'; }; /** * Function: scheduleUpdateAspect * * Schedules an asynchronous using the current . */ mxImageShape.prototype.scheduleUpdateAspect = function() { var img = new Image(); img.onload = mxUtils.bind(this, function() { mxImageShape.prototype.updateAspect.call(this, img.width, img.height); }); img.src = this.image; }; /** * Function: redraw * * Overrides to preserve the aspect ratio of images. */ mxImageShape.prototype.redraw = function() { mxShape.prototype.redraw.apply(this, arguments); if (this.imageNode != null && this.bounds != null) { // Horizontal and vertical flipping var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1; var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1; if (this.dialect == mxConstants.DIALECT_SVG) { var sx = 1; var sy = 1; var dx = 0; var dy = 0; if (flipH) { sx = -1; dx = -this.bounds.width - 2 * this.bounds.x; } if (flipV) { sy = -1; dy = -this.bounds.height - 2 * this.bounds.y; } // Adds image tansformation to existing transforms var transform = (this.imageNode.getAttribute('transform') || '') + ' scale('+sx+' '+sy+')'+ ' translate('+dx+' '+dy+')'; this.imageNode.setAttribute('transform', transform); } else { // Sets default size (no aspect) if (this.imageNode.nodeName != 'DIV') { this.imageNode.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px'; this.imageNode.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px'; } // Preserves image aspect if (this.preserveImageAspect) { this.scheduleUpdateAspect(); } if (flipH || flipV) { if (mxUtils.isVml(this.imageNode)) { if (flipH && flipV) { this.imageNode.style.rotation = '180'; } else if (flipH) { this.imageNode.style.flip = 'x'; } else { this.imageNode.style.flip = 'y'; } } else { var filter = (this.imageNode.nodeName == 'DIV') ? 'progid:DXImageTransform.Microsoft.AlphaImageLoader ' + '(src=\'' + this.image + '\', sizingMethod=\'scale\')' : ''; if (flipH && flipV) { filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)'; } else if (flipH) { filter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)'; } else { filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)'; } if (this.imageNode.style.filter != filter) { this.imageNode.style.filter = filter; } } } } } }; /** * Function: configureTransparentBackground * * Workaround for security warning in IE if this is used in the overlay pane * of a diagram. */ mxImageShape.prototype.configureTransparentBackground = function(node) { // do nothing }; /** * Function: redrawSvg * * Updates the SVG node(s) to reflect the latest bounds and scale. */ mxImageShape.prototype.redrawSvg = function() { this.updateSvgShape(this.innerNode); this.updateSvgShape(this.imageNode); if (this.bg != null) { this.updateSvgShape(this.bg); if (this.fill != null) { this.bg.setAttribute('fill', this.fill); } else { this.bg.setAttribute('fill', 'none'); } if (this.stroke != null) { this.bg.setAttribute('stroke', this.stroke); } else { this.bg.setAttribute('stroke', 'none'); } this.bg.setAttribute('shape-rendering', 'crispEdges'); } }; /** * Function: configureSvgShape * * Extends method to set opacity on images. */ mxImageShape.prototype.configureSvgShape = function(node) { mxShape.prototype.configureSvgShape.apply(this, arguments); if (this.imageNode != null) { if (this.opacity != null) { this.imageNode.setAttribute('opacity', this.opacity / 100); } else { this.imageNode.removeAttribute('opacity'); } } };