001package squidpony; 002 003import squidpony.annotation.Beta; 004import squidpony.squidmath.CoordPacker; 005 006/** 007 * Very early way of additional compression that can be applied to 2D double and byte arrays. This doesn't compress a 008 * well as using {@link LZSEncoding} on a simply-serialized String produced by {@link Converters#convertArrayDouble2D}, 009 * but you can use LZSEncoding on the results of this class to significantly reduce output size. Testing on a heat map 010 * of byte values from -128 to 127 from a world map: 011 * <pre> 012 * Base size : 1143142 // this is an uncompressed String produced by Converters 013 * LZS size : 89170 // this uses LZSEncoding alone on the above Base string 014 * Custom size : 216209 // this uses GridCompression alone 015 * Both size : 43120 // this uses GridCompression followed by LZSEncoding 016 * </pre> 017 * <br> 018 * This class is marked Beta because it still has some improvement that can be done on it, like having more support for 019 * other array types. 020 * <br> 021 * Created by Tommy Ettinger on 4/27/2020. 022 */ 023@Beta 024public class GridCompression { 025 public static String compress(double[][] grid) 026 { 027 return compress(doubleToByteGrid(grid)); 028 } 029 public static String compress(byte[][] grid) 030 { 031 CoordPacker.init(); 032 final int width = grid.length, height = grid[0].length; 033 StringBuilder[] packs = new StringBuilder[8]; 034 for (int i = 0; i < 8; i++) { 035 packs[i] = new StringBuilder(width * height >> 3); 036 } 037 StringBuilder packing = new StringBuilder(width * height + 128); 038 StringKit.appendHex(packing, width); 039 StringKit.appendHex(packing, height); 040 final int chunksX = width + 255 >> 8, chunksY = height + 127 >> 7; 041 short[] skip = new short[8]; 042 boolean[] on = new boolean[8]; 043 for (int bigX = 0, baseX = 0; bigX < chunksX; bigX++, baseX += 256) { 044 for (int bigY = 0, baseY = 0; bigY < chunksY; bigY++, baseY += 128) { 045 for (int p = 0; p < 8; p++) { 046 packs[p].append(';'); 047 skip[p] = 0; 048 on[p] = false; 049 } 050 boolean current; 051 short hx, hy; 052 int xSize = Math.min(256, width - baseX), ySize = Math.min(128, height - baseY), 053 limit = 0x8000, mapLimit = xSize * ySize, code; 054 if (xSize <= 128) { 055 limit >>= 1; 056 if (xSize <= 64) { 057 limit >>= 1; 058 if (ySize <= 64) { 059 limit >>= 1; 060 if (ySize <= 32) { 061 limit >>= 1; 062 if (xSize <= 32) { 063 limit >>= 1; 064 } 065 } 066 } 067 } 068 } 069 for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++) { 070 hx = CoordPacker.hilbertX[i]; 071 hy = CoordPacker.hilbertY[i]; 072 if (hx >= xSize || hy >= ySize) { 073 for (int p = 0; p < 8; p++) { 074 if (on[p]) { 075 on[p] = false; 076 packs[p].append((char) (skip[p] + 256)); 077 skip[p] = 0; 078 } 079 } 080 for (int p = 0; p < 8; p++) { 081 skip[p]++; 082 } 083 continue; 084 } 085 ml++; 086 code = (grid[baseX + hx][baseY + hy] & 255); 087 code ^= code >>> 1; // gray code; ensures only one bit changes between consecutive bytes 088 for (int p = 0; p < 8; p++) { 089 current = (code >>> p & 1) == 1; 090 if (current != on[p]) { 091 packs[p].append((char) (skip[p] + 256)); 092 skip[p] = 0; 093 on[p] = current; 094 } 095 } 096 for (int p = 0; p < 8; p++) { 097 skip[p]++; 098 } 099 } 100 for (int p = 0; p < 8; p++) { 101 if (on[p]) 102 { 103 packs[p].append((char) (skip[p] + 256)); 104 } 105 } 106 } 107 } 108 packing.append(packs[0]); 109 for (int p = 1; p < 8; p++) { 110 packing.append('.').append(packs[p]); 111 } 112 return packing.toString(); 113 } 114 public static byte[][] decompress(String compressed) 115 { 116 CoordPacker.init(); 117 final int width = StringKit.intFromHex(compressed), height = StringKit.intFromHex(compressed, 8, 16); 118 byte[][] target = new byte[width][height]; 119 final int chunksX = width + 255 >> 8, chunksY = height + 127 >> 7; 120 int startPack = 16, endPack, endLayer, idx; 121 boolean on; 122 for (int b = 0; b < 8; b++) { 123 endLayer = compressed.indexOf('.', startPack+1); 124 if (endLayer < 0) endLayer = compressed.length(); 125 for (int bigX = 0, baseX = 0; bigX < chunksX; bigX++, baseX += 256) { 126 for (int bigY = 0, baseY = 0; bigY < chunksY; bigY++, baseY += 128) { 127 ++startPack; 128 endPack = Math.min(endLayer, compressed.indexOf(';', startPack)); 129 if (endPack < 0) endPack = endLayer; 130 on = false; 131 idx = 0; 132 for (int p = startPack; p < endPack; p++, on = !on) { 133 if (on) { 134 for (int toSkip = idx + (compressed.charAt(p) - 256); idx < toSkip && idx < 0x8000; idx++) { 135 target[CoordPacker.hilbertX[idx] + baseX][CoordPacker.hilbertY[idx] + baseY] |= 1 << b; 136 } 137 } else { 138 idx += compressed.charAt(p) - 256; 139 } 140 } 141 startPack = endPack; 142 } 143 } 144 ++startPack; 145 } 146 for (int x = 0; x < width; x++) { 147 for (int y = 0; y < height; y++) { 148 int b = target[x][y] & 0xFF; 149 b ^= b >>> 1; 150 b ^= b >>> 2; 151 target[x][y] = (byte)(b ^ b >>> 4); 152 } 153 } 154 return target; 155 } 156 public static double[][] byteToDoubleGrid(byte[][] bytes) { 157 return byteToDoubleGrid(bytes, new double[bytes.length][bytes[0].length]); 158 } 159 public static double[][] byteToDoubleGrid(byte[][] bytes, double[][] doubles) { 160 final int width = Math.min(bytes.length, doubles.length), 161 height = Math.min(bytes[0].length, doubles[0].length); 162 for (int x = 0; x < width; x++) { 163 for (int y = 0; y < height; y++) { 164 doubles[x][y] = (bytes[x][y] & 0xFF) * 0x1p-8; 165 } 166 } 167 return doubles; 168 } 169 public static byte[][] doubleToByteGrid(double[][] doubles) { 170 return doubleToByteGrid(doubles, new byte[doubles.length][doubles[0].length]); 171 } 172 public static byte[][] doubleToByteGrid(double[][] doubles, byte[][] bytes) { 173 final int width = Math.min(doubles.length, bytes.length), 174 height = Math.min(doubles[0].length, bytes[0].length); 175 for (int x = 0; x < width; x++) { 176 for (int y = 0; y < height; y++) { 177 bytes[x][y] = (byte) (doubles[x][y] * 256); 178 } 179 } 180 return bytes; 181 } 182 public static byte[] doubleGridToByteArray(double[][] doubles) { 183 final int width = doubles.length, height = doubles[0].length; 184 byte[] bytes = new byte[width * height + 8]; 185 bytes[0] = (byte) (width >>> 24); 186 bytes[1] = (byte) (width >>> 16); 187 bytes[2] = (byte) (width >>> 8); 188 bytes[3] = (byte) (width); 189 bytes[4] = (byte) (height >>> 24); 190 bytes[5] = (byte) (height >>> 16); 191 bytes[6] = (byte) (height >>> 8); 192 bytes[7] = (byte) (height); 193 int i = 8; 194 for (int x = 0; x < width; x++) { 195 for (int y = 0; y < height; y++) { 196 bytes[i++] = (byte)(doubles[x][y] * 256); 197 } 198 } 199 return bytes; 200 } 201 public static double[][] byteArrayToDoubleGrid(byte[] bytes) { 202 final int width = bytes[0] << 24 | (bytes[1] << 16 & 0xFF0000) | (bytes[2] << 8 & 0xFF00) | (bytes[3] & 0xFF), 203 height = bytes[4] << 24 | (bytes[5] << 16 & 0xFF0000) | (bytes[6] << 8 & 0xFF00) | (bytes[7] & 0xFF); 204 double[][] doubles = new double[width][height]; 205 int i = 8; 206 for (int x = 0; x < width; x++) { 207 for (int y = 0; y < height; y++) { 208 doubles[x][y] = (bytes[i++] & 0xFF) * 0x1p-8; 209 } 210 } 211 return doubles; 212 } 213 214 public static byte[] byteGridToByteArray(byte[][] grid) { 215 final int width = grid.length, height = grid[0].length; 216 byte[] bytes = new byte[width * height + 8]; 217 bytes[0] = (byte) (width >>> 24); 218 bytes[1] = (byte) (width >>> 16); 219 bytes[2] = (byte) (width >>> 8); 220 bytes[3] = (byte) (width); 221 bytes[4] = (byte) (height >>> 24); 222 bytes[5] = (byte) (height >>> 16); 223 bytes[6] = (byte) (height >>> 8); 224 bytes[7] = (byte) (height); 225 int i = 8; 226 for (int x = 0; x < width; x++) { 227 for (int y = 0; y < height; y++) { 228 bytes[i++] = grid[x][y]; 229 } 230 } 231 return bytes; 232 } 233 public static byte[][] byteArrayToByteGrid(byte[] bytes) { 234 final int width = bytes[0] << 24 | (bytes[1] << 16 & 0xFF0000) | (bytes[2] << 8 & 0xFF00) | (bytes[3] & 0xFF), 235 height = bytes[4] << 24 | (bytes[5] << 16 & 0xFF0000) | (bytes[6] << 8 & 0xFF00) | (bytes[7] & 0xFF); 236 byte[][] grid = new byte[width][height]; 237 int i = 8; 238 for (int x = 0; x < width; x++) { 239 for (int y = 0; y < height; y++) { 240 grid[x][y] = bytes[i++]; 241 } 242 } 243 return grid; 244 } 245 246}