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

64.71% Statements 88/136
68.75% Branches 11/16
69.23% Functions 9/13
62.16% Lines 69/111

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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283                        25x 25x 25x 25x 25x 25x 25x                                                                     25x           62x 62x 62x 1423360x           1423360x   62x                 25x       30046x                       25x                             25x 3554624x                     25x             29984x 29984x 29984x   29984x 29984x 239872x 1911968x 1904960x 1904960x   7008x   1911968x     29984x               25x 21904x 21904x 21904x 175232x 175232x 175232x 1401856x 1401856x 1401856x 1401856x   175232x 175232x 175232x 175232x     21904x               25x                                       25x                             25x                                                                         25x       3872x 3872x 3872x 3872x 3872x 30976x 247808x 247808x 240800x 3872x   240800x 26228x         3872x 3872x 3872x           1401856x 350464x  
/**
 * A data wrapper for an image using indexed colors with one 8-bit value per pixel
 */
export type IndexedImage = {
  width: number;
  height: number;
  data: Uint8Array;
};
 
/**
 * A color value for use in IndexedImages
 */
enum Color {
  Transparent = 0,
  Light = 1,
  Mid = 2,
  Dark = 3,
  Divider = 254,
  Unknown = 255,
}
 
/**
 * A function for converting rgb pixel values to an IndexedImage Color
 */
export type ImageIndexFunction = (
  r: number,
  g: number,
  b: number,
  a: number
) => number;
 
/**
 * A wrapper for a slice of an image giving the sliced data and coordinates where the slice was taken
 */
export type SliceDef = {
  data: IndexedImage;
  coordinates: {
    x: number;
    y: number;
    width: number;
    height: number;
  };
};
 
/**
 * Convert an array of pixel data into an IndexedImage using a given index function
 *
 * @param width Image width
 * @param height Image height
 * @param pixels Raw RGB pixel data array
 * @param indexFn A function to convert RGB values to a color index
 * @returns A new IndexedImage representing the pixel data provided
 */
export const pixelDataToIndexedImage = (
  width: number,
  height: number,
  pixels: Buffer | Uint8ClampedArray,
  indexFn: ImageIndexFunction
): IndexedImage => {
  const output = makeIndexedImage(width, height);
  let ii = 0;
  for (let i = 0; i < pixels.length; i += 4) {
    output.data[ii] = indexFn(
      pixels[i],
      pixels[i + 1],
      pixels[i + 2],
      pixels[i + 3]
    );
    ii++;
  }
  return output;
};
 
/**
 * Create a blank IndexedImage with a given width & height
 * @param width Image width
 * @param height Image Height
 * @returns IndexedImage with blank data
 */
export const makeIndexedImage = (
  width: number,
  height: number
): IndexedImage => {
  return {
    width,
    height,
    data: new Uint8Array(width * height),
  };
};
 
/**
 * Create a deep clone of an IndexedImage
 * @param original An IndexedImage to clone
 * @returns A deep clone of the original IndexedImage
 */
export const cloneIndexedImage = (original: IndexedImage): IndexedImage => {
  return {
    width: original.width,
    height: original.height,
    data: new Uint8Array(original.data),
  };
};
 
/**
 * Convert an x/y coordinate to an index with an IndexedImage's data
 * @param x X coordinate
 * @param y Y coordinate
 * @param image IndexedImage to read
 * @returns Index value to using in image.data[]
 */
export const toIndex = (x: number, y: number, image: IndexedImage): number =>
  x + y * image.width;
 
/**
 * Create a new IndexedImage by slicing from a larger image.
 * @param inData IndexedImage to slice
 * @param startX Left coordinate
 * @param startY Top coordinate
 * @param width Width of slice
 * @param height Height of slice
 * @returns
 */
