diff --git a/chunk.py b/chunk.py index 2849c4b..6a02669 100644 --- a/chunk.py +++ b/chunk.py @@ -437,13 +437,6 @@ class ChunkRenderer(object): tileEntities = get_tileentity_data(self.level) - if self.world.useBiomeData: - biomeColorData = textures.getBiomeData(self.world.worlddir, - self.chunkX, self.chunkY) - # in the 32x32 block of biome data, what chunk is this?l - startX = self.chunkX % 32 - startY = self.chunkY % 32 - # Each block is 24x24 # The next block on the X axis adds 12px to x and subtracts 6px from y in the image # The next block on the Y axis adds 12px to x and adds 6px to y in the image diff --git a/src/composite.c b/src/composite.c index 16a826f..f7186f6 100644 --- a/src/composite.c +++ b/src/composite.c @@ -34,7 +34,7 @@ typedef struct { Imaging image; } ImagingObject; -Imaging +inline Imaging imaging_python_to_c(PyObject *obj) { PyObject *im; @@ -58,6 +58,45 @@ imaging_python_to_c(PyObject *obj) return image; } +/* helper function to setup s{x,y}, d{x,y}, and {x,y}size variables + in these composite functions -- even handles auto-sizing to src! */ +static inline void +setup_source_destination(Imaging src, Imaging dest, + int *sx, int *sy, int *dx, int *dy, int *xsize, int *ysize) +{ + /* handle negative/zero sizes appropriately */ + if (*xsize <= 0 || *ysize <= 0) { + *xsize = src->xsize; + *ysize = src->ysize; + } + + /* set up the source position, size and destination position */ + /* handle negative dest pos */ + if (*dx < 0) { + *sx = -(*dx); + *dx = 0; + } else { + *sx = 0; + } + + if (*dy < 0) { + *sy = -(*dy); + *dy = 0; + } else { + *sy = 0; + } + + /* set up source dimensions */ + *xsize -= *sx; + *ysize -= *sy; + + /* clip dimensions, if needed */ + if (*dx + *xsize > dest->xsize) + *xsize = dest->xsize - *dx; + if (*dy + *ysize > dest->ysize) + *ysize = dest->ysize - *dy; +} + /* convenience alpha_over with 1.0 as overall_alpha */ inline PyObject* alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, int xsize, int ysize) { @@ -129,39 +168,8 @@ alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alp /* how many bytes to skip to get to the next alpha byte */ mask_stride = imMask->pixelsize; - /* handle negative/zero sizes appropriately */ - if (xsize <= 0 || ysize <= 0) { - xsize = imSrc->xsize; - ysize = imSrc->ysize; - } - - /* set up the source position, size and destination position */ - /* handle negative dest pos */ - if (dx < 0) { - sx = -dx; - dx = 0; - } - else { - sx = 0; - } - - if (dy < 0) { - sy = -dy; - dy = 0; - } - else { - sy = 0; - } - - /* set up source dimensions */ - xsize -= sx; - ysize -= sy; - - /* clip dimensions, if needed */ - if (dx + xsize > imDest->xsize) - xsize = imDest->xsize - dx; - if (dy + ysize > imDest->ysize) - ysize = imDest->ysize - dy; + /* setup source & destination vars */ + setup_source_destination(imSrc, imDest, &sx, &sy, &dx, &dy, &xsize, &ysize); /* check that there remains any blending to be done */ if (xsize <= 0 || ysize <= 0) { @@ -195,13 +203,11 @@ alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alp out++, in++; *out = *in; out++, in++; - } - else if (in_alpha == 0) { + } else if (in_alpha == 0) { /* do nothing -- source is fully transparent */ out += 3; in += 3; - } - else { + } else { /* general case */ int alpha = in_alpha + MULDIV255(*outmask, 255 - in_alpha, tmp1); for (i = 0; i < 3; i++) { @@ -262,3 +268,89 @@ alpha_over_wrap(PyObject *self, PyObject *args) } return ret; } + +/* like alpha_over, but instead of src image it takes a source color + * also, it multiplies instead of doing an over operation + */ +inline PyObject * +tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, + PyObject *mask, int dx, int dy, int xsize, int ysize) { + /* libImaging handles */ + Imaging imDest, imMask; + /* cached blend properties */ + int mask_offset, mask_stride; + /* source position */ + int sx, sy; + /* iteration variables */ + unsigned int x, y; + /* temporary calculation variables */ + int tmp1, tmp2; + + imDest = imaging_python_to_c(dest); + imMask = imaging_python_to_c(mask); + + if (!imDest || !imMask) + return NULL; + + /* check the various image modes, make sure they make sense */ + if (strcmp(imDest->mode, "RGBA") != 0) { + PyErr_SetString(PyExc_ValueError, + "given destination image does not have mode \"RGBA\""); + return NULL; + } + + if (strcmp(imMask->mode, "RGBA") != 0 && strcmp(imMask->mode, "L") != 0) { + PyErr_SetString(PyExc_ValueError, + "given mask image does not have mode \"RGBA\" or \"L\""); + return NULL; + } + + /* how far into image the first alpha byte resides */ + mask_offset = (imMask->pixelsize == 4 ? 3 : 0); + /* how many bytes to skip to get to the next alpha byte */ + mask_stride = imMask->pixelsize; + + /* setup source & destination vars */ + setup_source_destination(imMask, imDest, &sx, &sy, &dx, &dy, &xsize, &ysize); + + /* check that there remains any blending to be done */ + if (xsize <= 0 || ysize <= 0) { + /* nothing to do, return */ + return dest; + } + + for (y = 0; y < ysize; y++) { + UINT8 *out = (UINT8 *)imDest->image[dy + y] + dx * 4; + UINT8 *inmask = (UINT8 *)imMask->image[sy + y] + sx * mask_stride + mask_offset; + + for (x = 0; x < xsize; x++) { + /* special cases */ + if (*inmask == 255) { + *out = MULDIV255(*out, sr, tmp1); + out++; + *out = MULDIV255(*out, sg, tmp1); + out++; + *out = MULDIV255(*out, sb, tmp1); + out++; + } else if (*inmask == 0) { + /* do nothing -- source is fully transparent */ + out += 3; + } else { + /* general case */ + + /* TODO work out general case */ + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sr, *inmask, tmp1), tmp2); + out++; + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sg, *inmask, tmp1), tmp2); + out++; + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sb, *inmask, tmp1), tmp2); + out++; + } + + out++; + inmask += mask_stride; + } + } + + return dest; +} diff --git a/src/iterate.c b/src/iterate.c index 8fab11b..71bd7e3 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -247,7 +247,7 @@ chunk_render(PyObject *self, PyObject *args) { /* set up the render mode */ rendermode = get_render_mode(&state); - rm_data = malloc(rendermode->data_size); + rm_data = calloc(1, rendermode->data_size); if (rendermode->start(rm_data, &state)) { free(rm_data); return Py_BuildValue("i", "-1"); diff --git a/src/overviewer.h b/src/overviewer.h index 6b9db21..ebf83ce 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -29,8 +29,9 @@ #include #include -/* macro for getting a value out of a 3D numpy byte array */ +/* macro for getting a value out of various numpy arrays */ #define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z)))) +#define getArrayShort1D(array, x) (*(unsigned short *)(PyArray_GETPTR1((array), (x)))) /* generally useful MAX / MIN macros */ #define MAX(a, b) ((a) > (b) ? (a) : (b)) @@ -43,6 +44,8 @@ PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, PyObject *alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha, int dx, int dy, int xsize, int ysize); PyObject *alpha_over_wrap(PyObject *self, PyObject *args); +PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, + PyObject *mask, int dx, int dy, int xsize, int ysize); /* in iterate.c */ typedef struct { diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index ca1b9c5..85e55ed 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -19,13 +19,75 @@ static int rendermode_normal_start(void *data, RenderState *state) { - /* do nothing */ + PyObject *chunk_x_py, *chunk_y_py, *world, *use_biomes, *worlddir; + RenderModeNormal *self = (RenderModeNormal *)data; + + chunk_x_py = PyObject_GetAttrString(state->self, "chunkX"); + chunk_y_py = PyObject_GetAttrString(state->self, "chunkY"); + + /* careful now -- C's % operator works differently from python's + we can't just do x % 32 like we did before */ + self->chunk_x = PyInt_AsLong(chunk_x_py); + self->chunk_y = PyInt_AsLong(chunk_y_py); + + while (self->chunk_x < 0) + self->chunk_x += 32; + while (self->chunk_y < 0) + self->chunk_y += 32; + + self->chunk_x %= 32; + self->chunk_y %= 32; + + /* fetch the biome data from textures.py, if needed */ + world = PyObject_GetAttrString(state->self, "world"); + worlddir = PyObject_GetAttrString(world, "worlddir"); + use_biomes = PyObject_GetAttrString(world, "useBiomeData"); + Py_DECREF(world); + + if (PyObject_IsTrue(use_biomes)) { + PyObject *facemasks_py; + + self->biome_data = PyObject_CallMethod(state->textures, "getBiomeData", "OOO", + worlddir, chunk_x_py, chunk_y_py); + self->foliagecolor = PyObject_GetAttrString(state->textures, "foliagecolor"); + self->grasscolor = PyObject_GetAttrString(state->textures, "grasscolor"); + + self->leaf_texture = PyObject_GetAttrString(state->textures, "biome_leaf_texture"); + self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture"); + + facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); + /* borrowed reference, needs to be incref'd if we keep it */ + self->facemask_top = PyTuple_GetItem(facemasks_py, 0); + Py_INCREF(self->facemask_top); + Py_DECREF(facemasks_py); + } else { + self->biome_data = NULL; + self->foliagecolor = NULL; + self->grasscolor = NULL; + + self->leaf_texture = NULL; + self->grass_texture = NULL; + self->facemask_top = NULL; + } + + Py_DECREF(use_biomes); + Py_DECREF(worlddir); + Py_DECREF(chunk_x_py); + Py_DECREF(chunk_y_py); + return 0; } static void rendermode_normal_finish(void *data, RenderState *state) { - /* do nothing */ + RenderModeNormal *self = (RenderModeNormal *)data; + + Py_XDECREF(self->biome_data); + Py_XDECREF(self->foliagecolor); + Py_XDECREF(self->grasscolor); + Py_XDECREF(self->leaf_texture); + Py_XDECREF(self->grass_texture); + Py_XDECREF(self->facemask_top); } static int @@ -44,7 +106,66 @@ rendermode_normal_occluded(void *data, RenderState *state) { static void rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + RenderModeNormal *self = (RenderModeNormal *)data; + + /* first, check to see if we should use biome-compatible src, mask */ + if (self->biome_data) { + switch (state->block) { + case 2: + src = mask = self->grass_texture; + break; + case 18: + src = mask = self->leaf_texture; + break; + default: + break; + }; + } + + /* draw the block! */ alpha_over(state->img, src, mask, state->imgx, state->imgy, 0, 0); + + if (self->biome_data) { + /* do the biome stuff! */ + unsigned int index; + PyObject *index_py, *color = NULL, *facemask = NULL; + unsigned char r, g, b; + + index = ((self->chunk_y * 16) + state->y) * 16 * 32 + (self->chunk_x * 16) + state->x; + /* TODO for some reason, this one doesn't work: + * index = getArrayShort1D(self->biome_data, index); + */ + index_py = PySequence_GetItem(self->biome_data, index); + index = PyInt_AsLong(index_py); + Py_DECREF(index_py); + + switch (state->block) { + case 2: + /* grass */ + color = PySequence_GetItem(self->grasscolor, index); + facemask = self->facemask_top; + break; + case 18: + /* leaves */ + color = PySequence_GetItem(self->foliagecolor, index); + facemask = mask; + break; + default: + break; + }; + + if (color) + { + /* we've got work to do */ + + r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0)); + g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1)); + b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)); + Py_DECREF(color); + + tint_with_mask(state->img, r, g, b, facemask, state->imgx, state->imgy, 0, 0); + } + } } RenderModeInterface rendermode_normal = { diff --git a/src/rendermodes.h b/src/rendermodes.h index 5ebd18b..d26091d 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -58,9 +58,16 @@ RenderModeInterface *get_render_mode(RenderState *state); /* NORMAL */ typedef struct { - /* normal mode does not have any special data, so just use a dummy int - this way, normal mode is just like any other type of render mode */ - int dummy; + /* coordinates of the chunk, inside its region file */ + int chunk_x, chunk_y; + /* biome data for the region */ + PyObject *biome_data; + /* grasscolor and foliagecolor lookup tables */ + PyObject *grasscolor, *foliagecolor; + /* biome-compatible grass/leaf textures */ + PyObject *grass_texture, *leaf_texture; + /* top facemask for grass biome tinting */ + PyObject *facemask_top; } RenderModeNormal; extern RenderModeInterface rendermode_normal; diff --git a/textures.py b/textures.py index 9e4a930..d0900a1 100644 --- a/textures.py +++ b/textures.py @@ -633,15 +633,8 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) if blockID == 2: # grass - top = transform_image(tintTexture(terrain_images[0],(115,175,71))) - side1 = transform_image_side(terrain_images[3]) - side2 = transform_image_side(terrain_images[3]).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) + top = tintTexture(terrain_images[0],(115,175,71)) + img = _build_block(top, terrain_images[3], 2) return (img.convert("RGB"), img.split()[3]) if blockID == 51: # fire @@ -661,15 +654,7 @@ def generate_special_texture(blockID, data): if blockID == 18: # leaves t = tintTexture(terrain_images[52], (37, 118, 25)) - top = transform_image(t) - side1 = transform_image_side(t) - side2 = transform_image_side(t).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) + img = _build_block(t, t, 18) return (img.convert("RGB"), img.split()[3]) if blockID == 17: # wood: normal, birch and pines @@ -990,31 +975,9 @@ def tintTexture(im, c): i.putalpha(im.split()[3]); # copy the alpha band back in. assuming RGBA return i -grassSide1 = transform_image_side(terrain_images[3]) -grassSide2 = transform_image_side(terrain_images[3]).transpose(Image.FLIP_LEFT_RIGHT) -def prepareGrassTexture(color): - top = transform_image(tintTexture(terrain_images[0],color)) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - img.paste(grassSide1, (0,6), grassSide1) - img.paste(grassSide2, (12,6), grassSide2) - img.paste(top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - - -def prepareLeafTexture(color): - t = tintTexture(terrain_images[52], color) - top = transform_image(t) - side1 = transform_image_side(t) - side2 = transform_image_side(t).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - img.paste(side1, (0,6), side1) - img.paste(side2, (12,6), side2) - img.paste(top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - +# generate biome (still grayscale) leaf, grass textures +biome_grass_texture = _build_block(terrain_images[0], terrain_images[3], 2) +biome_leaf_texture = _build_block(terrain_images[52], terrain_images[52], 18) currentBiomeFile = None