View Single Post
Seren Seren's Avatar

JCF Member

Joined: Feb 2010

Posts: 866

Seren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to all

Apr 20, 2024, 02:57 PM
Seren is offline
Reply With Quote
Back in late 2015 I wrote a piece of AngelScript that would take an image and output another, plus a palette, ready to be used in a tileset (except the wrong file format because outputting a bmp is awfully inconvenient).

I don't think we had layer sprite modes at the time, or if we did, they were very inefficient. The technique I used instead relied on translucent tiles and as such was 100% compatible with vanilla JJ2. Given that it only required 2 layers, only one of which was translucent, it was also lightning fast and only took up twice the space of a regular 8-bit tileset. There were some 16-bit pixel values it wasn't capable of outputting but you wouldn't really notice it.

The code is available below. Unfortunately I will not provide any assistance in figuring out how it works and there's a nonzero chance my process involved some manual adjustments after running the code (such as rearranging the colors and inserting some new ones to make the sprite palette look mostly right? Who knows!).

Code:
#pragma require "SEhicolor.bmp"
class IndexPair {
	int foreground, background;
}
void onLevelLoad() {
	const array<uint8> redValues = {0, 50, 100, 140, 200, 255};
	const array<uint8> greenValues = {0, 25, 54, 107, 144, 201, 230, 255};
	const array<uint8> blueValues = {0, 95, 105, 185, 255};
	const array<const array<uint8>@> componentValues = {redValues, greenValues, blueValues};
	const array<int> componentWeight = {1, blueValues.length(), blueValues.length() * greenValues.length()};
	const array<int> missingRedValues = {2, 28, 31};
	const array<int> missingGreenValues = {2, 8, 17, 31, 45, 54, 60, 63};
	const array<int> missingBlueValues = {1, 4, 6, 18, 21, 27, 29, 31};
	const array<const array<int>@> missingValues = {missingRedValues, missingGreenValues, missingBlueValues};
	array<array<IndexPair>> pairSets = {array<IndexPair>(32), array<IndexPair>(64), array<IndexPair>(32)};
	for (int i = 0; i < 3; i++) {
		int shift = i == 1 ? 4 : 5;
		array<IndexPair>@ pairs = pairSets[i];
		const array<uint8>@ values = componentValues[i];
		int length = values.length();
		const array<int>@ missing = missingValues[i];
		int missingLength = missing.length();
		for (int j = 0; j < length; j++) {
			int value = values[j] * 3 >> shift;
			for (int k = 0; k < length; k++) {
				IndexPair@ pair = pairs[value + (values[k] >> shift)];
				pair.foreground = j;
				pair.background = k;
			}
		}
		for (int j = 0; j < missingLength; j++) {
			int index = missing[j];
			pairs[index] = pairs[index - 1];
		}
	}
	jjPAL@ pal = jjPalette;
	int redLength = redValues.length();
	for (int entry = 16, i = 0; i < redLength; i++) {
		uint8 red = redValues[i];
		int greenLength = greenValues.length();
		for (int j = 0; j < greenLength; j++) {
			uint8 green = greenValues[j];
			int blueLength = blueValues.length();
			for (int k = 0; k < blueLength; k++) {
				uint8 blue = blueValues[k];
				pal.color[entry++] = jjPALCOLOR(red, green, blue);
			}
		}
	}
	pal.apply();
	array<jjPIXELMAP@> tiles(3970);
	for (int i = 10; i < 3970; i++) {
		@tiles[i] = jjPIXELMAP();
	}
	jjSTREAM bitmap("SEhicolor.bmp");
	uint offset;
	bitmap.discard(10);
	bitmap.pop(offset);
	bitmap.discard(offset - 14);
	for (int id = 1930, x = 0, y = 31, t = 0, i = 0; i < 0x1EF000; i++) {
		IndexPair pair;
		pair.foreground = pair.background = 16;
		for (int j = 0; j < 3; j++) {
			uint8 component;
			bitmap.pop(component);
			const IndexPair@ src = pairSets[2 - j][component >> (j == 1 ? 2 : 3)];
			int weight = componentWeight[j];
			pair.foreground += src.foreground * weight;
			pair.background += src.background * weight;
		}
		tiles[id][x, y] = pair.foreground;
		tiles[id + 1980][x, y] = pair.background;
		if (++x == 32) {
			x = 0;
			id++;
			if (++t == 60) {
				t = 0;
				if (--y == -1) {
					y = 31;
					id -= 120;
				} else {
					id -= 60;
				}
			}
		}
	}
	for (int i = 10; i < 3970; i++) {
		tiles[i].save(i);
	}
	for (int i = 10; i < 1990; i++) {
		jjTileType[i] = 1;
	}
	for (int id = 10, i = 4; i < 6; i++) {
		jjGenerateSettableTileArea(i, 0, 0, jjLayerWidth[i], jjLayerHeight[i]);
		for (int j = 0; j < jjLayerHeight[i]; j++) {
			for (int k = 0; k < jjLayerWidth[i]; k++) {
				jjTileSet(i, k, j, id++);
			}
		}
	}
	jjSTREAM file;
	string header = "P6 320 12704 255\n";
	int headerLength = header.length();
	for (int i = 0; i < headerLength; i++) {
		file.push(header[i]);
	}
	for (int i = 0; i < 12704; i++) {
		for (int j = 0; j < 10; j++) {
			jjPIXELMAP image(j + (i >> 5) * 10);
			for (int k = 0; k < 32; k++) {
				jjPALCOLOR color = jjPalette.color[image[k, i & 31]];
				file.push(color.red);
				file.push(color.green);
				file.push(color.blue);
			}
		}
	}
	file.save("SEhicolor.ppm.asdat");
	file.clear();
	const string head = "JASC-PAL\r\n0100\r\n256\r\n";
	for (uint i = 0; i < head.length(); i++) {
		file.push(head[i]);
	}
	for (uint i = 0; i < 256; i++) {
		jjPALCOLOR color = jjPalette.color[i];
		string text = color.red + ' ' + color.green + ' ' + color.blue + "\r\n";
		for (uint j = 0; j < text.length(); j++) {
			file.push(text[j]);
		}
	}
	file.save("SEhicolor.pal.asdat");
}
__________________

I am an official JJ2+ programmer and this has been an official JJ2+ statement.