From fd9d5c624ca042c418354069b8db487f62a31dfa Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Mon, 11 Jul 2011 13:08:44 -0400 Subject: [PATCH] absorbing the latest image library implementation --- image/private/js-impl.js | 30 - image/private/kernel.js | 1161 +++++++++++++++++++++++++++----------- 2 files changed, 841 insertions(+), 350 deletions(-) diff --git a/image/private/js-impl.js b/image/private/js-impl.js index b21d4c3..571c504 100644 --- a/image/private/js-impl.js +++ b/image/private/js-impl.js @@ -292,36 +292,6 @@ EXPORTS['line'] = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // EXPORTS['add-line'] = // plt.baselib.functions.makePrimitiveProcedure( // 'add-line', diff --git a/image/private/kernel.js b/image/private/kernel.js index bdc7bd1..297228c 100644 --- a/image/private/kernel.js +++ b/image/private/kernel.js @@ -1,3 +1,6 @@ +// Basic implementation of the image library. +// +// This should mimic the implementation of 2htdp/image. ////////////////////////////////////////////////////////////////////// @@ -10,7 +13,15 @@ var colorGreen = function(c) { return colorStruct.accessor(c, 1); }; var colorBlue = function(c) { return colorStruct.accessor(c, 2); }; ////////////////////////////////////////////////////////////////////// +var heir = plt.baselib.heir; +var clone = plt.baselib.clone; + + + + +// Produces true if the value is a color or a color string. +// On the Racket side of things, this is exposed as image-color?. var isColorOrColorString = function(thing) { return (isColor(thing) || ((plt.baselib.strings.isString(thing) || @@ -23,29 +34,51 @@ var isColorOrColorString = function(thing) { +var colorString = function(aColor) { + return ("rgb(" + + types.colorRed(aColor) + "," + + types.colorGreen(aColor) + ", " + + types.colorBlue(aColor) + ")"); +}; + + + + + +// Produces true if thing is an image. +var isImage = function(thing) { + return (thing instanceof BaseImage); +}; + + + + // Base class for all images. var BaseImage = function(pinholeX, pinholeY) { this.pinholeX = pinholeX; this.pinholeY = pinholeY; -} - - - -var isImage = function(thing) { - return (thing !== null && - thing !== undefined && - thing instanceof BaseImage); -} +}; BaseImage.prototype.updatePinhole = function(x, y) { - var aCopy = plt.baselib.clone(this); + var aCopy = clone(this); aCopy.pinholeX = x; aCopy.pinholeY = y; return aCopy; }; +BaseImage.prototype.getHeight = function(){ + return this.height; +}; + +BaseImage.prototype.getWidth = function(){ + return this.width; +}; + +BaseImage.prototype.getBaseline = function(){ + return this.height; +}; // render: context fixnum fixnum: -> void @@ -54,10 +87,6 @@ BaseImage.prototype.updatePinhole = function(x, y) { // NOTE: the rendering should be oblivous to the pinhole. BaseImage.prototype.render = function(ctx, x, y) { throw new Error('BaseImage.render unimplemented!'); - // plt.Kernel.throwMobyError( - // false, - // "make-moby-error-type:generic-runtime-error", - // "Unimplemented method render"); }; @@ -97,6 +126,9 @@ var withIeHack = function(canvas, f) { }; + +// Images are expected to define a render() method, which is used +// here to draw to the canvas. BaseImage.prototype.toDomNode = function(cache) { var that = this; var width = that.getWidth(); @@ -113,7 +145,6 @@ BaseImage.prototype.toDomNode = function(cache) { var ctx = canvas.getContext("2d"); that.render(ctx, 0, 0); }; - return canvas; }; @@ -123,7 +154,7 @@ BaseImage.prototype.toDomNode = function(cache) { BaseImage.prototype.toWrittenString = function(cache) { return ""; } BaseImage.prototype.toDisplayedString = function(cache) { return ""; } -BaseImage.prototype.isEqual = function(other, aUnionFind) { +BaseImage.prototype.equals = function(other, aUnionFind) { return (this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY); }; @@ -137,6 +168,8 @@ var isScene = function(x) { return ((x != undefined) && (x != null) && (x instanceof SceneImage)); }; + +////////////////////////////////////////////////////////////////////// // SceneImage: primitive-number primitive-number (listof image) -> Scene var SceneImage = function(width, height, children, withBorder) { BaseImage.call(this, 0, 0); @@ -145,7 +178,7 @@ var SceneImage = function(width, height, children, withBorder) { this.children = children; // arrayof [image, number, number] this.withBorder = withBorder; } -SceneImage.prototype = plt.baselib.heir(BaseImage.prototype); +SceneImage.prototype = heir(BaseImage.prototype); // add: image primitive-number primitive-number -> Scene @@ -182,15 +215,7 @@ SceneImage.prototype.render = function(ctx, x, y) { } }; -SceneImage.prototype.getWidth = function() { - return this.width; -}; - -SceneImage.prototype.getHeight = function() { - return this.height; -}; - -SceneImage.prototype.isEqual = function(other, aUnionFind) { +SceneImage.prototype.equals = function(other, aUnionFind) { if (!(other instanceof SceneImage)) { return false; } @@ -209,8 +234,8 @@ SceneImage.prototype.isEqual = function(other, aUnionFind) { if (rec1[1] !== rec2[1] || rec1[2] !== rec2[2] || !plt.baselib.equality.equals(rec1[0], - rec2[0], - aUnionFind)) { + rec2[0], + aUnionFind)) { return false; } } @@ -219,8 +244,7 @@ SceneImage.prototype.isEqual = function(other, aUnionFind) { ////////////////////////////////////////////////////////////////////// - - +// FileImage: string node -> Image var FileImage = function(src, rawImage) { BaseImage.call(this, 0, 0); var self = this; @@ -249,7 +273,7 @@ var FileImage = function(src, rawImage) { this.img.src = src; } } -FileImage.prototype = plt.baselib.heir(BaseImage.prototype); +FileImage.prototype = heir(BaseImage.prototype); var imageCache = {}; @@ -265,8 +289,8 @@ FileImage.installInstance = function(path, rawImage) { }; FileImage.installBrokenImage = function(path) { - imageCache[path] = new TextImage("Unable to load " + path, 10, - colorDb.get("red")); + imageCache[path] = new TextImage("Unable to load " + path, 10, colorDb.get("red"), + "normal", "Optimer","","",false); }; @@ -290,31 +314,135 @@ FileImage.prototype.toDomNode = function(cache) { return this.img.cloneNode(true); }; -FileImage.prototype.isEqual = function(other, aUnionFind) { +FileImage.prototype.equals = function(other, aUnionFind) { return (other instanceof FileImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && this.src == other.src); }; +////////////////////////////////////////////////////////////////////// +// VideoImage: String Node -> Video +var VideoImage = function(src, rawVideo) { + BaseImage.call(this, 0, 0); + var self = this; + this.src = src; + if (rawVideo) { + this.video = rawVideo; + this.width = self.video.videoWidth; + this.height = self.video.videoHeight; + this.pinholeX = self.width / 2; + this.pinholeY = self.height / 2; + this.video.volume = 1; + this.video.poster = "http://www.wescheme.org/images/broken.png"; + this.video.autoplay = true; + this.video.autobuffer=true; + this.video.loop = true; + this.video.play(); + } else { + // fixme: we may want to do something blocking here for + // onload, since we don't know at this time what the file size + // should be, nor will drawImage do the right thing until the + // file is loaded. + this.video = document.createElement('video'); + this.video.src = src; + this.video.addEventListener('canplay', function() { + this.width = self.video.videoWidth; + this.height = self.video.videoHeight; + this.pinholeX = self.width / 2; + this.pinholeY = self.height / 2; + this.video.poster = "http://www.wescheme.org/images/broken.png"; + this.video.autoplay = true; + this.video.autobuffer=true; + this.video.loop = true; + this.video.play(); + }); + this.video.addEventListener('error', function(e) { + self.video.onerror = ""; + self.video.poster = "http://www.wescheme.org/images/broken.png"; + }); + } +} +VideoImage.prototype = heir(BaseImage.prototype); + + +videos = {}; +VideoImage.makeInstance = function(path, rawVideo) { + if (! (path in VideoImage)) { + videos[path] = new VideoImage(path, rawVideo); + } + return videos[path]; +}; + +VideoImage.prototype.render = function(ctx, x, y) { + ctx.drawImage(this.video, x, y); +}; + +VideoImage.prototype.equals = function(other, aUnionFind) { + return (other instanceof VideoImage && + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY && + this.src == other.src); +}; + ////////////////////////////////////////////////////////////////////// - - -// OverlayImage: image image -> image +// OverlayImage: image image placeX placeY -> image // Creates an image that overlays img1 on top of the -// other image. shiftX and shiftY are deltas off the first -// image's pinhole. -var OverlayImage = function(img1, img2, shiftX, shiftY) { - var deltaX = img1.pinholeX - img2.pinholeX + shiftX; - var deltaY = img1.pinholeY - img2.pinholeY + shiftY; - var left = Math.min(0, deltaX); - var top = Math.min(0, deltaY); - var right = Math.max(deltaX + img2.getWidth(), - img1.getWidth()); - var bottom = Math.max(deltaY + img2.getHeight(), - img1.getHeight()); +// other image. +var OverlayImage = function(img1, img2, placeX, placeY) { + // calculate centers using width/height, so we are scene/image agnostic + var c1x = img1.getWidth()/2; + var c1y = img1.getHeight()/2; + var c2x = img2.getWidth()/2; + var c2y = img2.getHeight()/2; + var X, Y; + + // keep absolute X and Y values + // convert relative X,Y to absolute amounts + // we also handle "beside" and "above" + if (placeX == "right") + X = (c1x>c2x)? img2.getWidth()-(c1x+c2x) : img1.getWidth()-(c1x+c2x); + else if (placeX == "left") + X = (c1x>c2x)? img1.getWidth()-(c1x+c2x) : img2.getWidth()-(c1x+c2x); + else if (placeX == "beside") + X = c1x+c2x; + else if (placeX == "middle" || + placeX == "center") + X = 0; + else + X = placeX; + + if (placeY == "bottom") + Y = (c1y>c2y)? img2.getHeight()-(c1y+c2y) : img1.getHeight()-(c1y+c2y); + else if (placeY == "top") + Y = (c1y>c2y)? img1.getHeight()-(c1y+c2y) : img2.getHeight()-(c1y+c2y); + else if (placeY == "above") + Y = c1y+c2y; + else if (placeY == "baseline") + Y = img1.getBaseline()-img2.getBaseline(); + else if (placeY == "middle" || placeY == "center") + Y = 0; + else + Y = placeY; + + + // correct offsets when dealing with Scenes instead of images + if(isScene(img1)){ + X = X + c1x; Y = Y + c1x; + } + if(isScene(img2)){ + X = X - c2x; Y = Y - c2x; + } + + var deltaX = img1.pinholeX - img2.pinholeX + X; + var deltaY = img1.pinholeY - img2.pinholeY + Y; + + var left = Math.min(0, deltaX); + var top = Math.min(0, deltaY); + var right = Math.max(deltaX + img2.getWidth(), img1.getWidth()); + var bottom = Math.max(deltaY + img2.getHeight(), img1.getHeight()); BaseImage.call(this, Math.floor((right-left) / 2), Math.floor((bottom-top) / 2)); @@ -329,24 +457,17 @@ var OverlayImage = function(img1, img2, shiftX, shiftY) { this.img2Dy = deltaY - top; }; -OverlayImage.prototype = plt.baselib.heir(BaseImage.prototype); +OverlayImage.prototype = heir(BaseImage.prototype); OverlayImage.prototype.render = function(ctx, x, y) { + ctx.save(); this.img2.render(ctx, x + this.img2Dx, y + this.img2Dy); this.img1.render(ctx, x + this.img1Dx, y + this.img1Dy); + ctx.restore(); }; - -OverlayImage.prototype.getWidth = function() { - return this.width; -}; - -OverlayImage.prototype.getHeight = function() { - return this.height; -}; - -OverlayImage.prototype.isEqual = function(other, aUnionFind) { +OverlayImage.prototype.equals = function(other, aUnionFind) { return ( other instanceof OverlayImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && @@ -362,83 +483,77 @@ OverlayImage.prototype.isEqual = function(other, aUnionFind) { ////////////////////////////////////////////////////////////////////// - - // rotate: angle image -> image // Rotates image by angle degrees in a counter-clockwise direction. // based on http://stackoverflow.com/questions/3276467/adjusting-div-width-and-height-after-rotated var RotateImage = function(angle, img) { var sin = Math.sin(angle * Math.PI / 180), - cos = Math.cos(angle * Math.PI / 180); - - // (w,0) rotation - var x1 = Math.floor(cos * img.getWidth()), - y1 = Math.floor(sin * img.getWidth()); - - // (0,h) rotation - var x2 = Math.floor(-sin * img.getHeight()), - y2 = Math.floor( cos * img.getHeight()); - - // (w,h) rotation - var x3 = Math.floor(cos * img.getWidth() - sin * img.getHeight()), - y3 = Math.floor(sin * img.getWidth() + cos * img.getHeight()); - - var minX = Math.min(0, x1, x2, x3), - maxX = Math.max(0, x1, x2, x3), - minY = Math.min(0, y1, y2, y3), - maxY = Math.max(0, y1, y2, y3); - - var rotatedWidth = maxX - minX, - rotatedHeight = maxY - minY; - - // resize the image + cos = Math.cos(angle * Math.PI / 180); + + // (w,0) rotation + var x1 = Math.floor(cos * img.getWidth()), + y1 = Math.floor(sin * img.getWidth()); + + // (0,h) rotation + var x2 = Math.floor(-sin * img.getHeight()), + y2 = Math.floor( cos * img.getHeight()); + + // (w,h) rotation + var x3 = Math.floor(cos * img.getWidth() - sin * img.getHeight()), + y3 = Math.floor(sin * img.getWidth() + cos * img.getHeight()); + + var minX = Math.min(0, x1, x2, x3), + maxX = Math.max(0, x1, x2, x3), + minY = Math.min(0, y1, y2, y3), + maxY = Math.max(0, y1, y2, y3); + + var rotatedWidth = maxX - minX, + rotatedHeight = maxY - minY; + + // resize the image BaseImage.call(this, - Math.floor(rotatedWidth / 2), - Math.floor(rotatedHeight / 2)); - - this.img = img; - this.width = rotatedWidth; - this.height = rotatedHeight; + Math.floor(rotatedWidth / 2), + Math.floor(rotatedHeight / 2)); + + this.img = img; + this.width = rotatedWidth; + this.height = rotatedHeight; this.angle = angle; - this.translateX = -minX; - this.translateY = -minY; + this.translateX = -minX; + this.translateY = -minY; }; -RotateImage.prototype = plt.baselib.heir(BaseImage.prototype); +RotateImage.prototype = heir(BaseImage.prototype); -// translate drawing point, so that this.img appears in the UL corner. Then rotate and render this.img. +// translate the canvas using the calculated values, then draw at the rotated (x,y) offset. RotateImage.prototype.render = function(ctx, x, y) { - ctx.translate(this.translateX, this.translateY); - ctx.rotate(this.angle * Math.PI / 180); + // calculate the new x and y offsets, by rotating the radius formed by the hypoteneuse + var sin = Math.sin(this.angle * Math.PI / 180), + cos = Math.cos(this.angle * Math.PI / 180), + r = Math.sqrt(x*x + y*y); + x = Math.ceil(cos * r); + y = -Math.floor(sin * r); + ctx.save(); + ctx.translate(this.translateX, this.translateY); + ctx.rotate(this.angle * Math.PI / 180); this.img.render(ctx, x, y); - ctx.restore(); + ctx.restore(); }; - -RotateImage.prototype.getWidth = function() { - return this.width; -}; - -RotateImage.prototype.getHeight = function() { - return this.height; -}; - -RotateImage.prototype.isEqual = function(other, aUnionFind) { +RotateImage.prototype.equals = function(other, aUnionFind) { return ( other instanceof RotateImage && - this.pinholeX == other.pinholeX && - this.pinholeY == other.pinholeY && - this.width == other.width && - this.height == other.height && - this.angle == other.angle && - this.translateX == other.translateX && - this.translateY == other.translateY && - plt.baselib.equality.equals(this.img, other.img, aUnionFind) ); + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY && + this.width == other.width && + this.height == other.height && + this.angle == other.angle && + this.translateX == other.translateX && + this.translateY == other.translateY && + plt.baselib.equality.equals(this.img, other.img, aUnionFind) ); }; ////////////////////////////////////////////////////////////////////// - - // ScaleImage: factor factor image -> image // Scale an image var ScaleImage = function(xFactor, yFactor, img) { @@ -455,27 +570,19 @@ var ScaleImage = function(xFactor, yFactor, img) { this.yFactor = yFactor; }; -ScaleImage.prototype = plt.baselib.heir(BaseImage.prototype); +ScaleImage.prototype = heir(BaseImage.prototype); // scale the context, and pass it to the image's render function ScaleImage.prototype.render = function(ctx, x, y) { ctx.save(); ctx.scale(this.xFactor, this.yFactor); - this.img.render(ctx, x, y); + this.img.render(ctx, x / this.xFactor, y / this.yFactor); ctx.restore(); }; -ScaleImage.prototype.getWidth = function() { - return this.width; -}; - -ScaleImage.prototype.getHeight = function() { - return this.height; -}; - -ScaleImage.prototype.isEqual = function(other, aUnionFind) { +ScaleImage.prototype.equals = function(other, aUnionFind) { return ( other instanceof ScaleImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && @@ -487,18 +594,135 @@ ScaleImage.prototype.isEqual = function(other, aUnionFind) { }; ////////////////////////////////////////////////////////////////////// +// CropImage: startX startY width height image -> image +// Crop an image +var CropImage = function(x, y, width, height, img) { + + BaseImage.call(this, + Math.floor(width / 2), + Math.floor(height / 2)); + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.img = img; +}; + +CropImage.prototype = heir(BaseImage.prototype); +CropImage.prototype.render = function(ctx, x, y) { + ctx.save(); + ctx.translate(-this.x, -this.y); + this.img.render(ctx, x, y); + ctx.restore(); +}; -var colorString = function(aColor) { - return ("rgb(" + - colorRed(aColor) + "," + - colorGreen(aColor) + ", " + - colorBlue(aColor) + ")"); +CropImage.prototype.equals = function(other, aUnionFind) { + return ( other instanceof CropImage && + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY && + this.width == other.width && + this.height == other.height && + this.x == other.x && + this.y == other.y && + plt.baselib.equality.equals(this.img, other.img, aUnionFind) ); +}; + +////////////////////////////////////////////////////////////////////// +// FrameImage: factor factor image -> image +// Stick a frame around the image +var FrameImage = function(img) { + + BaseImage.call(this, + Math.floor(img.getWidth()/ 2), + Math.floor(img.getHeight()/ 2)); + + this.img = img; + this.width = img.getWidth(); + this.height = img.getHeight(); +}; + +FrameImage.prototype = heir(BaseImage.prototype); + + +// scale the context, and pass it to the image's render function +FrameImage.prototype.render = function(ctx, x, y) { + ctx.save(); + this.img.render(ctx, x, y); + ctx.beginPath(); + ctx.strokeStyle = "black"; + ctx.strokeRect(x, y, this.width, this.height); + ctx.closePath(); + ctx.restore(); +}; + +FrameImage.prototype.equals = function(other, aUnionFind) { + return ( other instanceof FrameImage && + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY && + plt.baselib.equality.equals(this.img, other.img, aUnionFind) ); +}; + +////////////////////////////////////////////////////////////////////// +// FlipImage: image string -> image +// Flip an image either horizontally or vertically +var FlipImage = function(img, direction) { + this.img = img; + this.width = img.getWidth(); + this.height = img.getHeight(); + this.direction = direction; + BaseImage.call(this, + img.pinholeX, + img.pinholeY); +}; + +FlipImage.prototype = heir(BaseImage.prototype); + + +FlipImage.prototype.render = function(ctx, x, y) { + // when flipping an image of dimension M and offset by N across an axis, + // we need to translate the canvas by M+2N in the opposite direction + ctx.save(); + if(this.direction == "horizontal"){ + ctx.scale(-1, 1); + ctx.translate(-(this.width+2*x), 0); + this.img.render(ctx, x, y); + } + if (this.direction == "vertical"){ + ctx.scale(1, -1); + ctx.translate(0, -(this.height+2*y)); + this.img.render(ctx, x, y); + } + ctx.restore(); +}; + + +FlipImage.prototype.getWidth = function() { + return this.width; +}; + +FlipImage.prototype.getHeight = function() { + return this.height; +}; + +FlipImage.prototype.equals = function(other, aUnionFind) { + return ( other instanceof FlipImage && + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY && + this.width == other.width && + this.height == other.height && + this.direction == other.direction && + plt.baselib.equality.equals(this.img, other.img, aUnionFind) ); }; + + +////////////////////////////////////////////////////////////////////// +// RectangleImage: Number Number Mode Color -> Image var RectangleImage = function(width, height, style, color) { BaseImage.call(this, width/2, height/2); this.width = width; @@ -506,7 +730,7 @@ var RectangleImage = function(width, height, style, color) { this.style = style; this.color = color; }; -RectangleImage.prototype = plt.baselib.heir(BaseImage.prototype); +RectangleImage.prototype = heir(BaseImage.prototype); RectangleImage.prototype.render = function(ctx, x, y) { @@ -538,7 +762,7 @@ RectangleImage.prototype.getHeight = function() { return this.height; }; -RectangleImage.prototype.isEqual = function(other, aUnionFind) { +RectangleImage.prototype.equals = function(other, aUnionFind) { return (other instanceof RectangleImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && @@ -550,123 +774,320 @@ RectangleImage.prototype.isEqual = function(other, aUnionFind) { ////////////////////////////////////////////////////////////////////// - -var TextImage = function(msg, size, color) { - BaseImage.call(this, 0, 0); - this.msg = msg; - this.size = size; +// RhombusImage: Number Number Mode Color -> Image +var RhombusImage = function(side, angle, style, color) { + // sin(angle/2-in-radians) * side = half of base + this.width = Math.sin(angle/2 * Math.PI / 180) * side * 2; + // cos(angle/2-in-radians) * side = half of height + this.height = Math.abs(Math.cos(angle/2 * Math.PI / 180)) * side * 2; + BaseImage.call(this, this.width/2, this.height/2); + this.side = side; + this.angle = angle; + this.style = style; this.color = color; - this.font = this.size + "px Optimer"; +}; +RhombusImage.prototype = heir(BaseImage.prototype); - - var canvas = makeCanvas(0, 0); - var ctx = canvas.getContext("2d"); - ctx.font = this.font; - var metrics = ctx.measureText(msg); - this.width = metrics.width; - // KLUDGE: I don't know how to get at the height. - this.height = ctx.measureText("m").width + 20; - -} - -TextImage.prototype = plt.baselib.heir(BaseImage.prototype); - -TextImage.prototype.render = function(ctx, x, y) { +RhombusImage.prototype.render = function(ctx, x, y) { ctx.save(); - ctx.font = this.font; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - ctx.fillStyle = colorString(this.color); - ctx.fillText(this.msg, x, y); + ctx.beginPath(); + // if angle < 180 start at the top of the canvas, otherwise start at the bottom + ctx.moveTo(x+this.getWidth()/2, y); + ctx.lineTo(x+this.getWidth(), y+this.getHeight()/2); + ctx.lineTo(x+this.getWidth()/2, y+this.getHeight()); + ctx.lineTo(x, y+this.getHeight()/2); + ctx.closePath(); + + if (this.style.toString().toLowerCase() == "outline") { + ctx.strokeStyle = colorString(this.color); + ctx.stroke(); + } + else { + ctx.fillStyle = colorString(this.color); + ctx.fill(); + } ctx.restore(); }; -TextImage.prototype.getWidth = function() { +RhombusImage.prototype.getWidth = function() { return this.width; }; -TextImage.prototype.getHeight = function() { +RhombusImage.prototype.getHeight = function() { return this.height; }; -TextImage.prototype.isEqual = function(other, aUnionFind) { - return (other instanceof TextImage && +RhombusImage.prototype.equals = function(other, aUnionFind) { + return (other instanceof RhombusImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && - this.msg == other.msg && - this.size == other.size && - plt.baselib.equality.equals(this.color, other.color, aUnionFind) && - this.font == other.font); -}; - - -////////////////////////////////////////////////////////////////////// - -var CircleImage = function(radius, style, color) { - BaseImage.call(this, radius, radius); - this.radius = radius; - this.style = style; - this.color = color; -} -CircleImage.prototype = plt.baselib.heir(BaseImage.prototype); - -CircleImage.prototype.render = function(ctx, x, y) { - ctx.save(); - ctx.beginPath(); - ctx.arc(x + this.radius, - y + this.radius, - this.radius, 0, 2*Math.PI, false); - ctx.closePath(); - if (this.style.toString().toLowerCase() == "outline") { - ctx.strokeStyle = colorString(this.color); - ctx.stroke(); - } else { - ctx.fillStyle = colorString(this.color); - ctx.fill(); - } - - ctx.restore(); -}; - -CircleImage.prototype.getWidth = function() { - return this.radius * 2; -}; - -CircleImage.prototype.getHeight = function() { - return this.radius * 2; -}; - -CircleImage.prototype.isEqual = function(other, aUnionFind) { - return (other instanceof CircleImage && - this.pinholeX == other.pinholeX && - this.pinholeY == other.pinholeY && - this.radius == other.radius && + this.side == other.side && + this.angle == other.angle && this.style == other.style && plt.baselib.equality.equals(this.color, other.color, aUnionFind)); }; - ////////////////////////////////////////////////////////////////////// +var ImageDataImage = function(imageData) { + BaseImage.call(this, 0, 0); + this.imageData = imageData; + this.width = imageData.width; + this.height = imageData.height; +}; + +ImageDataImage.prototype = heir(BaseImage.prototype); + +ImageDataImage.prototype.render = function(ctx, x, y) { + ctx.putImageData(this.imageData, x, y); +}; + +ImageDataImage.prototype.getWidth = function() { + return this.width; +}; + + +ImageDataImage.prototype.getHeight = function() { + return this.height; +}; + +ImageDataImage.prototype.equals = function(other, aUnionFind) { + return (other instanceof ImageDataImage && + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY); +}; + + + + +////////////////////////////////////////////////////////////////////// +// PolygonImage: Number Count Step Mode Color -> Image +// +// See http://www.algebra.com/algebra/homework/Polygons/Inscribed-and-circumscribed-polygons.lesson +// the polygon is inscribed in a circle, whose radius is length/2sin(pi/count) +// another circle is inscribed in the polygon, whose radius is length/2tan(pi/count) +// rotate a 3/4 quarter turn plus half the angle length to keep bottom base level +var PolygonImage = function(length, count, step, style, color) { + this.aVertices = []; + var xMax = 0; + var yMax = 0; + var xMin = 0; + var yMin = 0; + + this.outerRadius = Math.floor(length/(2*Math.sin(Math.PI/count))); + this.innerRadius = Math.floor(length/(2*Math.tan(Math.PI/count))); + var adjust = (3*Math.PI/2)+Math.PI/count; + + // rotate around outer circle, storing x,y pairs as vertices + // keep track of mins and maxs + var radians = 0; + for(var i = 0; i < count; i++) { + // rotate to the next vertex (skipping by this.step) + radians = radians + (step*2*Math.PI/count); + + var v = { x: this.outerRadius*Math.cos(radians-adjust), + y: this.outerRadius*Math.sin(radians-adjust) }; + if(v.x < xMin) xMin = v.x; + if(v.x > xMax) xMax = v.y; + if(v.y < yMin) yMin = v.x; + if(v.y > yMax) yMax = v.y; + this.aVertices.push(v); + } + // HACK: try to work around handling of non-integer coordinates in CANVAS + // by ensuring that the boundaries of the canvas are outside of the vertices + for(var i=0; i xMax) xMax = this.aVertices[i].x+1; + if(this.aVertices[i].y < yMin) yMin = this.aVertices[i].y-1; + if(this.aVertices[i].y > yMax) yMax = this.aVertices[i].y+1; + } + + this.width = Math.floor(xMax-xMin); + this.height = Math.floor(yMax-yMin); + this.length = length; + this.count = count; + this.step = step; + this.style = style; + this.color = color; + BaseImage.call(this, Math.floor(this.width/2), Math.floor(this.height/2)); +}; +PolygonImage.prototype = heir(BaseImage.prototype); + + +// shift all vertices by an offset to put the center of the polygon at the +// center of the canvas. Even-sided polygons highest points are in line with +// the innerRadius. Odd-sides polygons highest vertex is on the outerRadius +PolygonImage.prototype.render = function(ctx, x, y) { + var xOffset = x+Math.round(this.width/2); + var yOffset = y+((this.count % 2)? this.outerRadius : this.innerRadius); + + ctx.save(); + + ctx.beginPath(); + ctx.moveTo(xOffset+this.aVertices[0].x, yOffset+this.aVertices[0].y); + for(var i=1; i Image +////////////////////////////////////////////////////////////////////// +// TextImage: String Number Color String String String String any/c -> Image +var TextImage = function(msg, size, color, face, family, style, weight, underline) { + var metrics; + this.msg = msg; + this.size = size; + this.color = color; + this.face = face; + this.family = family; + this.style = (style == "slant")? "oblique" : style; // Racket's "slant" -> CSS's "oblique" + this.weight = (weight== "light")? "lighter" : weight; // Racket's "light" -> CSS's "lighter" + this.underline = underline; + // example: "bold italic 20px 'Times', sans-serif". + // Default weight is "normal", face is "Optimer" + var canvas = makeCanvas(0, 0); + var ctx = canvas.getContext("2d"); + + this.font = (this.weight + " " + + this.style + " " + + this.size + "px " + + maybeQuote(this.face) + " " + + maybeQuote(this.family)); + try { + ctx.font = this.font; + } catch (e) { + this.fallbackOnFont(); + ctx.font = this.font; + } + + // Defensive: on IE, this can break. + try { + metrics = ctx.measureText(msg); + this.width = metrics.width; + this.height = Number(this.size); + } catch(e) { + this.fallbackOnFont(); + } + BaseImage.call(this, Math.round(this.width/2), 0);// weird pinhole settings needed for "baseline" alignment +} + + +TextImage.prototype = heir(BaseImage.prototype); + +TextImage.prototype.fallbackOnFont = function() { + // Defensive: if the browser doesn't support certain features, we + // reduce to a smaller feature set and try again. + this.font = this.size + "px " + maybeQuote(this.family); + var canvas = makeCanvas(0, 0); + var ctx = canvas.getContext("2d"); + ctx.font = this.font; + var metrics = ctx.measureText(this.msg); + this.width = metrics.width; + // KLUDGE: I don't know how to get at the height. + this.height = Number(this.size);//ctx.measureText("m").width + 20; +}; + + +TextImage.prototype.render = function(ctx, x, y) { + ctx.save(); + + ctx.textAlign = 'left'; + ctx.textBaseline= 'top'; + ctx.fillStyle = colorString(this.color); + ctx.font = this.font; + try { + ctx.fillText(this.msg, x, y); + } catch (e) { + this.fallbackOnFont(); + ctx.font = this.font; + ctx.fillText(this.msg, x, y); + } + if(this.underline){ + ctx.beginPath(); + ctx.moveTo(x, y+this.size); + // we use this.size, as it is more accurate for underlining than this.height + ctx.lineTo(x+this.width, y+this.size); + ctx.closePath(); + ctx.strokeStyle = colorString(this.color); + ctx.stroke(); + } + ctx.restore(); +}; + + +TextImage.prototype.getBaseline = function() { + return this.size; +}; + +TextImage.prototype.equals = function(other, aUnionFind) { + return (other instanceof TextImage && + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY && + this.msg == other.msg && + this.size == other.size && + this.face == other.face && + this.family == other.family && + this.style == other.style && + this.weight == other.weight && + this.underline == other.underline && + types.equals(this.color, other.color, aUnionFind) && + this.font == other.font); +}; + + +////////////////////////////////////////////////////////////////////// // StarImage: fixnum fixnum fixnum color -> image var StarImage = function(points, outer, inner, style, color) { BaseImage.call(this, Math.max(outer, inner), Math.max(outer, inner)); - this.points = points; - this.outer = outer; - this.inner = inner; - this.style = style; - this.color = color; - - this.radius = Math.max(this.inner, this.outer); + this.points = points; + this.outer = outer; + this.inner = inner; + this.style = style; + this.color = color; + this.radius = Math.max(this.inner, this.outer); + this.width = this.radius*2; + this.height = this.radius*2; }; -StarImage.prototype = plt.baselib.heir(BaseImage.prototype); +StarImage.prototype = heir(BaseImage.prototype); var oneDegreeAsRadian = Math.PI / 180; @@ -691,22 +1112,10 @@ StarImage.prototype.render = function(ctx, x, y) { ctx.fillStyle = colorString(this.color); ctx.fill(); } - ctx.restore(); }; -// getWidth: -> fixnum -StarImage.prototype.getWidth = function() { - return this.radius * 2; -}; - - -// getHeight: -> fixnum -StarImage.prototype.getHeight = function() { - return this.radius * 2; -}; - -StarImage.prototype.isEqual = function(other, aUnionFind) { +StarImage.prototype.equals = function(other, aUnionFind) { return (other instanceof StarImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && @@ -714,25 +1123,26 @@ StarImage.prototype.isEqual = function(other, aUnionFind) { this.outer == other.outer && this.inner == other.inner && this.style == other.style && - plt.baselib.equality.equals(this.color, other.color, aUnionFind)); + types.equals(this.color, other.color, aUnionFind)); }; - -////////////////////////////////////////////////////////////////////// -//Triangle -/////// -var TriangleImage = function(side, style, color) { - this.width = side; - this.height = Math.ceil(side * Math.sqrt(3) / 2); - +///////////////////////////////////////////////////////////////////// +//TriangleImage: Number Number Mode Color -> Image +var TriangleImage = function(side, angle, style, color) { + // sin(angle/2-in-radians) * side = half of base + this.width = Math.sin(angle/2 * Math.PI / 180) * side * 2; + // cos(angle/2-in-radians) * side = height of altitude + this.height = Math.floor(Math.abs(Math.cos(angle/2 * Math.PI / 180)) * side); + BaseImage.call(this, Math.floor(this.width/2), Math.floor(this.height/2)); this.side = side; + this.angle = angle; this.style = style; this.color = color; } -TriangleImage.prototype = plt.baselib.heir(BaseImage.prototype); +TriangleImage.prototype = heir(BaseImage.prototype); TriangleImage.prototype.render = function(ctx, x, y) { @@ -740,11 +1150,18 @@ TriangleImage.prototype.render = function(ctx, x, y) { var height = this.getHeight(); ctx.save(); ctx.beginPath(); - ctx.moveTo(x + this.side/2, y); - ctx.lineTo(x + width, y + height); - ctx.lineTo(x, y + height); + // if angle < 180 start at the top of the canvas, otherwise start at the bottom + if(this.angle < 180){ + ctx.moveTo(x+width/2, y); + ctx.lineTo(x, y+height); + ctx.lineTo(x+width, y+height); + } else { + ctx.moveTo(x+width/2, y+height); + ctx.lineTo(x, y); + ctx.lineTo(x+width, y); + } ctx.closePath(); - + if (this.style.toString().toLowerCase() == "outline") { ctx.strokeStyle = colorString(this.color); ctx.stroke(); @@ -756,29 +1173,64 @@ TriangleImage.prototype.render = function(ctx, x, y) { ctx.restore(); }; - - -TriangleImage.prototype.getWidth = function() { - return this.width; -}; - -TriangleImage.prototype.getHeight = function() { - return this.height; -}; - -TriangleImage.prototype.isEqual = function(other, aUnionFind) { +TriangleImage.prototype.equals = function(other, aUnionFind) { return (other instanceof TriangleImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && this.side == other.side && + this.angle == other.angle && this.style == other.style && - plt.baselib.equality.equals(this.color, other.color, aUnionFind)); + types.equals(this.color, other.color, aUnionFind)); }; +///////////////////////////////////////////////////////////////////// +//RightTriangleImage: Number Number Mode Color -> Image +var RightTriangleImage = function(side1, side2, style, color) { + this.width = side1; + this.height = side2; + + BaseImage.call(this, Math.floor(this.width/2), Math.floor(this.height/2)); + this.side1 = side1; + this.side2 = side2; + this.style = style; + this.color = color; +} +RightTriangleImage.prototype = heir(BaseImage.prototype); +RightTriangleImage.prototype.render = function(ctx, x, y) { + var width = this.getWidth(); + var height = this.getHeight(); + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x, y+this.side2); + ctx.lineTo(x+this.side1, y+this.side2); + ctx.lineTo(x, y); + ctx.closePath(); + + if (this.style.toString().toLowerCase() == "outline") { + ctx.strokeStyle = colorString(this.color); + ctx.stroke(); + } + else { + ctx.fillStyle = colorString(this.color); + ctx.fill(); + } + ctx.restore(); +}; + +RightTriangleImage.prototype.equals = function(other, aUnionFind) { + return (other instanceof RightTriangleImage && + this.pinholeX == other.pinholeX && + this.pinholeY == other.pinholeY && + this.side1 == other.side1 && + this.side2 == other.side2 && + this.style == other.style && + types.equals(this.color, other.color, aUnionFind)); +}; + ////////////////////////////////////////////////////////////////////// -//Ellipse +//Ellipse : Number Number Mode Color -> Image var EllipseImage = function(width, height, style, color) { BaseImage.call(this, Math.floor(width/2), Math.floor(height/2)); this.width = width; @@ -787,7 +1239,7 @@ var EllipseImage = function(width, height, style, color) { this.color = color; }; -EllipseImage.prototype = plt.baselib.heir(BaseImage.prototype); +EllipseImage.prototype = heir(BaseImage.prototype); EllipseImage.prototype.render = function(ctx, aX, aY) { @@ -821,28 +1273,20 @@ EllipseImage.prototype.render = function(ctx, aX, aY) { ctx.restore(); }; -EllipseImage.prototype.getWidth = function() { - return this.width; -}; - -EllipseImage.prototype.getHeight = function() { - return this.height; -}; - -EllipseImage.prototype.isEqual = function(other, aUnionFind) { +EllipseImage.prototype.equals = function(other, aUnionFind) { return (other instanceof EllipseImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && this.width == other.width && this.height == other.height && this.style == other.style && - plt.baselib.equality.equals(this.color, other.color, aUnionFind)); + types.equals(this.color, other.color, aUnionFind)); }; ////////////////////////////////////////////////////////////////////// -//Line -var LineImage = function(x, y, color) { +//Line: Number Number Color Boolean -> Image +var LineImage = function(x, y, color, normalPinhole) { if (x >= 0) { if (y >= 0) { BaseImage.call(this, 0, 0); @@ -856,21 +1300,27 @@ var LineImage = function(x, y, color) { BaseImage.call(this, -x, -y); } } - - + this.x = x; this.y = y; this.color = color; this.width = Math.abs(x) + 1; this.height = Math.abs(y) + 1; + + // put the pinhle in the center of the image + if(normalPinhole){ + this.pinholeX = this.width/2; + this.pinholeY = this.height/2; + } } -LineImage.prototype = plt.baselib.heir(BaseImage.prototype); +LineImage.prototype = heir(BaseImage.prototype); LineImage.prototype.render = function(ctx, xstart, ystart) { ctx.save(); - + ctx.beginPath(); + ctx.strokeStyle = colorString(this.color); if (this.x >= 0) { if (this.y >= 0) { ctx.moveTo(xstart, ystart); @@ -890,28 +1340,19 @@ LineImage.prototype.render = function(ctx, xstart, ystart) { ctx.lineTo(xstart, ystart); } } - ctx.strokeStyle = colorString(this.color); + ctx.closePath(); ctx.stroke(); ctx.restore(); }; -LineImage.prototype.getWidth = function() { - return this.width; -}; - - -LineImage.prototype.getHeight = function() { - return this.height; -}; - -LineImage.prototype.isEqual = function(other, aUnionFind) { +LineImage.prototype.equals = function(other, aUnionFind) { return (other instanceof LineImage && this.pinholeX == other.pinholeX && this.pinholeY == other.pinholeY && this.x == other.x && this.y == other.y && - plt.baselib.equality.equals(this.color, other.color, aUnionFind)); + types.equals(this.color, other.color, aUnionFind)); }; @@ -925,11 +1366,14 @@ LineImage.prototype.isEqual = function(other, aUnionFind) { + + + var makeSceneImage = function(width, height, children, withBorder) { return new SceneImage(width, height, children, withBorder); }; var makeCircleImage = function(radius, style, color) { - return new CircleImage(radius, style, color); + return new EllipseImage(2*radius, 2*radius, style, color); }; var makeStarImage = function(points, outer, inner, style, color) { return new StarImage(points, outer, inner, style, color); @@ -937,32 +1381,80 @@ var makeStarImage = function(points, outer, inner, style, color) { var makeRectangleImage = function(width, height, style, color) { return new RectangleImage(width, height, style, color); }; -var makeTriangleImage = function(side, style, color) { - return new TriangleImage(side, style, color); +var makeRhombusImage = function(side, angle, style, color) { + return new RhombusImage(side, angle, style, color); +}; +var makePolygonImage = function(length, count, step, style, color) { + return new PolygonImage(length, count, step, style, color); +}; +var makeSquareImage = function(length, style, color) { + return new RectangleImage(length, length, style, color); +}; +var makeRightTriangleImage = function(side1, side2, style, color) { + return new RightTriangleImage(side1, side2, style, color); +}; +var makeTriangleImage = function(side, angle, style, color) { + return new TriangleImage(side, angle, style, color); }; var makeEllipseImage = function(width, height, style, color) { return new EllipseImage(width, height, style, color); }; -var makeLineImage = function(x, y, color) { - return new LineImage(x, y, color); +var makeLineImage = function(x, y, color, normalPinhole) { + return new LineImage(x, y, color, normalPinhole); }; -var makeOverlayImage = function(img1, img2, shiftX, shiftY) { - return new OverlayImage(img1, img2, shiftX, shiftY); +var makeOverlayImage = function(img1, img2, X, Y) { + return new OverlayImage(img1, img2, X, Y); }; var makeRotateImage = function(angle, img) { return new RotateImage(angle, img); }; var makeScaleImage = function(xFactor, yFactor, img) { - return new ScaleImage(xFactor, yFactor, img); + return new ScaleImage(xFactor, yFactor, img); }; -var makeTextImage = function(msg, size, color) { - return new TextImage(msg, size, color); +var makeCropImage = function(x, y, width, height, img) { + return new CropImage(x, y, width, height, img); +}; +var makeFrameImage = function(img) { + return new FrameImage(img); +}; +var makeFlipImage = function(img, direction) { + return new FlipImage(img, direction); +}; +var makeTextImage = function(msg, size, color, face, family, style, weight, underline) { + return new TextImage(msg, size, color, face, family, style, weight, underline); +}; +var makeImageDataImage = function(imageData) { + return new ImageDataImage(imageData); }; var makeFileImage = function(path, rawImage) { return FileImage.makeInstance(path, rawImage); }; +var makeVideoImage = function(path, rawVideo) { + return VideoImage.makeInstance(path, rawVideo); +}; +var isSceneImage = function(x) { return x instanceof SceneImage; }; +var isCircleImage = function(x) { return x instanceof EllipseImage && + x.width === x.height; }; +var isStarImage = function(x) { return x instanceof StarImage; }; +var isRectangleImage=function(x) { return x instanceof RectangleImage; }; +var isPolygonImage = function(x) { return x instanceof PolygonImage; }; +var isRhombusImage = function(x) { return x instanceof RhombusImage; }; +var isSquareImage = function(x) { return x instanceof SquareImage; }; +var isTriangleImage= function(x) { return x instanceof TriangleImage; }; +var isRightTriangleImage = function(x) { return x instanceof RightTriangleImage; }; +var isEllipseImage = function(x) { return x instanceof EllipseImage; }; +var isLineImage = function(x) { return x instanceof LineImage; }; +var isOverlayImage = function(x) { return x instanceof OverlayImage; }; +var isRotateImage = function(x) { return x instanceof RotateImage; }; +var isScaleImage = function(x) { return x instanceof ScaleImage; }; +var isCropImage = function(x) { return x instanceof CropImage; }; +var isFrameImage = function(x) { return x instanceof FrameImage; }; +var isFlipImage = function(x) { return x instanceof FlipImage; }; +var isTextImage = function(x) { return x instanceof TextImage; }; +var isFileImage = function(x) { return x instanceof FileImage; }; +var isFileVideo = function(x) { return x instanceof FileVideo; }; @@ -977,17 +1469,30 @@ EXPORTS.makeCanvas = makeCanvas; EXPORTS.BaseImage = BaseImage; EXPORTS.SceneImage = SceneImage; -EXPORTS.CircleImage = CircleImage; -EXPORTS.StarImage = StarImage; -EXPORTS.RectangleImage = RectangleImage; -EXPORTS.TriangleImage = TriangleImage; -EXPORTS.EllipseImage = EllipseImage; -EXPORTS.LineImage = LineImage; +EXPORTS.FileImage = FileImage; +EXPORTS.VideoImage = VideoImage; EXPORTS.OverlayImage = OverlayImage; EXPORTS.RotateImage = RotateImage; EXPORTS.ScaleImage = ScaleImage; +EXPORTS.CropImage = CropImage; +EXPORTS.FrameImage = FrameImage; +EXPORTS.FlipImage = FlipImage; +EXPORTS.RectangleImage = RectangleImage; +EXPORTS.RhombusImage = RhombusImage; +EXPORTS.ImageDataImage = ImageDataImage; +EXPORTS.PolygonImage = PolygonImage; EXPORTS.TextImage = TextImage; -EXPORTS.FileImage = FileImage; +EXPORTS.StarImage = StarImage; +EXPORTS.TriangleImage = TriangleImage; +EXPORTS.RightTriangleImage = RightTriangleImage; +EXPORTS.EllipseImage = EllipseImage; +EXPORTS.LineImage = LineImage; +EXPORTS.StarImage = StarImage; + + + + + EXPORTS.colorDb = colorDb; @@ -995,30 +1500,46 @@ EXPORTS.makeSceneImage = makeSceneImage; EXPORTS.makeCircleImage = makeCircleImage; EXPORTS.makeStarImage = makeStarImage; EXPORTS.makeRectangleImage = makeRectangleImage; +EXPORTS.makeRhombusImage = makeRhombusImage; +EXPORTS.makePolygonImage = makePolygonImage; +EXPORTS.makeSquareImage = makeSquareImage; +EXPORTS.makeRightTriangleImage = makeRightTriangleImage; EXPORTS.makeTriangleImage = makeTriangleImage; EXPORTS.makeEllipseImage = makeEllipseImage; EXPORTS.makeLineImage = makeLineImage; EXPORTS.makeOverlayImage = makeOverlayImage; EXPORTS.makeRotateImage = makeRotateImage; EXPORTS.makeScaleImage = makeScaleImage; +EXPORTS.makeCropImage = makeCropImage; +EXPORTS.makeFrameImage = makeFrameImage; +EXPORTS.makeFlipImage = makeFlipImage; EXPORTS.makeTextImage = makeTextImage; +EXPORTS.makeImageDataImage = makeImageDataImage; EXPORTS.makeFileImage = makeFileImage; - +EXPORTS.makeVideoImage = makeVideoImage; EXPORTS.isImage = isImage; EXPORTS.isScene = isScene; EXPORTS.isColorOrColorString = isColorOrColorString; -EXPORTS.isSceneImage = function(x) { return x instanceof SceneImage; }; -EXPORTS.isCircleImage = function(x) { return x instanceof CircleImage; }; -EXPORTS.isStarImage = function(x) { return x instanceof StarImage; }; -EXPORTS.isRectangleImage = function(x) { return x instanceof RectangleImage; }; -EXPORTS.isTriangleImage = function(x) { return x instanceof TriangleImage; }; -EXPORTS.isEllipseImage = function(x) { return x instanceof EllipseImage; }; -EXPORTS.isLineImage = function(x) { return x instanceof LineImage; }; -EXPORTS.isOverlayImage = function(x) { return x instanceof OverlayImage; }; -EXPORTS.isRotateImage = function(x) { return x instanceof RotateImage; }; -EXPORTS.isScaleImage = function(x) { return x instanceof ScaleImage; }; -EXPORTS.isTextImage = function(x) { return x instanceof TextImage; }; -EXPORTS.isFileImage = function(x) { return x instanceof FileImage; }; +EXPORTS.isSceneImage = isSceneImage; +EXPORTS.isCircleImage = isCircleImage; +EXPORTS.isStarImage = isStarImage; +EXPORTS.isRectangleImage = isRectangleImage; +EXPORTS.isPolygonImage = isPolygonImage; +EXPORTS.isRhombusImage = isRhombusImage; +EXPORTS.isSquareImage = isSquareImage; +EXPORTS.isTriangleImage = isTriangleImage; +EXPORTS.isRightTriangleImage = isRightTriangleImage; +EXPORTS.isEllipseImage = isEllipseImage; +EXPORTS.isLineImage = isLineImage; +EXPORTS.isOverlayImage = isOverlayImage; +EXPORTS.isRotateImage = isRotateImage; +EXPORTS.isScaleImage = isScaleImage; +EXPORTS.isCropImage = isCropImage; +EXPORTS.isFrameImage = isFrameImage; +EXPORTS.isFlipImage = isFlipImage; +EXPORTS.isTextImage = isTextImage; +EXPORTS.isFileImage = isFileImage; +EXPORTS.isFileVideo = isFileVideo;