Jump to content

.Exe FileType Plugin


Recommended Posts

Just relaxing, decided to make this for fun.

QaGHa.jpg

Hidden Content: C# source



using System;
using System.Collections.Generic;
using System.Text;
using PaintDotNet;
using PaintDotNet.Data;
using PaintDotNet.Rendering;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Linq;

namespace DotExeFileType
{
 public class MyFileType : FileType
 {
public MyFileType()
  : base("Exe File",
	  FileTypeFlags.SupportsSaving,
	  new String[] { ".exe" })
{
}

protected override void OnSave(Document input, Stream output, SaveConfigToken token,
  Surface scratchSurface, ProgressEventHandler callback)
{
  RenderArgs ra = new RenderArgs(new Surface(input.Size));

  // input.Render(ra);
  input.Render(ra, false);
  var img = ra.Bitmap;
  Bitmap cells = new Bitmap(80 * 8, 25 * 12);
  Graphics ctx = Graphics.FromImage(cells);
  ctx.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
  ctx.DrawImage(img, 0, 0, 80 * 8, 25 * 12);
  ctx.Dispose();
  byte[] outArr = new byte[80*25*4];


  // Downsample to 4-bit colors
  for (int y = 0; y < 25*12; y++)
  {
	for (int x = 0; x < 80*8; x++)
	{
	  Color c = cells.GetPixel(x, y);
	  cells.SetPixel(x, y, bestMatch(ColorBgra.FromColor(cells.GetPixel(x, y))));
	}
  }

  // Histogram the 8x12 characters and find
  // the closest match
  for (int y = 0; y < 25; y++)
  {
	for (int x = 0; x < 80; x++)
	{
	  Dictionary<ColorBgra, int> histo = new Dictionary<ColorBgra, int>();
	  for (int j = 0; j < 12; j++)
	  {
		for (int i = 0; i < 8; i++)
		{
		  ColorBgra col = ColorBgra.FromColor(cells.GetPixel(i + x * 8, j + y * 12));
		  if (!histo.ContainsKey(col))
			histo.Add(col, 1);
		  else histo[col]++;
		}
	  }


	  var histoList = histo
		.OrderByDescending(k => k.Value).Select(k => k.Key)
		.ToList();
	  byte foreground = (byte)Enumerable.Range(0, ConsoleColors.Length)
		.Where(i => ConsoleColors[i] == histoList[0])
		.First();
	  byte background;
	  if (histoList.Count > 1)
	  {
		background = (byte)Enumerable.Range(0, ConsoleColors.Length)
		  .Where(i => ConsoleColors[i] == histoList[1])
		  .First();
	  }
	  else { background = foreground; }
	  double ratio;
	  if (histoList.Count > 1)
		ratio = 96 * histo[histoList[0]] / (histo[histoList[0]] + histo[histoList[1]]);
	  else ratio = 0;

	  int bestCodepoint = 0;
	  double bestRatioDiff = Double.MaxValue;
	  for (int i = 0; i < CP437.Length; i++)
	  {
		if (Math.Abs((double)(CP437[i]) - ratio) < bestRatioDiff)
		{
		  bestRatioDiff = Math.Abs((double)(CP437[i]) - ratio);
		  bestCodepoint = i;
		}
	  }

	  var index = (y * 80 + x) * 4;
	  outArr[index] = (byte)bestCodepoint;
	  outArr[index + 1] = (byte)0;
	  outArr[index + 2] = (byte)(foreground | (background << 4));
	  outArr[index + 3] = (byte)0;
	}
  }

  output.Write(ExeOut.OutExe, 0, (int)ArrayOffset);
  output.Write(outArr, 0, outArr.Length);
  output.Write(ExeOut.OutExe, (int)(ArrayOffset + outArr.Length),
	(int)(ExeOut.OutExe.Length - (ArrayOffset + outArr.Length)));
}

protected override Document OnLoad(Stream input)
{
	throw new NotImplementedException();
}

ColorBgra[] ConsoleColors = {
	ColorBgra.FromOpaqueInt32(0x000000),
	ColorBgra.FromOpaqueInt32(0x000080),
	ColorBgra.FromOpaqueInt32(0x008000),
	ColorBgra.FromOpaqueInt32(0x008080),
	ColorBgra.FromOpaqueInt32(0x800000),
	ColorBgra.FromOpaqueInt32(0x800080),
	ColorBgra.FromOpaqueInt32(0x808000),
	ColorBgra.FromOpaqueInt32(0xC0C0C0),
	ColorBgra.FromOpaqueInt32(0x808080),
	ColorBgra.FromOpaqueInt32(0x0000FF),
	ColorBgra.FromOpaqueInt32(0x00FF00),
	ColorBgra.FromOpaqueInt32(0x00FFFF),
	ColorBgra.FromOpaqueInt32(0xFF0000),
	ColorBgra.FromOpaqueInt32(0xFF00FF),
	ColorBgra.FromOpaqueInt32(0xFFFF00),
	ColorBgra.FromOpaqueInt32(0xFFFFFF)
	};

// Generated from CodeLab and a screenshot!
int[] CP437 = { 00, 38, 60, 38, 32, 40, 44, 20, 76, 32, 64, 37, 32, 32, 48, 40,
	29, 29, 30, 28, 47, 40, 21, 36, 24, 24, 15, 15, 13, 20, 32, 32,
	00, 22, 14, 42, 30, 20, 40, 08, 18, 18, 24, 14, 08, 08, 06, 14,
	45, 23, 30, 27, 32, 29, 30, 27, 38, 30, 12, 16, 18, 12, 18, 20,
	40, 36, 41, 30, 38, 35, 33, 34, 38, 22, 28, 38, 30, 45, 45, 34,
	33, 41, 40, 31, 26, 36, 34, 38, 34, 30, 35, 22, 14, 22, 12, 08,
	24, 33, 20, 33, 24, 26, 32, 33, 22, 28, 32, 24, 30, 25, 24, 32,
	32, 23, 20, 22, 25, 22, 26, 22, 28, 22, 20, 16, 20, 14, 24, 23
	};

double colorDistance(ColorBgra a, ColorBgra 
{
	HsvColor aH = HsvColor.FromColor(a.ToColor());
	HsvColor bH = HsvColor.FromColor(b.ToColor());
	return Math.Abs(bH.Hue - aH.Hue) +
		Math.Abs(bH.Saturation - aH.Saturation) +
		Math.Abs(bH.Value - aH.Value);
}

ColorBgra bestMatch(ColorBgra c)
{
	double bestDist = Double.MaxValue;
	ColorBgra bestCol = ColorBgra.Black;

	for (int n = 0; n < 16; n++)
	{
		double curDist = colorDistance(c, ConsoleColors[n]);
		if (curDist < bestDist)
		{
			bestCol = ConsoleColors[n];
			bestDist = curDist;
		}
	}

	return bestCol;
}

uint ArrayOffset = 0x418;
 }

