All files / src/shared/lib/tiles autoFlip.ts

100% Statements 51/51
85.71% Branches 12/14
100% Functions 8/8
100% Lines 45/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 1305x         5x         5x               5x 149760x 149760x 149760x 149760x     5x 9360x   5x 4680x 4680x 37440x 37440x   4680x     5x                 10x 10x 10x 6x 6x     10x 4680x   4680x 4680x 4680x 4680x 4680x                                   5417x 4680x 4457x 4457x   223x 223x 223x       10x             5x                 5x 5x 5x 5x 90x 2880x                         5x            
import {
  TILE_COLOR_PROP_FLIP_VERTICAL,
  TILE_COLOR_PROP_FLIP_HORIZONTAL,
  TILE_SIZE,
} from "consts";
import {
  IndexedImage,
  indexedImageTo2bppTileData,
  sliceIndexedImage,
} from "shared/lib/tiles/indexedImage";
import { TileLookup, hashTileData } from "shared/lib/tiles/tileData";
 
interface AutoFlipResult {
  tileData: Uint8Array[];
  tileAttrs: number[];
  tilesetData: Uint8Array[];
}
 
const reverseByte = (value: number): number => {
  let output = value;
  output = ((output & 0xf0) >> 4) | ((output & 0x0f) << 4);
  output = ((output & 0xcc) >> 2) | ((output & 0x33) << 2);
  return ((output & 0xaa) >> 1) | ((output & 0x55) << 1);
};
 
const flipTileDataX = (tile: Uint8Array): Uint8Array =>
  Uint8Array.from(tile, reverseByte);
 
const flipTileDataY = (tile: Uint8Array): Uint8Array => {
  const output = new Uint8Array(tile.length);
  for (let row = 0; row < 8; row++) {
    output[row * 2] = tile[(7 - row) * 2] ?? 0;
    output[row * 2 + 1] = tile[(7 - row) * 2 + 1] ?? 0;
  }
  return output;
};
 
export const autoFlipTileData = ({
  tileData,
  tileColors,
  commonTileData,
}: {
  tileData: Uint8Array[];
  tileColors: readonly number[];
  commonTileData: Uint8Array[];
}): AutoFlipResult => {
  const newTileData: Uint8Array[] = [];
  const newTileColors = [...tileColors];
  const tileLookup: TileLookup = commonTileData.reduce((memo, data) => {
    memo[hashTileData(data)] = data;
    return memo;
  }, {} as TileLookup);
 
  tileData.forEach((origData, index) => {
    const attr = tileColors[index] ?? 0;
    const clearedAttr =
      attr & ~(TILE_COLOR_PROP_FLIP_VERTICAL | TILE_COLOR_PROP_FLIP_HORIZONTAL);
    const flipX = flipTileDataX(origData);
    const flipY = flipTileDataY(origData);
    const flipXY = flipTileDataX(flipY);
    const variants = [
      { data: origData, hash: hashTileData(origData), mask: 0 },
      {
        data: flipX,
        hash: hashTileData(flipX),
        mask: TILE_COLOR_PROP_FLIP_HORIZONTAL,
      },
      {
        data: flipY,
        hash: hashTileData(flipY),
        mask: TILE_COLOR_PROP_FLIP_VERTICAL,
      },
      {
        data: flipXY,
        hash: hashTileData(flipXY),
        mask: TILE_COLOR_PROP_FLIP_HORIZONTAL | TILE_COLOR_PROP_FLIP_VERTICAL,
      },
    ];
    const matched = variants.find((variant) => tileLookup[variant.hash]);
    if (matched) {
      newTileData.push(matched.data);
      newTileColors[index] = clearedAttr | matched.mask;
    } else {
      tileLookup[variants[0].hash] = origData;
      newTileColors[index] = clearedAttr;
      newTileData.push(origData);
    }
  });
 
  return {
    tileData: newTileData,
    tileAttrs: newTileColors,
    tilesetData: [...commonTileData, ...newTileData],
  };
};
 
export const autoFlipTiles = ({
  indexedImage,
  tileColors,
  commonTileData,
}: {
  indexedImage: IndexedImage;
  tileColors: readonly number[];
  commonTileData: Uint8Array[];
}): AutoFlipResult => {
  const tileData: Uint8Array[] = [];
  const width = Math.floor(indexedImage.width / TILE_SIZE);
  const height = Math.floor(indexedImage.height / TILE_SIZE);
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      tileData.push(
        indexedImageTo2bppTileData(
          sliceIndexedImage(
            indexedImage,
            x * TILE_SIZE,
            y * TILE_SIZE,
            TILE_SIZE,
            TILE_SIZE,
          ),
        ),
      );
    }
  }
  return autoFlipTileData({
    tileData,
    tileColors,
    commonTileData,
  });
};