export const sliceIndexedImage = (
  inData: IndexedImage,
  startX: number,
  startY: number,
  width: number,
  height: number
): IndexedImage => {
  const output = makeIndexedImage(width, height);
  const inWidth = inData.width;
  const inHeight = inData.height;
 
  let ii = 0;
  for (let y = startY; y < startY + height; y++) {
    for (let x = startX; x < startX + width; x++) {
      if (x < inWidth && y < inHeight && x >= 0 && y >= 0) {
        const i = toIndex(x, y, inData);
        output.data[ii] = inData.data[i];
      } else {
        output.data[ii] = Color.Transparent;
      }
      ii++;
    }
  }
  return output;
};
 
/**
 * Convert an 8px tile to GB 2bpp format
 * @param image Tile image to read (should be 8x8px)
 * @returns Array of GB format 2bpp image data
 */
export const indexedImageTo2bppTileData = (image: IndexedImage): Uint8Array => {
  const output = new Uint8Array(16);
  let i = 0;
  for (let y = 0; y < 8; y++) {
    let row1 = "";
    let row2 = "";
    for (let x = 0; x < 8; x++) {
      const index = toIndex(x, y, image);
      const binary = bin2(image.data[index]);
      row1 += binary[1];
      row2 += binary[0];
    }
    output[i] = binDec(row1);
    i++;
    output[i] = binDec(row2);
    i++;
  }
 
  return output;
};
 
/**
 * Flip IndexedImage horizontally
 * @param inData Image to flip
 * @returns Horizontally flipped IndexedImage
 */
export const flipIndexedImageX = (inData: IndexedImage): IndexedImage => {
  const output = makeIndexedImage(inData.width, inData.height);
  const width = inData.width;
  const height = inData.height;
  let ii = 0;
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const i = toIndex(width - x - 1, y, inData);
      output.data[ii] = inData.data[i];
      ii++;
    }
  }
  return output;
};
 
/**
 * Flip IndexedImage vertically
 * @param inData Image to flip
 * @returns Vertically flipped IndexedImage
 */
export const flipIndexedImageY = (inData: IndexedImage): IndexedImage => {
  const output = makeIndexedImage(inData.width, inData.height);
  const width = inData.width;
  const height = inData.height;
  let ii = 0;
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const i = toIndex(x, height - y - 1, inData);
      output.data[ii] = inData.data[i];
      ii++;
    }
  }
  return output;
};
 
export const trimIndexedImage = (
  inData: IndexedImage,
  trimValue: number
): SliceDef => {
  const width = inData.width;
  const height = inData.height;
  let minX = width;
  let maxX = 0;
  let minY = height;
  let maxY = 0;
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const i = toIndex(x, y, inData);
      Iif (inData.data[i] !== trimValue) {
        Iif (x < minX) {
          minX = x;
        }
        Iif (x > maxX) {
          maxX = x;
        }
        Iif (y < minY) {
          minY = y;
        }
        Iif (y > maxY) {
          maxY = y;
        }
      }
    }
  }
  const sliceW = Math.max(0, maxX - minX + 1);
  const sliceH = Math.max(0, maxY - minY + 1);
  return {
    data: sliceIndexedImage(inData, minX, minY, sliceW, sliceH),
    coordinates: { x: minX, y: minY, width: sliceW, height: sliceH },
  };
};
 
export const trimIndexedImageHorizontal = (
  inData: IndexedImage,
  trimValue: number
): SliceDef => {
  const width = inData.width;
  const height = inData.height;
  let minX = width;
  let maxX = 0;
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const i = toIndex(x, y, inData);
      if (inData.data[i] !== trimValue) {
        if (x < minX) {
          minX = x;
        }
        if (x > maxX) {
          maxX = x;
        }
      }
    }
  }
  const sliceW = Math.max(0, maxX - minX + 1);
  const sliceH = inData.height;
  return {
    data: sliceIndexedImage(inData, minX, 0, sliceW, sliceH),
    coordinates: { x: minX, y: 0, width: sliceW, height: sliceH },
  };
};
 
const bin2 = (value: Color) => value.toString(2).padStart(2, "0");
const binDec = (binary: string) => parseInt(binary, 2);