Aegisub/prs/prs_file.cpp

325 lines
8.0 KiB
C++

// Copyright (c) 2005, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:zeratul@cellosoft.com
//
///////////
// Headers
#include <stdio.h>
#include <vector>
#include <algorithm>
#include "prs_file.h"
#include "prs_entry.h"
#include "prs_image.h"
#include "prs_display.h"
///////////////
// Constructor
PRSFile::PRSFile () {
}
//////////////
// Destructor
PRSFile::~PRSFile() {
Reset();
}
//////////////
// Reset file
void PRSFile::Reset() {
// Clear list of entries
std::list<PRSEntry*>::iterator cur;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
delete *cur;
}
entryList.clear();
}
////////
// Save
void PRSFile::Save(std::string path) {
// I'm using C's stdio instead of C++'s fstream because I feel that fstream is
// pretty lame for binary data, so I need to make sure that no exceptions are thrown from here.
// TODO: Make this endianness-independent
// Open file
FILE *fp = fopen(path.c_str(),"wb");
if (!fp) throw new std::exception("Failed to open file");
try {
// Write the "PRS" (zero-terminated) string ID (4 bytes)
fwrite("PRS",1,4,fp);
// Write version number (4 bytes)
unsigned __int32 temp = 1;
fwrite(&temp,4,1,fp);
// Write stream name (for future scalability, there is only one for now)
// This is writen as 4 bytes for length, and then length bytes for actual name.
// Since we set length to zero, the actual name string is never writen.
temp = 0;
fwrite(&temp,4,1,fp);
// Write data blocks
std::vector<char> vec;
std::list<PRSEntry*>::iterator cur;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
// Data blocks take care of writing themselves
// All of them start with a 4-byte string identifier, and a 4-byte length identifier
// A decoder can (and should!) ignore any block that it doesn't recognize
vec.resize(0);
(*cur)->WriteData(vec);
fwrite(&vec[0],1,vec.size(),fp);
}
}
// Rethrow exceptions
catch (...) {
fclose(fp);
throw;
}
// Close file
fclose(fp);
}
////////
// Load
void PRSFile::Load(std::string path, bool reset) {
// Reset first, if requested
if (reset) Reset();
// Open file
FILE *fp = fopen(path.c_str(),"rb");
if (!fp) throw new std::exception("Failed to open file");
try {
// Read first four bytes
char buf[5];
buf[4] = 0;
fread(buf,1,4,fp);
if (strcmp(buf,"PRS") != 0) throw new std::exception("Invalid file type.");
// Read version number
unsigned __int32 temp = 0;
fread(&temp,4,1,fp);
if (temp != 1) throw new std::exception("Invalid version.");
// Read stream name length
fread(&temp,4,1,fp);
// Read stream name
if (temp > 0) {
char *streamName = new char[temp+1];
fread(streamName,1,temp,fp);
// We don't need it, so delete afterwards
delete streamName;
}
// Temporary vector
std::vector<char> vec;
// Read data blocks
while (!feof(fp)) {
// Read identifier and size
fread(buf,1,4,fp);
fread(&temp,4,1,fp);
// Image block
if (strcmp(buf,"IMG") == 0) {
// Read data
vec.resize(temp);
fread(&vec[0],1,temp,fp);
// Create object
PRSImage *img = new PRSImage;
img->ReadData(vec);
AddEntry(img);
}
// Display block
else if (strcmp(buf,"DSP") == 0) {
// Read data
vec.resize(temp);
fread(&vec[0],1,temp,fp);
// Create object
PRSDisplay *disp = new PRSDisplay;
disp->ReadData(vec);
AddEntry(disp);
}
// Unknown block, ignore it
else {
fseek(fp,temp,SEEK_CUR);
}
}
}
// Rethrow exceptions
catch (...) {
fclose(fp);
throw;
}
// Close file
fclose(fp);
}
/////////////
// Add entry
void PRSFile::AddEntry(PRSEntry *entry) {
entryList.push_back(entry);
}
//////////////////////////////////////////////////
// Checks if there is any duplicate of this image
PRSImage *PRSFile::FindDuplicateImage(PRSImage *img) {
// Scan looking for duplicate hashes
PRSImage *orig;
std::list<PRSEntry*>::iterator cur;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
orig = PRSEntry::GetImage(*cur);
if (orig) {
// Compare data lengths
if (orig->dataLen == img->dataLen) {
// Identical data lengths, compare hashes
if (memcmp(orig->md5,img->md5,16) == 0) {
// Identical hashes, compare image data to be sure
if (memcmp(orig->data,img->data,orig->dataLen) == 0) {
// Identical data, return
return orig;
}
}
}
}
}
// No duplicate found
return NULL;
}
////////////////
// Draw a frame
void PRSFile::DrawFrame(int n,PRSVideoFrame *frame) {
// Get list of display blocks
std::vector<PRSDisplay*> blocks;
GetDisplayBlocksAtFrame(n,blocks);
// Draw the blocks
int nblocks = (int) blocks.size();
for (int i=0;i<nblocks;i++) {
// Get display and image pair
PRSDisplay *display = blocks[i];
PRSImage *image = GetImageByID(display->id);
if (!image) continue;
// Decode PNG
PRSVideoFrame *overFrame = image->GetDecodedFrame();
// Draw image on frame
if (overFrame) overFrame->Overlay(frame,display->x,display->y,display->alpha,display->blend);
// Clean up
delete overFrame;
}
}
////////////////////////////////////////////////
// Finds which display blocks are at a position
void PRSFile::GetDisplayBlocksAtFrame(int n,std::vector<PRSDisplay*> &blocks) {
// Find all blocks that match
unsigned int fn = n;
std::list<PRSEntry*>::iterator cur;
PRSDisplay *display;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
display = PRSEntry::GetDisplay(*cur);
if (display) {
if (display->startFrame <= fn && display->endFrame >= fn) {
blocks.push_back(display);
}
}
}
// Sort them by layer
// TODO
}
////////////////////////////////////////////
// Checks if there is anything at the frame
bool PRSFile::HasDataAtFrame(int n) {
// Find all blocks that match
unsigned int fn = n;
std::list<PRSEntry*>::iterator cur;
PRSDisplay *display;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
display = PRSEntry::GetDisplay(*cur);
if (display) {
if (display->startFrame <= fn && display->endFrame >= fn) {
return true;
}
}
}
return false;
}
///////////////////////////////////////////////////////////////
// Gets a PRSImage by its ID, returns NULL if it doesn't exist
PRSImage *PRSFile::GetImageByID(int id) {
// Search for image
std::list<PRSEntry*>::iterator cur;
PRSImage *img;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
img = PRSEntry::GetImage(*cur);
if (img && img->id == id) return img;
}
// Not found
return NULL;
}