/**
     * Decodes a single frame of animation. Does not colour the frame with the palette afterwards.
     *
     * @param framenum The number of the frame to decode.
     * @param lastbytes Frame data of the last frame decoded.
     * @return Raw decoded frame data.
     */
    private ByteBuffer decodeFrame(int framenum, ByteBuffer lastbytes) {

      int offset = wsaoffsets[framenum];
      int sourcelength = wsaoffsets[framenum + 1] - offset;

      // Source frame data (is at frame offset + palette size)
      ByteBuffer sourcebytes =
          com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(
              sourcelength);
      try {
        inputchannel.read(sourcebytes);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      sourcebytes.rewind();

      // Intermediate and final frame data
      int framesize = width() * height();
      ByteBuffer intbytes =
          com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(framesize);
      ByteBuffer framebytes =
          com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(framesize);

      // First decompress from Format80, then decode as Format40
      CodecUtility.decodeFormat80(sourcebytes, intbytes);
      CodecUtility.decodeFormat40(intbytes, framebytes, lastbytes);

      return framebytes;
    }
  /**
   * Constructor, creates a new shp file with the given name and file data.
   *
   * @param name The name of this file.
   * @param bytechannel Data of the file.
   */
  public ShpFileCNC(String name, ReadableByteChannel bytechannel) {

    super(name);

    try {
      // Construct file header
      ByteBuffer headerbytes =
          com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(
              ShpFileHeaderCNC.HEADER_SIZE);
      try {
        bytechannel.read(headerbytes);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      headerbytes.rewind();
      shpfileheader = new ShpFileHeaderCNC(headerbytes);

      // numImages() + 2 for the 0 offset and EOF pointer
      ShpImageOffsetCNC[] offsets = new ShpImageOffsetCNC[numImages() + 2];
      for (int i = 0; i < offsets.length; i++) {
        ByteBuffer offsetbytes =
            com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(
                ShpImageOffsetCNC.OFFSET_SIZE);
        try {
          bytechannel.read(offsetbytes);
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
        offsets[i] = new ShpImageOffsetCNC((ByteBuffer) offsetbytes.rewind());
      }

      // Decompresses the raw SHP data into palette-index data
      shpimages = new ByteBuffer[numImages()];

      // Decompress every frame
      for (int i = 0; i < numImages(); i++) {
        ShpImageOffsetCNC imageoffset = offsets[i];

        // Format conversion buffers
        ByteBuffer sourcebytes =
            com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(
                offsets[i + 1].offset - imageoffset.offset);
        try {
          bytechannel.read(sourcebytes);
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
        sourcebytes.rewind();
        ByteBuffer destbytes =
            com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(
                width() * height());

        switch (imageoffset.offsetformat) {

            // Format80 image
          case FORMAT80:
            CodecUtility.decodeFormat80(sourcebytes, destbytes);
            break;

            // Format40 image
          case FORMAT40:
            int refoffset = imageoffset.refoff;
            int j;
            for (j = 0; j < numImages(); j++) {
              if (refoffset == offsets[j].offset) {
                break;
              }
            }
            CodecUtility.decodeFormat40(sourcebytes, destbytes, shpimages[j]);
            break;

            // Format20 image
          case FORMAT20:
            CodecUtility.decodeFormat20(sourcebytes, shpimages[i - 1], destbytes);
            break;
        }

        // Add the decompressed image to the image array
        shpimages[i] = destbytes;
      }
    } finally {
      try {
        bytechannel.close();
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }