/** * 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; }
/** {@inheritDoc} */ @Override public void write(GatheringByteChannel outputchannel) { try { int numimages = numImages(); // Encode each image ByteBuffer[] images = new ByteBuffer[numimages]; for (int i = 0; i < images.length; i++) { ByteBuffer image = com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer( shpimages[i].capacity()); CodecUtility.encodeFormat80(shpimages[i], image); images[i] = image; } // Construct image offset headers for each image ByteBuffer[] offsets = new ByteBuffer[numimages + 2]; int offsettotal = ShpFileHeaderCNC.HEADER_SIZE + (ShpImageOffsetCNC.OFFSET_SIZE * offsets.length); for (int i = 0; i < numImages(); i++) { offsets[i] = new ShpImageOffsetCNC(offsettotal, FORMAT80, 0, (byte) 0).toByteBuffer(); offsettotal += images[i].limit(); } // The 2 special image offsets at the end of the offset array offsets[numimages] = new ShpImageOffsetCNC(offsettotal, (byte) 0, 0, (byte) 0).toByteBuffer(); offsets[numimages + 1] = new ShpImageOffsetCNC(0, (byte) 0, 0, (byte) 0).toByteBuffer(); // Build header ByteBuffer header = shpfileheader.toByteBuffer(); // Write file try { outputchannel.write(header); outputchannel.write(offsets); outputchannel.write(images); } catch (IOException e) { throw new RuntimeException(e); } } finally { try { outputchannel.close(); } catch (IOException e) { throw new RuntimeException(e); } } }
/** * 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); } } }
/** {@inheritDoc} */ @Override public void write(GatheringByteChannel outputchannel) { int numimages = numImages(); // Build header ByteBuffer header = wsaheader.toByteBuffer(); // Build palette ByteBuffer palette = wsapalette.toByteBuffer(); // Encode each frame, construct matching offsets ByteBuffer[] frames = new ByteBuffer[isLooping() ? numimages + 1 : numimages]; ByteBuffer lastbytes = com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer( width() * height()); ByteBuffer frameoffsets = com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer( (numimages + 2) * 4); int offsettotal = WsaFileHeaderCNC.HEADER_SIZE + ((numimages + 2) * 4); for (int i = 0; i < frames.length; i++) { ByteBuffer framebytes = wsaframes[i]; ByteBuffer frameint = com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer( (int) (framebytes.capacity() * 1.5)); ByteBuffer frame = com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer( (int) (framebytes.capacity() * 1.5)); // First encode in Format40, then Format80 CodecUtility.encodeFormat40(framebytes, frameint, lastbytes); CodecUtility.encodeFormat80(frameint, frame); frames[i] = frame; lastbytes = framebytes; frameoffsets.putInt(offsettotal); offsettotal += frame.limit(); } // Last offset for EOF frameoffsets.putInt(offsettotal); frameoffsets.rewind(); // Write file to disk try { outputchannel.write(header); outputchannel.write(frameoffsets); outputchannel.write(palette); outputchannel.write(frames); } catch (IOException e) { throw new RuntimeException(e); } // Generate high-res colour lookup table if (!srcnohires) { // Figure-out the appropriate file name String lookupname = filename.contains(".") ? filename.substring(0, filename.lastIndexOf('.')) + ".pal" : filename + ".pal"; // Write the index of the closest interpolated palette colour // TODO: Perform proper colour interpolation ByteBuffer lookup = com.mikeduvall.redhorizon.util.ByteBufferFactory.createLittleEndianByteBuffer(256); for (int i = 0; i < 256; i++) { lookup.put((byte) i); } lookup.rewind(); try (FileChannel lookupfile = FileChannel.open(Paths.get(lookupname), WRITE)) { for (int i = 0; i < 256; i++) { lookupfile.write(lookup); } } // TODO: Should be able to soften the auto-close without needing this catch (IOException ex) { throw new RuntimeException(ex); } } }