Ghost Text

Size
10,419 Kb
Views
32,384

How do I make an ghost text?

The outline of letters is extracted using marching squares, the outline is then projected outward. I have to admit this was the result of a "happy accident". I was going for something much less interesting, I'm very happy with this result :). What is a ghost text? How do you make a ghost text? This script and codes were developed by Sakri Rosenstrom on 06 September 2022, Tuesday.

Ghost Text Previews

Ghost Text - Script Codes HTML Codes

<!DOCTYPE html>
<html >
<head> <meta charset="UTF-8"> <title>Ghost Text</title> <link rel="stylesheet" href="css/style.css">
</head>
<body> <div id="canvasContainer"></div> <script src="js/index.js"></script>
</body>
</html>

Ghost Text - Script Codes CSS Codes

html, body{ margin : 0px; width : 100%; height : 100%; overflow: hidden; background-color:#000000;
}
#canvasContainer{ position: absolute; margin : 0px; width : 100%; height : 100%;
}
#controls{ position: absolute;
}

Ghost Text - Script Codes JS Codes

/* * * @author Sakri Rosenstrom * http://www.sakri.net * https://twitter.com/sakri * http://www.devstate.net * Sources for this can be found at: * https://github.com/sakri/sakriNetCommonJS */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri;	Sakri.MathUtil = {};	//used for radiansToDegrees and degreesToRadians	Sakri.MathUtil.PI_180 = Math.PI/180;	Sakri.MathUtil.ONE80_PI = 180/Math.PI;	//precalculations for values of 90, 270 and 360 in radians	Sakri.MathUtil.PI2 = Math.PI*2;	Sakri.MathUtil.HALF_PI = Math.PI/2;	Sakri.MathUtil.PI_AND_HALF = Math.PI+ Math.PI/2;	Sakri.MathUtil.NEGATIVE_HALF_PI = -Math.PI/2; //keep degrees between 0 and 360 Sakri.MathUtil.constrainDegreeTo360 = function(degree){ return (360 + degree % 360) % 360;//hmmm... looks a bit weird?! }; Sakri.MathUtil.constrainRadianTo2PI = function(rad){ return (Sakri.MathUtil.PI2 + rad % Sakri.MathUtil.PI2) % Sakri.MathUtil.PI2;//equally so... }; Sakri.MathUtil.radiansToDegrees = function(rad){ return rad*Sakri.MathUtil.ONE80_PI; }; Sakri.MathUtil.degreesToRadians = function(degree){ return degree * Sakri.MathUtil.PI_180; };	//return number between 1 and 0	Sakri.MathUtil.normalize = function(value, minimum, maximum){	return (value - minimum) / (maximum - minimum);	};	//map normalized number to values	Sakri.MathUtil.interpolate = function(normValue, minimum, maximum){	return minimum + (maximum - minimum) * normValue;	};	//map a value from one set to another	Sakri.MathUtil.map = function(value, min1, max1, min2, max2){	return Sakri.MathUtil.interpolate( Sakri.MathUtil.normalize(value, min1, max1), min2, max2);	}; Sakri.MathUtil.clamp = function(min,max,value){ if(value < min){ return min; } if(value > max){ return max; } return value; }; Sakri.MathUtil.clampRGB = function(value){ return Sakri.MathUtil.clamp(0, 255, value); };	Sakri.MathUtil.getRandomNumberInRange = function(min, max){	return min + Math.random() * (max - min);	};	Sakri.MathUtil.getRandomIntegerInRange = function(min, max){	return Math.round(Sakri.MathUtil.getRandomNumberInRange(min, max));	}; //Move to geom?	Sakri.MathUtil.getCircumferenceOfEllipse = function(width,height){	return ((Math.sqrt(.5 * ((width * width) + (height * height)))) * (Math.PI * 2)) / 2;	}; //from : http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb Sakri.MathUtil.rgbToHex = function(r, g, b) { return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } Sakri.MathUtil.hexToRgb = function(hex) { // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, function(m, r, g, b) { return r + r + g + g + b + b; }); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }
}(window));
//has a dependency on Sakri.MathUtil
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri;	Sakri.Geom = {}; //================================================== //=====================::POINT::==================== //================================================== Sakri.Geom.Point = function (x,y){ this.x = isNaN(x) ? 0 : x; this.y = isNaN(y) ? 0 : y; }; Sakri.Geom.Point.prototype.clone = function(){ return new Sakri.Geom.Point(this.x,this.y); }; Sakri.Geom.Point.prototype.update = function(x, y){ this.x = isNaN(x) ? this.x : x; this.y = isNaN(y) ? this.y : y; }; Sakri.Geom.Point.prototype.add = function(x, y){ this.x += isNaN(x) ? 0 : x; this.y += isNaN(y) ? 0 : y; }; Sakri.Geom.Point.prototype.equals = function(point){ return this.x==point.x && this.y==point.y; }; Sakri.Geom.Point.prototype.toString = function(){ return "{x:"+this.x+" , y:"+this.y+"}"; }; Sakri.Geom.Point.interpolate = function(pointA, pointB, normal){ return new Sakri.Geom.Point(Sakri.MathUtil.interpolate(normal, pointA.x, pointB.x) , Sakri.MathUtil.interpolate(normal, pointA.y, pointB.y)); }; Sakri.Geom.Point.distanceBetweenTwoPoints = function( point1, point2 ){ //console.log("Math.pow(point2.x - point1.x,2) : ",Math.pow(point2.x - point1.x,2)); return Math.sqrt( Math.pow(point2.x - point1.x,2) + Math.pow(point2.y - point1.y,2) ); }; Sakri.Geom.Point.angleBetweenTwoPoints = function(p1,p2){ return Math.atan2(p1.y-p2.y, p1.x-p2.x); }; Sakri.Geom.mirrorPointInRectangle = function(point,rect){ return new Sakri.Geom.Point(rect.width-point.x,rect.height-point.y); }; Sakri.Geom.randomizePoint = function(point,randomValue){ return new Sakri.Geom.Point(-randomValue+Math.random()*randomValue+point.x,-randomValue+Math.random()*randomValue+point.y); };	//==================================================	//===================::RECTANGLE::==================	//==================================================	Sakri.Geom.Rectangle = function (x, y, width, height){	this.update(x, y, width, height);	};	Sakri.Geom.Rectangle.prototype.update = function(x, y, width, height){	this.x = isNaN(x) ? 0 : x;	this.y = isNaN(y) ? 0 : y;	this.width = isNaN(width) ? 0 : width;	this.height = isNaN(height) ? 0 : height;	}; //TODO : doesn't work Sakri.Geom.Rectangle.prototype.inflate = function(x, y){ this.x -= isNaN(x) ? 0 : x; this.y -= isNaN(y) ? 0 : y; this.width += isNaN(x) ? 0 : x * 2; this.height += isNaN(y) ? 0 : y * 2; };	Sakri.Geom.Rectangle.prototype.updateToRect = function(rect){	this.x = rect.x;	this.y = rect.y;	this.width = rect.width;	this.height = rect.height;	};	Sakri.Geom.Rectangle.prototype.scaleX = function(scaleBy){	this.width *= scaleBy;	};	Sakri.Geom.Rectangle.prototype.scaleY = function(scaleBy){	this.height *= scaleBy;	};	Sakri.Geom.Rectangle.prototype.scale = function(scaleBy){	this.scaleX(scaleBy);	this.scaleY(scaleBy);	};	Sakri.Geom.Rectangle.prototype.getRight = function(){	return this.x + this.width;	};	Sakri.Geom.Rectangle.prototype.getBottom = function(){	return this.y + this.height;	}; Sakri.Geom.Rectangle.prototype.getCenter = function(){ return new Sakri.Geom.Point(this.getCenterX(), this.getCenterY()); }; Sakri.Geom.Rectangle.prototype.getCenterX = function(){ return this.x + this.width/2; }; Sakri.Geom.Rectangle.prototype.getCenterY=function(){ return this.y + this.height/2; }; Sakri.Geom.Rectangle.prototype.containsPoint = function(x, y){ return x >= this.x && y >= this.y && x <= this.getRight() && y <= this.getBottom(); }; Sakri.Geom.Rectangle.prototype.containsRect = function(rect){ return this.containsPoint(rect.x, rect.y) && this.containsPoint(rect.getRight(), rect.getBottom()); };	Sakri.Geom.Rectangle.prototype.isSquare = function(){	return this.width == this.height;	};	Sakri.Geom.Rectangle.prototype.isLandscape = function(){	return this.width > this.height;	};	Sakri.Geom.Rectangle.prototype.isPortrait = function(){	return this.width < this.height;	};	Sakri.Geom.Rectangle.prototype.getSmallerSide = function(){	return Math.min(this.width, this.height);	};	Sakri.Geom.Rectangle.prototype.getBiggerSide = function(){	return Math.max(this.width,this.height);	};	Sakri.Geom.Rectangle.prototype.getArea = function(){	return this.width * this.height;	};	Sakri.Geom.Rectangle.prototype.floor = function(){	this.x = Math.floor(this.x);	this.y = Math.floor(this.y);	this.width = Math.floor(this.width);	this.height = Math.floor(this.height);	};	Sakri.Geom.Rectangle.prototype.ceil = function(){	this.x = Math.ceil(this.x);	this.y = Math.ceil(this.y);	this.width = Math.ceil(this.width);	this.height = Math.ceil(this.height);	};	Sakri.Geom.Rectangle.prototype.round = function(){	this.x=Math.round(this.x);	this.y=Math.round(this.y);	this.width=Math.round(this.width);	this.height=Math.round(this.height);	};	Sakri.Geom.Rectangle.prototype.roundIn = function(){	this.x = Math.ceil(this.x);	this.y = Math.ceil(this.y);	this.width = Math.floor(this.width);	this.height = Math.floor(this.height);	};	Sakri.Geom.Rectangle.prototype.roundOut = function(){	this.x = Math.floor(this.x);	this.y = Math.floor(this.y);	this.width = Math.ceil(this.width);	this.height = Math.ceil(this.height);	};	Sakri.Geom.Rectangle.prototype.clone = function(){	return new Sakri.Geom.Rectangle(this.x, this.y, this.width, this.height);	};	Sakri.Geom.Rectangle.prototype.toString = function(){	return "Rectangle{x:"+this.x+" , y:"+this.y+" , width:"+this.width+" , height:"+this.height+"}";	};
}(window));
/** * Created by sakri on 27-1-14. */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.CanvasTextUtil = {}; //this method renders text into a canvas, then resizes the image by shrinkPercent //loops through the non transparent pixels of the resized image and returns those as an array //fontProperties should be an object of type Sakri.CanvasTextProperties Sakri.CanvasTextUtil.createTextParticles = function(text, shrinkPercent, fontProps){ var renderCanvas = document.createElement('canvas'); var renderContext = renderCanvas.getContext('2d'); var fontString = fontProperties.getFontString(); //console.log(fontString); renderContext.font = fontString; renderContext.textBaseline = "top"; //console.log(renderContext.measureText(text).width); renderCanvas.width = renderContext.measureText(text).width; renderCanvas.height = fontProps.fontSize + 10;//TODO : Need to implement getFirstNonTransparentPixel() //after a resize of a canvas, we have to reset these properties renderContext.font = fontString; renderContext.textBaseline = "top"; //console.log(renderCanvas.width, renderCanvas.height); renderContext.fillStyle = "#FF0000"; renderContext.fillText(text, 0, 0); var shrunkenCanvas = document.createElement('canvas'); shrunkenCanvas.width = Math.round(renderCanvas.width*shrinkPercent); shrunkenCanvas.height = Math.round(renderCanvas.height*shrinkPercent); var shrunkenContext = shrunkenCanvas.getContext('2d'); shrunkenContext.drawImage(renderCanvas, 0, 0, shrunkenCanvas.width , shrunkenCanvas.height ); var pixels = shrunkenContext.getImageData(0, 0, shrunkenCanvas.width, shrunkenCanvas.height); var data = pixels.data; var particles = []; var i, x, y; for(i = 0; i < data.length; i += 4) { if(data[i]>200){ x = ((i/4)%shrunkenCanvas.width)/shrinkPercent; y = Math.floor((i/4)/shrunkenCanvas.width)/shrinkPercent; particles.push(new Sakri.Geom.Point(x, y)); } } delete renderCanvas; delete shrunkenCanvas; return particles; }; Sakri.CanvasTextUtil.createImagesFromString = function(string, fillStyle, strokeStyle, strokeWidth, fontProps){ var fontString = fontProps.getFontString(); var characters = string.split(""); var images = []; var canvas, context, image, metrics, i,character; canvas = document.createElement("canvas"); for(i=0; i<characters.length; i++){ character = characters[i]; context = canvas.getContext("2d"); context.textBaseline = "top"; context.font = fontString; metrics = context.measureText(character); canvas.width = metrics.width; canvas.height = fontProps.fontSize;// TODO : use getFirstNonTransparentPixel for dynamic sizing //these properties have to be set twice as they vanish after setting a canvas width and height context = canvas.getContext("2d"); context.textBaseline = "top"; context.font = fontString; image = new Image(); image.width = canvas.width; image.height = canvas.height; if(fillStyle){ context.fillStyle = fillStyle; context.fillText (character,0, 0); } if(strokeStyle){ context.strokeStyle = strokeStyle; context.lineWidth = strokeWidth; context.strokeText(character, 0, 0); } image.src = canvas.toDataURL(); images[i] = image; } delete canvas; return images; }; //========================================================================================= //==============::CANVAS TEXT PROPERTIES::==================================== //======================================================== Sakri.CanvasTextProperties = function(fontWeight, fontStyle, fontSize, fontFace){ this.setFontWeight(fontWeight); this.setFontStyle(fontStyle); this.setFontSize(fontSize); this.fontFace = fontFace ? fontFace : "sans-serif"; }; Sakri.CanvasTextProperties.NORMAL = "normal"; Sakri.CanvasTextProperties.BOLD = "bold"; Sakri.CanvasTextProperties.BOLDER = "bolder"; Sakri.CanvasTextProperties.LIGHTER = "lighter"; Sakri.CanvasTextProperties.ITALIC = "italic"; Sakri.CanvasTextProperties.OBLIQUE = "oblique"; Sakri.CanvasTextProperties.prototype.setFontWeight = function(fontWeight){ switch (fontWeight){ case Sakri.CanvasTextProperties.NORMAL: case Sakri.CanvasTextProperties.BOLD: case Sakri.CanvasTextProperties.BOLDER: case Sakri.CanvasTextProperties.LIGHTER: this.fontWeight = fontWeight; break; default: this.fontWeight = Sakri.CanvasTextProperties.NORMAL; } }; Sakri.CanvasTextProperties.prototype.setFontStyle = function(fontStyle){ switch (fontStyle){ case Sakri.CanvasTextProperties.NORMAL: case Sakri.CanvasTextProperties.ITALIC: case Sakri.CanvasTextProperties.OBLIQUE: this.fontStyle = fontStyle; break; default: this.fontStyle = Sakri.CanvasTextProperties.NORMAL; } }; Sakri.CanvasTextProperties.prototype.setFontSize = function(fontSize){ if(fontSize && fontSize.indexOf && fontSize.indexOf("px")>-1){ var size = fontSize.split("px")[0]; fontProperites.fontSize = isNaN(size) ? 24 : size;//24 is just an arbitrary number return; } this.fontSize = isNaN(fontSize) ? 24 : fontSize;//24 is just an arbitrary number }; Sakri.CanvasTextProperties.prototype.getFontString = function(){ return this.fontWeight + " " + this.fontStyle + " " + this.fontSize + "px " + this.fontFace; };
}(window));
/** * Created by sakri on 27-1-14. */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.BitmapUtil = {}; //TODO : rename "canvas" to "source", if it's an img, create a canvas and draw the img into it Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown = function(canvas){ var context = canvas.getContext("2d"); var y, i, rowData; for(y=0; y<canvas.height; y++){ rowData = context.getImageData(0, y, canvas.width, 1).data; for(i=0; i<rowData.length; i+=4){ if(rowData[i+0] + rowData[i+1] + rowData[i+2] + rowData[i+3] > 0){ return new Sakri.Geom.Point(i/4, y); } } } return null; }; Sakri.BitmapUtil.getFirstNonTransparentPixelBottomUp = function(canvas){ var context = canvas.getContext("2d"); var y, i, rowData; for(y = canvas.height-1; y>-1; y--){ rowData = context.getImageData(0, y, canvas.width, 1).data; for(i=0; i<rowData.length; i+=4){ if(rowData[i+0] + rowData[i+1] + rowData[i+2] + rowData[i+3] > 0){ return new Sakri.Geom.Point(i/4, y); } } } return null; }; Sakri.BitmapUtil.getFirstNonTransparentPixelLeftToRight = function(canvas){ var context = canvas.getContext("2d"); var x, i, colData; for(x = 0; x < canvas.width; x++){ colData = context.getImageData(x, 0, 1, canvas.height).data; for(i=0; i<colData.length; i+=4){ if(colData[i+0] + colData[i+1] + colData[i+2] + colData[i+3] > 0){ return new Sakri.Geom.Point(x, i/4); } } } return null; }; Sakri.BitmapUtil.getFirstNonTransparentPixelRightToLeft = function(canvas){ var context = canvas.getContext("2d"); var x, i, colData; for(x = canvas.width-1; x >-1; x--){ colData = context.getImageData(x, 0, 1, canvas.height).data; for(i=0; i<colData.length; i+=4){ if(colData[i+0] + colData[i+1] + colData[i+2] + colData[i+3] > 0){ return new Sakri.Geom.Point(x, i/4); } } } return null; }; //cuts out rows and columns of pixels without color data from the top, bottom, left and right Sakri.BitmapUtil.trimImage = function(image){ var trimCanvas = Sakri.BitmapUtil.createTrimmedCanvas(image); image.src = trimCanvas.toDataURL(); }; Sakri.BitmapUtil.trimCanvas = function(canvas){ console.log("trimCanvas()", canvas.width, canvas.height); var trimCanvas = Sakri.BitmapUtil.createTrimmedCanvas(canvas); canvas.width = trimCanvas.width; canvas.height = trimCanvas.height; console.log("\t=>" , canvas.width, canvas.height); var context = canvas.getContext("2d"); context.drawImage(trimCanvas, 0, 0); }; Sakri.BitmapUtil.getCanvasTrimRectangle = function(canvas){ var rect = new Sakri.Geom.Rectangle(); rect.x = Sakri.BitmapUtil.getFirstNonTransparentPixelLeftToRight(canvas).x; rect.y = Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown(canvas).y; rect.width = Sakri.BitmapUtil.getFirstNonTransparentPixelRightToLeft(canvas).x - rect.x + 1; rect.height = Sakri.BitmapUtil.getFirstNonTransparentPixelBottomUp(canvas).y - rect.y + 1; return rect; } Sakri.BitmapUtil.createTrimmedCanvas = function(imageOrCanvas){ var trimCanvas = document.createElement("canvas"); var trimContext = trimCanvas.getContext("2d"); trimCanvas.width = imageOrCanvas.width; trimCanvas.height = imageOrCanvas.height; trimContext.drawImage(imageOrCanvas, 0, 0); var rect = Sakri.BitmapUtil.getCanvasTrimRectangle(trimCanvas); //console.log("createTrimmedCanvas() ", rect.toString()); trimCanvas.width = rect.width; trimCanvas.height = rect.height; trimContext = trimCanvas.getContext("2d"); trimContext.drawImage(imageOrCanvas, rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height); return trimCanvas; }; //capture rect is the content on canvas to be reflected, border defines the space between the original content and the reflection //captureRect must contain the properties x, y, width, height //For more interesting results add a gradient on top of the reflection Sakri.BitmapUtil.renderReflection = function(canvas, captureRect, border){ if(!border){ border = 5; } var context = canvas.getContext("2d"); context.save(); //move and flip vertically context.translate(captureRect.x, captureRect.y + captureRect.height*2 + border); context.scale(1, -1); context.drawImage(	canvas, captureRect.x, captureRect.y, captureRect.width, captureRect.height, 0, 0, captureRect.width, captureRect.height);//img,sx,sy,swidth,sheight,x,y,width,height context.restore(); };
}(window));
/** * Created by @sakri on 28-1-14. * * Somewhat Naive implementation in that there are cases where the edge detection gets stuck in an eternal loop. * This is currently "handled" by a MAX_POINTS variable. * This implementation is "good enough" for most use cases though. * */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.MarchingSquares = {}; //Update this when working with large shapes (large bitmaps) //the "edge detection loop" stops at this figure. This is in place in the event that an infinite loop somehow appears (Should never happen). Sakri.MarchingSquares.MAX_POINTS = 10000; //This is a lookup table of all possible 4 pixel grids, used to decide "scanning positions" during the edge detection process //Zeros represent transparent pixels, Ones represent a non transparent pixel Sakri.MarchingSquares.possibleGrids = { "0011":new Sakri.Geom.Point(1,0), "1011":new Sakri.Geom.Point(1,0), "0001":new Sakri.Geom.Point(1,0), "1001":new Sakri.Geom.Point(1,0), "0100":new Sakri.Geom.Point(0,-1), "0101":new Sakri.Geom.Point(0,-1), "0111":new Sakri.Geom.Point(0,-1), "0110":new Sakri.Geom.Point(0,-1), "1100":new Sakri.Geom.Point(-1,0), "1000":new Sakri.Geom.Point(-1,0), "1101":new Sakri.Geom.Point(-1,0), "1110":new Sakri.Geom.Point(0,1), "1010":new Sakri.Geom.Point(0,1), "0010":new Sakri.Geom.Point(0,1) }; /** * Apparently there were (are?) cases where duplicate points are registered along horizontal or vertical sets * of adjacent points. I haven't been able to reproduce this, but have left this option in place for now. */ Sakri.MarchingSquares.getUniquePoints = function(points){ console.log("MarchingSquares.getUniquePoints() points.length : ",points.length); var unique = {}; var uniquePoints = []; var pointString, p, i; for(i=0; i < points.length; i++){ p = points[i]; pointString = p.x+":"+p.y; if(unique[pointString] == null){ unique[pointString] = true; uniquePoints.push(p); } } console.log("MarchingSquares.getUniquePoints() uniquePoints.length : ",uniquePoints.length); return uniquePoints; }; //source can be a Canvas or an img element. See comment above getUniquePoints concerning the checkUnique flag Sakri.MarchingSquares.getBlobOutlinePoints = function(source, checkUnique){ //Create a copy with a one pixel blank "border" in case source image/canvas has pixels which touch the border //The edge scan operates with an offset of -1,-1 meaning the returned points are accurate var canvas = document.createElement("canvas"); canvas.width = source.width + 2; canvas.height = source.height + 2; var context = canvas.getContext("2d"); context.drawImage(source,1,1); if(checkUnique){ return Sakri.MarchingSquares.getUniquePoints(Sakri.MarchingSquares.scanOutlinePoints(canvas)); }else{ return Sakri.MarchingSquares.scanOutlinePoints(canvas); } }; //this should be private, should not be called directly Sakri.MarchingSquares.scanOutlinePoints = function(canvas){ var points = []; points[0] = Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown(canvas); if(points[0] == null){ return points; } points[0].add(-1, -1);//in order for the lookup to work, we move the position up and back one var context = canvas.getContext("2d"); var currentPosition = points[0]; var gridString = Sakri.MarchingSquares.getGridStringFromPoint(context, currentPosition); var next, i; for(i=1; i<Sakri.MarchingSquares.MAX_POINTS; i++){ next = Sakri.MarchingSquares.getNextEdgePoint(currentPosition, gridString); if(next.equals(points[0])){ break; } points[i] = next; currentPosition = next; gridString = Sakri.MarchingSquares.getGridStringFromPoint(context, currentPosition); } //Failsafe when the marching squares get stuck in an eternal loop. See note at the top. if(i >= Sakri.MarchingSquares.MAX_POINTS){ console.log("MarchingSquares.scanOutlinePoints Sakri.MarchingSquares.MAX_POINTS reached"); return []; } return points; }; Sakri.MarchingSquares.getGridStringFromPoint = function(context, point){ var gridString = ""; var data = context.getImageData(point.x, point.y, 2, 2).data; for(i=0; i<16; i+=4){ gridString += (data[i+0] + data[i+1] + data[i+2] + data[i+3] > 0 ? "1" : "0"); } return gridString; }; Sakri.MarchingSquares.getNextEdgePoint = function(point, gridString){ var offsetPoint = Sakri.MarchingSquares.possibleGrids[gridString]; if(point==null){ throw new Error("MarchingSquares Error : gridString:"+gridString+" , not found in possibleGrids"); } return new Sakri.Geom.Point(point.x + offsetPoint.x, point.y + offsetPoint.y); };
}(window));
window.requestAnimationFrame = window.__requestAnimationFrame || window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || (function () { return function (callback, element) { var lastTime = element.__lastTime; if (lastTime === undefined) { lastTime = 0; } var currTime = Date.now(); var timeToCall = Math.max(1, 33 - (currTime - lastTime)); window.setTimeout(callback, timeToCall); element.__lastTime = currTime + timeToCall; }; })();
var readyStateCheckInterval = setInterval( function() { if (document.readyState === "complete") { clearInterval(readyStateCheckInterval); init(); }
}, 10);
//========================
//general properties for demo set up
//========================
var canvas;
var context;
var canvasContainer;
var htmlBounds;
var bounds;
var minimumStageWidth = 250;
var minimumStageHeight = 250;
var intervalId = -1;
var timeoutId = -1;
var resizeTimeoutId = -1;
function init(){ canvas = document.createElement('canvas'); canvas.style.position = "absolute"; context = canvas.getContext("2d"); canvasContainer = document.getElementById("canvasContainer"); canvasContainer.appendChild(canvas); window.onresize = resizeHandler; commitResize();
}
function getWidth( element ){return Math.max(element.scrollWidth,element.offsetWidth,element.clientWidth );}
function getHeight( element ){return Math.max(element.scrollHeight,element.offsetHeight,element.clientHeight );}
//avoid running resize scripts repeatedly if a browser window is being resized by dragging
function resizeHandler(){ renderBackground(); clearTimeoutsAndIntervals(); clearTimeout (resizeTimeoutId); resizeTimeoutId = setTimeout(commitResize, 300 );
}
function commitResize(){ htmlBounds = new Sakri.Geom.Rectangle(0,0, getWidth(this.canvasContainer) , getHeight(canvasContainer)); if(htmlBounds.width>=800){ canvas.width = 800; canvas.style.left = htmlBounds.getCenterX() - 400+"px"; }else{ canvas.width = htmlBounds.width; canvas.style.left ="0px"; } if(htmlBounds.height>500){ canvas.height = 500; canvas.style.top = htmlBounds.getCenterY() - 300+"px"; }else{ canvas.height = htmlBounds.height; canvas.style.top ="0px"; } bounds = new Sakri.Geom.Rectangle(0,0, canvas.width, canvas.height); console.log("commitResize() : "+bounds.toString()); renderBackground(); context.font = fontProperties.getFontString(); context.textBaseline = "top"; if(bounds.width<minimumStageWidth || bounds.height<minimumStageHeight){ stageTooSmallHandler(); return; } startDemo();
}
function stageTooSmallHandler(){ var warning = "Sorry, bigger screen required :("; var props = new Sakri.CanvasTextProperties(null,null,24); context.font = props.getFontString(); context.fillText(warning, bounds.getCenterX() - context.measureText(warning).width/2, bounds.getCenterY()-12);
}
//========================
//Demo specific properties
//========================
var words = ["GHOST", "SAKRI", "CANVAS", "CodePen", "DevState" ];
var currentWord;
var wordIndex = 0;
var wordBounds = new Sakri.Geom.Rectangle();
var wordAlpha = 0;
//var bgColor = "#0c0d43";
var bgColor = "#000000";
var outlines;//stores outline points of characters from marching squares
var shinePoint = new Sakri.Geom.Point();
var shineTarget = new Sakri.Geom.Point();
var maxRayLength = 80;
var outlinePointsSkip = 4;//the effect doesn't need all the outline points, this amount gets skipped. Poor bastards.
var minMoveSpeed = 2;
var maxMoveSpeed = 4;
var moveSpeed;
var maxEffectDistance;
var fontProperties = new Sakri.CanvasTextProperties(Sakri.CanvasTextProperties.BOLD, null, 160);
var charCenter = new Sakri.Geom.Point();
var average = new Sakri.Geom.Point();
var rayPoint = new Sakri.Geom.Point();
var furthestPoint = new Sakri.Geom.Point();
var animating = false;
function clearTimeoutsAndIntervals(){ animating = false; clearInterval (intervalId); clearTimeout (timeoutId);
}
function startDemo(){ renderBackground(); currentWord = words[wordIndex]; var testCanvas = document.createElement('canvas'); var testContext = testCanvas.getContext("2d"); testContext.fillStyle = "#000000"; outlines = []; wordBounds.width = context.measureText(currentWord).width; wordBounds.height = fontProperties.fontSize; wordBounds.x = bounds.getCenterX() - wordBounds.width/2; wordBounds.y = bounds.getCenterY() - fontProperties.fontSize/2;//more or less maxEffectDistance = wordBounds.width/4; var xOffset = wordBounds.x + 0; var character, i, j, outlineCopy, point; for(i=0; i<currentWord.length; i++){ character = currentWord.charAt(i); testContext.font = fontProperties.getFontString(); testCanvas.width = testContext.measureText(character).width; testCanvas.height = fontProperties.fontSize * 1.5;//times 1.5 to be safe testContext.font = fontProperties.getFontString(); testContext.textBaseline = "top"; testContext.fillText(character,0,0); outlines[i] = Sakri.MarchingSquares.getBlobOutlinePoints(testCanvas); outlineCopy = []; for(j=0; j<outlines[i].length; j += outlinePointsSkip){ point = outlines[i][j]; point.x += xOffset; point.y += wordBounds.y; outlineCopy.push(point); } outlines[i] = outlineCopy; xOffset += testCanvas.width; } wordAlpha = 0; intervalId = setInterval(fadeCurrentWordIn, 20);
}
function fadeCurrentWordIn(){ renderBackground(); context.globalAlpha = wordAlpha; renderCurrentWord(); context.globalAlpha = 1; wordAlpha += .05; if(wordAlpha>1){ clearInterval(intervalId); startShine(); }
}
function renderBackground(){ context.fillStyle = bgColor; context.fillRect(0,0,bounds.width, bounds.height);
}
function renderCurrentWord(){ context.fillStyle = "#FFFFFF"; context.fillText(currentWord, wordBounds.x, wordBounds.y); context.strokeStyle = "#FFFFFF"; context.lineWidth = 2; context.strokeText(currentWord, wordBounds.x, wordBounds.y);
}
function renderCurrentWordWithShading(){ context.save(); context.shadowColor = "#FFFFFF"; charCenter.y = wordBounds.y+wordBounds.height/2; var dist, i; var xOffset = wordBounds.x; for(i=0; i<currentWord.length;i++){ charCenter.x = xOffset + context.measureText(currentWord.charAt(i)).width/2; dist = Sakri.Geom.Point.distanceBetweenTwoPoints(shinePoint, charCenter); if(dist > maxEffectDistance){ context.globalAlpha = 1; context.shadowBlur = 1; context.fillStyle = "#000011"; context.strokeStyle = "#000011"; }else{ context.globalAlpha = Sakri.MathUtil.map(dist, 0, maxEffectDistance, .9 , .2); context.shadowBlur = Sakri.MathUtil.map(dist, 0, maxEffectDistance, 25 , 5); context.fillStyle = "#FFFFFF"; context.strokeStyle = "#FFFFFF"; } context.fillText(currentWord.charAt(i), xOffset, wordBounds.y); context.strokeText(currentWord.charAt(i), xOffset, wordBounds.y); xOffset += context.measureText(currentWord.charAt(i)).width; } context.restore();
}
function startShine(){ //set start and end coordinates for shinePoint and shineTarget var xValues = [wordBounds.x - wordBounds.height, wordBounds.getRight() + wordBounds.height]; shinePoint.x = Math.random()>.5 ? xValues.shift() : xValues.pop(); shineTarget.x = xValues[0]; shinePoint.y = shineTarget.y = wordBounds.y + Math.random() * wordBounds.height; moveSpeed = (shineTarget.x - shinePoint.x > 0 ? 1 : -1) * Sakri.MathUtil.getRandomNumberInRange(minMoveSpeed, maxMoveSpeed); renderBackground(); renderCurrentWord(); updateFunction = Math.random()>.5 ? renderBlobs : renderOutlines; animating = true; loop();
}
function loop(){ updateShine() if(animating){ window.requestAnimationFrame(loop, canvas); }
}
function updateShine(){ shinePoint.x += moveSpeed; if(Math.abs(shineTarget.x-shinePoint.x) <= Math.abs(moveSpeed)){ endShine(); return; } context.globalAlpha = .1; renderBackground(); context.globalAlpha = 1; context.shadowColor = "#FFFFFF"; renderCurrentWordWithShading(); context.shadowBlur = 20; context.shadowOffsetX = 0; context.shadowOffsetY = 0; for(var i=0; i<outlines.length; i++){ updateFunction(outlines[i]); } context.globalAlpha = 1; context.shadowBlur = 0;
}
function renderBlobs(outline){ var angle, point, i; var dist = Sakri.Geom.Point.distanceBetweenTwoPoints(shinePoint, outline[0]); if(dist > maxEffectDistance){ return; } var rayLength = Sakri.MathUtil.map(dist, 0, maxEffectDistance, maxRayLength , maxRayLength/4); context.beginPath(); furthestPoint.x = 0; point = outline[0]; angle = Sakri.Geom.Point.angleBetweenTwoPoints(shinePoint, point) + Math.PI; context.moveTo(point.x + Math.cos(angle) * rayLength, point.y + Math.sin(angle) * rayLength); for(i=1; i<outline.length; i++){ point = outline[i]; angle = Sakri.Geom.Point.angleBetweenTwoPoints(shinePoint, point) + Math.PI; rayPoint.x = point.x + Math.cos(angle) * rayLength; rayPoint.y = point.y + Math.sin(angle) * rayLength; if(rayPoint.x > furthestPoint.x){ furthestPoint.update(rayPoint.x, rayPoint.y); } average.x += rayPoint.x; average.y += rayPoint.y; context.lineTo(rayPoint.x, rayPoint.y); } average.x /= outline.length; average.y /= outline.length; context.closePath(); var gradient = context.createRadialGradient(average.x, average.y, 10, average.x, average.y, Sakri.Geom.Point.distanceBetweenTwoPoints(average, furthestPoint)); gradient.addColorStop(0,"rgba(255,255,255,.8)"); gradient.addColorStop(1,"rgba(255,255,255,0)"); context.fillStyle = gradient; //context.stroke(); context.fill();
}
function renderOutlines(outline){ var angle, point, i; var dist = Sakri.Geom.Point.distanceBetweenTwoPoints(shinePoint, outline[0]); if(dist > maxEffectDistance){ return; } context.globalAlpha = Sakri.MathUtil.map(dist, 0, maxEffectDistance, .3, .05); var rayLength = Sakri.MathUtil.map(dist, 0, maxEffectDistance, maxRayLength , maxRayLength/4); context.beginPath(); point = outline[0]; angle = Sakri.Geom.Point.angleBetweenTwoPoints(shinePoint, point) + Math.PI; context.moveTo(point.x + Math.cos(angle) * rayLength, point.y + Math.sin(angle) * rayLength); for(i=1; i<outline.length; i++){ point = outline[i]; angle = Sakri.Geom.Point.angleBetweenTwoPoints(shinePoint, point) + Math.PI; context.lineTo(point.x + Math.cos(angle) * rayLength, point.y + Math.sin(angle) * rayLength); } context.closePath(); context.stroke(); context.globalAlpha = 1;
}
function endShine(){ animating = false; wordAlpha = 1; intervalId = setInterval(fadeCurrentWordOut, 20);
}
function fadeCurrentWordOut(){ renderBackground(); context.globalAlpha = wordAlpha; renderCurrentWord(); context.globalAlpha = 1; wordAlpha -= .05; if(wordAlpha <= 0){ clearInterval(intervalId); wordIndex++; wordIndex %= words.length; timeoutId = setTimeout(startDemo, 500); }
}
Ghost Text - Script Codes
Ghost Text - Script Codes
Home Page Home
Developer Sakri Rosenstrom
Username sakri
Uploaded September 06, 2022
Rating 4.5
Size 10,419 Kb
Views 32,384
Do you need developer help for Ghost Text?

Find the perfect freelance services for your business! Fiverr's mission is to change how the world works together. Fiverr connects businesses with freelancers offering digital services in 500+ categories. Find Developer!

Sakri Rosenstrom (sakri) Script Codes
Create amazing love letters with AI!

Jasper is the AI Content Generator that helps you and your team break through creative blocks to create amazing, original content 10X faster. Discover all the ways the Jasper AI Content Platform can help streamline your creative workflows. Start For Free!