whalesong/image/private/kernel.js
Danny Yoo 3ed2d19eab adding expectations for what happens for module-scoping test.
fixing up the namespace stuff so it goes through getters and setters
trying to add the necessary to the il, but running into typed racket issues
corrected compilation of toplevelref so it works more correctly on module
variables.
2012-02-26 22:59:37 -05:00

1672 lines
50 KiB
JavaScript

// Basic implementation of the image library.
//
// This should mimic the implementation of 2htdp/image.
//////////////////////////////////////////////////////////////////////
var colorNamespace = MACHINE.modules['whalesong/image/private/color.rkt'].getNamespace();
var colorStruct = colorNamespace.get('struct:color');
var makeColor = function(r,g,b,a) { return colorStruct.constructor([r,g,b,a]); };
var isColor = colorStruct.predicate;
var colorRed = function(c) { return colorStruct.accessor(c, 0); };
var colorGreen = function(c) { return colorStruct.accessor(c, 1); };
var colorBlue = function(c) { return colorStruct.accessor(c, 2); };
var colorAlpha = function(c) { return colorStruct.accessor(c, 3); };
//////////////////////////////////////////////////////////////////////
var heir = plt.baselib.heir;
var clone = plt.baselib.clone;
var isAngle = function(x) {
return plt.baselib.numbers.isReal(x) &&
jsnums.greaterThanOrEqual(x, 0) &&
jsnums.lessThan(x, 360);
};
// 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) ||
plt.baselib.symbols.isSymbol(thing)) &&
typeof(colorDb.get(thing)) != 'undefined'));
}
var colorString = function(aColor) {
return ("rgb(" +
colorRed(aColor) + "," +
colorGreen(aColor) + ", " +
colorBlue(aColor) + ")");
};
var isSideCount = function(x) {
return plt.baselib.numbers.isInteger(x) && jsnums.greaterThanOrEqual(x, 3);
};
var isStepCount = function(x) {
return plt.baselib.numbers.isInteger(x) && jsnums.greaterThanOrEqual(x, 1);
};
var isPointsCount = function(x) {
return plt.baselib.numbers.isNatural(x) && jsnums.greaterThanOrEqual(x, 2);
};
// Produces true if thing is an image-like object.
var isImage = function(thing) {
if (typeof(thing.getHeight) !== 'function')
return false;
if (typeof(thing.getWidth) !== 'function')
return false;
if (typeof(thing.getBaseline) !== 'function')
return false;
if (typeof(thing.updatePinhole) !== 'function')
return false;
if (typeof(thing.render) !== 'function')
return false;
return true;
};
// Base class for all images.
var BaseImage = function(pinholeX, pinholeY) {
this.pinholeX = pinholeX;
this.pinholeY = pinholeY;
};
BaseImage.prototype.updatePinhole = function(x, y) {
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
// Render the image, where the upper-left corner of the image is drawn at
// (x, y).
// NOTE: the rendering should be oblivous to the pinhole.
BaseImage.prototype.render = function(ctx, x, y) {
throw new Error('BaseImage.render unimplemented!');
};
// makeCanvas: number number -> canvas
// Constructs a canvas object of a particular width and height.
var makeCanvas = function(width, height) {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
$(canvas).css('width', canvas.width + "px");
$(canvas).css('height', canvas.height + "px");
$(canvas).css('padding', '0px');
// KLUDGE: IE compatibility uses /js/excanvas.js, and dynamic
// elements must be marked this way.
if (window.G_vmlCanvasManager) {
canvas = window.G_vmlCanvasManager.initElement(canvas);
}
return canvas;
};
var withIeHack = function(canvas, f) {
// canvas.style.display = 'none';
// document.body.appendChild(canvas);
// try {
var result = f(canvas);
// } catch(e) {
// document.body.removeChild(canvas);
// canvas.style.display = '';
// throw e;
// }
// document.body.removeChild(canvas);
// canvas.style.display = '';
return result;
};
// Images are expected to define a render() method, which is used
// here to draw to the canvas.
BaseImage.prototype.toDomNode = function(params) {
var that = this;
var width = that.getWidth();
var height = that.getHeight();
var canvas = makeCanvas(width, height);
var ctx;
// // Try best effort to render to screen at this point.
// try {
// ctx = canvas.getContext("2d");
// that.render(ctx, 0, 0);
// } catch (e) {
// }
// KLUDGE: on IE, the canvas rendering functions depend on a
// context where the canvas is attached to the DOM tree.
// We initialize an afterAttach hook; the client's responsible
// for calling this after the dom node is attached to the
// document.
var onAfterAttach = function(event) {
// $(canvas).unbind('afterAttach', onAfterAttach);
var ctx = this.getContext("2d");
that.render(ctx, 0, 0);
};
$(canvas).bind('afterAttach', onAfterAttach);
// Canvases lose their drawn content on cloning. data may help us to preserve it.
$(canvas).data('toRender', onAfterAttach);
return canvas;
};
BaseImage.prototype.toWrittenString = function(cache) { return "<image>"; }
BaseImage.prototype.toDisplayedString = function(cache) { return "<image>"; }
BaseImage.prototype.equals = function(other, aUnionFind) {
return (this.pinholeX == other.pinholeX &&
this.pinholeY == other.pinholeY);
};
// isScene: any -> boolean
// Produces true when x is a scene.
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);
this.width = width;
this.height = height;
this.children = children; // arrayof [image, number, number]
this.withBorder = withBorder;
}
SceneImage.prototype = heir(BaseImage.prototype);
// add: image primitive-number primitive-number -> Scene
SceneImage.prototype.add = function(anImage, x, y) {
return new SceneImage(this.width,
this.height,
this.children.concat([[anImage,
x - anImage.pinholeX,
y - anImage.pinholeY]]),
this.withBorder);
};
// render: 2d-context primitive-number primitive-number -> void
SceneImage.prototype.render = function(ctx, x, y) {
var i;
var childImage, childX, childY;
// Clear the scene.
ctx.clearRect(x, y, this.width, this.height);
// Then ask every object to render itself.
for(i = 0; i < this.children.length; i++) {
childImage = this.children[i][0];
childX = this.children[i][1];
childY = this.children[i][2];
ctx.save();
childImage.render(ctx, childX + x, childY + y);
ctx.restore();
}
// Finally, draw the black border if withBorder is true
if (this.withBorder) {
ctx.strokeStyle = 'black';
ctx.strokeRect(x, y, this.width, this.height);
}
};
SceneImage.prototype.equals = function(other, aUnionFind) {
if (!(other instanceof SceneImage)) {
return false;
}
if (this.pinholeX != other.pinholeX ||
this.pinholeY != other.pinholeY ||
this.width != other.width ||
this.height != other.height ||
this.children.length != other.children.length) {
return false;
}
for (var i = 0; i < this.children.length; i++) {
var rec1 = this.children[i];
var rec2 = other.children[i];
if (rec1[1] !== rec2[1] ||
rec1[2] !== rec2[2] ||
!plt.baselib.equality.equals(rec1[0],
rec2[0],
aUnionFind)) {
return false;
}
}
return true;
};
//////////////////////////////////////////////////////////////////////
// FileImage: string node -> Image
var FileImage = function(src, rawImage) {
BaseImage.call(this, 0, 0);
var self = this;
this.src = src;
this.isLoaded = false;
// animationHack: see installHackToSupportAnimatedGifs() for details.
this.animationHackImg = undefined;
if (rawImage && rawImage.complete) {
this.img = rawImage;
this.isLoaded = true;
this.pinholeX = self.img.width / 2;
this.pinholeY = self.img.height / 2;
} 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.img = new Image();
this.img.onload = function() {
self.isLoaded = true;
self.pinholeX = self.img.width / 2;
self.pinholeY = self.img.height / 2;
};
this.img.onerror = function(e) {
self.img.onerror = "";
self.img.src = "http://www.wescheme.org/images/broken.png";
}
this.img.src = src;
}
}
FileImage.prototype = heir(BaseImage.prototype);
var imageCache = {};
FileImage.makeInstance = function(path, rawImage) {
if (! (path in imageCache)) {
imageCache[path] = new FileImage(path, rawImage);
}
return imageCache[path];
};
FileImage.installInstance = function(path, rawImage) {
imageCache[path] = new FileImage(path, rawImage);
};
FileImage.installBrokenImage = function(path) {
imageCache[path] = new TextImage("Unable to load " + path, 10, colorDb.get("red"),
"normal", "Optimer","","",false);
};
FileImage.prototype.render = function(ctx, x, y) {
this.installHackToSupportAnimatedGifs();
ctx.drawImage(this.animationHackImg, x, y);
};
// The following is a hack that we use to allow animated gifs to show
// as animating on the canvas.
FileImage.prototype.installHackToSupportAnimatedGifs = function() {
if (this.animationHackImg) { return; }
this.animationHackImg = this.img.cloneNode(true);
document.body.appendChild(this.animationHackImg);
this.animationHackImg.width = 0;
this.animationHackImg.height = 0;
};
FileImage.prototype.getWidth = function() {
return this.img.width;
};
FileImage.prototype.getHeight = function() {
return this.img.height;
};
// Override toDomNode: we don't need a full-fledged canvas here.
FileImage.prototype.toDomNode = function(params) {
return this.img.cloneNode(true);
};
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);
var 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 placeX placeY -> image
// Creates an image that overlays img1 on top of the
// 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;
// calculate absolute offset of 2nd image's *CENTER*
// convert relative X,Y to center offsets,
// if placeX and placeY are UL corner offsets, convert to center offsets
if (placeX == "left" ) var xOffset = img2.getWidth()-(c1x+c2x);
else if (placeX == "right" ) var xOffset = img1.getWidth()-(c1x+c2x);
else if (placeX == "beside") var xOffset = c1x+c2x;
else if (placeX == "middle") var xOffset = 0;
else if (placeX == "center") var xOffset = 0;
else var xOffset = placeX - (c1x-c2x);
if (placeY == "bottom") var yOffset = img1.getHeight()-(c1y+c2y);
else if (placeY == "top") var yOffset = img2.getHeight()-(c1y+c2y);
else if (placeY == "above" ) var yOffset = c1y+c2y;
else if (placeY == "middle") var yOffset = 0;
else if (placeY == "center") var yOffset = 0;
else if (placeY == "baseline") var yOffset= img1.getBaseline()-img2.getBaseline();
else var yOffset = placeY - (c1y-c2y);
// Correct offsets when dealing with Scenes instead of images,
// by adding the center values
if(isScene(img1)){xOffset =+c1x; yOffset =+c1y;}
if(isScene(img2)){xOffset =+c2x; yOffset =+c2y;}
// The *center* of the 2nd image, once overlaid, changes by the original difference in centers,
// plus the size of the offsets. Calculate this delta for X and Y.
var deltaX = c1x - c2x + xOffset;
var deltaY = c1y - c2y + yOffset;
// Each edge of the new, combined image may be grown or shrunk, depending on deltaX or deltaY
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());
// Calculate the new width, height and center based on edge lengths
this.width = right - left;
this.height = bottom - top;
BaseImage.call(this,
Math.floor((right-left) / 2),
Math.floor((bottom-top) / 2));
// store the overlaid images, and the offsets for each
this.img1 = img1;
this.img2 = img2;
this.img1Dx = -left;
this.img1Dy = -top;
this.img2Dx = deltaX - left;
this.img2Dy = deltaY - top;
};
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.equals = function(other, aUnionFind) {
return ( other instanceof OverlayImage &&
this.pinholeX == other.pinholeX &&
this.pinholeY == other.pinholeY &&
this.width == other.width &&
this.height == other.height &&
this.img1Dx == other.img1Dx &&
this.img1Dy == other.img1Dy &&
this.img2Dx == other.img2Dx &&
this.img2Dy == other.img2Dy &&
plt.baselib.equality.equals(this.img1, other.img1, aUnionFind) &&
plt.baselib.equality.equals(this.img2, other.img2, 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
BaseImage.call(this,
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;
};
RotateImage.prototype = heir(BaseImage.prototype);
// translate the canvas using the calculated values, then draw at the rotated (x,y) offset.
RotateImage.prototype.render = function(ctx, x, y) {
// 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);
ctx.save();
ctx.translate(x + this.translateX, y + this.translateY);
ctx.rotate(this.angle * Math.PI / 180);
this.img.render(ctx, 0, 0);
ctx.restore();
};
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) );
};
//////////////////////////////////////////////////////////////////////
// ScaleImage: factor factor image -> image
// Scale an image
var ScaleImage = function(xFactor, yFactor, img) {
// resize the image
BaseImage.call(this,
Math.floor((img.getWidth() * xFactor) / 2),
Math.floor((img.getHeight() * yFactor) / 2));
this.img = img;
this.width = img.getWidth() * xFactor;
this.height = img.getHeight() * yFactor;
this.xFactor = xFactor;
this.yFactor = yFactor;
};
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 / this.xFactor, y / this.yFactor);
ctx.restore();
};
ScaleImage.prototype.equals = function(other, aUnionFind) {
return ( other instanceof ScaleImage &&
this.pinholeX == other.pinholeX &&
this.pinholeY == other.pinholeY &&
this.width == other.width &&
this.height == other.height &&
this.xFactor == other.xFactor &&
this.yFactor == other.yFactor &&
plt.baselib.equality.equals(this.img, other.img, 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();
};
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;
this.height = height;
this.style = style;
this.color = color;
};
RectangleImage.prototype = heir(BaseImage.prototype);
RectangleImage.prototype.render = function(ctx, x, y) {
if (this.style.toString().toLowerCase() == "outline") {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = colorString(this.color);
ctx.strokeRect(x, y, this.width, this.height);
ctx.closePath();
ctx.restore();
} else {
ctx.save();
ctx.beginPath();
ctx.fillStyle = colorString(this.color);
ctx.fillRect(x, y, this.width, this.height);
ctx.closePath();
ctx.restore();
}
};
RectangleImage.prototype.getWidth = function() {
return this.width;
};
RectangleImage.prototype.getHeight = function() {
return this.height;
};
RectangleImage.prototype.equals = function(other, aUnionFind) {
return (other instanceof RectangleImage &&
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));
};
//////////////////////////////////////////////////////////////////////
// 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;
};
RhombusImage.prototype = heir(BaseImage.prototype);
RhombusImage.prototype.render = function(ctx, x, y) {
ctx.save();
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();
};
RhombusImage.prototype.getWidth = function() {
return this.width;
};
RhombusImage.prototype.getHeight = function() {
return this.height;
};
RhombusImage.prototype.equals = function(other, aUnionFind) {
return (other instanceof RhombusImage &&
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));
};
//////////////////////////////////////////////////////////////////////
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<this.aVertices.length; i++){
if(this.aVertices[i].x < xMin) xMin = this.aVertices[i].x-1;
if(this.aVertices[i].x > 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<this.aVertices.length; i++){
ctx.lineTo(xOffset+this.aVertices[i].x, yOffset+this.aVertices[i].y);
}
ctx.lineTo(xOffset+this.aVertices[0].x, yOffset+this.aVertices[0].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();
};
PolygonImage.prototype.equals = function(other, aUnionFind) {
return (other instanceof PolygonImage &&
this.pinholeX == other.pinholeX &&
this.pinholeY == other.pinholeY &&
this.length == other.length &&
this.step == other.step &&
this.count == other.count &&
this.style == other.style &&
plt.baselib.equality.equals(this.color, other.color, aUnionFind));
};
var maybeQuote = function(s) {
if (/ /.test(s)) {
return "\"" + s + "\"";
}
return s;
}
//////////////////////////////////////////////////////////////////////
// TextImage: String Number Color String String String String any/c -> 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 &&
plt.baselib.equality.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.width = this.radius*2;
this.height = this.radius*2;
};
StarImage.prototype = heir(BaseImage.prototype);
var oneDegreeAsRadian = Math.PI / 180;
// render: context fixnum fixnum -> void
// Draws a star on the given context.
// Most of this code here adapted from the Canvas tutorial at:
// http://developer.apple.com/safari/articles/makinggraphicswithcanvas.html
StarImage.prototype.render = function(ctx, x, y) {
ctx.save();
ctx.beginPath();
for( var pt = 0; pt < (this.points * 2) + 1; pt++ ) {
var rads = ( ( 360 / (2 * this.points) ) * pt ) * oneDegreeAsRadian - 0.5;
var radius = ( pt % 2 == 1 ) ? this.outer : this.inner;
ctx.lineTo(x + this.radius + ( Math.sin( rads ) * radius ),
y + this.radius + ( Math.cos( rads ) * radius ) );
}
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();
};
StarImage.prototype.equals = function(other, aUnionFind) {
return (other instanceof StarImage &&
this.pinholeX == other.pinholeX &&
this.pinholeY == other.pinholeY &&
this.points == other.points &&
this.outer == other.outer &&
this.inner == other.inner &&
this.style == other.style &&
plt.baselib.equality.equals(this.color, other.color, aUnionFind));
};
/////////////////////////////////////////////////////////////////////
//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 = heir(BaseImage.prototype);
TriangleImage.prototype.render = function(ctx, x, y) {
var width = this.getWidth();
var height = this.getHeight();
ctx.save();
ctx.beginPath();
// 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();
}
else {
ctx.fillStyle = colorString(this.color);
ctx.fill();
}
ctx.restore();
};
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));
};
/////////////////////////////////////////////////////////////////////
//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 &&
plt.baselib.equality.equals(this.color, other.color, aUnionFind));
};
//////////////////////////////////////////////////////////////////////
//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;
this.height = height;
this.style = style;
this.color = color;
};
EllipseImage.prototype = heir(BaseImage.prototype);
EllipseImage.prototype.render = function(ctx, aX, aY) {
ctx.save();
ctx.beginPath();
// Most of this code is taken from:
// http://webreflection.blogspot.com/2009/01/ellipse-and-circle-for-canvas-2d.html
var hB = (this.width / 2) * .5522848,
vB = (this.height / 2) * .5522848,
eX = aX + this.width,
eY = aY + this.height,
mX = aX + this.width / 2,
mY = aY + this.height / 2;
ctx.moveTo(aX, mY);
ctx.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY);
ctx.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY);
ctx.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY);
ctx.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY);
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();
};
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));
};
//////////////////////////////////////////////////////////////////////
//Line: Number Number Color Boolean -> Image
var LineImage = function(x, y, color, normalPinhole) {
if (x >= 0) {
if (y >= 0) {
BaseImage.call(this, 0, 0);
} else {
BaseImage.call(this, 0, -y);
}
} else {
if (y >= 0) {
BaseImage.call(this, -x, 0);
} else {
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 = 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);
ctx.lineTo((xstart + this.x),
(ystart + this.y));
} else {
ctx.moveTo(xstart, ystart + (-this.y));
ctx.lineTo(xstart + this.x, ystart);
}
} else {
if (this.y >= 0) {
ctx.moveTo(xstart + (-this.x), ystart);
ctx.lineTo(xstart,
(ystart + this.y));
} else {
ctx.moveTo(xstart + (-this.x), ystart + (-this.y));
ctx.lineTo(xstart, ystart);
}
}
ctx.closePath();
ctx.stroke();
ctx.restore();
};
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));
};
var imageToColorList = function(img) {
var width = img.getWidth(),
height = img.getHeight(),
canvas = makeCanvas(width, height),
ctx = canvas.getContext("2d"),
imageData,
data,
i,
r, g, b, a;
img.render(ctx, 0, 0);
imageData = ctx.getImageData(0, 0, width, height);
data = imageData.data;
var colors = [];
for (i = 0 ; i < data.length; i += 4) {
r = data[i];
g = data[i+1];
b = data[i+2];
a = data[i+3];
colors.push(makeColor(r, g, b, a));
}
return plt.baselib.lists.arrayToList(colors);
}
var colorListToImage = function(listOfColors,
width,
height,
pinholeX,
pinholeY) {
var canvas = makeCanvas(jsnums.toFixnum(width),
jsnums.toFixnum(height)),
ctx = canvas.getContext("2d"),
imageData = ctx.createImageData(jsnums.toFixnum(width),
jsnums.toFixnum(height)),
data = imageData.data,
aColor, i = 0;
while (listOfColors !== plt.baselib.lists.EMPTY) {
aColor = listOfColors.first;
data[i] = jsnums.toFixnum(colorRed(aColor));
data[i+1] = jsnums.toFixnum(colorGreen(aColor));
data[i+2] = jsnums.toFixnum(colorBlue(aColor));
data[i+3] = jsnums.toFixnum(colorAlpha(aColor));
i += 4;
listOfColors = listOfColors.rest;
};
return makeImageDataImage(imageData);
};
var makeSceneImage = function(width, height, children, withBorder) {
return new SceneImage(width, height, children, withBorder);
};
var makeCircleImage = function(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);
};
var makeRectangleImage = function(width, height, style, color) {
return new RectangleImage(width, height, 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, normalPinhole) {
return new LineImage(x, y, color, normalPinhole);
};
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);
};
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; };
///////////////////////////////////////////////////////////////
// Exports
// These functions are available for direct access without the typechecks
// of the Racket-exposed functions.
EXPORTS.makeCanvas = makeCanvas;
EXPORTS.BaseImage = BaseImage;
EXPORTS.SceneImage = SceneImage;
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.StarImage = StarImage;
EXPORTS.TriangleImage = TriangleImage;
EXPORTS.RightTriangleImage = RightTriangleImage;
EXPORTS.EllipseImage = EllipseImage;
EXPORTS.LineImage = LineImage;
EXPORTS.StarImage = StarImage;
EXPORTS.colorDb = colorDb;
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.imageToColorList = imageToColorList;
EXPORTS.colorListToImage = colorListToImage;
EXPORTS.isImage = isImage;
EXPORTS.isScene = isScene;
EXPORTS.isColorOrColorString = isColorOrColorString;
EXPORTS.isAngle = isAngle;
EXPORTS.isSideCount = isSideCount;
EXPORTS.isStepCount = isStepCount;
EXPORTS.isPointsCount = isPointsCount;
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;
EXPORTS.makeColor = makeColor;
EXPORTS.isColor = isColor;
EXPORTS.colorRed = colorRed;
EXPORTS.colorGreen = colorGreen;
EXPORTS.colorBlue = colorBlue;
EXPORTS.colorAlpha = colorAlpha;