Import Image as Map now implemented.

Removed mapCache from WorldEditor (questionable)
createMap ignores idcounts.dat and updates it.
Catch LevelFormatErrors while getting map datas.
This commit is contained in:
David Vierra 2015-07-09 11:09:24 -10:00
parent d60ff4c552
commit f46ce88d9a
4 changed files with 151 additions and 28 deletions

View File

@ -6,9 +6,11 @@ import logging
from PySide import QtGui, QtCore
from PySide.QtCore import Qt
import numpy
from mcedit2.command import SimpleRevisionCommand
from mcedit2.util.load_ui import load_ui
from mcedit2.util.screen import centerWidgetInScreen
from mceditlib.anvil.adapter import AnvilMapData
from mceditlib.exceptions import LevelFormatError
log = logging.getLogger(__name__)
@ -41,12 +43,22 @@ class MapListModel(QtCore.QAbstractListModel):
return self.editorSession.worldEditor.getMap(mapID)
def imageForMapID(self, mapID):
map = self.getMap(mapID)
colorsRGBA = map.getColorsAsRGBA()
colorsBGRA = numpy.ascontiguousarray(numpy.roll(colorsRGBA, 1, -1)[..., ::-1])
image = QtGui.QImage(colorsBGRA, map.width, map.height, QtGui.QImage.Format_ARGB32)
try:
mapData = self.getMap(mapID)
except LevelFormatError as e:
log.exception("Invalid map for ID %s (while getting map image)", mapID)
return None
colorsRGBA = mapData.getColorsAsRGBA()
colorsBGRA = rgbaToBgra(colorsRGBA)
image = QtGui.QImage(colorsBGRA, mapData.width, mapData.height, QtGui.QImage.Format_ARGB32)
return image
def rgbaToBgra(colors):
return numpy.ascontiguousarray(numpy.roll(colors, 1, -1)[..., ::-1])
bgraToRgba = rgbaToBgra
class MapPanel(QtGui.QWidget):
def __init__(self, editorSession):
"""
@ -58,6 +70,7 @@ class MapPanel(QtGui.QWidget):
self.editorSession = editorSession
self.pixmapItem = None
self.mapListModel = None
load_ui("panels/map.ui", baseinstance=self)
@ -67,9 +80,8 @@ class MapPanel(QtGui.QWidget):
action.triggered.connect(self.toggleView)
self._toggleViewAction = action
self.mapListModel = MapListModel(self.editorSession)
self.reloadModel()
self.mapListView.setModel(self.mapListModel)
self.mapListView.clicked.connect(self.mapListClicked)
self.splitter.splitterMoved.connect(self.updatePixmapSize)
@ -106,17 +118,25 @@ class MapPanel(QtGui.QWidget):
def displayMapID(self, mapID):
if mapID is None:
mapData = None
else:
try:
mapData = self.mapListModel.getMap(mapID)
except LevelFormatError as e:
log.exception("Invalid data for map ID %s (while getting map info)", mapID)
mapData = None
if mapData is None:
self.widthLabel.setText("(N/A)")
self.heightLabel.setText("(N/A)")
self.dimensionLabel.setText("(N/A)")
self.scaleLabel.setText("(N/A)")
self.mapGraphicsView.setScene(None)
else:
map = self.mapListModel.getMap(mapID)
self.widthLabel.setText(str(map.width))
self.heightLabel.setText(str(map.height))
self.dimensionLabel.setText(str(map.dimension))
self.scaleLabel.setText(str(map.scale))
self.widthLabel.setText(str(mapData.width))
self.heightLabel.setText(str(mapData.height))
self.dimensionLabel.setText(str(mapData.dimension))
self.scaleLabel.setText(str(mapData.scale))
self.updateScene(mapID)
def updateScene(self, mapID):
@ -149,15 +169,29 @@ class MapPanel(QtGui.QWidget):
if filename:
colorTable = AnvilMapData.colorTable # xxxx dispatch through WorldEditor
dialog = ImportMapDialog(filename, colorTable)
dialog.importDone.connect(self.importImageDone)
dialog.exec_()
if dialog.result():
convertedImages = dialog.getConvertedImages()
command = MapImportCommand(self.editorSession, self.tr("Import Image as Map"))
with command.begin():
for x, y, image in convertedImages:
colors = numpy.fromstring(image.bits(), dtype=numpy.uint8)
colors.shape = 128, 128
newMap = self.editorSession.worldEditor.createMap()
newMap.colors[:] = colors
newMap.save()
def importImageDone(self, importInfo):
"""ImportMapCommand"""
self.reloadModel()
def reloadModel(self):
self.mapListModel = MapListModel(self.editorSession)
self.mapListView.setModel(self.mapListModel)
class MapImportCommand(SimpleRevisionCommand):
pass
class ImportMapDialog(QtGui.QDialog):
importDone = QtCore.Signal(object)
def __init__(self, imageFilename, colorTable):
super(ImportMapDialog, self).__init__()
@ -172,6 +206,7 @@ class ImportMapDialog(QtGui.QDialog):
self.lines = []
self.previewGroupItems = []
self.convertedImages = []
self.colorTable = [(255)] * 256
colorTable = numpy.array(colorTable)
@ -206,6 +241,8 @@ class ImportMapDialog(QtGui.QDialog):
self.previewGroup.removeFromGroup(item)
self.previewGroupItems[:] = []
self.convertedImages[:] = []
tilesWide = self.tilesWideSpinbox.value()
tilesHigh = self.tilesHighSpinbox.value()
@ -252,6 +289,8 @@ class ImportMapDialog(QtGui.QDialog):
Qt.KeepAspectRatio if not expandImage else Qt.IgnoreAspectRatio)
convertedImage = scaledImage.convertToFormat(QtGui.QImage.Format_Indexed8, self.colorTable)
convertedPixmap = QtGui.QPixmap.fromImage(convertedImage)
self.convertedImages.append((x, y, convertedImage))
convertedPixmapItem = QtGui.QGraphicsPixmapItem(convertedPixmap)
convertedPixmapItem.setPos(x * tileOffset, y * tileOffset)
self.previewGroup.addToGroup(convertedPixmapItem)
@ -285,3 +324,5 @@ class ImportMapDialog(QtGui.QDialog):
def showEvent(self, event):
self.updateImageSize()
def getConvertedImages(self):
return list(self.convertedImages)

View File

@ -132,5 +132,38 @@
</layout>
</widget>
<resources/>
<connections/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>742</x>
<y>722</y>
</hint>
<hint type="destinationlabel">
<x>527</x>
<y>-15</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>651</x>
<y>716</y>
</hint>
<hint type="destinationlabel">
<x>373</x>
<y>-15</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -979,27 +979,78 @@ class AnvilWorldAdapter(object):
yield int(mapID)
def getMap(self, mapID):
return AnvilMapData(self.getMapTag(mapID), mapID, self)
def getMapTag(self, mapID):
mapPath = "data/map_%s.dat" % mapID
if not self.selectedRevision.containsFile(mapPath):
raise KeyError("Map %s not found" % mapID)
mapData = self.selectedRevision.readFile(mapPath)
mapNBT = nbt.load(buf=mapData)
return AnvilMapData(mapNBT)
return mapNBT
def saveMapTag(self, mapID, mapTag):
mapPath = "data/map_%s.dat" % mapID
self.selectedRevision.writeFile(mapPath, mapTag.save())
def createMap(self):
# idcounts.dat should hold the ID number of the last created map
# but we can't trust it because of bugs in the old map import filters
mapIDs = list(self.listMaps())
if len(mapIDs):
maximumID = max()
mapID = maximumID + 1
else:
mapID = 0
idcountsTag = nbt.TAG_Compound()
idcountsTag["map"] = nbt.TAG_Short(mapID)
# idcounts.dat is not compressed.
self.selectedRevision.writeFile("data/idcounts.dat", idcountsTag.save(compressed=False))
mapData = AnvilMapData.create(mapID, self)
mapData.save()
return mapData
class AnvilMapData(object):
def __init__(self, rootTag):
if "data" not in rootTag:
def __init__(self, mapTag, mapID, adapter):
if "data" not in mapTag:
raise LevelFormatError("Map NBT is missing required tag 'data'")
self.rootTag = rootTag["data"]
self.mapTag = mapTag
self.rootTag = mapTag["data"]
if self._colors.shape[0] != self.width * self.height:
raise LevelFormatError("Map colors array does not match map size. (%dx%d != %d)"
% (self.width, self.height, self.colors.shape[0]))
self.mapID = mapID
self.adapter = adapter
@classmethod
def create(cls, mapID, adapter, width=128, height=128):
mapTag = nbt.TAG_Compound()
mapDataTag = nbt.TAG_Compound()
mapTag["data"] = mapDataTag
mapDataTag["colors"] = nbt.TAG_Byte_Array(numpy.zeros((width * height,), dtype=numpy.uint8))
mapData = cls(mapTag, mapID, adapter)
mapData.dimension = 0
mapData.width = width
mapData.height = height
mapData.scale = 1
mapData.xCenter = -1 << 30
mapData.zCenter = -1 << 30
return mapData
def save(self):
self.adapter.saveMapTag(self.mapID, self.mapTag)
dimension = nbtattr.NBTAttr('dimension', nbt.TAG_Byte)
height = nbtattr.NBTAttr('height', nbt.TAG_Short)
width = nbtattr.NBTAttr('width', nbt.TAG_Short)
height = nbtattr.NBTAttr('height', nbt.TAG_Short, 128)
width = nbtattr.NBTAttr('width', nbt.TAG_Short, 128)
scale = nbtattr.NBTAttr('scale', nbt.TAG_Byte)
xCenter = nbtattr.NBTAttr('xCenter', nbt.TAG_Int, 0)
zCenter = nbtattr.NBTAttr('zCenter', nbt.TAG_Int, 0)

View File

@ -215,7 +215,6 @@ class WorldEditor(object):
:rtype: WorldEditor
"""
self.playerCache = {}
self.mapCache = {}
assert not (create and readonly)
assert not create or adapterClass, "create=True requires an adapterClass"
@ -565,11 +564,10 @@ class WorldEditor(object):
:param mapID: Map ID returned by listMaps
:return:
"""
map = self.mapCache.get(mapID)
if map is None:
map = self.adapter.getMap(mapID)
self.mapCache[mapID] = map
return map
return self.adapter.getMap(mapID)
def createMap(self):
return self.adapter.createMap()
# --- Players ---