JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<!-- by AimeePlaysMSM -->
<h1>Sprite sheet to images converter</h1>
<small>red outline = rotated sprite. mouseover to view label. premultiplier and de-premultiplied alpha output supported. click to download sprite with rotation corrected). click download button to download all as a ZIP.</small><br />
Sprite bitmap:<br />
<input id="sprite" type="file" accept=".png" /><input id="depre" type="checkbox" checked="checked"><label for="depre" title="removes black fringing, but check with your graphics software to see what it expects">de-premultiply alpha</label><input id="respectorigins" type="checkbox"><label for="respectorigins">respect origins</label></label><br />
Sprite XML:<br />
<input id="xml" type="file" accept=".xml" disabled="disabled" />
<span id="xmldata"></span>
<hr />
Sheet:<br />
<img id="spriteimg" style="display:none;" /><!-- need to keep one in memory for reference anyway -->
<div style="display:flex; flex-flow:row wrap; align-items:flex-start; justify-content:start;">
<canvas id="spritecnv" style="display:none;"></canvas>
<span id="spritename"></span>
</div>
<canvas id="lutcnv" style="display:none;"></canvas> <!-- ditto -->
<button id="dlall" disabled="disabled">download all sprites (ZIP)</button>

CSS

body {
  font-family: sans-serif;
  font-size:8pt;
}
h1 {
  padding:0;
  margin:0;
}

JavaScript

let spriteRects = [];
let spriteName = "";
let lutdata;
sprite.onclick = function () {
	sprite.value = "";
  xml.disabled = true;
  dlall.disabled = true;
  spritecnv.style.display = "none";
}
sprite.onchange = function () {
  spriteimg.onload = function() {
	  xml.disabled = false;
  }
  spriteimg.src = window.URL.createObjectURL(sprite.files[0])
  spriteName = sprite.value.replace(/^.*[\\\/]/, '');
};
xml.onclick = function () {
	xml.value = "";
}
xml.onchange = function () {
	  const file = xml.files[0];
	  const fr = new FileReader();
	  fr.onload = function (e) {
    	const parser = new DOMParser();
			const data = parser.parseFromString(e.target.result, "text/xml");
      const atlas = data.getElementsByTagName("TextureAtlas")[0];
      const sprites = atlas.getElementsByTagName("sprite");
      const numSprites = sprites.length;
      xmldata.textContent = `Sprites found: ${numSprites}`;

      spritecnv.width = lutcnv.width = spriteimg.naturalWidth;
      spritecnv.height = lutcnv.height = spriteimg.naturalHeight;
      const ctx = spritecnv.getContext("2d");
      const lutctx = lutcnv.getContext("2d");
      ctx.drawImage(spriteimg,0,0);
      ctx.imageSmoothingEnabled = false;
      lutctx.imageSmoothingEnabled = false;

			if (depre.checked) {
        // de-premultiply the sprite
        const imageData = ctx.getImageData(0,0,spritecnv.width,spritecnv.height);
        const pixels = imageData.data;
        const pixelsLen = pixels.length;
        for (let i = 0; i < pixelsLen; i += 4) {
          const a = pixels[i+3]; // Alpha
          if ((a < 255) && (a > 0)) {
            const mult = a / 255.0
            pixels[i] /= mult; // Red
            pixels[i + 1] /= mult; // Green
            pixels[i + 2] /= mult; // Blue
          }
        }
        ctx.putImageData(imageData,0,0);
      }
      
      ctx.translate(0.5, 0.5); // to fix blurry edges
      // lutctx.translate(0.5, 0.5);
      for (let i = 0; i < numSprites; i += 1) {
      	const...