2006-03-30 00:39:41 +02:00
// Copyright (c) 2006, 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.
//
2009-07-29 07:43:02 +02:00
// Aegisub Project http://www.aegisub.org/
2006-03-30 00:39:41 +02:00
//
2009-07-29 07:43:02 +02:00
// $Id$
/// @file subtitle_format_prs.cpp
/// @brief Writing of pre-rendered subtitle format files
/// @ingroup subtitle_io
///
2006-03-30 00:39:41 +02:00
///////////
// Headers
2009-01-04 07:31:48 +01:00
# include "config.h"
2006-12-23 04:55:00 +01:00
# if USE_PRS == 1
2006-03-30 01:53:42 +02:00
# include <wx/image.h>
2006-03-30 08:15:58 +02:00
# include <wx/mstream.h>
2006-03-31 11:44:48 +02:00
# include <wx/filename.h>
# include <wx/docview.h>
2006-03-30 00:39:41 +02:00
# include "subtitle_format_prs.h"
# include "ass_file.h"
# include "ass_dialogue.h"
2006-03-31 06:13:38 +02:00
# include "ass_override.h"
2006-03-30 00:39:41 +02:00
# include "avisynth_wrap.h"
# include "video_box.h"
# include "video_display.h"
# include "video_provider.h"
# include "main.h"
# include "frame_main.h"
# include "vfr.h"
2006-03-30 05:32:45 +02:00
# include "utils.h"
2006-03-31 06:13:38 +02:00
# include "md5.h"
2006-03-31 14:01:40 +02:00
# include "dialog_progress.h"
2009-07-14 23:28:49 +02:00
# include "charset_conv.h"
2006-04-03 15:51:09 +02:00
# include "../prs/prs.h"
2006-03-30 00:39:41 +02:00
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Can write to file?
/// @param filename
/// @return
///
2006-03-30 00:39:41 +02:00
bool PRSSubtitleFormat : : CanWriteFile ( wxString filename ) {
# ifdef __WINDOWS__
2006-08-07 23:30:45 +02:00
return false ;
//return (filename.Right(4).Lower() == _T(".prs"));
2006-03-30 00:39:41 +02:00
# else
return false ;
# endif
}
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Get name
/// @return
///
2006-12-26 19:26:13 +01:00
wxString PRSSubtitleFormat : : GetName ( ) {
return _T ( " Pre-Rendered Subtitles " ) ;
}
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Get write wildcards
/// @return
///
2006-12-26 19:26:13 +01:00
wxArrayString PRSSubtitleFormat : : GetWriteWildcards ( ) {
wxArrayString formats ;
formats . Add ( _T ( " prs " ) ) ;
return formats ;
}
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Write file
/// @param filename
/// @param encoding
/// @return
///
2006-03-30 00:39:41 +02:00
void PRSSubtitleFormat : : WriteFile ( wxString filename , wxString encoding ) {
# ifdef __WINDOWS__
// Video loaded?
2007-01-21 07:30:19 +01:00
if ( VideoContext : : Get ( ) - > IsLoaded ( ) ) throw _T ( " Video not loaded! " ) ;
2006-03-30 00:39:41 +02:00
2006-03-31 08:38:20 +02:00
// Create the PRS file
2006-03-30 00:39:41 +02:00
PRSFile file ;
2006-04-04 19:30:37 +02:00
// Create the temporary .ass file
wxString tempFile1 = wxFileName : : CreateTempFileName ( _T ( " aegisub " ) ) ;
wxRemoveFile ( tempFile1 ) ;
wxString tempFile = tempFile1 + _T ( " .ass " ) ;
GetAssFile ( ) - > Save ( tempFile , false , false ) ;
2006-03-30 00:39:41 +02:00
// Open two Avisynth environments
AviSynthWrapper avs1 , avs2 ;
IScriptEnvironment * env1 = avs1 . GetEnv ( ) ;
IScriptEnvironment * env2 = avs2 . GetEnv ( ) ;
2006-03-31 08:38:20 +02:00
// Prepare the Avisynth environments, that is, generate blank clips and hardsub into them
2007-01-21 07:30:19 +01:00
wxString val = wxString : : Format ( _T ( " BlankClip(pixel_type= \" RGB32 \" ,length=%i,width=%i,height=%i,fps=%f " ) , VideoContext : : Get ( ) - > GetLength ( ) , VideoContext : : Get ( ) - > GetWidth ( ) , VideoContext : : Get ( ) - > GetHeight ( ) , VideoContext : : Get ( ) - > GetFPS ( ) ) ;
2006-03-30 00:39:41 +02:00
AVSValue script1 = env1 - > Invoke ( " Eval " , AVSValue ( wxString ( val + _T ( " ,color=$000000) " ) ) . mb_str ( wxConvUTF8 ) ) ) ;
AVSValue script2 = env2 - > Invoke ( " Eval " , AVSValue ( wxString ( val + _T ( " ,color=$FFFFFF) " ) ) . mb_str ( wxConvUTF8 ) ) ) ;
char temp [ 512 ] ;
2009-07-14 23:28:49 +02:00
strcpy ( temp , tempFile . mb_str ( csConvLocal ) ) ;
2006-03-30 00:39:41 +02:00
AVSValue args1 [ 2 ] = { script1 . AsClip ( ) , temp } ;
AVSValue args2 [ 2 ] = { script2 . AsClip ( ) , temp } ;
try {
script1 = env1 - > Invoke ( " TextSub " , AVSValue ( args1 , 2 ) ) ;
script2 = env2 - > Invoke ( " TextSub " , AVSValue ( args2 , 2 ) ) ;
}
catch ( AvisynthError & err ) {
2009-07-14 23:28:49 +02:00
throw _T ( " AviSynth error: " ) + wxString ( err . msg , csConvLocal ) ;
2006-03-30 00:39:41 +02:00
}
2006-03-31 11:44:48 +02:00
PClip clip1 = script1 . AsClip ( ) ;
PClip clip2 = script2 . AsClip ( ) ;
2006-03-30 00:39:41 +02:00
2006-03-31 08:12:10 +02:00
// Get range
std : : vector < int > frames = GetFrameRanges ( ) ;
2006-04-01 01:12:03 +02:00
int totalFrames = frames . size ( ) ;
int toDraw = 0 ;
for ( int i = 0 ; i < totalFrames ; i + + ) {
if ( frames [ i ] = = 2 ) toDraw + + ;
}
2006-03-31 08:12:10 +02:00
2006-03-31 11:44:48 +02:00
// Set variables
id = 0 ;
lastDisplay = NULL ;
2006-04-01 11:40:32 +02:00
optimizer = 1 ; // 0 = none, 1 = optipng, 2 = pngout
2006-03-31 14:01:40 +02:00
// Progress
2006-04-01 02:16:52 +02:00
bool canceled = false ;
DialogProgress * progress = new DialogProgress ( NULL , _ ( " Exporting PRS " ) , & canceled , _ ( " Writing file " ) , 0 , toDraw ) ;
2006-03-31 14:01:40 +02:00
progress - > Show ( ) ;
2006-04-01 01:12:03 +02:00
progress - > SetProgress ( 0 , toDraw ) ;
2006-03-31 11:44:48 +02:00
// Render all frames that were detected to contain subtitles
2006-04-01 13:45:38 +02:00
int lastFrameDrawn = 0 ;
2006-04-01 01:12:03 +02:00
int drawn = 0 ;
2006-03-31 08:12:10 +02:00
for ( int framen = 0 ; framen < totalFrames ; framen + + ) {
2006-04-01 02:16:52 +02:00
// Canceled?
if ( canceled ) break ;
2006-03-31 11:44:48 +02:00
// Is this frame supposed to be rendered?
2006-03-31 08:12:10 +02:00
if ( frames [ framen ] = = 0 ) continue ;
2006-03-31 14:01:40 +02:00
// Update progress
2006-04-01 01:12:03 +02:00
progress - > SetProgress ( drawn , toDraw ) ;
2006-04-02 05:34:56 +02:00
progress - > SetText ( wxString : : Format ( _T ( " Writing PRS file. Line: %i/%i (%.2f%%) " ) , framen , totalFrames , MIN ( float ( drawn ) * 100 / toDraw , 100.0 ) ) ) ;
if ( frames [ framen ] = = 2 ) drawn + + ;
2006-03-31 14:01:40 +02:00
2006-03-31 11:44:48 +02:00
// Read the frame image
2006-03-31 08:12:10 +02:00
PVideoFrame frame1 = clip1 - > GetFrame ( framen , env1 ) ;
PVideoFrame frame2 = clip2 - > GetFrame ( framen , env2 ) ;
2006-03-31 11:44:48 +02:00
// Prepare to get wxImage
2006-03-31 08:12:10 +02:00
int x = 0 , y = 0 ;
2006-03-31 08:38:20 +02:00
int maxalpha = 0 ;
2006-03-31 11:44:48 +02:00
// Get wxImage
wxImage bmp = CalculateAlpha ( frame1 - > GetReadPtr ( ) , frame2 - > GetReadPtr ( ) , frame1 - > GetRowSize ( ) , frame1 - > GetHeight ( ) , frame1 - > GetPitch ( ) , & x , & y , & maxalpha ) ;
if ( ! bmp . Ok ( ) ) continue ;
2006-04-01 13:45:38 +02:00
lastFrameDrawn = framen ;
2006-03-31 11:44:48 +02:00
2006-04-01 01:12:03 +02:00
// Get the list of rectangles
std : : vector < wxRect > rects ;
GetSubPictureRectangles ( bmp , rects ) ;
// Add each sub-image to file
int nrects = rects . size ( ) ;
2006-04-01 16:46:13 +02:00
int useFrameN ;
2006-04-01 01:12:03 +02:00
for ( int i = 0 ; i < nrects ; i + + ) {
// Pick either full image or subimage, as appropriate
wxImage curImage ;
if ( rects [ i ] . x = = 0 & & rects [ i ] . y = = 0 & & rects [ i ] . width = = bmp . GetWidth ( ) & & rects [ i ] . height = = bmp . GetHeight ( ) ) curImage = bmp ;
else curImage = SubImageWithAlpha ( bmp , rects [ i ] ) ;
2006-04-02 20:11:06 +02:00
if ( ! curImage . Ok ( ) ) continue ;
// Optimize image
2006-04-03 15:45:47 +02:00
OptimizeImage ( curImage ) ;
2006-04-01 01:12:03 +02:00
// Insert the image
2006-04-01 16:46:13 +02:00
useFrameN = framen ;
InsertFrame ( file , useFrameN , frames , curImage , x + rects [ i ] . x , y + rects [ i ] . y , maxalpha ) ;
2006-04-01 01:12:03 +02:00
}
2006-04-01 16:46:13 +02:00
framen = useFrameN ;
2006-03-31 08:12:10 +02:00
}
2006-03-31 14:01:40 +02:00
// Destroy progress bar
2006-04-01 02:16:52 +02:00
if ( ! canceled ) progress - > Destroy ( ) ;
else return ;
2006-03-31 14:01:40 +02:00
2006-03-31 08:12:10 +02:00
// Save file
2009-07-14 23:28:49 +02:00
file . Save ( ( const char * ) filename . mb_str ( csConvLocal ) ) ;
2006-04-02 17:32:04 +02:00
wxString filename2 = filename + _T ( " .prsa " ) ;
2009-07-14 23:28:49 +02:00
file . SaveText ( ( const char * ) filename2 . mb_str ( csConvLocal ) ) ;
2006-04-04 19:30:37 +02:00
// Delete temp file
wxRemoveFile ( tempFile ) ;
2006-03-31 08:12:10 +02:00
# endif
}
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Insert frame into file
/// @param file
/// @param framen
/// @param frames
/// @param bmp
/// @param x
/// @param y
/// @param maxalpha
///
2006-03-31 11:44:48 +02:00
void PRSSubtitleFormat : : InsertFrame ( PRSFile & file , int & framen , std : : vector < int > & frames , wxImage & bmp , int x , int y , int maxalpha ) {
// Generic data holder
size_t datasize = 0 ;
char * rawData = NULL ;
std : : vector < char > data ;
2006-03-31 14:01:40 +02:00
//bmp.SaveFile(wxString::Format(_T("test_%i.png"),id),wxBITMAP_TYPE_PNG);
2006-03-31 11:44:48 +02:00
2006-03-31 14:01:40 +02:00
// pngout/optipng optimize
if ( optimizer ) {
// Get temporary filename
2006-03-31 11:44:48 +02:00
wxString tempFile = wxFileName : : CreateTempFileName ( _T ( " aegiprs " ) ) ;
wxString tempOut = tempFile + _T ( " out.png " ) ;
2006-03-31 14:01:40 +02:00
// Prepare arrays to capture output
2006-03-31 11:52:31 +02:00
wxArrayString output ;
wxArrayString errors ;
2006-03-31 14:01:40 +02:00
// Generate the temporary PNG and run the optimizer program on it
if ( optimizer = = 1 ) {
bmp . SaveFile ( tempOut , wxBITMAP_TYPE_PNG ) ;
wxExecute ( AegisubApp : : folderName + _T ( " optipng.exe -zc9 -zm8 -zs0-3 -f0 " ) + tempOut , output , errors ) ;
}
if ( optimizer = = 2 ) {
bmp . SaveFile ( tempFile , wxBITMAP_TYPE_PNG ) ;
wxExecute ( AegisubApp : : folderName + _T ( " pngout.exe " ) + tempFile + _T ( " " ) + tempOut + _T ( " /f0 /y /q " ) , output , errors ) ;
}
2006-03-31 11:44:48 +02:00
// Read file back
2009-07-14 23:28:49 +02:00
FILE * fp = fopen ( tempOut . mb_str ( csConvLocal ) , " rb " ) ;
2006-03-31 11:44:48 +02:00
fseek ( fp , 0 , SEEK_END ) ;
datasize = ftell ( fp ) ;
data . resize ( datasize ) ;
rawData = & data [ 0 ] ;
rewind ( fp ) ;
fread ( rawData , 1 , datasize , fp ) ;
fclose ( fp ) ;
// Destroy temporary files
wxRemoveFile ( tempFile ) ;
wxRemoveFile ( tempOut ) ;
}
// No optimization (much faster)
else {
// Convert wxImage to PNG directly
wxMemoryOutputStream stream ;
bmp . SaveFile ( stream , wxBITMAP_TYPE_PNG ) ;
datasize = stream . GetSize ( ) ;
data . resize ( datasize ) ;
rawData = & data [ 0 ] ;
stream . CopyTo ( rawData , datasize ) ;
}
// Find start and end times
int startf = framen ;
int totalFrames = frames . size ( ) ;
while ( + + framen < totalFrames & & frames [ framen ] = = 1 ) ;
2006-04-05 21:11:07 +02:00
framen - - ; // need -1, otherwise all sub-images are extended by one frame
2006-04-02 17:32:04 +02:00
int endf = framen ;
2006-03-31 11:44:48 +02:00
int start = VFR_Output . GetTimeAtFrame ( startf , true ) ;
int end = VFR_Output . GetTimeAtFrame ( endf , false ) ;
// Create PRSImage
PRSImage * img = new PRSImage ;
img - > id = id ;
img - > imageType = PNG_IMG ;
img - > w = bmp . GetWidth ( ) ;
img - > h = bmp . GetHeight ( ) ;
img - > maxAlpha = maxalpha ;
img - > dataLen = datasize ;
img - > data = new char [ img - > dataLen ] ;
memcpy ( img - > data , rawData , img - > dataLen ) ;
// Hash the PRSImage data
md5_state_t state ;
md5_init ( & state ) ;
md5_append ( & state , ( md5_byte_t * ) img - > data , img - > dataLen ) ;
md5_finish ( & state , ( md5_byte_t * ) img - > md5 ) ;
// Check for duplicates
PRSImage * dupe = file . FindDuplicateImage ( img ) ;
int useID = id ;
// Dupe found, use that instead
if ( dupe ) {
useID = dupe - > id ;
delete img ;
img = NULL ;
}
// Frame is all OK, add it to file
else {
file . AddEntry ( img ) ;
id + + ;
}
// Set blend data
unsigned char alpha = 255 ;
unsigned char blend = 0 ;
// Check if it's just an extension of last display
2007-01-24 04:54:32 +01:00
if ( lastDisplay & & lastDisplay - > id = = ( unsigned ) useID & & ( signed ) lastDisplay - > endFrame = = startf - 1 & &
2006-03-31 11:44:48 +02:00
lastDisplay - > x = = x & & lastDisplay - > y = = y & & lastDisplay - > alpha = = alpha & & lastDisplay - > blend = = blend )
{
lastDisplay - > end = start ;
lastDisplay - > endFrame = startf ;
}
// It isn't; needs a new display command
else {
// Create PRSDisplay
PRSDisplay * display = new PRSDisplay ;
display - > start = start ;
display - > end = end ;
display - > startFrame = startf ;
display - > endFrame = endf ;
display - > id = useID ;
display - > x = x ;
display - > y = y ;
display - > alpha = alpha ;
display - > blend = blend ;
lastDisplay = display ;
// Insert into list
file . AddEntry ( display ) ;
}
}
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Get rectangles of useful glyphs
/// @param image
/// @param rects
///
2006-04-02 20:11:06 +02:00
void PRSSubtitleFormat : : GetSubPictureRectangles ( wxImage & image , std : : vector < wxRect > & rects ) {
2006-04-01 02:16:52 +02:00
// Boundaries
int w = image . GetWidth ( ) ;
int h = image . GetHeight ( ) ;
int startx = 0 ;
int starty = 0 ;
int endx = w - 1 ;
int endy = h - 1 ;
// Variables
bool hasSubImage = false ;
bool isBlankRow = true ;
const unsigned char * data = image . GetAlpha ( ) ;
const unsigned char * src = data ;
unsigned char a ;
// For each row
for ( int y = 0 ; y < = h ; y + + ) {
if ( y < h ) {
// Reset row data
isBlankRow = true ;
if ( ! hasSubImage ) {
startx = w ;
endx = - 1 ;
}
// Check row
for ( int x = 0 ; x < w ; x + + ) {
a = * src + + ;
if ( a ) {
isBlankRow = false ;
if ( x < startx ) startx = x ;
if ( x > endx ) endx = x ;
}
}
// Set sub image status
if ( ! isBlankRow & & ! hasSubImage ) {
starty = y ;
hasSubImage = true ;
}
}
// If the processed row is totally blank and there is a subimage, separate them
if ( ( isBlankRow & & hasSubImage ) | | y = = h ) {
// Insert rectangle
endy = y - 1 ;
rects . push_back ( wxRect ( startx , starty , endx - startx + 1 , endy - starty + 1 ) ) ;
hasSubImage = false ;
}
}
2006-04-01 01:12:03 +02:00
}
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Get frame ranges
/// @return
///
2006-03-31 08:12:10 +02:00
std : : vector < int > PRSSubtitleFormat : : GetFrameRanges ( ) {
2006-03-30 00:39:41 +02:00
// Loop through subtitles in file
2006-03-31 06:13:38 +02:00
std : : vector < int > frames ;
2006-04-04 19:30:37 +02:00
for ( entryIter cur = Line - > begin ( ) ; cur ! = Line - > end ( ) ; cur + + ) {
2006-03-31 06:13:38 +02:00
AssDialogue * diag = AssEntry : : GetAsDialogue ( * cur ) ;
2006-03-30 00:39:41 +02:00
// Dialogue found
2006-04-02 05:34:56 +02:00
if ( diag & & ! diag - > Comment ) {
2006-03-31 04:06:50 +02:00
// Parse tags
diag - > ParseASSTags ( ) ;
2006-03-31 06:13:38 +02:00
// Check if there is any animation tag, to flag the line as animated, forcing storage of every frame
// THIS NEEDS OPTIMIZATION!
// Currently it will redraw whole line, even if only some time of it is animated.
// This is later hopefully removed by duplicate checker, but it would be faster if done here
2006-03-31 04:06:50 +02:00
bool hasAnimation = false ;
int blocks = diag - > Blocks . size ( ) ;
AssDialogueBlockOverride * block ;
for ( int i = 0 ; i < blocks ; i + + ) {
block = AssDialogueBlock : : GetAsOverride ( diag - > Blocks [ i ] ) ;
if ( block ) {
2006-03-31 06:13:38 +02:00
// Found an override block, see if it contains any animation tags
int tags = block - > Tags . size ( ) ;
for ( int j = 0 ; j < tags ; j + + ) {
wxString tagName = block - > Tags [ j ] - > Name ;
if ( tagName = = _T ( " \\ t " ) | | tagName = = _T ( " \\ move " ) | | tagName = = _T ( " \\ k " ) | | tagName = = _T ( " \\ K " ) | |
tagName = = _T ( " \\ kf " ) | | tagName = = _T ( " \\ ko " ) | | tagName = = _T ( " \\ fad " ) | | tagName = = _T ( " \\ fade " ) ) {
hasAnimation = true ;
}
}
2006-03-31 04:06:50 +02:00
}
}
2006-03-31 06:13:38 +02:00
// Calculate start and end times
size_t start = VFR_Output . GetFrameAtTime ( diag - > Start . GetMS ( ) , true ) ;
size_t end = VFR_Output . GetFrameAtTime ( diag - > End . GetMS ( ) , false ) ;
// Ensure that the vector is long enough
2006-04-02 05:34:56 +02:00
// Yes, +1, this is an optimization for something below
if ( frames . size ( ) < = end + 1 ) frames . resize ( end + 2 ) ;
2006-03-31 06:13:38 +02:00
// Fill data
// 2 = Store this frame
// 1 = Repeat last frame
// 0 = Don't store this frame
bool lastOn = false ;
for ( size_t i = start ; i < = end ; i + + ) {
// Put a keyframe at the very start, or everywhere if it's animated
if ( i = = start | | hasAnimation ) frames [ i ] = 2 ;
else {
// Already set to 1 or 2, meaning that another subtitle is here already
if ( frames [ i ] ! = 0 ) lastOn = true ;
// Set to 0, so nothing is here
else {
// Just came out of a subtitle end, put a keyframe here
if ( lastOn ) {
frames [ i ] = 2 ;
lastOn = false ;
}
// Otherwise, just repeat
frames [ i ] = 1 ;
}
}
2006-04-01 17:21:13 +02:00
// Ends right before another "1" block, so make this a "2"
if ( i = = end & & frames [ i + 1 ] = = 1 ) frames [ i ] = 2 ;
2006-03-31 04:06:50 +02:00
}
// Clean up
diag - > ClearBlocks ( ) ;
2006-03-30 00:39:41 +02:00
}
}
2006-03-31 08:12:10 +02:00
// Done
return frames ;
2006-03-30 00:39:41 +02:00
}
2006-03-30 01:53:42 +02:00
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// DOCME
2006-04-02 20:11:06 +02:00
# define IN_ERROR_MARGIN(col1,col2,error) ((col1 > col2 ? ((int)(col1-col2)) : ((int)(col2-col1))) <= (error))
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief e.g. if you have a pixel with 25% opacity, then a difference of as much as 3 in the color channels won't have a visual impact This "flood fills" the image based on alpha, to make it easier to compress, without affecting visual quality Now, since I don't expect anyone to be able to decypher this...
/// @param image
///
2006-04-02 20:11:06 +02:00
void PRSSubtitleFormat : : OptimizeImage ( wxImage & image ) {
// Get the raw data
unsigned char * data = ( unsigned char * ) image . GetData ( ) ;
unsigned char * alpha = ( unsigned char * ) image . GetAlpha ( ) ;
int w = image . GetWidth ( ) ;
int h = image . GetHeight ( ) ;
int len = w * h ;
// Create mask for status and fill with zeroes
char * status = new char [ len ] ;
for ( int i = 0 ; i < len ; i + + ) status [ i ] = 0 ;
// Find highest alpha
unsigned char highAlpha = 0 ;
for ( int i = 0 ; i < len ; i + + ) {
if ( status [ i ] ! = 2 & & alpha [ i ] > highAlpha ) highAlpha = alpha [ i ] ;
}
// Fill mask of "correct" pixels with 2 on highAlpha pixels
for ( int i = 0 ; i < len ; i + + ) {
if ( alpha [ i ] = = highAlpha ) status [ i ] = 2 ;
}
// Alpha finding loop
bool outerLoop = true ;
while ( outerLoop ) {
// Loop through
int totalModified = 0 ;
bool doRepeat = true ;
while ( doRepeat ) {
int modified = 0 ;
unsigned char * cur = data ;
unsigned char c1 , c2 , c3 ;
unsigned char d1 , d2 , d3 ;
int error ;
for ( int i = 0 ; i < len ; i + + ) {
// Get colors
c1 = * ( cur + + ) ;
c2 = * ( cur + + ) ;
c3 = * ( cur + + ) ;
// Check status
if ( status [ i ] ! = 0 ) continue ;
// Get error
2006-04-03 15:45:47 +02:00
int a = alpha [ i ] ;
if ( a = = 0 ) error = 255 ;
else error = 1024 / a ;
2006-04-02 20:11:06 +02:00
// Right pixel
if ( i ! = len - 1 & & status [ i + 1 ] = = 2 ) {
// Get colors
d1 = * ( cur ) ;
d2 = * ( cur + 1 ) ;
d3 = * ( cur + 2 ) ;
// Compare error
if ( IN_ERROR_MARGIN ( d1 , c1 , error ) & & IN_ERROR_MARGIN ( d2 , c2 , error ) & & IN_ERROR_MARGIN ( d3 , c3 , error ) ) {
* ( cur - 3 ) = d1 ;
* ( cur - 2 ) = d2 ;
* ( cur - 1 ) = d3 ;
status [ i ] = 2 ;
modified + + ;
continue ;
}
}
// Left pixel
if ( i ! = 0 & & status [ i - 1 ] = = 2 ) {
// Get colors
d1 = * ( cur - 6 ) ;
d2 = * ( cur - 5 ) ;
d3 = * ( cur - 4 ) ;
// Compare error
if ( IN_ERROR_MARGIN ( d1 , c1 , error ) & & IN_ERROR_MARGIN ( d2 , c2 , error ) & & IN_ERROR_MARGIN ( d3 , c3 , error ) ) {
* ( cur - 3 ) = d1 ;
* ( cur - 2 ) = d2 ;
* ( cur - 1 ) = d3 ;
status [ i ] = 2 ;
modified + + ;
continue ;
}
}
// Top pixel
if ( i > = w & & status [ i - w ] = = 2 ) {
// Get colors
d1 = * ( cur - w * 3 - 3 ) ;
d2 = * ( cur - w * 3 - 2 ) ;
d3 = * ( cur - w * 3 - 1 ) ;
// Compare error
if ( IN_ERROR_MARGIN ( d1 , c1 , error ) & & IN_ERROR_MARGIN ( d2 , c2 , error ) & & IN_ERROR_MARGIN ( d3 , c3 , error ) ) {
* ( cur - 3 ) = d1 ;
* ( cur - 2 ) = d2 ;
* ( cur - 1 ) = d3 ;
status [ i ] = 2 ;
modified + + ;
continue ;
}
}
// Bottom pixel
if ( i < len - w & & status [ i + w ] = = 2 ) {
// Get colors
d1 = * ( cur + w * 3 - 3 ) ;
d2 = * ( cur + w * 3 - 2 ) ;
d3 = * ( cur + w * 3 - 1 ) ;
// Compare error
if ( IN_ERROR_MARGIN ( d1 , c1 , error ) & & IN_ERROR_MARGIN ( d2 , c2 , error ) & & IN_ERROR_MARGIN ( d3 , c3 , error ) ) {
* ( cur - 3 ) = d1 ;
* ( cur - 2 ) = d2 ;
* ( cur - 1 ) = d3 ;
status [ i ] = 2 ;
modified + + ;
continue ;
}
}
}
// End repetion
totalModified + = modified ;
if ( ! modified ) doRepeat = false ;
}
// End outer loop
if ( ! totalModified ) outerLoop = false ;
// Copy values 1 to 2
int changes = 0 ;
for ( int i = 0 ; i < len ; i + + ) {
if ( status [ i ] = = 1 ) {
status [ i ] = 2 ;
changes + + ;
}
}
if ( ! changes ) outerLoop = false ;
}
// Just for tests, fill alpha with 0xFF
//for (int i=0;i<len;i++) alpha[i] = 0xFF;
// Delete mask
delete [ ] status ;
}
2006-03-30 05:32:45 +02:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// TODO!! MOVE THE TWO FUNCTIONS BELOW INTO A CLASS OF THEIR OWN, THEY MIGHT FIND USE ELSEWHERE. //
// Obvious choice would be the subtitles_rasterizer.h derivation for vsfilter, when that exists. //
///////////////////////////////////////////////////////////////////////////////////////////////////
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Frame 2 should have the same image on a WHITE background Frame 1 should have the image on a BLACK background ------------------------------------------ Generates a 32-bit wxImage from two frames
/// @param frame1
/// @param frame2
/// @param w
/// @param h
/// @param pitch
/// @param dstx
/// @param dsty
/// @param maxalpha
/// @return
///
2006-03-31 08:38:20 +02:00
wxImage PRSSubtitleFormat : : CalculateAlpha ( const unsigned char * frame1 , const unsigned char * frame2 , int w , int h , int pitch , int * dstx , int * dsty , int * maxalpha ) {
2006-03-30 01:53:42 +02:00
// Allocate image data
2006-03-30 02:25:15 +02:00
unsigned char * data = ( unsigned char * ) malloc ( sizeof ( unsigned char ) * w * h * 3 ) ;
unsigned char * alpha = ( unsigned char * ) malloc ( sizeof ( unsigned char ) * w * h ) ;
2006-03-30 01:53:42 +02:00
// Pointers
const unsigned char * src1 = frame1 ;
const unsigned char * src2 = frame2 ;
2006-03-30 02:25:15 +02:00
unsigned char * dst = data + ( ( h - 1 ) * w * 3 / 4 ) ;
unsigned char * dsta = alpha + ( ( h - 1 ) * w / 4 ) ;
2006-03-30 05:32:45 +02:00
// Boundaries
int minx = w ;
int miny = h ;
int maxx = 0 ;
int maxy = 0 ;
2006-03-30 01:53:42 +02:00
// Process
2006-03-31 08:38:20 +02:00
int maxA = 0 ;
2006-03-30 12:00:56 +02:00
unsigned char r1 , g1 , b1 , r2 , g2 , b2 ;
unsigned char r , g , b , a ;
2006-03-30 05:32:45 +02:00
for ( int y = h ; - - y > = 0 ; ) {
2006-03-30 01:53:42 +02:00
for ( int x = 0 ; x < w ; x + = 4 ) {
// Read pixels
b1 = * ( src1 + + ) ;
b2 = * ( src2 + + ) ;
g1 = * ( src1 + + ) ;
g2 = * ( src2 + + ) ;
r1 = * ( src1 + + ) ;
r2 = * ( src2 + + ) ;
src1 + + ;
src2 + + ;
// Calculate new values
a = 255 + r1 - r2 ;
if ( a = = 0 ) {
r = 0 ;
g = 0 ;
b = 0 ;
}
else {
2006-03-30 05:32:45 +02:00
// Update range
if ( x < minx ) minx = x ;
else if ( x > maxx ) maxx = x ;
if ( y < miny ) miny = y ;
else if ( y > maxy ) maxy = y ;
// Calculate colour components
2006-04-03 15:45:47 +02:00
//int mod = MAX(0,128/a-1);
//r = MAX(0,r1-mod)*255 / a;
//g = MAX(0,g1-mod)*255 / a;
//b = MAX(0,b1-mod)*255 / a;
r = r1 * 255 / a ;
g = g1 * 255 / a ;
b = b1 * 255 / a ;
2006-03-30 01:53:42 +02:00
}
// Write to destination
* ( dst + + ) = r ;
2006-03-30 02:25:15 +02:00
* ( dst + + ) = g ;
* ( dst + + ) = b ;
* ( dsta + + ) = a ;
2006-03-31 08:38:20 +02:00
// Store maximum alpha
if ( a > maxA ) maxA = a ;
2006-03-30 01:53:42 +02:00
}
// Roll back dst
2006-03-30 02:25:15 +02:00
dst - = w * 3 / 2 ;
dsta - = w / 2 ;
2006-03-30 01:53:42 +02:00
}
2006-03-31 08:38:20 +02:00
// Store maximum alpha
if ( maxalpha ) * maxalpha = maxA ;
2006-03-30 12:00:56 +02:00
// Calculate sizes
minx / = 4 ;
maxx / = 4 ;
if ( dstx ) * dstx = minx ;
if ( dsty ) * dsty = miny ;
int width = maxx - minx + 1 ;
int height = maxy - miny + 1 ;
2006-03-31 14:01:40 +02:00
// 100% transparent image; clean up and return an empty one
if ( width < = 0 | | height < = 0 ) {
delete [ ] data ;
delete [ ] alpha ;
return wxImage ( ) ;
}
2006-03-30 12:00:56 +02:00
2006-03-30 05:32:45 +02:00
// Create the actual image
2006-03-30 02:25:15 +02:00
wxImage img ( w / 4 , h , data , false ) ;
img . SetAlpha ( alpha , false ) ;
2006-03-30 05:32:45 +02:00
// Return subimage
2006-03-30 12:00:56 +02:00
wxImage subimg = SubImageWithAlpha ( img , wxRect ( minx , miny , width , height ) ) ;
2006-03-30 05:32:45 +02:00
return subimg ;
}
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @brief Modified from wx's source Creates a sub image preserving alpha channel
/// @param source
/// @param rect
///
2006-04-02 20:11:06 +02:00
wxImage PRSSubtitleFormat : : SubImageWithAlpha ( wxImage & source , const wxRect & rect ) {
2006-03-30 05:32:45 +02:00
wxImage image ;
wxCHECK_MSG ( source . Ok ( ) , image , wxT ( " invalid image " ) ) ;
wxCHECK_MSG ( ( rect . GetLeft ( ) > = 0 ) & & ( rect . GetTop ( ) > = 0 ) & & ( rect . GetRight ( ) < = source . GetWidth ( ) ) & & ( rect . GetBottom ( ) < = source . GetHeight ( ) ) , image , wxT ( " invalid subimage size " ) ) ;
int subwidth = rect . GetWidth ( ) ;
const int subheight = rect . GetHeight ( ) ;
image . Create ( subwidth , subheight , false ) ;
image . SetAlpha ( ) ;
unsigned char * subdata = image . GetData ( ) ;
unsigned char * data = source . GetData ( ) ;
unsigned char * subalpha = image . GetAlpha ( ) ;
unsigned char * alpha = source . GetAlpha ( ) ;
wxCHECK_MSG ( subdata , image , wxT ( " unable to create image " ) ) ;
const int subleft = 3 * rect . GetLeft ( ) ;
const int width = 3 * source . GetWidth ( ) ;
const int afullwidth = source . GetWidth ( ) ;
int awidth = subwidth ;
subwidth * = 3 ;
data + = rect . GetTop ( ) * width + subleft ;
alpha + = rect . GetTop ( ) * afullwidth + rect . GetLeft ( ) ;
for ( long j = 0 ; j < subheight ; + + j ) {
memcpy ( subdata , data , subwidth ) ;
memcpy ( subalpha , alpha , awidth ) ;
subdata + = subwidth ;
subalpha + = awidth ;
data + = width ;
alpha + = afullwidth ;
}
return image ;
2006-03-30 01:53:42 +02:00
}
2006-04-08 21:34:57 +02:00
2006-12-23 04:55:00 +01:00
# endif
2009-07-29 07:43:02 +02:00
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00