 public class MyFileTypeFactory : IFileTypeFactory
 {
public FileType[] GetFileTypeInstances()
{
  return new FileType[] { new MyFileType() };
}
 }
}

Hidden Content: c++ source


#define WIN32_LEAN_AND_MEAN

#include <Windows.h>

// Stuff to pass WriteConsoleOutputW.  These are in the
// .data segment (0x2400 in the .exe) in whatever order
// the compiler felt like.
CONSOLE_SCREEN_BUFFER_INFO outInfo;
COORD outSize = {80, 25};
COORD outPos = {0, 0};

// Const, so it will be in (because we merged .rdata
// into it) .text at 0x400 in the .exe.
// Totally note I got the order wrong here but it doesn't
// really matter since we're filling in these values elsewhere.
const CHAR_INFO outChar[80][25] = { { {L'0', 0xf} } };

// Custom entry-point to remind you not to link this
// with the Visual C Run-Time library.
int Main()
{
 // Even though we use Unicode output, it's rare for
 // a Windows console to use Unicode.
 HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
 HANDLE conIn  = GetStdHandle(STD_INPUT_HANDLE );
 GetConsoleScreenBufferInfo(conOut, &outInfo);
 outInfo.dwCursorPosition.X = 0;
 WriteConsoleOutputW(conOut, (CHAR_INFO*)outChar,
outSize, outPos, &outInfo.srWindow);
 ReadConsoleW(conIn, (LPVOID)&outInfo, 1, 0, 0);
 outInfo.dwCursorPosition.Y += 25;
 SetConsoleCursorPosition(conOut, outInfo.dwCursorPosition);
 return 0;
}

Compile as x86, use /MERGE:".rdata=.text", /NODEFAULTLIB, and /MANIFEST:NO to link.

Download: DotExeFileType.zip

(Edited: Messed up the 'ratio' variable)

~~

Link to comment
Share on other sites

How does this exactly work?

In the DLL there's an embedded resource with the .exe produced by building the C code. That executable has a static array from `const CHAR_INFO outChar[80][25] = { { {L'0', 0xf} } };` which is printed directly to the console using ConsoleWriteW. The Plugin writes that .exe to file but writes over the static array inside the executable to change the "image" that gets written to the console.

It's just a "for-fun" plugin; there's absolutely no point to saving an image as an executable file, and even though you could do it as a full-color bitmap and use Windows GDI stuff to display it, that's also pointless when there's Windows Photo Viewer. If I really wanted to have fun, I'd do a 16-bit .COM FileType using int 21 stuff, but I'm on a 64-bit system, so meh.

~~

Link to comment
Share on other sites

  • 1 month later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...