spaceaceiigs/tools/packvideo/source/packvideo.cpp
Rebecca Heineman 7fd252644d Added video assets
Added all video assets encoded as GIF files and included the packvideo
tool that will compress the art into the IIgs native format. Binaries
for Mac and PC are supplied
2015-07-30 01:01:53 -07:00

641 lines
16 KiB
C++

/***************************************
Tool to pre-process video data for Space Ace IIgs
Copyright (c) 1995-2015 by Rebecca Ann Heineman <becky@burgerbecky.com>
It is released under an MIT Open Source license. Please see LICENSE
for license details. Yes, you can use it in a
commercial title without paying anything, just give me a credit.
Please? It's not like I'm asking you for money!
***************************************/
#include "packvideo.h"
/***************************************
Convert the IIgs palette to RGBAWord8_t
***************************************/
static void BURGER_API ConvertPalette(RGBAWord8_t *pOutput,const Word8 *pInput)
{
Word uIndex = 0;
do {
pOutput->m_uRed = Renderer::RGB4ToRGB8Table[pInput[1]&0xFU];
pOutput->m_uGreen = Renderer::RGB4ToRGB8Table[pInput[0]>>4U];
pOutput->m_uBlue = Renderer::RGB4ToRGB8Table[pInput[0]&0xFU];
pOutput->m_uAlpha = 0xFF;
pInput+=2;
++pOutput;
} while (++uIndex<16);
}
/***************************************
Convert the RGBAWord8_t palette to IIgs
***************************************/
static void BURGER_API ConvertPalette(Word8 *pOutput,const RGBAWord8_t *pInput)
{
Word uIndex = 0;
do {
Word uTemp = pInput->m_uGreen&0xF0U;
uTemp |= (pInput->m_uBlue>>4U);
pOutput[0] = static_cast<Word8>(uTemp);
pOutput[1] = static_cast<Word8>(pInput->m_uRed>>4U);
++pInput;
pOutput+=2;
} while (++uIndex<16);
}
/***************************************
Test if the IIgs palette has changed
***************************************/
static Word BURGER_API ComparePalette(const Word8 *pInput1,const Word8 *pInput2)
{
return MemoryCompare(pInput1,pInput2,32);
}
/***************************************
Convert bitmap to IIgs format
8 bits per pixel converted to 4 bits per pixel
***************************************/
static void BURGER_API ConvertPixelsToIIgs(Word8 *pOutput,const Image *pInput)
{
WordPtr uStride = pInput->GetStride()-320;
const Word8 *pPixels = pInput->GetImage();
WordPtr j=200;
do {
WordPtr i=320/2;
do {
Word uTemp = pPixels[0]<<4U;
uTemp |= (pPixels[1]&0xFU);
pOutput[0] = static_cast<Word8>(uTemp);
pPixels+=2;
++pOutput;
} while (--i);
pPixels+=uStride;
} while (--j);
}
/***************************************
Compress a IIgs keyframe
The compression only packs runs of a minimum of 3 matching
bytes to reduce mode switching during decompression
***************************************/
static void BURGER_API CompressKeyFrame(OutputMemoryStream *pOutput,const Word8 *pInput)
{
// Number of bytes to process
WordPtr uInputLength = 320*200/2;
do {
// If two bytes or less, just store and exit
if (uInputLength<3) {
pOutput->Append(static_cast<Word8>(uInputLength));
pOutput->Append(pInput,uInputLength);
break;
}
// Check for repeater of at LEAST three bytes
Word uMatchTest = pInput[0];
// Is there a run?
if ((pInput[1] == uMatchTest) && (pInput[2] == uMatchTest)) {
// Maximum length of a matched run
WordPtr uMaximumRun = 127;
if (uInputLength<127) {
uMaximumRun = uInputLength; // 1-127
}
WordPtr uRun = 3-1;
while (++uRun<uMaximumRun) {
// Find end of repeater
if (pInput[uRun]!=uMatchTest) {
break;
}
}
// Encode 128-255 for 0-127 run
pOutput->Append(static_cast<Word8>(0x80|uRun));
pOutput->Append(static_cast<Word8>(uMatchTest));
uInputLength-=uRun;
pInput += uRun;
} else {
// Raw run, minimum size of 2 bytes
WordPtr uMaximumRun = 127;
if (uInputLength<127) {
uMaximumRun = uInputLength;
}
// Preload the next byte
uMatchTest = pInput[1];
WordPtr uRun = 2-1;
while (++uRun<uMaximumRun) {
// Scan for next repeater
if (pInput[uRun]==uMatchTest && (pInput[uRun+1]==uMatchTest)) {
// Remove from the run
--uRun;
break;
}
// Get the next byte
uMatchTest = pInput[uRun];
}
// Perform a raw data transfer
// Run 1-128
// Encode 0-127
pOutput->Append(static_cast<Word8>(uRun));
pOutput->Append(pInput,uRun);
uInputLength-=uRun;
pInput += uRun;
}
} while (uInputLength);
// Mark the end of compressed data
pOutput->Append(static_cast<Word8>(0));
}
/***************************************
Compress a IIgs animation frame
***************************************/
static void BURGER_API CompressAnimFrame(OutputMemoryStream *pOutput,const Word8 *pPreviousFrame,const Word8 *pCurrentFrame)
{
// Number of bytes to process
WordPtr uInputLength = 320*200/2;
do {
// Check if there were any differences between the frames to create a skip token
WordPtr uMaximumRun = 127; // Skip token maximum value
if (uInputLength<uMaximumRun) {
uMaximumRun = uInputLength;
}
// Test from the previous frame to the current frame
WordPtr uRun = 0;
do {
if (pPreviousFrame[uRun]!=pCurrentFrame[uRun]) {
break;
}
} while (++uRun<uMaximumRun);
// If the run is at least 2 bytes or end of the data, use it as is
if ((uRun==uInputLength) || (uRun>=3)) {
// Output a "skip" data token
pOutput->Append(static_cast<Word8>(uRun));
pPreviousFrame+=uRun;
pCurrentFrame+=uRun;
uInputLength-=uRun;
} else {
// Maximum length of a matched run
uMaximumRun = 255;
if (uInputLength<255) {
uMaximumRun = uInputLength; // 1-255
}
Word uMatchTest = pCurrentFrame[0];
uRun = 1;
while (uRun<uMaximumRun) {
// Find end of repeater
if (pCurrentFrame[uRun]!=uMatchTest) {
break;
}
++uRun;
}
// Is there a run of 4 or greater?
if (uRun>=4) {
// Encode the run length
pOutput->Append(static_cast<Word8>(0));
pOutput->Append(static_cast<Word8>(uRun));
pOutput->Append(static_cast<Word8>(uMatchTest));
uInputLength -= uRun;
pCurrentFrame += uRun;
pPreviousFrame += uRun;
} else {
// Raw run
uMaximumRun = 127;
if (uInputLength<127) {
uMaximumRun = uInputLength;
}
uRun = 0;
while (++uRun<uMaximumRun) {
// Scan for next repeater
if (pCurrentFrame[uRun]==uMatchTest && (pCurrentFrame[uRun+1]==uMatchTest) && (pCurrentFrame[uRun+2]==uMatchTest)) {
// Remove from the run
--uRun;
break;
}
if ((pCurrentFrame[uRun]==pPreviousFrame[uRun]) &&
(pCurrentFrame[uRun+1]==pPreviousFrame[uRun+1]) &&
(pCurrentFrame[uRun+2]==pPreviousFrame[uRun+2])) {
break;
}
// Get the next byte
uMatchTest = pCurrentFrame[uRun];
}
// Handle some data optimizations
// If it's only a single byte run and it's the same as the previous
// frame? Just skip
if ((uRun==1) && (pCurrentFrame[0]==pPreviousFrame[0])) {
pOutput->Append(static_cast<Word8>(1));
--uInputLength;
++pCurrentFrame;
++pPreviousFrame;
// If it's only a single byte run and it's the same as the previous
// frame? Just skip
} else if ((uRun==2) &&
(pCurrentFrame[0]==pPreviousFrame[0]) &&
(pCurrentFrame[1]==pPreviousFrame[1])) {
pOutput->Append(static_cast<Word8>(2));
uInputLength-=2;
pCurrentFrame+=2;
pPreviousFrame+=2;
} else {
// Perform a raw data transfer
// Run 1-128
pOutput->Append(static_cast<Word8>(0x80|uRun));
pOutput->Append(pCurrentFrame,uRun);
uInputLength-=uRun;
pCurrentFrame += uRun;
pPreviousFrame+=uRun;
}
}
}
} while (uInputLength>=2);
// Simple check if there's only one byte left
if (uInputLength==1) {
if (pCurrentFrame[0]==pPreviousFrame[0]) {
pOutput->Append(static_cast<Word8>(1));
} else {
pOutput->Append(static_cast<Word8>(0x81));
pOutput->Append(pCurrentFrame[0]);
}
}
}
/***************************************
Process a video file into space ace format
***************************************/
static Word ExtractVideo(OutputMemoryStream *pOutput,const Word8 *pInput,WordPtr uInputLength)
{
InputMemoryStream InputMem(pInput,uInputLength,TRUE);
Image MyImage;
FileGIF Giffy;
Word8 IIgsPalette[32];
Word8 NewIIgsPalette[32];
Word uResult = 10;
if (!Giffy.Load(&MyImage,&InputMem)) {
if ((MyImage.GetWidth()!=320) || (MyImage.GetHeight()!=200)) {
printf("Input file is not 320 x 200");
} else {
// Initialize the IIgs palette to invalid values
MemoryFill(IIgsPalette,255,sizeof(IIgsPalette));
Word8 *pCurrentFrame = static_cast<Word8 *>(Alloc(320*200/2));
Word8 *pPreviousFrame = static_cast<Word8 *>(Alloc(320*200/2));
int i = 1;
do {
// Process a frame
// Save space for the chunk size
WordPtr uOutputMark = pOutput->GetSize();
pOutput->Append(static_cast<Word16>(0));
// Convert the palette to IIgs format
ConvertPalette(NewIIgsPalette,Giffy.GetPalette());
// Set the default chunk type
Word8 uTypeFlag = 0x01;
// Is there a palette update?
if (ComparePalette(NewIIgsPalette,IIgsPalette)) {
MemoryCopy(IIgsPalette,NewIIgsPalette,sizeof(IIgsPalette));
uTypeFlag |= 0x80U;
}
// Initial frame?
if (i==1) {
uTypeFlag |= 0x60;
}
// Send the data type byte
pOutput->Append(static_cast<Word8>(uTypeFlag));
if (uTypeFlag&0x80U) {
pOutput->Append(IIgsPalette,32);
}
ConvertPixelsToIIgs(pCurrentFrame,&MyImage);
if (uTypeFlag&0x40) {
CompressKeyFrame(pOutput,pCurrentFrame);
} else {
CompressAnimFrame(pOutput,pPreviousFrame,pCurrentFrame);
}
MemoryCopy(pPreviousFrame,pCurrentFrame,320*200/2);
// Update the chunk size
Word16 uChuckShort;
LittleEndian::Store(&uChuckShort,static_cast<Word16>(pOutput->GetSize()-uOutputMark));
pOutput->Overwrite(&uChuckShort,2,uOutputMark);
++i;
} while (!Giffy.LoadNextFrame(&MyImage,&InputMem));
Free(pCurrentFrame);
Free(pPreviousFrame);
// Append an "End of data" marker
pOutput->Append(static_cast<Word16>(0xFF00U));
uResult = 0;
}
} else {
printf("Gif input file error!\n");
}
return uResult;
}
/***************************************
Convert a Space Ace file to an animated GIF file
***************************************/
static char Name[] = "filexxx.gif";
static Word EncapsulateToGIF(OutputMemoryStream *pOutput,Filename *pOutputFilename,const Word8 *pInput,WordPtr uInputLength)
{
// Too small?
if (uInputLength<2) {
return 10;
}
FileGIF GIF;
Image MyImage;
// Create an initial image
MyImage.Init(320,200,Image::PIXELTYPE8BIT);
MyImage.ClearBitmap();
MemoryClear(GIF.GetPalette(),sizeof(GIF.GetPalette()[0])*256);
//
// Decompress a chunk
//
Word uFrame = 0;
for (;;) {
Word uChunkSize = LittleEndian::LoadAny(reinterpret_cast<const Word16 *>(pInput));
if (uChunkSize>=0xFF00) {
printf("End of data, frames = %u\n",uFrame);
break;
}
++uFrame;
if (uChunkSize>uInputLength) {
printf("Premature end of data\n");
return 10;
}
if (uChunkSize<2) {
printf("Chunk size too small\n");
return 10;
}
printf("Chunk is %u bytes\n",uChunkSize);
const Word8 *pWork = pInput+2;
uInputLength -= uChunkSize;
pInput+= uChunkSize;
uChunkSize-=2;
// Get the palette token
if (uChunkSize) {
Word uType = pWork[0];
++pWork;
--uChunkSize;
printf("Token = 0x%02X\n",uType);
if (uType&0x80) {
// Clear out the palette
MemoryClear(GIF.GetPalette(),sizeof(GIF.GetPalette()[0])*256);
ConvertPalette(GIF.GetPalette(),pWork);
pWork+=32;
uChunkSize-=32;
}
// Full image or animation frame?
if (uType&0x40) {
Word uTemp;
Word8 *pDest = MyImage.GetImage();
for (;;) {
uTemp = pWork[0];
++pWork;
if (!uTemp) {
break;
}
if (uTemp&0x80) {
uTemp&=0x7f;
if (uTemp) {
// Run length compressed loop
Word uSecond = pWork[0];
++pWork;
Word uFirst = uSecond>>4U;
uSecond&=0xF;
do {
pDest[0] = static_cast<Word8>(uFirst);
pDest[1] = static_cast<Word8>(uSecond);
pDest+=2;
} while (--uTemp);
}
} else {
// Uncompressed loop
do {
Word uColor = pWork[0];
++pWork;
pDest[0] = static_cast<Word8>(uColor>>4U);
pDest[1] = static_cast<Word8>(uColor&0xF);
pDest+=2;
} while (--uTemp);
}
}
} else {
Word uTemp;
Word8 *pDest = MyImage.GetImage();
Word8 *pEnd = pDest+(320*200);
do {
uTemp = pWork[0];
++pWork;
if (!uTemp) {
uTemp = pWork[0];
++pWork;
if (uTemp) {
// Run length compressed loop
Word uSecond = pWork[0];
++pWork;
Word uFirst = uSecond>>4U;
uSecond&=0xF;
do {
pDest[0] = static_cast<Word8>(uFirst);
pDest[1] = static_cast<Word8>(uSecond);
pDest+=2;
} while (--uTemp);
}
} else if (uTemp&0x80) {
uTemp&=0x7f;
if (uTemp) {
// Uncompressed loop
do {
Word uColor = pWork[0];
++pWork;
pDest[0] = static_cast<Word8>(uColor>>4U);
pDest[1] = static_cast<Word8>(uColor&0xF);
pDest+=2;
} while (--uTemp);
}
} else {
pDest+=(uTemp*2);
}
} while (pDest<pEnd);
}
}
GIF.Save(pOutput,&MyImage);
Filename TempName(pOutputFilename[0]);
TempName.SetFileExtension(NULL);
NumberString Namex(static_cast<Word32>(uFrame),LEADINGZEROS|3);
String Name2(TempName.GetPtr());
Name2.Remove(Name2.GetLength()-1);
Name2.Append(Namex,3);
TempName.Set(Name2.GetPtr());
TempName.SetFileExtension("gif");
printf("Frame %s\n",TempName.GetPtr());
pOutput->SaveFile(&TempName);
pOutput->Clear();
}
return 0;
}
/***************************************
Main dispatcher
***************************************/
int BURGER_ANSIAPI main(int argc,const char **argv)
{
ConsoleApp MyApp(argc,argv);
CommandParameterBooleanTrue DoVideo("Process Video","v");
CommandParameterBooleanTrue ConvertToGIF("Convert to GIF","g");
const CommandParameter *MyParms[] = {
&DoVideo,
&ConvertToGIF
};
#if 0
Filename DeathName;
DeathName.SetFromNative("D:\\projects\\burger\\games\\spaceace\\iigs\\assets\\death\\death07.gif");
FileGIF Giffy;
InputMemoryStream InputMem;
if (!InputMem.Open(&DeathName)) {
Image MyImage;
if (!Giffy.Load(&MyImage,&InputMem)) {
OutputMemoryStream OutputMem;
int i = 1;
do {
OutputMem.Clear();
Giffy.Save(&OutputMem,&MyImage);
char name[256];
sprintf(name,"D:\\projects\\burger\\games\\spaceace\\iigs\\assets\\death\\death07x%d.gif",i);
DeathName.SetFromNative(name);
OutputMem.SaveFile(&DeathName);
++i;
} while (!Giffy.LoadNextFrame(&MyImage,&InputMem));
}
}
#endif
argc = MyApp.GetArgc();
argv = MyApp.GetArgv();
argc = CommandParameter::Process(argc,argv,MyParms,sizeof(MyParms)/sizeof(MyParms[0]),
"Usage: packvideo InputFile OutputFile\n\n"
"Preprocess video data for Space Ace IIgs.\nCopyright by Rebecca Ann Heineman\n",3);
if (argc<0) {
Globals::SetErrorCode(10);
} else {
MyApp.SetArgc(argc);
Filename InputName;
InputName.SetFromNative(argv[1]);
WordPtr uInputLength;
Word8 *pInput = static_cast<Word8 *>(FileManager::LoadFile(&InputName,&uInputLength));
if (!pInput) {
printf("Can't open %s!\n",argv[1]);
Globals::SetErrorCode(10);
} else {
// Convert gif to data
if (DoVideo.GetValue()) {
OutputMemoryStream Output;
if (ExtractVideo(&Output,pInput,uInputLength)) {
printf("Can't convert %s!\n",argv[1]);
Globals::SetErrorCode(10);
} else {
Filename OutputName;
OutputName.SetFromNative(argv[2]);
if (Output.SaveFile(&OutputName)) {
printf("Can't save %s!\n",argv[2]);
Globals::SetErrorCode(10);
}
}
// Convert raw video to GIF
} else if (ConvertToGIF.GetValue()) {
Filename OutputName;
OutputName.SetFromNative(argv[2]);
OutputMemoryStream Output;
if (EncapsulateToGIF(&Output,&OutputName,pInput,uInputLength)) {
printf("Can't convert %s!\n",argv[1]);
Globals::SetErrorCode(10);
} else {
// if (Output.SaveFile(&OutputName)) {
// printf("Can't save %s!\n",argv[2]);
// Globals::SetErrorCode(10);
// }
}
} else {
printf("No conversion selected for %s!\n",argv[1]);
Globals::SetErrorCode(10);
}
Free(pInput);
}
}
return Globals::GetErrorCode();
}