Destructionator XIII wrote:If you know of where we can find a NES C compiler, then we might be in business though. The assembler was too puny and I found it hard to get any serious work done with it (though I am a much better programmer now than I was then, maybe if I go back it won't be so bad). Still, C would make it much, much easier.
I think there is one. I can look into it tomorrow.
In the meantime, enjoy the wyrd program I wrote today. It's an ASCII art converter. It can read 24-bit BMPs and 32-bit TGAs (and probably 24-bit too, although I haven't tested). It saves a text file with the same name as the specified image, and also a 24-bit BMP file, generated with Windows' GDI. If you're running on a non-Windows machine, remove the BMP saving and loading, and it should work fine (or just the saving and write your own file structs). Also, I mixed in a little C file loading code, because I reused the image loading from another project. There's probably a few bugs but it seems to work okay.
Here's a download if you don't want to compile it yourself.
Code: Select all
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include "windows.h"
class AGradient {
// ASCII gradient class
std::string g; // The letter gradient to be used
public:
AGradient() {
g = "MWYASQPDBNZOUVCXTFRJLI890657432mwyaspqdbnzouvcxtfrj1li\\/!:;"*_~-¨^,.'`´ ";
}
char GetChar(unsigned char n) {
// Returns a matching letter, scaled over the gradient's size
float f = float(n) / 256.f;
int i = int(float(g.size()) * f);
return g[i];
}
};
class CBitmap {
// Class for loading bitmaps
bool loaded;
std::string name;
unsigned char *data;
int width,height;
int BPP;
public:
CBitmap() {
loaded = false;
data = NULL;
width = height = BPP = 0;
}
~CBitmap() {
delete [] data;
}
bool Load(std::string filename);
void Convert();
};
using namespace std;
/////////////////////////////////////////////////
// Class CBitmap
bool CBitmap::Load(string filename)
{
// Loads a bitmap into memory
if (loaded==true) // This bitmap is already loaded!
return false;
name = filename;
// Create a temporary, all lower-case filename
string lName = name;
int (*pf)(int)=tolower;
transform(lName.begin(),lName.end(), lName.begin(), pf);
int imageSize=0; // Used to store the image size when setting aside ram
int bytesPerPixel=0; // Holds number of bytes per pixel used in the file
if (lName.find(".bmp")!=string::npos) // Load a .bmp file
{
// These are both defined in Windows.h
BITMAPFILEHEADER BitmapFileHeader;
BITMAPINFOHEADER BitmapInfoHeader;
// open filename in "read binary" mode
FILE *file = fopen(filename.c_str(), "rb");
if (file == NULL) {
return false;
}
// Header
fread (&BitmapFileHeader, sizeof (BITMAPFILEHEADER), 1, file);
if (BitmapFileHeader.bfType != 'MB') {
fclose (file);
return false;
}
// Information
fread (&BitmapInfoHeader, 1, sizeof (BITMAPINFOHEADER), file);
if (BitmapInfoHeader.biWidth==0 || BitmapInfoHeader.biHeight==0) {
fclose (file);
return false;
}
width = BitmapInfoHeader.biWidth;
height = BitmapInfoHeader.biHeight;
BPP = 3; // Only load 24-bit Bitmaps
bytesPerPixel = 3;
imageSize = width*height*bytesPerPixel; // Set image size
// Seek position in file (no headers in the data, please!)
fseek (file, BitmapFileHeader.bfOffBits, SEEK_SET);
data = new unsigned char [imageSize];
if (data == NULL) { // Error allocating memory!
delete [] data;
fclose (file);
return false; // Oh noes!
}
fread (data, 1, imageSize, file); // Read the file into memory
// Turn BGR to RBG
for (int i = 0; i < (int) imageSize; i += 3) {
int temp = data [i];
data [i + 0] = data [i + 2];
data [i + 2] = temp;
}
fclose(file); // Close file
} else if (lName.find(".tga")!=string::npos) // Load a .tga file
{
unsigned char TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0}; // Uncompressed TGA Header
unsigned char TGAcompare[12]; // Used To Compare TGA Header
unsigned char header[6]; // First 6 Useful Bytes From The Header
FILE *file = fopen(filename.c_str(), "rb"); // Open The TGA File
if( file==NULL || // Does File Even Exist?
fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) || // Are There 12 Bytes To Read?
memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0 || // Does The Header Match What We Want?
fread(header,1,sizeof(header),file)!=sizeof(header)) // If So Read Next 6 Header Bytes
{
if (file == NULL) // Did The File Not Exist?
return false; // Return False
else // Otherwise
{
fclose(file); // If Anything Failed, Close The File
return false; // Return False
}
}
// If went OK, so continue the loading
width = header[1] * 256 + header[0]; // Determine The TGA Width
height = header[3] * 256 + header[2]; // Determine The TGA Height
if( width <=0 || // Is The Width Less Than Or Equal To Zero
height <=0 || // Is The Height Less Than Or Equal To Zero
(header[4]!=24 && header[4]!=32)) // Is The TGA 24 or 32 Bit?
{
fclose(file); // If Anything Failed, Close The File
return false; // Return error
}
BPP = header[4]/8; // Grab The TGA's Bits Per Pixel (24 or 32)
bytesPerPixel = BPP; // Divide By 8 To Get The Bytes Per Pixel
imageSize = width*height*bytesPerPixel; // Calculate The Memory Required For The TGA Data
data = new unsigned char[imageSize]; // Reserve Memory To Hold The TGA Data
if( data==NULL || // Does The Storage Memory Exist?
fread(data, 1, imageSize, file)!=imageSize) // Does The Image Size Match The Memory Reserved?
{
if(data!=NULL)
delete [] data;
fclose(file); // Close The File
return false;
}
for(int i=0; i<int(imageSize); i+=bytesPerPixel) // Loop Through The Image Data
{ // Swaps The 1st And 3rd Bytes (Red and Blue)
int temp=data[i];
data[i] = data[i + 2];
data[i + 2] = temp;
}
fclose (file); // Close The File
} else { // Unknown file format
return false;
}
loaded = true; // Bitmap is now loaded. Hooray!
return true; // Everything went OK
}
void CBitmap::Convert() {
// Converts an image to text using the color information to produce a gradient
if (loaded == false) return;
int sizeX = 8;
int sizeY = 15;
int tWidth = width/sizeX;
int tHeight = height/sizeY;
int size = tWidth*tHeight*3;
if (size == 0) return;
unsigned char *cData = new unsigned char[size];
// Downsample the image using sizeX*sizeY blocks
for (int y=0, fx=0; y<tHeight && fx<size; ++y) {
for (int x=0; x<tWidth && fx<size; ++x, fx+=3) {
int r = 0,b = 0,g = 0;
for (int dx=0; dx<sizeX; ++dx) {
for (int dy=0; dy<sizeY; ++dy) {
if (x*sizeX + dx >= width || y*sizeY + dy >= height) break;
int p = (x*sizeX + dx) * BPP + (y*sizeY + dy) * width * BPP;
r += data[p];
g += data[p+1];
b += data[p+2];
}
}
int d = sizeX*sizeY; // Average and store the colour
r /= d; g /= d; b /= d;
cData[fx] = (unsigned char)(r);
cData[fx+1] = (unsigned char)(g);
cData[fx+2] = (unsigned char)(b);
}
}
// Convert the downsampled image to a "text gradient"
AGradient grad;
string str;
for (int y=tHeight-1; y>=0; --y) { // Image is upside down...
for (int x=0; x<tWidth; ++x) {
int p = (x+y*tWidth)*3;
int r = cData[p];
int g = cData[p+1];
int b = cData[p+2];
int l = (r + g + b) / 3;
str += grad.GetChar((unsigned char)(l));
}
str += "\n";
}
{ // Write a text file
int index = name.rfind(".");
string filename(name,0,index);
filename += ".txt";
ofstream file(filename.c_str());
file << str;
file.close();
}
{ // Write a bitmap using GDI text stuff
int index = name.rfind(".");
string filename(name,0,index);
filename += "_t.bmp";
// Create a device context in memory
HDC hdc = CreateCompatibleDC(NULL);
// Apply a bitmap
BITMAPINFO bi;
ZeroMemory( &bi.bmiHeader, sizeof(BITMAPINFOHEADER) );
bi.bmiHeader.biWidth=tWidth*sizeX;
bi.bmiHeader.biHeight=tHeight*sizeY;
bi.bmiHeader.biPlanes=1;
bi.bmiHeader.biBitCount=24;
bi.bmiHeader.biSizeImage=0;
bi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biClrUsed= 0;
bi.bmiHeader.biClrImportant= 0;
VOID *pvBits;
HBITMAP hbmp= CreateDIBSection( hdc,
&bi,
DIB_RGB_COLORS,
&pvBits,
NULL,
0);
SelectObject(hdc,hbmp);
// Create font
HFONT font = CreateFont(sizeY,0,0,0,FW_NORMAL,FALSE,FALSE,0,ANSI_CHARSET,
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH | FF_SWISS,
"Fixedsys");
// Select font and set colours etc.
SelectObject(hdc,font);
SetBkMode(hdc,OPAQUE);
SetBkColor(hdc,RGB(192,192,192));
// Write coloured letters
for (int y=0; y<tHeight; ++y) {
for (int x=0; x<tWidth; ++x) {
int p = (x+(tHeight-y-1)*tWidth)*3;
int r = cData[p];
int g = cData[p+1];
int b = cData[p+2];
SetTextColor(hdc,RGB(r,g,b));
int l = (r + g + b) / 6; // Darker
char c = grad.GetChar((unsigned char)(l));
TextOut(hdc,x*sizeX,y*sizeY,&c,1);
}
}
// Don't need font anymore
DeleteObject(font);
// prepare the bitmap file header
BITMAPFILEHEADER bmfh;
bmfh.bfType = 'MB';
bmfh.bfSize = sizeof(bmfh) + sizeof(bi.bmiHeader) + bi.bmiHeader.biSizeImage;
bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(bmfh) + sizeof(bi.bmiHeader);
BITMAP bmp;
GetObject(hbmp,sizeof(BITMAP),&bmp);
int imageBytes = bi.bmiHeader.biWidth * bi.bmiHeader.biHeight * 3;
char *pBits = new char [imageBytes];
int scanLineCount = GetDIBits(hdc, hbmp, 0, bmp.bmHeight,
pBits, &bi, DIB_RGB_COLORS);
// Open a file for writing
ofstream file(filename.c_str(),ios::binary);
file.write((char*)(&bmfh), sizeof(BITMAPFILEHEADER));
file.write((char*)(&bi.bmiHeader), sizeof(BITMAPINFOHEADER));
file.write((char*)(pBits), imageBytes);
file.close();
// Delete image data
delete [] pBits;
// Delete bitmap object
DeleteObject(hbmp);
// And device context
DeleteDC(hdc);
}
delete [] cData;
}
/////////////////////////////////////////////////
int main(int argc, char *argv[])
{
while (1) { // Enter main loop
cout << "Enter filename:\n";
string file;
if (cin >> file) {
CBitmap bmap;
if (!bmap.Load(file)) {
cout << "Error loading file!\n";
} else {
bmap.Convert();
cout << "File converted...\n";
}
} else {
cout << "Error!!\n";
}
cout << "\nLoad another file? Y/N";
string c;
cin >> c;
if (c == "n" || c == "N" || c == "no") break;
system("cls");
}
return EXIT_SUCCESS;
}
Here's a few tests I did. I'm sure there are already a lot of programs like this though. If you can recompile it you can change the gradient string and maybe get better results. Of course, I could make a config file for all that...