/MapImageEngine/src/FaigerSYS/MapImageEngine/storage/MapImage.php

https://github.com/FaigerSYS/MapImageEngine · PHP · 292 lines · 151 code · 37 blank · 104 comment · 18 complexity · 3b445f70712e129ac67098beece4b599 MD5 · raw file

  1. <?php
  2. namespace FaigerSYS\MapImageEngine\storage;
  3. use pocketmine\utils\BinaryStream;
  4. use pocketmine\utils\UUID;
  5. use pocketmine\network\mcpe\protocol\BatchPacket;
  6. class MapImage {
  7. /**
  8. * Image parsed succesfully
  9. */
  10. const R_OK = 1;
  11. /**
  12. * Image corrupted or has unsupported format
  13. */
  14. const R_CORRUPTED = 2;
  15. /**
  16. * Image has unsupported API version
  17. */
  18. const R_UNSUPPORTED_VERSIONS = 3;
  19. /**
  20. * Curren version of MIE image binary
  21. */
  22. const CURRENT_VERSION = 2;
  23. /**
  24. * List of supported versions of MIE image binary
  25. */
  26. const SUPPORTED_VERSIONS = [2];
  27. /** @var int */
  28. private $blocks_width;
  29. private $blocks_height;
  30. /** @var int */
  31. private $default_chunk_width;
  32. private $default_chunk_height;
  33. /** @var MapImageChunk[][] */
  34. private $chunks = [];
  35. /** @var UUID */
  36. private $uuid;
  37. /**
  38. * @param int $blocks_width
  39. * @param int $blocks_height
  40. * @param MapImageChunk[][] $chunks
  41. * @param UUID $uuid
  42. * @param int $default_chunk_width
  43. * @param int $default_chunk_height
  44. */
  45. public function __construct(int $blocks_width, int $blocks_height, array $chunks = [], UUID $uuid = null, int $default_chunk_width = 128, int $default_chunk_height = 128) {
  46. if ($blocks_width < 0 || $blocks_height < 0) {
  47. throw new \InvalidArgumentException('Blocks width/height must be greater than 0');
  48. }
  49. $this->blocks_width = $blocks_width;
  50. $this->blocks_height = $blocks_height;
  51. $this->uuid = $uuid ?? UUID::fromRandom();
  52. $this->default_chunk_width = $default_chunk_width;
  53. $this->default_chunk_height = $default_chunk_height;
  54. $this->setChunks($chunks);
  55. }
  56. /**
  57. * Returns image blocks width
  58. *
  59. * @return int
  60. */
  61. public function getBlocksWidth() : int {
  62. return $this->blocks_width;
  63. }
  64. /**
  65. * Returns image blocks height
  66. *
  67. * @return int
  68. */
  69. public function getBlocksHeight() : int {
  70. return $this->blocks_height;
  71. }
  72. /**
  73. * Sets image blocks width
  74. *
  75. * @param int $blocks_width
  76. */
  77. public function setBlocksWidth(int $blocks_width) {
  78. if ($blocks_width < 0) {
  79. throw new \InvalidArgumentException('Blocks width must be greater than 0');
  80. }
  81. $this->blocks_width = $blocks_width;
  82. $this->checkChunks();
  83. }
  84. /**
  85. * Sets image blocks height
  86. *
  87. * @param int $blocks_width
  88. */
  89. public function setBlocksHeight(int $blocks_height) {
  90. if ($blocks_height < 0) {
  91. throw new \InvalidArgumentException('Blocks height must be greater than 0');
  92. }
  93. $this->blocks_height = $blocks_height;
  94. $this->checkChunks();
  95. }
  96. /**
  97. * Returns the image chunk at specified position
  98. *
  99. * @param int $block_x
  100. * @param int $block_y
  101. *
  102. * @return MapImageChunk|null
  103. */
  104. public function getChunk(int $block_x, int $block_y) {
  105. return $this->chunks[$block_y][$block_x] ?? null;
  106. }
  107. /**
  108. * Returns all image chunks
  109. *
  110. * @return MapImageChunk|null
  111. */
  112. public function getChunks() : array {
  113. return $this->chunks;
  114. }
  115. /**
  116. * Sets the image chunk at specified position
  117. *
  118. * @param int $block_x
  119. * @param int $block_y
  120. * @param MapImageChunk $chunk
  121. */
  122. public function setChunk(int $block_x, int $block_y, MapImageChunk $chunk) {
  123. if ($block_x < 0 || $block_y < 0) {
  124. throw new \InvalidArgumentException('Block X/Y must be greater than 0');
  125. }
  126. if ($block_x >= $this->blocks_width) {
  127. throw new \InvalidArgumentException('Block X cannot be greater than width');
  128. }
  129. if ($block_y >= $this->blocks_height) {
  130. throw new \InvalidArgumentException('Block Y cannot be greater than height');
  131. }
  132. $this->chunks[$block_y][$block_x] = $chunk;
  133. }
  134. /**
  135. * Rewrites all image chunks
  136. *
  137. * @param MapImageChunk[][] $chunks
  138. */
  139. public function setChunks(array $chunks) {
  140. $this->chunks = $chunks;
  141. $this->checkChunks();
  142. }
  143. /**
  144. * Generates bathed packet for all of image chunks
  145. *
  146. * @param int $compression_level
  147. *
  148. * @return BatchPacket
  149. */
  150. public function generateBatchedMapImagesPacket(int $compression_level = 6) {
  151. $pk = new BatchPacket();
  152. $pk->setCompressionLevel($compression_level);
  153. foreach ($this->chunks as $chunk) {
  154. $pk->addPacket($chunk->generateMapImagePacket());
  155. }
  156. return $pk;
  157. }
  158. /**
  159. * Generates bathed packet for all of image chunks
  160. *
  161. * @param int $compression_level
  162. *
  163. * @return BatchPacket
  164. */
  165. public function generateBatchedCustomMapImagesPacket(int $compression_level = 6) {
  166. $pk = new BatchPacket();
  167. $pk->setCompressionLevel($compression_level);
  168. foreach ($this->chunks as $chunk) {
  169. $pk->addPacket($chunk->generateCustomMapImagePacket());
  170. }
  171. return $pk;
  172. }
  173. /**
  174. * Returns the image UUID
  175. *
  176. * @return UUID
  177. */
  178. public function getUUID() : UUID {
  179. return $this->uuid;
  180. }
  181. /**
  182. * Returns the image UUID hash
  183. *
  184. * @return string
  185. */
  186. public function getHashedUUID() : string {
  187. return hash('sha1', $this->uuid->toBinary());
  188. }
  189. /**
  190. * Creates new MapImage object from MIE image binary
  191. *
  192. * @param stirng $buffer
  193. * @param int &$state
  194. *
  195. * @return MapImage|null
  196. */
  197. public static function fromBinary(string $buffer, &$state = null) {
  198. try {
  199. $buffer = new BinaryStream($buffer);
  200. $header = $buffer->get(4);
  201. if ($header !== 'MIEI') {
  202. $state = self::R_CORRUPTED;
  203. return;
  204. }
  205. $api = $buffer->getInt();
  206. if (!in_array($api, self::SUPPORTED_VERSIONS)) {
  207. $state = self::R_UNSUPPORTED_VERSIONS;
  208. return;
  209. }
  210. $is_compressed = $buffer->getByte();
  211. if ($is_compressed) {
  212. $buffer = $buffer->get(true);
  213. $buffer = @zlib_decode($buffer);
  214. if ($buffer === false) {
  215. $state = self::R_CORRUPTED;
  216. return;
  217. }
  218. $buffer = new BinaryStream($buffer);
  219. }
  220. $uuid = UUID::fromBinary($buffer->get(16), 4);
  221. $blocks_width = $buffer->getInt();
  222. $blocks_height = $buffer->getInt();
  223. $chunks = [];
  224. for ($block_y = 0; $block_y < $blocks_height; $block_y++) {
  225. for ($block_x = 0; $block_x < $blocks_width; $block_x++) {
  226. $chunk_width = $buffer->getInt();
  227. $chunk_height = $buffer->getInt();
  228. $chunk_data = $buffer->get($chunk_width * $chunk_height * 4);
  229. $chunks[$block_y][$block_x] = new MapImageChunk($chunk_width, $chunk_height, $chunk_data);
  230. }
  231. }
  232. $state = self::R_OK;
  233. return new MapImage($blocks_width, $blocks_height, $chunks, $uuid);
  234. } catch (\Throwable $e) {
  235. $state = self::R_CORRUPTED;
  236. }
  237. }
  238. private function checkChunks() {
  239. $chunks = $this->chunks;
  240. $this->chunks = [];
  241. for ($y = 0; $y < $this->blocks_height; $y++) {
  242. for ($x = 0; $x < $this->blocks_width; $x++) {
  243. $this->chunks[$y][$x] = ($chunks[$y][$x] ?? null) instanceof MapImageChunk ? $chunks[$y][$x] : MapImageChunk::generateImageChunk($this->default_chunk_width, $this->default_chunk_height);
  244. }
  245. }
  246. }
  247. public function __clone() {
  248. for ($y = 0; $y < $this->blocks_height; $y++) {
  249. for ($x = 0; $x < $this->blocks_width; $x++) {
  250. $this->chunks[$y][$x] = clone $this->chunks[$y][$x];
  251. }
  252. }
  253. $this->uuid = UUID::fromRandom();
  254. }
  255. }