Read back blueprint images

master
bzt 2019-12-15 17:58:16 +01:00
parent a4f3ec14b7
commit c36e2ea891
16 changed files with 231 additions and 56 deletions

View File

@ -8,7 +8,7 @@ See also the [GUI usage](https://gitlab.com/bztsrc/mtsedit/blob/master/docs/usag
```
MineTest Schematics Editor by bzt Copyright (C) 2019 MIT license
./mtsedit [-h] [-v] [-l lang] [-d|-p|-P|-b] [-m map] <.mts|.schematic> [out.mts]
./mtsedit [-h] [-v] [-l lang] [-d|-p|-P|-b|-B] [-m map] <.mts|.schematic> [out.mts]
./mtsedit -g <block.png>
./mtsedit -t <blockimgs.csv> [blockid]
./mtsedit -i [Minetest mods dir]
@ -18,7 +18,7 @@ MineTest Schematics Editor by bzt Copyright (C) 2019 MIT license
-d: dump layers to output
-p: save preview
-P: save preview, cut structure in half
-b: save blueprint
-b, -B: save blueprint
-m map: replace block type mapping
-g: generate slab and stairs from block image
-t: generate block images from texture data
@ -38,15 +38,17 @@ This will print out mapping and each block as hex values layer by layer.
./mtsedit -d structure.mts
```
### Generate blueprint
### Generate Blueprint
Pretty much the same as dump, but instead of dumping the MTS to the console, it saves "structure_bp.png".
```
./mtsedit -b structure.mts
```
Each layer will be padded by 1 x 1 pixels of transparency. On the left, there's a black pixel after each layer so
Each layer will be padded by 1 x 1 pixels of transparency. On the left, there's a black pixel at each layer start so
that programs can detect the dimensions from the image. On the right, there's a red line for the ground layer.
The `-b` flag will also save a blue background and block legend on the image. `-B` only saves the raw blueprint.
### Conversion (or Remap)
You can convert Minecraft NBT Schematics with this tool.

23
docs/import.md Normal file
View File

@ -0,0 +1,23 @@
Import File Formats
===================
This document describes the format that MTSEdit can import schematics from. For the native format, that can be written too, see
the [MTS format](https://gitlab.com/bztsrc/mtsedit/blob/master/docs/mts_format.md).
Schematic
---------
These are [Minecraft NBT](https://minecraft.gamepedia.com/Schematic_file_format) files, used by many Minecraft-compatible
editors. Only the [blocks](https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening/Block_IDs) section is parsed,
and only used to import schematics. MTSEdit does not save .schematic files.
Limitations: rotation info are not imported properly, but works for basic nodes most of the time.
Blueprint PNG
-------------
MTSEdit is capable to save schematic blueprints to PNG files using the `-b` or `-B` flags. These can be imported too.
If blueprint is not detected on the image, then the entire image is read as a one layer blueprint finding the blocks that
are closest match in color to each pixel.
Limitations: rotation info is not stored in blueprint images at all.

View File

@ -1,12 +1,8 @@
File Formats
============
MTS File Format
===============
Schematic
---------
These are [Minecraft NBT](https://minecraft.gamepedia.com/Schematic_file_format) files, used by many Minecraft-compatible
editors. Only the [blocks](https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening/Block_IDs) section is parsed,
and only used to import schematics. MTSEdit does not save .schematic files.
This document describes the MTS format native to MTSEdit and the Minetest Engine. For other formats that are supported
by MTSEdit to import schematics, see [import formats](https://gitlab.com/bztsrc/mtsedit/blob/master/docs/import.md).
MTS
---

View File

@ -117,6 +117,7 @@ Main editor window:
| <kbd>P</kbd> | save preview PNG |
| <kbd>Shift</kbd> + <kbd>P</kbd> | save preview with structure cut in half |
| <kbd>E</kbd> | export blueprint PNG |
| <kbd>Shift</kbd> + <kbd>E</kbd> | export plain blueprint without legend |
| <kbd>R</kbd> | change orientation, rotate entire structure CCW |
| <kbd>Shift</kbd> + <kbd>R</kbd> | change orientation, rotate entire structure CW |
| <kbd>F</kbd> | flip the entire structure along the Z axis |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -41,7 +41,7 @@ endif
endif
endif
CFLAGS += -Wall -Wextra -ansi -pedantic -D_$(PACKAGE)_=1
CFLAGS += -Wall -Wextra -ansi -pedantic -D_$(PACKAGE)_=1 -g
all: configure data.h $(OBJS) $(TARGET)

View File

@ -244,6 +244,7 @@ void blocks_parse()
sc.img_buffer_end = sc.img_buffer_original_end = img + size;
ri.bits_per_channel = 8;
blocks[j].img = (unsigned char*)stbi__png_load(&sc, &w, &h, &r, 0, &ri);
free(img);
if(blocks[j].img) {
blocks[j].numpar2 = (h >> 5) & PARAM2_MAX;
if(r != 4) {
@ -275,8 +276,16 @@ void blocks_parse()
n++;
}
}
if(n) blocks[j].color = 0xFF000000 | (((b/n) & 0xFF) << 16) | (((g/n) & 0xFF) << 8) | ((r/n) & 0xFF);
else blocks[j].color = 0xFFFF0000;
if(n) {
blocks[j].color = 0xFF000000 | (((b/n) & 0xFF) << 16) | (((g/n) & 0xFF) << 8) | ((r/n) & 0xFF);
while(!is_color_unique(blocks[j].color)) {
img = (unsigned char*)&blocks[j].color;
if(img[1] < 255) img[1]++; /* increase green channel first if we can */
else if(img[0] < 255) img[0]++;
else if(img[2] < 255) img[2]++;
else img[3]--; /* for full white, decrease opacity as last resort */
}
} else blocks[j].color = 0xFFFF0000;
l++;
if(!dx || !dz || (!memcmp(s, "Cobblestone", 11) && s[11] != '_' && s[11] != ' ')) {
detcube(32, 32, blocks[j].img);
@ -284,7 +293,6 @@ void blocks_parse()
dz = _y1 - _y0;
}
}
free(img);
}
}
}

View File

@ -173,11 +173,11 @@ int main(int argc, char** argv, char** envp)
if(argc < 2) {
usage: printf("MineTest Schematics Editor by bzt Copyright (C) 2019 MIT license\r\n\r\n"
"./mtsedit [-h] [-v] [-l lang] [-d|-p|-P|-b] [-m map] <.mts|.schematic> [out.mts]\r\n"
"./mtsedit [-h] [-v] [-l lang] [-d|-p|-P|-b|-B] [-m map] <.mts|.schematic> [out.mts]\r\n"
"./mtsedit -g <block.png>\r\n"
"./mtsedit -t <blockimgs.csv> [blockid]\r\n"
"./mtsedit -i [Minetest mods dir]\r\n"
"\r\n -v: %s\r\n -l lang: %s\r\n -d: %s\r\n -p: %s\r\n -P: %s\r\n -b: %s\r\n -m map: %s\r\n -g: %s\r\n"
"\r\n -v: %s\r\n -l lang: %s\r\n -d: %s\r\n -p: %s\r\n -P: %s\r\n -b, -B: %s\r\n -m map: %s\r\n -g: %s\r\n"
" -t: %s\r\n -i: %s\r\n out.mts: %s\r\n",
lang[INF_VERB], lang[INF_LANG], lang[INF_DUMP], lang[INF_PRE1], lang[INF_PRE2], lang[INF_BPRINT], lang[INF_MAP],
lang[INF_GEN], lang[INF_BLK], lang[INF_MOD], lang[INF_OUT]);
@ -192,6 +192,7 @@ usage: printf("MineTest Schematics Editor by bzt Copyright (C) 2019 MIT license
case 'p': opt = 2; break;
case 'P': opt = 3; break;
case 'b': opt = 4; break;
case 'B': opt = 4; shift = 1; break;
case 'g': opt = 5; break;
case 't': opt = 6; break;
case 'i': opt = 7; break;
@ -246,10 +247,10 @@ usage: printf("MineTest Schematics Editor by bzt Copyright (C) 2019 MIT license
case 1: ret = mts_dump(); break;
case 2:
case 3: ret = mts_view(opt-2); break;
case 4: ret = mts_blueprint(); break;
case 5: ret = stairgen(); break;
case 6: ret = blockgen(argv[i+1]); break;
case 7: /* handle this in sdl.c, because extern does not know sizeof() */
case 4: /* handle these in sdl.c, because extern does not know sizeof() */
case 7:
case 0:
/* start the main user interface */
ret = sdlmain(opt);

View File

@ -55,6 +55,8 @@
#define THEME_TABBG 8
#define THEME_SAVEACT 9
#define THEME_SAVEINACT 10
#define THEME_BP_FG 11
#define THEME_BP_BG 12
#define HIST_EMPTY 0
#define HIST_NODE 1
@ -193,20 +195,23 @@ void detcube(int w, int h, unsigned char *block);
int instmod(unsigned char *data, int size);
int stairgen();
int blockgen(char *blockid);
int is_color_unique(uint32_t color);
int color2blockid(uint32_t color);
/* blocks.c */
void blocks_getdir(char **argv, char **envp);
void blocks_parse();
void blocks_free();
/* schem.c */
/* schemimp.c */
void schem_load(unsigned char *data, unsigned int size);
void bprint_load(unsigned char *data, unsigned int size);
/* mts.c */
void mts_load(unsigned char *data, unsigned int size);
int mts_save();
int mts_view(int type);
int mts_blueprint();
int mts_blueprint(int type);
int mts_dump();
int mts_getbounds(int sanitize, unsigned short *tr, unsigned short *tr2);
void mts_layerprob(int diff);

View File

@ -173,7 +173,7 @@ void mts_load(unsigned char *data, unsigned int size)
for(z = 0; z < mts_z; z++)
for(x = 0; x < mts_x; x++)
if(nodes[y+min_y][z+min_z][x+min_x].param2 == 0x20) {
gndlayer = currlayer = y+min_y;
gndlayer = y+min_y;
nodes[y+min_y][z+min_z][x+min_x].param2 = 0;
}
status = lang[LOADED];
@ -414,13 +414,14 @@ int mts_view(int type)
/**
* Export a PNG blueprint file
*/
int mts_blueprint()
int mts_blueprint(int type)
{
int i, w, h, x, y, z;
char pngfile[MAXPATHLEN], *c;
int oldsmax = strmaxw, i, j, k, l, bw, w, h, x, y, z;
char pngfile[MAXPATHLEN], *s, *e, *c;
unsigned char *buff = NULL;
uint32_t *dst;
SDL_Surface *bprint;
SDL_Surface *oldscr = screen;
SDL_Rect rect;
FILE *f;
status = lang[ERR_PREVIEW];
@ -428,35 +429,72 @@ int mts_blueprint()
if(mix > max) return 1;
bound_valid = 0;
/* calculate dimensions and create images */
w = maz - miz + 3;
h = (may - miy + 1) * (max - mix + 2) + 3;
bprint = SDL_CreateRGBSurface(0, w, h, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
memset(bprint->pixels, 0, w * h * 4);
dst = (uint32_t*)bprint->pixels;
strcpy(pngfile, mtsfile);
s = strrchr(pngfile, DIRSEP);
if(!s) s = pngfile; else s++;
e = strrchr(pngfile, '.');
if(!e) e = &pngfile[strlen(pngfile)];
/* calculate dimensions and create image */
bw = maz - miz;
h = (may - miy + 1) * (max - mix + 2) + 3;
if(!type) {
for(i = 1, j = l = 0; i < numblocks; i++)
if(blocks[i].numref) {
l += font->height;
k = mbstrlen(blocks[i].name);
if(k > j) j = k;
}
w = bw + 8 + (4 + j) * (font->width + 1);
if(l > h) h = l;
h += font->height + 5;
} else
w = bw + 3;
screen = SDL_CreateRGBSurface(0, w, h, 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
memset(screen->pixels, 0, w * h * 4);
dst = (uint32_t*)screen->pixels;
if(!type) {
for(i = 0; i < w * h; i++) dst[i] = theme[THEME_BP_BG];
/* print title */
for(c = s; c < e; c++)
if(*c == '_') *c = ' ';
*e = 0; strmaxw = screen->w - 1;
i = mbstrlen(s) * (font->width+1);
sdlprint((screen->w - (i < screen->w ? i : screen->w)) / 2, 0, THEME_BP_FG, THEME_BP_BG, s);
/* print legend */
rect.x = bw + font->height; rect.y = font->height + 4; rect.w = rect.h = font->height - 2;
for(i = 1; i < numblocks; i++)
if(blocks[i].numref) {
SDL_FillRect(screen, &rect, blocks[i].color);
sdlprint(rect.x + 4 + font->height, rect.y, THEME_BP_FG, THEME_BP_BG, blocks[i].name);
rect.y += font->height;
}
i = w * (font->height + 5);
} else
i = w;
/* generate blueprint into surface */
for(i = w, y = may; y >= miy; y--, i += w) {
for(y = may; y >= miy; y--, i += w) {
dst[i] = (y == gndlayer ? 0xFF0000FF : 0xFF000000);
for(x = mix; x <= max; x++) {
i++;
for(z = maz; z >= miz; z--, i++)
if(nodes[y][z][x].param0)
dst[i] = blocks[nodes[y][z][x].param0].color;
dst[i] = (nodes[y][z][x].param0 ? blocks[nodes[y][z][x].param0].color : 0);
if(y == gndlayer)
dst[i] = 0xFF0000FF;
i++;
i += w - bw - 2;
}
dst[i] = y == gndlayer ? 0xFF0000FF : 0xFF000000;
}
/* write result to PNG file */
buff = (unsigned char *)stbi_write_png_to_mem(bprint->pixels, bprint->pitch, bprint->w, bprint->h, 4, &i);
SDL_FreeSurface(bprint);
buff = (unsigned char *)stbi_write_png_to_mem(screen->pixels, screen->pitch, screen->w, screen->h, 4, &i);
SDL_FreeSurface(screen);
screen = oldscr;
strmaxw = oldsmax;
if(buff) {
strcpy(pngfile, mtsfile);
c = strrchr(pngfile, '.');
if(!c) c = &pngfile[strlen(pngfile)];
strcpy(c, "_bp.png");
strcpy(e, "_bp.png");
f = fopen(pngfile,"wb");
if(f) {
fwrite(buff, i, 1, f);

View File

@ -1,5 +1,5 @@
/*
* mtsedit/schem.c
* mtsedit/schemimp.c
*
* Copyright (C) 2019 bzt (bztsrc@gitlab)
*
@ -23,26 +23,24 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @brief Import a Minecraft NBT .schematic file
* @brief Import various non-MTS formats
*
*/
#include "main.h"
/* parse an integer, the only type we are interested */
#define SCHEM_GETINT(v,t) do{switch(t){case 1:v=d[0];d++;break;case 2:v=(d[0]<<8)|d[1];d+=2;break; \
case 3:v=(d[0]<<24)|(d[1]<<16)|(d[2]<<8)|d[3];d+=4;break;}}while(0)
/**
* Load a Minecraft NBT Schematic file
*/
/* parse an integer, the only dynamic type we are interested in. */
#define SCHEM_GETINT(v,t) do{switch(t){case 1:v=d[0];d++;break;case 2:v=(d[0]<<8)|d[1];d+=2;break; \
case 3:v=(d[0]<<24)|(d[1]<<16)|(d[2]<<8)|d[3];d+=4;break;}}while(0)
void schem_load(unsigned char *data, unsigned int size)
{
int i, j, k, x, y, z, min_x = 0, min_y = 0, min_z = 0;
unsigned char *d, *blk, *blkd = NULL, *end = data + size;
/* we don't care about that overcomplicated NBT mess */
mts_y = mts_z = mts_x = 0;
for(d = data; d < end && (!mts_y || !mts_z || !mts_x); d++) {
if(!memcmp(d, "\000\006Height", 8)) { d += 8; SCHEM_GETINT(mts_y, d[-9]); d--; } else
if(!memcmp(d, "\000\006Length", 8)) { d += 8; SCHEM_GETINT(mts_z, d[-9]); d--; } else
@ -54,6 +52,7 @@ void schem_load(unsigned char *data, unsigned int size)
min_x = 127 - mts_x / 2;
min_y = 127 - mts_y / 2;
min_z = 127 - mts_z / 2;
gndlayer = min_y;
/* now that we know the dimensions, find the blocks data */
for(d = data; d < end; d++) {
/* it must be type of byte array */
@ -70,6 +69,7 @@ void schem_load(unsigned char *data, unsigned int size)
for(j = 0; j < 4; j++)
if(blocks[i].blockids[j] == *d) { k = i; i = numblocks; break; }
if(k) {
layerprob[y+min_y] = 127;
nodes[y+min_y][z+min_z][x+min_x].param0 = k;
nodes[y+min_y][z+min_z][x+min_x].param1 = 127;
if(blkd)
@ -83,3 +83,62 @@ void schem_load(unsigned char *data, unsigned int size)
}
}
}
/**
* Load a blueprint from a PNG file (or voxelize any other PNG image)
*/
void bprint_load(unsigned char *data, unsigned int size)
{
stbi__context s;
stbi__result_info ri;
int w = 0, h = 0, xs = 1, ys = 0, yg = 0, min_x = 0, min_y = 0, min_z = 0, len, x, y, z;
uint32_t *d;
mts_y = mts_z = mts_x = gndlayer = 0;
s.read_from_callbacks = 0;
s.img_buffer = s.img_buffer_original = data;
s.img_buffer_end = s.img_buffer_original_end = data + size;
ri.bits_per_channel = 8;
d = (uint32_t*)stbi__png_load(&s, &w, &h, &len, 0, &ri);
if(!d || len != 4) return;
/* detect dimensions */
for(y = 0; y < h; y++) {
if(d[y*w] == 0xFF000000 || d[y*w] == 0xFF0000FF) {
if(!mts_y) {
ys = y;
for(mts_z = 1; mts_z + 1 < w - 1 && d[y*w+mts_z+1] != theme[THEME_BP_BG]; mts_z++);
} else if(mts_y == 1) mts_x = y - ys - 1;
if(d[y*w] == 0xFF0000FF) yg = mts_y;
mts_y++;
}
}
if(!mts_x) mts_x = h - ys - 2;
if(!mts_y) { ys = yg = xs = 0; mts_y = 1; mts_z = w; mts_x = h; }
if(mts_x < 1 || mts_y < 1 || mts_z < 1) {
mts_y = mts_z = mts_x = 0;
return;
}
if(mts_y > 255) mts_y = 255;
if(mts_z > 255) mts_z = 255;
if(mts_x > 255) mts_x = 255;
blocks[0].numref -= mts_y * mts_z * mts_x;
min_x = 127 - mts_x / 2;
min_y = 127 + mts_y / 2;
min_z = 127 - mts_z / 2;
gndlayer = min_y - yg;
/* read in the layers */
for(y = 0; y < mts_y; y++)
for(x = 0; x < mts_x && (y*(mts_x+1)+ys+x) < h; x++)
for(z = 0; z < mts_z; z++) {
nodes[min_y-y][mts_z+min_z-z][x+min_x].param0 = color2blockid(d[(y*(mts_x+1)+ys+x)*w + z + xs]);
if(nodes[min_y-y][mts_z+min_z-z][x+min_x].param0) {
layerprob[min_y-y] = 127;
nodes[min_y-y][mts_z+min_z-z][x+min_x].param1 = 127;
blocks[nodes[min_y-z][mts_z+min_z-z][x+min_x].param0].numref++;
blocks[0].numref--;
}
}
status = lang[LOADED];
}

View File

@ -31,7 +31,7 @@
#include "data.h"
uint32_t theme[] = { 0xFFF0F0F0, 0xFFF4F4F4, 0xFFBEBEBE, 0xFF808080, 0xFF5C5C5C, 0xFF4C4C4C, 0xFF454545, 0xFF383838, 0xFF303030,
0xFFC00000, 0xFF800000 };
0xFFC00000, 0xFF800000, 0xFF7F7F7F, 0xFF3F0000 };
int quitting = 0, activetool = -1, overtool = -1, activeblock = 0, overblock = -1, palette[16], strmaxw, strsepar = 1;
int shift = 0, ctrl = 0, help = 0, systemcursor = 0;
@ -325,7 +325,7 @@ void sdldo(int opt)
case 9: hist_prepare(HIST_DELZ, cz); mts_delz(cz); hist_commit(); break;
case 10: hist_prepare(HIST_ADDX, cx); mts_addx(cx, shift); hist_commit(); break;
case 11: hist_prepare(HIST_DELX, cx); mts_delx(cx); hist_commit(); break;
case 12: mts_blueprint(); break;
case 12: mts_blueprint(shift); break;
}
SDL_SetCursor(pointer);
SDL_ShowCursor(0); systemcursor = 0;
@ -345,13 +345,16 @@ int sdlmain(int opt)
SDL_Event event, lookahead;
/* this is tricky, because extern wouldn't know sizeof() */
if(opt) return instmod(binary_mtsedit, sizeof(binary_mtsedit));
if(opt == 7) return instmod(binary_mtsedit, sizeof(binary_mtsedit));
/* get our font */
font = (psf_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)binary_font_psf_gz + 10,
sizeof(binary_font_psf_gz) - 10, 32768, &len, 0);
if(!font) error(lang[ERR_MEM]);
/* as an additional dependency, blueprint needs the font */
if(opt) return mts_blueprint(shift);
/* initialize SDL */
SDL_Init(SDL_INIT_VIDEO);
fn = strrchr(mtsfile, DIRSEP);

View File

@ -81,7 +81,7 @@ void readschem()
if(isdir(mtsfile)) return;
bound_valid = 0;
mts_x = mts_y = mts_z = 0; currlayer = 127;
mts_x = mts_y = mts_z = 0;
memset(layerprob, 127, sizeof(layerprob));
memset(nodes, 0, sizeof(nodes));
for(x = 0; x < numblocks; x++) {
@ -90,7 +90,7 @@ void readschem()
}
blocks[0].numref = 256 * 256 * 256;
savepal = savemapgen = savebiome = 0;
currlayer = cx = cz = 127;
currlayer = gndlayer = cx = cz = 127;
status = lang[ERR_LOAD];
f = fopen(mtsfile, "rb");
@ -121,7 +121,9 @@ void readschem()
/* if it's an MTS file */
if(!memcmp(data, "MTSM", 4)) mts_load(data, size); else
/* Could be a Minecraft NBT */
if(!memcmp(data + 3, "Schematic", 9)) schem_load(data, size);
if(!memcmp(data + 3, "Schematic", 9)) schem_load(data, size); else
/* Blueprint PNG */
if(!memcmp(data, "\x89PNG", 4)) bprint_load(data, size);
/* make sure that all non-air blocks have their probability set */
for(y = 0; y < 256; y++)
@ -131,6 +133,7 @@ void readschem()
if(!(nodes[y][z][x].param1 & 0x7F)) nodes[y][z][x].param1 |= 127;
nodes[y][z][x].param2 &= PARAM2_MAX;
}
currlayer = gndlayer;
}
if(data) free(data);
}
@ -1254,3 +1257,39 @@ int blockgen(char *blockid)
printf("mtsedit: %d PNGs, OK. \r\n", o);
return 0;
}
/**
* Returns true if color is not used by any blocks
*/
int is_color_unique(uint32_t color)
{
int i;
if(color == 0xFF000000 || color == 0xFFFF0000 || color == theme[THEME_BP_BG]) return 0;
for(i = 1; i < numblocks - 1; i++)
if(blocks[i].color == color) return 0;
return 1;
}
/**
* Returns block id for the closest color match
*/
int color2blockid(uint32_t color)
{
unsigned char *c = (unsigned char*)&color, *d;
int i, j, k, ld, diff = 255;
/* unknown and air blocks */
if(color == 0xFFFF0000 || color == theme[THEME_BP_BG] || c[3] < 128 || (!c[0] && !c[1] && !c[2])) return 0;
for(i = 1, j = 0; i < numblocks; i++) {
/* get local maximum */
ld = 0; d = (unsigned char*)&blocks[i].color;
k = d[0] > c[0] ? d[0] - c[0] : c[0] - d[0]; if(k > ld) ld = k;
k = d[1] > c[1] ? d[1] - c[1] : c[1] - d[1]; if(k > ld) ld = k;
k = d[2] > c[2] ? d[2] - c[2] : c[2] - d[2]; if(k > ld) ld = k;
k = d[3] > c[3] ? d[3] - c[3] : c[3] - d[3]; if(k > ld) ld = k;
/* is it smaller than global minimum? */
if(ld < diff) { diff = ld; j = i; }
}
return j;
}