Simulate CRT

From TheAlmightyGuru
Jump to: navigation, search

Simulate CRT is a JScript program I wrote that distorts a screenshot to make it look like it has been displayed on a CRT monitor. It does this by adding scanlines to the image, resizing it to match the specified pixel ratio, and then distorting the image to look curved along a CRT screen. I created this script to better illustrate what classic programs looked like when played on the original hardware. This script requires that ImageMagick be installed and able to be accessed as an ActiveX object.

Here is an example of what the script can do:

CGA Example - Pal 1 - High.png
Emulated MS-DOS screenshot.

CGA Example - Simulated 320x200.png
1:1.2 pixel ratio, 25% scanlines, distorted.

Legend of Zelda, The - NES - Screenshot - Title.png
Emulated NES screenshot.

Legend of Zelda, The - NES - Screenshot - Title - Simulated CRT.png
1.24:1 pixel ratio, 25% scanlines, distorted.

You'll need to know the proper pixel aspect ratio in order to properly stretch the screen. Here are some common resolutions and what ratio they need.

Resolution Pixel Ratio Platforms
320x200 1:1.2 MS-DOS
320x240 1:1 MS-DOS
640x350 1:1.371428 MS-DOS
640x400 1:1.2 MS-DOS
640x480 1:1 MS-DOS, Windows 3
256x224 1.24:1 NES, SNES

Source

// This script will attempt to simulate a 4:3 CRT monitor.
// © Copyright: Dean Tersigni, 2018.
// Date Created: 2018-06-18.

// Set to the desired pixel ratio here.
var fRatioX = 1.0;
var fRatioY = 1.2;

// Set to the desired scanline visibility percentage.
var iScanlinePercent = 25;

var oArguments = WScript.Arguments;
var iArgumentCount = oArguments.Length;

// Verify that at least one argument was passed in.
if(iArgumentCount == 0) {
	WScript.Echo("Either drop an image file onto this script or run this script by passing in the file path of an image file.");
	WScript.Quit(1);
}

var oFSO = new ActiveXObject("Scripting.FileSystemObject");
var sTempPath = oFSO.GetSpecialFolder(2) + "\\";
var iArgument = 0;

// Convert each image using ImageMagick.
var oIM = new ActiveXObject("ImageMagickObject.MagickImage.1");

// Loop through each argument.
for(iArgument = 0; iArgument < iArgumentCount; iArgument++) {
	var sFile = oArguments.Item(iArgument);
	
	// Verify that the argument is a file that exists.
	if(oFSO.FileExists(sFile) == true) {
		var sPath = oFSO.GetParentFolderName(sFile);
		var sName = oFSO.GetBaseName(sFile);
		var sExtension = oFSO.GetExtensionName(sFile);
		
		// Get the new output file name.
		var sOutput = sPath + "\\" + sName + " - " + fRatioX.toString() + "x" + fRatioY.toString() + " CRT Monitor.png";

		// Scale up by 3 before adding scanlines.
		oIM.convert(sFile,
			"-resize", "300%x300%",
			sTempPath + sName + " - CRT-x3.png");

		// Create a scanline graphic.
		oIM.convert(
			"-size", "3x3",
			"xc:transparent", 
			"+antialias",
			"-stroke", "rgba(0,0,0," + (iScanlinePercent * 0.01) + ")",
			"-fill", "rgba(0,0,0," + (iScanlinePercent * 0.01) + ")",
			"-draw", "line 0,0 2,0",
			sTempPath + sName + " - Scanlines3.png");

		// Add scanlines to the image.
		oIM.composite(
			"-tile", sTempPath + sName + " - Scanlines3.png",
			sTempPath + sName + " - CRT-x3.png",
			sTempPath + sName + " - CRT-Lines.png");
			
		// Resize the image to a correct pixel ratio.
		oIM.convert(sTempPath + sName + " - CRT-Lines.png",
			"-resize", (100 * fRatioX).toString() + "%x" + (100 * fRatioY).toString() + "%",
			sTempPath + sName + " - CRT-Ratio.png");

		// Warp the shape to resemble a CRT monitor, and shrink down to a reasonable size.
		oIM.convert(sTempPath + sName + " - CRT-Ratio.png", 
			"-background", "black",
			"-virtual-pixel", "Background", 
			"-distort", "Barrel", "0,0,0.1,0.9",
			"-gravity", "center",
			"-extent", "100%x104",
			"-resize", "640x480",
			sOutput);

		// Clean up work files.
		oFSO.DeleteFile(sTempPath + sName + " - CRT-x3.png");
		oFSO.DeleteFile(sTempPath + sName + " - Scanlines3.png");
		oFSO.DeleteFile(sTempPath + sName + " - CRT-Lines.png");
		oFSO.DeleteFile(sTempPath + sName + " - CRT-Ratio.png");
	} else {
		WScript.Echo("File: " + sFile + " does not exist.");
	}
}