Apr 20, 2024, 03:46 AM | |
New cutting-edge method of making backgrounds in JJ2+ (16 bit mode only)
I just found an edge-cutting method of creating 16-bit color backgrounds in JJ2+!
Essentially, you subdivide an image in its cyan, magenta and yellow values, then you put them in front of eachother, and on sprite mode you choose Blend Multiply. With this little trick, I managed to insert a 16 bit color image in a level. Here's what it looks like: ![]() The only downside is that it doesn't work in 8 bit mode, but who uses 8 bit mode nowadays? To prove you the image isn't counterfeit, I linked the level and tileset. https://www.dropbox.com/scl/fi/7fye0...=uddu5hnk&dl=0
__________________
Free will was a mistake. - God |
Apr 20, 2024, 09:40 AM | |
This is neat! But I think it's mostly a showcase of how fast tiles are. I tried to adapt it more generally and it seems to be slower than TrueColor, not to mention it relies on a specific palette and I don't think the math works for alpha channel.
Compare a TrueColor version with this addition to Cottage.j2as to draw as sprites instead of tiles: Code:
uint cottageFrameID; void onLevelLoad() { jjAnimSets[ANIM::CUSTOM[0]].allocate(array |
Apr 20, 2024, 02:57 PM | |
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. |
Apr 20, 2024, 03:13 PM | |
When I developed this, the prospect of 16-bit tilesets was briefly very exciting to me until I considered the limitations - the inconvenience of use, incompatibility with actual tile types and sprite modes, extra space usage, etc. But the real eye-opener for me was this: here's the screenshot from the first post in this thread but reduced to 128 colors:
![]()
__________________
I am an official JJ2+ programmer and this has been an official JJ2+ statement. |
Apr 24, 2024, 02:42 PM | |
Imo the inconvenience people have with 8-bit color palettes is more about having to deal with color reduction and/or draw with limited colors in mind than with the final quality of the visuals.
People specifically looking to make a JJ2 tileset will know their stuff sooner or later. But it appears that experienced artists of this day and age willing to draw an episode cover image (like those for the community HH episodes) are alienated by the thought of having to deal with limited colors. Personally, I think the combination of flattening and dithering, if done right, can give charm to the final result so I don't really feel the need for JJ2 to be compatible with 16-bit tilesets beyond these proof-of-concepts.
__________________
Add SlazRabbit on Xbox Live if you want to play some GoW1/2/3/J. Jazz Jackrabbit 2 Forever!! Civilian Defence Force - Jazz2 Visual Fantasers ![]() |
![]() |
«
Previous Thread
|
Next Thread
»
Thread Tools | |
|
|
All times are GMT -8. The time now is 11:10 AM.
Jazz2Online © 1999-INFINITY (Site Credits). Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats. Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Original site design by Ovi Demetrian. DrJones is the puppet master. Eat your lima beans, Johnny.