diff options
Diffstat (limited to '')
-rw-r--r-- | Tools/AnvilStats/.gitignore | 4 | ||||
-rw-r--r-- | Tools/AnvilStats/AnvilStats.cpp | 3 | ||||
-rw-r--r-- | Tools/AnvilStats/AnvilStats.txt | 1 | ||||
-rw-r--r-- | Tools/AnvilStats/AnvilStats.vcproj | 16 | ||||
-rw-r--r-- | Tools/AnvilStats/Callback.h | 46 | ||||
-rw-r--r-- | Tools/AnvilStats/HeightBiomeMap.cpp | 230 | ||||
-rw-r--r-- | Tools/AnvilStats/HeightBiomeMap.h | 81 | ||||
-rw-r--r-- | Tools/AnvilStats/HeightMap.h | 4 | ||||
-rw-r--r-- | Tools/AnvilStats/ImageComposingCallback.cpp | 219 | ||||
-rw-r--r-- | Tools/AnvilStats/ImageComposingCallback.h | 105 | ||||
-rw-r--r-- | Tools/AnvilStats/Processor.cpp | 8 |
11 files changed, 698 insertions, 19 deletions
diff --git a/Tools/AnvilStats/.gitignore b/Tools/AnvilStats/.gitignore index 4ed720fed..5d98f06ec 100644 --- a/Tools/AnvilStats/.gitignore +++ b/Tools/AnvilStats/.gitignore @@ -1,5 +1,9 @@ .xls Statistics.txt *.bmp +Debug/ +Release/ Profiling *.png +world/ +*.html
\ No newline at end of file diff --git a/Tools/AnvilStats/AnvilStats.cpp b/Tools/AnvilStats/AnvilStats.cpp index f0b9dd7e6..d98c21985 100644 --- a/Tools/AnvilStats/AnvilStats.cpp +++ b/Tools/AnvilStats/AnvilStats.cpp @@ -8,6 +8,7 @@ #include "Statistics.h" #include "BiomeMap.h" #include "HeightMap.h" +#include "HeightBiomeMap.h" #include "ChunkExtract.h" #include "SpringStats.h" @@ -26,6 +27,7 @@ int main(int argc, char * argv[]) LOG(" 2 - height map"); LOG(" 3 - extract chunks"); LOG(" 4 - count lava- and water- springs"); + LOG(" 5 - biome and height map"); LOG("\nNo method number present, aborting."); return -1; } @@ -48,6 +50,7 @@ int main(int argc, char * argv[]) case 2: Factory = new cHeightMapFactory; break; case 3: Factory = new cChunkExtractFactory(WorldFolder); break; case 4: Factory = new cSpringStatsFactory; break; + case 5: Factory = new cHeightBiomeMapFactory; break; default: { LOG("Unknown method \"%s\", aborting.", argv[1]); diff --git a/Tools/AnvilStats/AnvilStats.txt b/Tools/AnvilStats/AnvilStats.txt index 19aa4f324..1d8130aa2 100644 --- a/Tools/AnvilStats/AnvilStats.txt +++ b/Tools/AnvilStats/AnvilStats.txt @@ -15,6 +15,7 @@ Possible usage: - count the per-chunk density of specific blocks - count the per-chunk density of dungeons, by measuring the number of zombie/skeleton/regularspider spawners - count the per-chunk-per-biome density of trees, by measuring the number of dirt-log vertical transitions, correlating to biome data + - draw a vertical map of the world based on a specific measured value (biome, elevation, ...) This project is Windows-only, although it shouldn't be too difficult to make it portable. diff --git a/Tools/AnvilStats/AnvilStats.vcproj b/Tools/AnvilStats/AnvilStats.vcproj index ed4ffa9a5..038f32b97 100644 --- a/Tools/AnvilStats/AnvilStats.vcproj +++ b/Tools/AnvilStats/AnvilStats.vcproj @@ -314,6 +314,14 @@ > </File> <File + RelativePath=".\HeightBiomeMap.cpp" + > + </File> + <File + RelativePath=".\HeightBiomeMap.h" + > + </File> + <File RelativePath=".\HeightMap.cpp" > </File> @@ -322,6 +330,14 @@ > </File> <File + RelativePath=".\ImageComposingCallback.cpp" + > + </File> + <File + RelativePath=".\ImageComposingCallback.h" + > + </File> + <File RelativePath=".\Processor.cpp" > </File> diff --git a/Tools/AnvilStats/Callback.h b/Tools/AnvilStats/Callback.h index 83b330651..eda4a8478 100644 --- a/Tools/AnvilStats/Callback.h +++ b/Tools/AnvilStats/Callback.h @@ -22,44 +22,53 @@ class cParsedNBT; /** The base class for all chunk-processor callbacks, declares the interface. The processor calls each virtual function in the order they are declared here with the specified args. -If the function returns true, the processor moves on to next chunk and starts calling the callbacks again from start with -the new chunk data. +If the function returns true, the processor doesn't process the data item, moves on to the next chunk +and starts calling the callbacks again from start with the new chunk data. So if a statistics collector doesn't need data decompression at all, it can stop the processor from doing so early-enough and still get meaningful data. -A callback is guaranteed to run in a single thread and always the same thread. +A callback is guaranteed to run in a single thread and always the same thread for the same chunk. A callback is guaranteed to run on all chunks in a region and one region is guaranteed to be handled by only callback. */ class cCallback abstract { public: + enum + { + CALLBACK_CONTINUE = false, + CALLBACK_ABORT = true, + } ; + virtual ~cCallback() {} // Force a virtual destructor in each descendant + + /// Called when a new region file is about to be opened; by default allow the region + virtual bool OnNewRegion(int a_RegionX, int a_RegionZ) { return CALLBACK_CONTINUE; } /// Called to inform the stats module of the chunk coords for newly processing chunk virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) = 0; /// Called to inform about the chunk's data offset in the file (chunk mini-header), the number of sectors it uses and the timestamp field value - virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) { return true; } + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) { return CALLBACK_ABORT; } /// Called to inform of the compressed chunk data size and position in the file (offset from file start to the actual data) - virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) { return true; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) { return CALLBACK_ABORT; } /// Just in case you wanted to process the NBT yourself ;) - virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) { return true; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) { return CALLBACK_ABORT; } /// The chunk's NBT should specify chunk coords, these are sent here: - virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) { return true; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) { return CALLBACK_ABORT; } /// The chunk contains a LastUpdate value specifying the last tick in which it was saved. - virtual bool OnLastUpdate(Int64 a_LastUpdate) { return true; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) { return CALLBACK_ABORT; } - virtual bool OnTerrainPopulated(bool a_Populated) { return true; } + virtual bool OnTerrainPopulated(bool a_Populated) { return CALLBACK_ABORT; } - virtual bool OnBiomes(const unsigned char * a_BiomeData) { return true; } + virtual bool OnBiomes(const unsigned char * a_BiomeData) { return CALLBACK_ABORT; } /** Called when a heightmap for the chunk is read from the file. Note that the heightmap is given in big-endian ints, so if you want it, you need to ntohl() it first! */ - virtual bool OnHeightMap(const int * a_HeightMapBE) { return true; } + virtual bool OnHeightMap(const int * a_HeightMapBE) { return CALLBACK_ABORT; } /** If there is data for the section, this callback is called; otherwise OnEmptySection() is called instead. All OnSection() callbacks are called first, and only then all the remaining sections are reported in OnEmptySection(). @@ -71,16 +80,16 @@ public: const NIBBLETYPE * a_BlockMeta, const NIBBLETYPE * a_BlockLight, const NIBBLETYPE * a_BlockSkyLight - ) { return true; } + ) { return CALLBACK_ABORT; } /** If there is no data for a section, this callback is called; otherwise OnSection() is called instead. OnEmptySection() callbacks are called after all OnSection() callbacks. */ - virtual bool OnEmptySection(unsigned char a_Y) { return false; } + virtual bool OnEmptySection(unsigned char a_Y) { return CALLBACK_CONTINUE; } /** Called after all sections have been processed via either OnSection() or OnEmptySection(). */ - virtual bool OnSectionsFinished(void) { return true; } + virtual bool OnSectionsFinished(void) { return CALLBACK_ABORT; } /** Called for each entity in the chunk. Common parameters are parsed from the NBT. @@ -98,7 +107,7 @@ public: char a_IsOnGround, cParsedNBT & a_NBT, int a_NBTTag - ) { return true; } + ) { return CALLBACK_ABORT; } /** Called for each tile entity in the chunk. Common parameters are parsed from the NBT. @@ -110,14 +119,17 @@ public: int a_PosX, int a_PosY, int a_PosZ, cParsedNBT & a_NBT, int a_NBTTag - ) { return true; } + ) { return CALLBACK_ABORT; } /// Called for each tile tick in the chunk virtual bool OnTileTick( int a_BlockType, int a_TicksLeft, int a_PosX, int a_PosY, int a_PosZ - ) { return true; } + ) { return CALLBACK_ABORT; } + + /// Called after the entire region file has been processed. No more callbacks for this region will be called. No processing by default + virtual void OnRegionFinished(int a_RegionX, int a_RegionZ) {} } ; typedef std::vector<cCallback *> cCallbacks; diff --git a/Tools/AnvilStats/HeightBiomeMap.cpp b/Tools/AnvilStats/HeightBiomeMap.cpp new file mode 100644 index 000000000..36918f644 --- /dev/null +++ b/Tools/AnvilStats/HeightBiomeMap.cpp @@ -0,0 +1,230 @@ + +// HeightBiomeMap.cpp + +// Declares the cHeightBiomeMap class representing a stats module that produces an image of heights and biomes combined + +#include "Globals.h" +#include "HeightBiomeMap.h" +#include "HeightMap.h" + + + + + +cHeightBiomeMap::cHeightBiomeMap(void) : + super("HeBi"), + m_MinRegionX(100000), + m_MaxRegionX(-100000), + m_MinRegionZ(100000), + m_MaxRegionZ(-100000) +{ +} + + + + + +bool cHeightBiomeMap::OnNewRegion(int a_RegionX, int a_RegionZ) +{ + if (a_RegionX < m_MinRegionX) + { + m_MinRegionX = a_RegionX; + } + if (a_RegionX > m_MaxRegionX) + { + m_MaxRegionX = a_RegionX; + } + if (a_RegionZ < m_MinRegionZ) + { + m_MinRegionZ = a_RegionZ; + } + if (a_RegionZ > m_MaxRegionZ) + { + m_MaxRegionZ = a_RegionZ; + } + return super::OnNewRegion(a_RegionX, a_RegionZ); +} + + + + + +bool cHeightBiomeMap::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + m_CurrentChunkX = a_ChunkX; + m_CurrentChunkZ = a_ChunkZ; + m_CurrentChunkRelX = m_CurrentChunkX - m_CurrentRegionX * 32; + m_CurrentChunkRelZ = m_CurrentChunkZ - m_CurrentRegionZ * 32; + + ASSERT((m_CurrentChunkRelX >= 0) && (m_CurrentChunkRelX < 32)); + ASSERT((m_CurrentChunkRelZ >= 0) && (m_CurrentChunkRelZ < 32)); + + memset(m_BlockTypes, 0, sizeof(m_BlockTypes)); + + return CALLBACK_CONTINUE; +} + + + + + + +bool cHeightBiomeMap::OnBiomes(const unsigned char * a_BiomeData) +{ + memcpy(m_ChunkBiomes, a_BiomeData, sizeof(m_ChunkBiomes)); + + return CALLBACK_CONTINUE; +} + + + + + +bool cHeightBiomeMap::OnHeightMap(const int * a_HeightMapBE) +{ + for (int i = 0; i < ARRAYCOUNT(m_ChunkHeight); i++) + { + m_ChunkHeight[i] = ntohl(a_HeightMapBE[i]); + } // for i - m_ChunkHeight + + return CALLBACK_CONTINUE; +} + + + + + +bool cHeightBiomeMap::OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight +) +{ + // Copy the section data into the appropriate place in the internal buffer + memcpy(m_BlockTypes + a_Y * 16 * 16 * 16, a_BlockTypes, 16 * 16 * 16); + return CALLBACK_CONTINUE; +} + + + + + +bool cHeightBiomeMap::OnSectionsFinished(void) +{ + static const int BiomePalette[] = + { + // ARGB: + 0xff0000ff, /* Ocean */ + 0xff00cf3f, /* Plains */ + 0xffffff00, /* Desert */ + 0xff7f7f7f, /* Extreme Hills */ + 0xff00cf00, /* Forest */ + 0xff007f3f, /* Taiga */ + 0xff3f7f00, /* Swampland */ + 0xff003fff, /* River */ + 0xff7f0000, /* Hell */ + 0xff007fff, /* Sky */ + 0xff3f3fff, /* Frozen Ocean */ + 0xff3f3fff, /* Frozen River */ + 0xff7fffcf, /* Ice Plains */ + 0xff3fcf7f, /* Ice Mountains */ + 0xffcf00cf, /* Mushroom Island */ + 0xff7f00ff, /* Mushroom Island Shore */ + 0xffffff3f, /* Beach */ + 0xffcfcf00, /* Desert Hills */ + 0xff00cf3f, /* Forest Hills */ + 0xff006f1f, /* Taiga Hills */ + 0xff7f8f7f, /* Extreme Hills Edge */ + 0xff004f00, /* Jungle */ + 0xff003f00, /* Jungle Hills */ + } ; + + // Remove trees and other unwanted stuff from the heightmap: + for (int z = 0; z < 16; z++) + { + int PixelLine[16]; // line of 16 pixels that is used as a buffer for setting the image pixels + for (int x = 0; x < 16; x++) + { + int Height = m_ChunkHeight[16 * z + x]; + for (int y = Height; y >= 0; y--) + { + if (cHeightMap::IsGround(m_BlockTypes[256 * y + 16 * z + x])) + { + Height = y; + break; // for y + } + } // for y + + // Set the color based on the biome and height: + char Biome = m_ChunkBiomes[16 * z + x]; + PixelLine[x] = ShadeColor(BiomePalette[Biome], Height); + } // for x + + // Set the pixelline into the image: + SetPixelURow(m_CurrentChunkRelX * 16, m_CurrentChunkRelZ * 16 + z, 16, PixelLine); + } // for z + return CALLBACK_ABORT; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeightBiomeMapFactory: + +cHeightBiomeMapFactory::~cHeightBiomeMapFactory() +{ + // Get the min and max region coords: + int MinRegionX = 100000; + int MaxRegionX = -100000; + int MinRegionZ = 100000; + int MaxRegionZ = -100000; + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + cHeightBiomeMap * cb = (cHeightBiomeMap *)(*itr); + if (cb->m_MinRegionX < MinRegionX) + { + MinRegionX = cb->m_MinRegionX; + } + if (cb->m_MaxRegionX > MaxRegionX) + { + MaxRegionX = cb->m_MaxRegionX; + } + if (cb->m_MinRegionZ < MinRegionZ) + { + MinRegionZ = cb->m_MinRegionZ; + } + if (cb->m_MaxRegionZ > MaxRegionZ) + { + MaxRegionZ = cb->m_MaxRegionZ; + } + } + + // If the size is small enough, write an HTML file referencing all the images in a table: + if ((MaxRegionX >= MinRegionX) && (MaxRegionZ >= MinRegionZ) && (MaxRegionX - MinRegionX < 100) && (MaxRegionZ - MinRegionZ < 100)) + { + cFile HTML("HeBi.html", cFile::fmWrite); + if (HTML.IsOpen()) + { + HTML.Printf("<html><body><table cellspacing=0 cellpadding=0>\n"); + for (int z = MinRegionZ; z <= MaxRegionZ; z++) + { + HTML.Printf("<tr>"); + for (int x = MinRegionX; x <= MaxRegionX; x++) + { + HTML.Printf("<td><img src=\"HeBi.%d.%d.bmp\" /></td>", x, z); + } + HTML.Printf("</tr>\n"); + } + HTML.Printf("</table></body></html>"); + } + } +} + + + + diff --git a/Tools/AnvilStats/HeightBiomeMap.h b/Tools/AnvilStats/HeightBiomeMap.h new file mode 100644 index 000000000..d38fa4733 --- /dev/null +++ b/Tools/AnvilStats/HeightBiomeMap.h @@ -0,0 +1,81 @@ + +// HeightBiomeMap.h + +// Declares the cHeightBiomeMap class representing a stats module that produces an image of heights and biomes combined + + + + + +#pragma once + +#include "ImageComposingCallback.h" + + + + + +class cHeightBiomeMap : + public cImageComposingCallback +{ + typedef cImageComposingCallback super; + +public: + // Minima and maxima for the regions processed through this callback + int m_MinRegionX, m_MaxRegionX; + int m_MinRegionZ, m_MaxRegionZ; + + cHeightBiomeMap(void); + +protected: + int m_CurrentChunkX; // Absolute chunk coords + int m_CurrentChunkZ; + int m_CurrentChunkRelX; // Chunk offset from the start of the region + int m_CurrentChunkRelZ; + + char m_ChunkBiomes[16 * 16]; ///< Biome-map for the current chunk + int m_ChunkHeight[16 * 16]; ///< Height-map for the current chunk + BLOCKTYPE m_BlockTypes [16 * 16 * 256]; ///< Block data for the current chunk (between OnSection() and OnSectionsFinished() ) + + // cCallback overrides: + virtual bool OnNewRegion(int a_RegionX, int a_RegionZ) override; + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return CALLBACK_CONTINUE; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return CALLBACK_CONTINUE; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return CALLBACK_CONTINUE; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return CALLBACK_CONTINUE; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return CALLBACK_CONTINUE; } + virtual bool OnTerrainPopulated(bool a_Populated) override { return a_Populated ? CALLBACK_CONTINUE : CALLBACK_ABORT; } // If not populated, we don't want it! + virtual bool OnBiomes(const unsigned char * a_BiomeData) override; + virtual bool OnHeightMap(const int * a_HeightMapBE) override; + virtual bool OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight + ) override; + virtual bool OnSectionsFinished(void) override; + +} ; + + + + + +class cHeightBiomeMapFactory : + public cCallbackFactory +{ +public: + virtual ~cHeightBiomeMapFactory(); + + virtual cCallback * CreateNewCallback(void) override + { + return new cHeightBiomeMap; + } +} ; + + + + diff --git a/Tools/AnvilStats/HeightMap.h b/Tools/AnvilStats/HeightMap.h index c0e71cbc1..e1d73f300 100644 --- a/Tools/AnvilStats/HeightMap.h +++ b/Tools/AnvilStats/HeightMap.h @@ -23,6 +23,8 @@ public: void Finish(void); + static bool IsGround(BLOCKTYPE a_BlockType); + protected: int m_CurrentChunkX; // Absolute chunk coords int m_CurrentChunkZ; @@ -55,8 +57,6 @@ protected: virtual bool OnSectionsFinished(void) override; void StartNewRegion(int a_RegionX, int a_RegionZ); - - static bool IsGround(BLOCKTYPE a_BlockType); } ; diff --git a/Tools/AnvilStats/ImageComposingCallback.cpp b/Tools/AnvilStats/ImageComposingCallback.cpp new file mode 100644 index 000000000..eb43ad49f --- /dev/null +++ b/Tools/AnvilStats/ImageComposingCallback.cpp @@ -0,0 +1,219 @@ + +// ImageComposingCallback.cpp + +// Implements the cImageComposingCallback class that implements a subset of cCallback for composing per-region images + +#include "Globals.h" +#include "ImageComposingCallback.h" + + + + + +cImageComposingCallback::cImageComposingCallback(const AString & a_FileNamePrefix) : + m_FileNamePrefix(a_FileNamePrefix), + m_CurrentRegionX(INVALID_REGION_COORD), + m_CurrentRegionZ(INVALID_REGION_COORD), + m_ImageData(new int[32 * 16 * 32 * 16]) +{ +} + + + + + +cImageComposingCallback::~cImageComposingCallback() +{ + delete[] m_ImageData; +} + + + + + +bool cImageComposingCallback::OnNewRegion(int a_RegionX, int a_RegionZ) +{ + ASSERT(m_CurrentRegionX == INVALID_REGION_COORD); + ASSERT(m_CurrentRegionZ == INVALID_REGION_COORD); // Has any previous region been finished properly? + + m_CurrentRegionX = a_RegionX; + m_CurrentRegionZ = a_RegionZ; + OnEraseImage(); + + return CALLBACK_CONTINUE; +} + + + + + +void cImageComposingCallback::OnRegionFinished(int a_RegionX, int a_RegionZ) +{ + ASSERT(m_CurrentRegionX != INVALID_REGION_COORD); + ASSERT(m_CurrentRegionZ != INVALID_REGION_COORD); // Has a region been started properly? + ASSERT(m_CurrentRegionX == a_RegionX); + ASSERT(m_CurrentRegionZ == a_RegionZ); // Is it the same region that has been started? + + AString FileName = GetFileName(a_RegionX, a_RegionZ); + if (!FileName.empty()) + { + OnBeforeImageSaved(a_RegionX, a_RegionZ, FileName); + SaveImage(FileName); + OnAfterImageSaved(a_RegionX, a_RegionZ, FileName); + } + + m_CurrentRegionX = INVALID_REGION_COORD; + m_CurrentRegionZ = INVALID_REGION_COORD; +} + + + + + +AString cImageComposingCallback::GetFileName(int a_RegionX, int a_RegionZ) +{ + return Printf("%s.%d.%d.bmp", m_FileNamePrefix.c_str(), a_RegionX, a_RegionZ); +} + + + + + +void cImageComposingCallback::OnEraseImage(void) +{ + // By default erase the image to black: + EraseImage(0); +} + + + + + +void cImageComposingCallback::EraseImage(int a_Color) +{ + for (int i = 0; i < PIXEL_COUNT; i++) + { + m_ImageData[i] = a_Color; + } +} + + + + + +void cImageComposingCallback::EraseChunk(int a_Color, int a_RelChunkX, int a_RelChunkZ) +{ + int Base = a_RelChunkZ * IMAGE_HEIGHT + a_RelChunkX * 16; + for (int v = 0; v < 16; v++) + { + int BaseV = Base + v * IMAGE_HEIGHT; + for (int u = 0; u < 16; u++) + { + m_ImageData[BaseV + u] = a_Color; + } + } // for y +} + + + + + +void cImageComposingCallback::SetPixel(int a_RelU, int a_RelV, int a_Color) +{ + ASSERT((a_RelU >= 0) && (a_RelU < IMAGE_WIDTH)); + ASSERT((a_RelV >= 0) && (a_RelV < IMAGE_HEIGHT)); + + m_ImageData[a_RelU + IMAGE_WIDTH * a_RelV] = a_Color; +} + + + + + +int cImageComposingCallback::GetPixel(int a_RelU, int a_RelV) +{ + if ((a_RelU < 0) || (a_RelU >= IMAGE_WIDTH) || (a_RelV < 0) || (a_RelV >= IMAGE_HEIGHT)) + { + // Outside the image data + return -1; + } + + return m_ImageData[a_RelU + IMAGE_WIDTH * a_RelV]; +} + + + + + + +void cImageComposingCallback::SetPixelURow(int a_RelUStart, int a_RelV, int a_CountU, int * a_Pixels) +{ + ASSERT((a_RelUStart >= 0) && (a_RelUStart + a_CountU <= IMAGE_WIDTH)); + ASSERT((a_RelV >= 0) && (a_RelV < IMAGE_HEIGHT)); + ASSERT(a_Pixels != NULL); + + int Base = a_RelUStart + a_RelV * IMAGE_WIDTH; + for (int u = 0; u < a_CountU; u++) + { + m_ImageData[Base + u] = a_Pixels[u]; + } +} + + + + + +int cImageComposingCallback::ShadeColor(int a_Color, int a_Shade) +{ + if (a_Shade < 64) + { + return MixColor(0, a_Color, a_Shade * 4); + } + return MixColor(a_Color, 0xffffff, (a_Shade - 64) * 4); +} + + + + + +int cImageComposingCallback::MixColor(int a_Src, int a_Dest, int a_Amount) +{ + int r = a_Src & 0xff; + int g = (a_Src >> 8) & 0xff; + int b = (a_Src >> 16) & 0xff; + int rd = a_Dest & 0xff; + int gd = (a_Dest >> 8) & 0xff; + int bd = (a_Dest >> 16) & 0xff; + int nr = r + (rd - r) * a_Amount / 256; + int ng = g + (gd - g) * a_Amount / 256; + int nb = b + (bd - b) * a_Amount / 256; + return nr | (ng << 8) | (nb << 16); +} + + + + +void cImageComposingCallback::SaveImage(const AString & a_FileName) +{ + cFile f(a_FileName, cFile::fmWrite); + if (!f.IsOpen()) + { + return; + } + + // Header for BMP files (is the same for the same-size files) + static const unsigned char BMPHeader[] = + { + 0x42, 0x4D, 0x36, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + } ; + + f.Write(BMPHeader, sizeof(BMPHeader)); + f.Write(m_ImageData, PIXEL_COUNT * 4); +} + + + + diff --git a/Tools/AnvilStats/ImageComposingCallback.h b/Tools/AnvilStats/ImageComposingCallback.h new file mode 100644 index 000000000..2936361d6 --- /dev/null +++ b/Tools/AnvilStats/ImageComposingCallback.h @@ -0,0 +1,105 @@ + +// ImageComposingCallback + +// Declares the cImageComposingCallback class that implements a subset of cCallback for composing per-region images + + + + + +#pragma once + +#include "Callback.h" + + + + +/** Implements the plumbing for composing per-region images from multiple chunks. +To use this class, create a descendant that writes the image data using +SetPixel() or SetPixelURow() functions. + +For the purpose of this class the image data is indexed U (horz) * V (vert), to avoid confusion with other coords. +The image is a 32bpp raw imagedata, written into a BMP file. +*/ +class cImageComposingCallback : + public cCallback +{ +public: + enum + { + INVALID_REGION_COORD = 99999, ///< Used for un-assigned region coords + IMAGE_WIDTH = 32 * 16, + IMAGE_HEIGHT = 32 * 16, + PIXEL_COUNT = IMAGE_WIDTH * IMAGE_HEIGHT, ///< Total pixel count of the image data + } ; + + cImageComposingCallback(const AString & a_FileNamePrefix); + virtual ~cImageComposingCallback(); + + // cCallback overrides: + virtual bool OnNewRegion(int a_RegionX, int a_RegionZ) override; + virtual void OnRegionFinished(int a_RegionX, int a_RegionZ) override; + + // New introduced overridable functions: + + /// Called when a file is about to be saved, to generate the filename + virtual AString GetFileName(int a_RegionX, int a_RegionZ); + + /// Called before the file is saved + virtual void OnBeforeImageSaved(int a_RegionX, int a_RegionZ, const AString & a_FileName) {} + + /// Called after the image is saved to a file + virtual void OnAfterImageSaved(int a_RegionX, int a_RegionZ, const AString & a_FileName) {} + + /// Called when a new region is beginning, to erase the image data + virtual void OnEraseImage(void); + + // Functions for manipulating the image: + + /// Erases the entire image with the specified color + void EraseImage(int a_Color); + + /// Erases the specified chunk's portion of the image with the specified color. Note that chunk coords are relative to the current region + void EraseChunk(int a_Color, int a_RelChunkX, int a_RelChunkZ); + + /// Returns the current region X coord + int GetCurrentRegionX(void) const { return m_CurrentRegionX; } + + /// Returns the current region Z coord + int GetCurrentRegionZ(void) const { return m_CurrentRegionZ; } + + /// Sets the pixel at the specified UV coords to the specified color + void SetPixel(int a_RelU, int a_RelV, int a_Color); + + /// Returns the color of the pixel at the specified UV coords; -1 if outside + int GetPixel(int a_RelU, int a_RelV); + + /// Sets a row of pixels. a_Pixels is expected to be a_CountU pixels wide. a_RelUStart + a_CountU is assumed less than image width + void SetPixelURow(int a_RelUStart, int a_RelV, int a_CountU, int * a_Pixels); + + /** "Shades" the given color based on the shade amount given + Shade amount 0 .. 63 shades the color from black to a_Color. + Shade amount 64 .. 127 shades the color from a_Color to white. + All other shade amounts have undefined results. + */ + static int ShadeColor(int a_Color, int a_Shade); + + /// Mixes the two colors in the specified ratio; a_Ratio is between 0 and 256, 0 returning a_Src + static int MixColor(int a_Src, int a_Dest, int a_Ratio); + +protected: + /// Prefix for the filenames, when generated by the default GetFileName() function + AString m_FileNamePrefix; + + /// Coords of the currently processed region + int m_CurrentRegionX, m_CurrentRegionZ; + + /// Raw image data; 1 MiB worth of data, therefore unsuitable for stack allocation. [u + IMAGE_WIDTH * v] + int * m_ImageData; + + void SaveImage(const AString & a_FileName); +} ; + + + + diff --git a/Tools/AnvilStats/Processor.cpp b/Tools/AnvilStats/Processor.cpp index e7f7eb21d..8e1cc4c4b 100644 --- a/Tools/AnvilStats/Processor.cpp +++ b/Tools/AnvilStats/Processor.cpp @@ -76,6 +76,12 @@ void cProcessor::cThread::ProcessFile(const AString & a_FileName) return; } + if (m_Callback.OnNewRegion(RegionX, RegionZ)) + { + // Callback doesn't want the region file processed + return; + } + cFile f; if (!f.Open(a_FileName, cFile::fmRead)) { @@ -92,6 +98,8 @@ void cProcessor::cThread::ProcessFile(const AString & a_FileName) } ProcessFileData(FileContents.data(), FileContents.size(), RegionX * 32, RegionZ * 32); + + m_Callback.OnRegionFinished(RegionX, RegionZ); } |