631 lines
25 KiB
Python
631 lines
25 KiB
Python
# This file is part of the Minecraft Overviewer.
|
|
#
|
|
# Minecraft Overviewer is free software: you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or (at
|
|
# your option) any later version.
|
|
#
|
|
# Minecraft Overviewer is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
# Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import sys
|
|
import os
|
|
import os.path
|
|
import zipfile
|
|
from cStringIO import StringIO
|
|
import math
|
|
|
|
import numpy
|
|
from PIL import Image, ImageEnhance, ImageOps
|
|
|
|
import util
|
|
import composite
|
|
|
|
def _find_file(filename, mode="rb"):
|
|
"""Searches for the given file and returns an open handle to it.
|
|
This searches the following locations in this order:
|
|
|
|
* The program dir (same dir as this file)
|
|
* On Darwin, in /Applications/Minecraft
|
|
* Inside minecraft.jar, which is looked for at these locations
|
|
|
|
* On Windows, at %APPDATA%/.minecraft/bin/minecraft.jar
|
|
* On Darwin, at $HOME/Library/Application Support/minecraft/bin/minecraft.jar
|
|
* at $HOME/.minecraft/bin/minecraft.jar
|
|
|
|
* The current working directory
|
|
* The program dir / textures
|
|
|
|
"""
|
|
programdir = util.get_program_path()
|
|
path = os.path.join(programdir, filename)
|
|
if os.path.exists(path):
|
|
return open(path, mode)
|
|
|
|
if sys.platform == "darwin":
|
|
path = os.path.join("/Applications/Minecraft", filename)
|
|
if os.path.exists(path):
|
|
return open(path, mode)
|
|
|
|
# Find minecraft.jar.
|
|
jarpaths = []
|
|
if "APPDATA" in os.environ:
|
|
jarpaths.append( os.path.join(os.environ['APPDATA'], ".minecraft",
|
|
"bin", "minecraft.jar"))
|
|
if "HOME" in os.environ:
|
|
jarpaths.append(os.path.join(os.environ['HOME'], "Library",
|
|
"Application Support", "minecraft","bin","minecraft.jar"))
|
|
jarpaths.append(os.path.join(os.environ['HOME'], ".minecraft", "bin",
|
|
"minecraft.jar"))
|
|
jarpaths.append(programdir)
|
|
jarpaths.append(os.getcwd())
|
|
|
|
for jarpath in jarpaths:
|
|
if os.path.exists(jarpath):
|
|
try:
|
|
jar = zipfile.ZipFile(jarpath)
|
|
return jar.open(filename)
|
|
except (KeyError, IOError):
|
|
pass
|
|
|
|
path = filename
|
|
if os.path.exists(path):
|
|
return open(path, mode)
|
|
|
|
path = os.path.join(programdir, "textures", filename)
|
|
if os.path.exists(path):
|
|
return open(path, mode)
|
|
|
|
raise IOError("Could not find the file {0}. Is Minecraft installed? If so, I couldn't find the minecraft.jar file.".format(filename))
|
|
|
|
def _load_image(filename):
|
|
"""Returns an image object"""
|
|
fileobj = _find_file(filename)
|
|
buffer = StringIO(fileobj.read())
|
|
return Image.open(buffer)
|
|
|
|
def _get_terrain_image():
|
|
return _load_image("terrain.png")
|
|
|
|
def _split_terrain(terrain):
|
|
"""Builds and returns a length 256 array of each 16x16 chunk of texture"""
|
|
textures = []
|
|
(terrain_width, terrain_height) = terrain.size
|
|
texture_resolution = terrain_width / 16
|
|
for y in xrange(16):
|
|
for x in xrange(16):
|
|
left = x*texture_resolution
|
|
upper = y*texture_resolution
|
|
right = left+texture_resolution
|
|
lower = upper+texture_resolution
|
|
region = terrain.transform(
|
|
(16, 16),
|
|
Image.EXTENT,
|
|
(left,upper,right,lower),
|
|
Image.BICUBIC)
|
|
textures.append(region)
|
|
|
|
return textures
|
|
|
|
# This maps terainids to 16x16 images
|
|
terrain_images = _split_terrain(_get_terrain_image())
|
|
|
|
def transform_image(img, blockID=None):
|
|
"""Takes a PIL image and rotates it left 45 degrees and shrinks the y axis
|
|
by a factor of 2. Returns the resulting image, which will be 24x12 pixels
|
|
|
|
"""
|
|
|
|
if blockID in (81,): # cacti
|
|
# Resize to 15x15, since the cactus texture is a little smaller than the other textures
|
|
img = img.resize((15, 15), Image.BILINEAR)
|
|
|
|
else:
|
|
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
|
|
# even number that can be split in half twice
|
|
img = img.resize((17, 17), Image.BILINEAR)
|
|
|
|
# Build the Affine transformation matrix for this perspective
|
|
transform = numpy.matrix(numpy.identity(3))
|
|
# Translate up and left, since rotations are about the origin
|
|
transform *= numpy.matrix([[1,0,8.5],[0,1,8.5],[0,0,1]])
|
|
# Rotate 45 degrees
|
|
ratio = math.cos(math.pi/4)
|
|
#transform *= numpy.matrix("[0.707,-0.707,0;0.707,0.707,0;0,0,1]")
|
|
transform *= numpy.matrix([[ratio,-ratio,0],[ratio,ratio,0],[0,0,1]])
|
|
# Translate back down and right
|
|
transform *= numpy.matrix([[1,0,-12],[0,1,-12],[0,0,1]])
|
|
# scale the image down by a factor of 2
|
|
transform *= numpy.matrix("[1,0,0;0,2,0;0,0,1]")
|
|
|
|
transform = numpy.array(transform)[:2,:].ravel().tolist()
|
|
|
|
newimg = img.transform((24,12), Image.AFFINE, transform)
|
|
return newimg
|
|
|
|
def transform_image_side(img, blockID=None):
|
|
"""Takes an image and shears it for the left side of the cube (reflect for
|
|
the right side)"""
|
|
|
|
if blockID in (44,): # step block
|
|
# make the top half transparent
|
|
# (don't just crop img, since we want the size of
|
|
# img to be unchanged
|
|
mask = img.crop((0,8,16,16))
|
|
n = Image.new(img.mode, img.size, (38,92,255,0))
|
|
composite.alpha_over(n, mask,(0,0,16,8), mask)
|
|
img = n
|
|
if blockID in (78,): # snow
|
|
# make the top three quarters transparent
|
|
mask = img.crop((0,12,16,16))
|
|
n = Image.new(img.mode, img.size, (38,92,255,0))
|
|
composite.alpha_over(n, mask,(0,12,16,16), mask)
|
|
img = n
|
|
|
|
# Size of the cube side before shear
|
|
img = img.resize((12,12))
|
|
|
|
# Apply shear
|
|
transform = numpy.matrix(numpy.identity(3))
|
|
transform *= numpy.matrix("[1,0,0;-0.5,1,0;0,0,1]")
|
|
|
|
transform = numpy.array(transform)[:2,:].ravel().tolist()
|
|
|
|
newimg = img.transform((12,18), Image.AFFINE, transform)
|
|
return newimg
|
|
|
|
|
|
def _build_block(top, side, blockID=None):
|
|
"""From a top texture and a side texture, build a block image.
|
|
top and side should be 16x16 image objects. Returns a 24x24 image
|
|
|
|
"""
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
|
|
top = transform_image(top, blockID)
|
|
|
|
if not side:
|
|
composite.alpha_over(img, top, (0,0), top)
|
|
return img
|
|
|
|
side = transform_image_side(side, blockID)
|
|
|
|
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
# Darken the sides slightly. These methods also affect the alpha layer,
|
|
# so save them first (we don't want to "darken" the alpha layer making
|
|
# the block transparent)
|
|
sidealpha = side.split()[3]
|
|
side = ImageEnhance.Brightness(side).enhance(0.9)
|
|
side.putalpha(sidealpha)
|
|
othersidealpha = otherside.split()[3]
|
|
otherside = ImageEnhance.Brightness(otherside).enhance(0.8)
|
|
otherside.putalpha(othersidealpha)
|
|
|
|
## special case for non-block things
|
|
# TODO once torches are handled by generate_special_texture, remove
|
|
# them from this list
|
|
if blockID in (37,38,6,39,40,50,83): ## flowers, sapling, mushrooms, regular torch, reeds
|
|
# instead of pasting these blocks at the cube edges, place them in the middle:
|
|
# and omit the top
|
|
composite.alpha_over(img, side, (6,3), side)
|
|
composite.alpha_over(img, otherside, (6,3), otherside)
|
|
return img
|
|
|
|
|
|
if blockID in (81,): # cacti!
|
|
composite.alpha_over(img, side, (2,6), side)
|
|
composite.alpha_over(img, otherside, (10,6), otherside)
|
|
composite.alpha_over(img, top, (0,2), top)
|
|
elif blockID in (44,): # half step
|
|
# shift each texture down 6 pixels
|
|
composite.alpha_over(img, side, (0,12), side)
|
|
composite.alpha_over(img, otherside, (12,12), otherside)
|
|
composite.alpha_over(img, top, (0,6), top)
|
|
elif blockID in (78,): # snow
|
|
# shift each texture down 9 pixels
|
|
composite.alpha_over(img, side, (0,6), side)
|
|
composite.alpha_over(img, otherside, (12,6), otherside)
|
|
composite.alpha_over(img, top, (0,9), top)
|
|
else:
|
|
composite.alpha_over(img, side, (0,6), side)
|
|
composite.alpha_over(img, otherside, (12,6), otherside)
|
|
composite.alpha_over(img, top, (0,0), top)
|
|
|
|
# Manually touch up 6 pixels that leave a gap because of how the
|
|
# shearing works out. This makes the blocks perfectly tessellate-able
|
|
for x,y in [(13,23), (17,21), (21,19)]:
|
|
# Copy a pixel to x,y from x-1,y
|
|
img.putpixel((x,y), img.getpixel((x-1,y)))
|
|
for x,y in [(3,4), (7,2), (11,0)]:
|
|
# Copy a pixel to x,y from x+1,y
|
|
img.putpixel((x,y), img.getpixel((x+1,y)))
|
|
|
|
return img
|
|
|
|
|
|
def _build_blockimages():
|
|
"""Returns a mapping from blockid to an image of that block in perspective
|
|
The values of the mapping are actually (image in RGB mode, alpha channel).
|
|
This is not appropriate for all block types, only block types that are
|
|
proper cubes"""
|
|
|
|
# Top textures of all block types. The number here is the index in the
|
|
# texture array (terrain_images), which comes from terrain.png's cells, left to right top to
|
|
# bottom.
|
|
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
topids = [ -1, 1, 0, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33,
|
|
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
|
34, 21, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, # Cloths are left out
|
|
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
|
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 6, 6, 7, 8, 35, # Gold/iron blocks? Doublestep? TNT from above?
|
|
# 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
|
36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 1, 1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post
|
|
# 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
|
-1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67, # door,ladder left out. Minecart rail orientation
|
|
# 80 81 82 83 84 85 86 87 88 89 90 91
|
|
66, 69, 72, 73, 74, -1,102,103,104,105,-1, 102 # clay?
|
|
]
|
|
|
|
# NOTE: For non-block textures, the sideid is ignored, but can't be -1
|
|
|
|
# And side textures of all block types
|
|
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
sideids = [ -1, 1, 3, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33,
|
|
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
|
34, 20, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1,- 1, -1, -1, -1,
|
|
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
|
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 5, 5, 7, 8, 35,
|
|
# 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
|
36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 44, 61, -1,
|
|
# 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
|
-1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67,
|
|
# 80 81 82 83 84 85 86 87 88 89 90 91
|
|
66, 69, 72, 73, 74,-1 ,118,103,104,105, -1, 118
|
|
]
|
|
|
|
# This maps block id to the texture that goes on the side of the block
|
|
if len(topids) != len(sideids):
|
|
raise Exception("mismatched lengths")
|
|
|
|
allimages = []
|
|
for toptextureid, sidetextureid,blockID in zip(topids, sideids,range(len(topids))):
|
|
if toptextureid == -1 or sidetextureid == -1:
|
|
allimages.append(None)
|
|
continue
|
|
|
|
toptexture = terrain_images[toptextureid]
|
|
sidetexture = terrain_images[sidetextureid]
|
|
|
|
## _build_block needs to know about the block ID, not just the textures
|
|
## of the block or the texture ID
|
|
img = _build_block(toptexture, sidetexture, blockID)
|
|
|
|
allimages.append((img.convert("RGB"), img.split()[3]))
|
|
|
|
# Future block types:
|
|
while len(allimages) < 256:
|
|
allimages.append(None)
|
|
return allimages
|
|
blockmap = _build_blockimages()
|
|
|
|
def load_water():
|
|
"""Evidentially, the water and lava textures are not loaded from any files
|
|
in the jar (that I can tell). They must be generated on the fly. While
|
|
terrain.png does have some water and lava cells, not all texture packs
|
|
include them. So I load them here from a couple pngs included.
|
|
|
|
This mutates the blockmap global list with the new water and lava blocks.
|
|
Block 9, standing water, is given a block with only the top face showing.
|
|
Block 8, flowing water, is given a full 3 sided cube."""
|
|
|
|
watertexture = _load_image("water.png")
|
|
w1 = _build_block(watertexture, None)
|
|
blockmap[9] = w1.convert("RGB"), w1
|
|
w2 = _build_block(watertexture, watertexture)
|
|
blockmap[8] = w2.convert("RGB"), w2
|
|
|
|
lavatexture = _load_image("lava.png")
|
|
lavablock = _build_block(lavatexture, lavatexture)
|
|
blockmap[10] = lavablock.convert("RGB"), lavablock
|
|
blockmap[11] = blockmap[10]
|
|
load_water()
|
|
|
|
|
|
def generate_special_texture(blockID, data):
|
|
"""Generates a special texture, such as a correctly facing minecraft track"""
|
|
#print "%s has ancillary data: %X" %(blockID, data)
|
|
# TODO torches, redstone torches, crops, ladders, stairs,
|
|
# levers, doors, buttons, and signs all need to be handled here (and in chunkpy)
|
|
if blockID == 66: # minetrack:
|
|
raw_straight = terrain_images[128]
|
|
raw_corner = terrain_images[112]
|
|
|
|
## use transform_image to scale and shear
|
|
if data == 0:
|
|
track = transform_image(raw_straight, blockID)
|
|
elif data == 6:
|
|
track = transform_image(raw_corner, blockID)
|
|
elif data == 7:
|
|
track = transform_image(raw_corner.rotate(270), blockID)
|
|
elif data == 8:
|
|
# flip
|
|
track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90),
|
|
blockID)
|
|
elif data == 9:
|
|
track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM),
|
|
blockID)
|
|
elif data == 1:
|
|
track = transform_image(raw_straight.rotate(90), blockID)
|
|
else:
|
|
# TODO render carts that slop up or down
|
|
track = transform_image(raw_straight, blockID)
|
|
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
composite.alpha_over(img, track, (0,12), track)
|
|
|
|
return (img.convert("RGB"), img.split()[3])
|
|
if blockID == 59: # crops
|
|
raw_crop = terrain_images[88+data]
|
|
crop1 = transform_image(raw_crop, blockID)
|
|
crop2 = transform_image_side(raw_crop, blockID)
|
|
crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
composite.alpha_over(img, crop1, (0,12), crop1)
|
|
composite.alpha_over(img, crop2, (6,3), crop2)
|
|
composite.alpha_over(img, crop3, (6,3), crop3)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
|
|
if blockID == 61: #furnace
|
|
top = transform_image(terrain_images[1])
|
|
side1 = transform_image_side(terrain_images[45])
|
|
side2 = transform_image_side(terrain_images[44]).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)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
|
|
if blockID in (86,91): # jack-o-lantern
|
|
top = transform_image(terrain_images[102])
|
|
frontID = 119 if blockID == 86 else 120
|
|
side1 = transform_image_side(terrain_images[frontID])
|
|
side2 = transform_image_side(terrain_images[118]).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)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
|
|
if blockID == 62: # lit furnace
|
|
top = transform_image(terrain_images[1])
|
|
side1 = transform_image_side(terrain_images[45])
|
|
side2 = transform_image_side(terrain_images[45+16]).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)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
|
|
if blockID == 65: # ladder
|
|
raw_texture = terrain_images[83]
|
|
#print "ladder is facing: %d" % data
|
|
if data == 5:
|
|
# normally this ladder would be obsured by the block it's attached to
|
|
# but since ladders can apparently be placed on transparent blocks, we
|
|
# have to render this thing anyway. same for data == 2
|
|
tex = transform_image_side(raw_texture)
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
composite.alpha_over(img, tex, (0,6), tex)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
if data == 2:
|
|
tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
composite.alpha_over(img, tex, (12,6), tex)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
if data == 3:
|
|
tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
composite.alpha_over(img, tex, (0,0), tex)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
if data == 4:
|
|
tex = transform_image_side(raw_texture)
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
composite.alpha_over(img, tex, (12,0), tex)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
|
|
if blockID in (64,71): #wooden door, or iron door
|
|
if data & 0x8 == 0x8: # top of the door
|
|
raw_door = terrain_images[81 if blockID == 64 else 82]
|
|
else: # bottom of the door
|
|
raw_door = terrain_images[97 if blockID == 64 else 98]
|
|
|
|
# if you want to render all doors as closed, then force
|
|
# force swung to be False
|
|
if data & 0x4 == 0x4:
|
|
swung=True
|
|
else:
|
|
swung=False
|
|
|
|
# mask out the high bits to figure out the orientation
|
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
|
if (data & 0x03) == 0:
|
|
if not swung:
|
|
tex = transform_image_side(raw_door)
|
|
composite.alpha_over(img, tex, (0,6), tex)
|
|
else:
|
|
# flip first to set the doornob on the correct side
|
|
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
|
tex = tex.transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(img, tex, (0,0), tex)
|
|
|
|
if (data & 0x03) == 1:
|
|
if not swung:
|
|
tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(img, tex, (0,0), tex)
|
|
else:
|
|
tex = transform_image_side(raw_door)
|
|
composite.alpha_over(img, tex, (12,0), tex)
|
|
|
|
if (data & 0x03) == 2:
|
|
if not swung:
|
|
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
|
composite.alpha_over(img, tex, (12,0), tex)
|
|
else:
|
|
tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(img, tex, (12,6), tex)
|
|
|
|
if (data & 0x03) == 3:
|
|
if not swung:
|
|
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(img, tex, (12,6), tex)
|
|
else:
|
|
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
|
composite.alpha_over(img, tex, (0,6), tex)
|
|
|
|
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)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
|
|
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)
|
|
return (img.convert("RGB"), img.split()[3])
|
|
|
|
|
|
return None
|
|
|
|
def tintTexture(im, c):
|
|
# apparently converting to grayscale drops the alpha channel?
|
|
i = ImageOps.colorize(ImageOps.grayscale(im), (0,0,0), 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])
|
|
|
|
#useBiomeData = os.path.exists(os.path.join(self.world, EXTRACTEDBIOMES))
|
|
#if not useBiomeData:
|
|
# logging.info("Notice: Not using biome data for tinting")
|
|
|
|
try:
|
|
grasscolor = list(Image.open(_find_file("grasscolor.png")).getdata())
|
|
foliagecolor = list(Image.open(_find_file("foliagecolor.png")).getdata())
|
|
except:
|
|
grasscolor = None
|
|
foliagecolor = None
|
|
|
|
currentBiomeFile = None
|
|
currentBiomeData = None
|
|
def prepareBiomeData(worlddir, chunkX, chunkY):
|
|
'''Opens the worlddir and reads in the biome color information
|
|
from the .biome files. See also:
|
|
http://www.minecraftforum.net/viewtopic.php?f=25&t=80902
|
|
'''
|
|
t = sys.modules[__name__]
|
|
if not os.path.exists(os.path.join(worlddir, "EXTRACTEDBIOMES")):
|
|
raise Exception("EXTRACTEDBIOMES not found")
|
|
|
|
biomeFile = "%d.%d.biome" % (
|
|
int(math.floor(chunkX/8)*8),
|
|
int(math.floor(chunkY/8)*8)
|
|
)
|
|
if biomeFile == t.currentBiomeFile:
|
|
return currentBiomeData
|
|
|
|
t.currentBiomeFile = biomeFile
|
|
|
|
f = open(os.path.join(worlddir, "EXTRACTEDBIOMES", biomeFile), "rb")
|
|
rawdata = f.read()
|
|
f.close()
|
|
|
|
data = numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2"))
|
|
|
|
t.currentBiomeData = data
|
|
return data
|
|
|
|
# This set holds block ids that require special pre-computing. These are typically
|
|
# things that require ancillary data to render properly (i.e. ladder plus orientation)
|
|
special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18])
|
|
|
|
# this is a map of special blockIDs to a list of all
|
|
# possible values for ancillary data that it might have.
|
|
special_map = {}
|
|
special_map[66] = range(10) # minecrart tracks
|
|
special_map[59] = range(8) # crops
|
|
special_map[61] = (0,) # furnace
|
|
special_map[62] = (0,) # burning furnace
|
|
special_map[65] = (2,3,4,5) # ladder
|
|
special_map[64] = range(16) # wooden door
|
|
special_map[71] = range(16) # iron door
|
|
special_map[91] = range(5) # jack-o-lantern
|
|
special_map[86] = range(5) # pumpkin
|
|
# apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown
|
|
# what that data represents. For now, assume that the range for data is 0 to 5
|
|
# like torches
|
|
special_map[2] = (0,) # grass
|
|
special_map[18] = range(16) # leaves
|
|
# grass and leaves are now graysacle in terrain.png
|
|
# we treat them as special so we can manually tint them
|
|
# it is unknown how the specific tint (biomes) is calculated
|
|
|
|
# leaves have ancilary data, but its meaning is unknown (age perhaps?)
|
|
|
|
specialblockmap = {}
|
|
|
|
for blockID in special_blocks:
|
|
for data in special_map[blockID]:
|
|
specialblockmap[(blockID, data)] = generate_special_texture(blockID, data)
|