whalesong/image/private/kernel.js
2011-08-07 20:51:00 -04:00

1642 lines
48 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'].namespace;
var colorStruct = colorNamespace['struct:color'];
var makeColor = colorStruct.constructor;
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) + ")");
};
// Produces true if thing is an image.
var isImage = function(thing) {
return (thing instanceof BaseImage);
};
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);
};
// 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 && typeof window.G_vmlCanvasManager != 'undefined') {
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(cache) {
var that = this;
var width = that.getWidth();
var height = that.getHeight();
var canvas = makeCanvas(width, height);
// 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.
$(canvas).bind('afterAttach',
function(event) {
var ctx = canvas.getContext("2d");
that.render(ctx, 0, 0);
});
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;
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) {
ctx.drawImage(this.img, x, y);
};
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(cache) {
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;
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));
this.img1 = img1;
this.img2 = img2;
this.width = right - left;
this.height = bottom - top;
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);
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();
};
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.makeList.apply(null, 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;