For another project, please consult its home page to locate the current issue tracker.
View Issue Details [ Jump to Notes ] | [ Print ] | ||||||||
ID | Project | Category | View Status | Date Submitted | Last Update | ||||
0009281 | ParaView | Feature | public | 2009-07-17 09:27 | 2009-12-14 16:24 | ||||
Reporter | Michael Wild | ||||||||
Assigned To | |||||||||
Priority | normal | Severity | feature | Reproducibility | always | ||||
Status | closed | Resolution | open | ||||||
Platform | OS | OS Version | |||||||
Product Version | Development | ||||||||
Target Version | Fixed in Version | ||||||||
Summary | 0009281: Add Ogg/Theora animation export to ParaView | ||||||||
Description | The vtkFFMPEGWriter has lately (http://www.vtk.org/Bug/view.php?id=8925 [^]) been changed to write MJPEG movies. This has the advantage of resolving licensing issues, as MPEG4 is patented. Further, FFMPEG can't be included in the ParaView source code because it contains GPL'ed code. This is why it would be beneficial for ParaView to offer export to Ogg/Theora. Both, the Ogg container format and the Theora video codec are free of any known patent and the reference implementations are BSD-style licensed. This format is now also integrated into the latest versions of the Firefox and Opera web browsers, and QuickTime and DirectShow components are available. | ||||||||
Additional Information | Attached are three patches implementing Ogg/Theora animation export in ParaView-CVS: 0001-ENH-Added-vtkoggtheora-library-Ogg-Theora-to-VTK.patch: Adds libogg and libtheora as a single vtkoggtheora library to VTK. libtheora has been stripped of some unused sources (decoder). 0002-ENH-Added-vtkOggTheoraWriter-to-VTK.patch: Adds the vtkOggTheoraWriter class to VTK (based upon vtkFFMPEGWriter). 0003-ENH-Integrated-vtkOggTheoraWriter-into-ParaView.patch: Integrates the vtkOggTheoraWriter into ParaView. | ||||||||
Tags | No tags attached. | ||||||||
Project | |||||||||
Topic Name | |||||||||
Type | |||||||||
Attached Files | ![]() ![]() From 3bde764ee918b492596da32d1ad5e4b1bcb78bc3 Mon Sep 17 00:00:00 2001 From: Michael Wild <themiwi@users.sourceforge.net> Date: Wed, 15 Jul 2009 11:25:50 +0200 Subject: [PATCH 2/3] ENH: Added vtkOggTheoraWriter to VTK Signed-off-by: Michael Wild <themiwi@users.sourceforge.net> --- VTK/IO/CMakeLists.txt | 1 + VTK/IO/Testing/Cxx/CMakeLists.txt | 2 + VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx | 104 +++++ VTK/IO/vtkOggTheoraWriter.cxx | 566 ++++++++++++++++++++++++++++ VTK/IO/vtkOggTheoraWriter.h | 77 ++++ 5 files changed, 750 insertions(+), 0 deletions(-) create mode 100644 VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx create mode 100644 VTK/IO/vtkOggTheoraWriter.cxx create mode 100644 VTK/IO/vtkOggTheoraWriter.h diff --git a/VTK/IO/CMakeLists.txt b/VTK/IO/CMakeLists.txt index 84d78ac..4950549 100644 --- a/VTK/IO/CMakeLists.txt +++ b/VTK/IO/CMakeLists.txt @@ -80,6 +80,7 @@ vtkMoleculeReaderBase.cxx vtkNetCDFCOARDSReader.cxx vtkNetCDFReader.cxx vtkOBJReader.cxx +vtkOggTheoraWriter.cxx vtkOutputStream.cxx vtkPDBReader.cxx vtkPLOT3DReader.cxx diff --git a/VTK/IO/Testing/Cxx/CMakeLists.txt b/VTK/IO/Testing/Cxx/CMakeLists.txt index 30ab30c..6f99852 100644 --- a/VTK/IO/Testing/Cxx/CMakeLists.txt +++ b/VTK/IO/Testing/Cxx/CMakeLists.txt @@ -58,6 +58,7 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CxxTests.cxx TestXML.cxx TestCompress.cxx TestSQLDatabaseSchema.cxx + TestOggTheoraWriter.cxx ${ConditionalTests} EXTRA_INCLUDE vtkTestDriver.h ) @@ -110,6 +111,7 @@ IF (VTK_LARGE_DATA_ROOT) ENDIF (VTK_USE_DISPLAY AND VTK_USE_RENDERING) ENDIF (VTK_LARGE_DATA_ROOT) +ADD_TEST(TestOggTheoraWriter ${CXX_TEST_PATH}/${KIT}CxxTests TestOggTheoraWriter) ADD_TEST(TestSQLDatabaseSchema ${CXX_TEST_PATH}/${KIT}CxxTests TestSQLDatabaseSchema) IF(WIN32 AND VTK_USE_VIDEO_FOR_WINDOWS) diff --git a/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx b/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx new file mode 100644 index 0000000..684a31c --- /dev/null +++ b/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx @@ -0,0 +1,104 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: TestOggTheoraWriter.cxx,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +// .NAME TestOggTheoraWriter - Tests vtkOggTheoraWriter. +// .SECTION Description +// Creates a scene and uses OggTheoraWriter to generate a movie file. Test passes +// if the file exists and has non zero length. + + +#include "vtkImageCast.h" +#include "vtkImageData.h" +#include "vtkImageMandelbrotSource.h" +#include "vtkImageMapToColors.h" +#include "vtkLookupTable.h" +#include "vtkOggTheoraWriter.h" +#include "vtksys/SystemTools.hxx" + +int TestOggTheoraWriter(int vtkNotUsed(argc), char* vtkNotUsed(argv)[]) +{ + int err = 0; + int cc = 0; + int exists = 0; + unsigned long length = 0; + vtkImageMandelbrotSource* Fractal0 = vtkImageMandelbrotSource::New(); + Fractal0->SetWholeExtent( 0, 247, 0, 247, 0, 0 ); + Fractal0->SetProjectionAxes( 0, 1, 2 ); + Fractal0->SetOriginCX( -1.75, -1.25, 0, 0 ); + Fractal0->SetSizeCX( 2.5, 2.5, 2, 1.5 ); + Fractal0->SetMaximumNumberOfIterations( 100); + + vtkImageCast* cast = vtkImageCast::New(); + cast->SetInputConnection(Fractal0->GetOutputPort()); + cast->SetOutputScalarTypeToUnsignedChar(); + + vtkLookupTable* table = vtkLookupTable::New(); + table->SetTableRange(0, 100); + table->SetNumberOfColors(100); + table->Build(); + table->SetTableValue(99, 0, 0, 0); + + vtkImageMapToColors* colorize = vtkImageMapToColors::New(); + colorize->SetOutputFormatToRGB(); + colorize->SetLookupTable(table); + colorize->SetInputConnection(cast->GetOutputPort()); + + vtkOggTheoraWriter *w = vtkOggTheoraWriter::New(); + w->SetInputConnection(colorize->GetOutputPort()); + w->SetFileName("TestOggTheoraWriter.ogv"); + cout << "Writing file TestOggTheoraWriter.ogv..." << endl; + w->Start(); + for ( cc = 2; cc < 99; cc ++ ) + { + cout << "."; + Fractal0->SetMaximumNumberOfIterations(cc); + table->SetTableRange(0, cc); + table->SetNumberOfColors(cc); + table->ForceBuild(); + table->SetTableValue(cc-1, 0, 0, 0); + w->Write(); + } + w->End(); + cout << endl; + cout << "Done writing file TestOggTheoraWriter.ogv..." << endl; + w->Delete(); + + exists = (int) vtksys::SystemTools::FileExists("TestOggTheoraWriter.ogv"); + length = vtksys::SystemTools::FileLength("TestOggTheoraWriter.ogv"); + cout << "TestOggTheoraWriter.ogv file exists: " << exists << endl; + cout << "TestOggTheoraWriter.ogv file length: " << length << endl; + if (!exists) + { + err = 1; + cerr << "ERROR: 1 - Test failing because TestOggTheoraWriter.ogv file doesn't exist..." << endl; + } + else + { + vtksys::SystemTools::RemoveFile("TestOggTheoraWriter.ogv"); + } + if (0==length) + { + err = 2; + cerr << "ERROR: 2 - Test failing because TestOggTheoraWriter.ogv file has zero length..." << endl; + } + + colorize->Delete(); + table->Delete(); + cast->Delete(); + Fractal0->Delete(); + + // err == 0 means test passes... + // + return err; +} diff --git a/VTK/IO/vtkOggTheoraWriter.cxx b/VTK/IO/vtkOggTheoraWriter.cxx new file mode 100644 index 0000000..71295ac --- /dev/null +++ b/VTK/IO/vtkOggTheoraWriter.cxx @@ -0,0 +1,566 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkOggTheoraWriter.cxx,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +#include "vtkOggTheoraWriter.h" + +#include "vtkImageData.h" +#include "vtkObjectFactory.h" +#include "vtkErrorCode.h" + +#include "vtk_oggtheora.h" + +//--------------------------------------------------------------------------- +class vtkOggTheoraWriterInternal +{ +public: + vtkOggTheoraWriterInternal(vtkOggTheoraWriter *creator); + ~vtkOggTheoraWriterInternal(); + + int Start(); + int Write(vtkImageData *id); + void End(); + + int Dim[2]; + int FrameRate; + +private: + + // Helper function to convert an RGB image into the Y'CbCr color space and + // into the data structure required by theora (i.e. 4:2:0 subsampling will be + // used, which is the only supported one). Refer to + // http://www.theora.org/doc/Theora.pdf sections 4.3 and 4.3.2. Actually, + // the equations are inverted. However, I'm not sure whether VTK uses + // gamma-corrected RGB or not. I assume they are not, which is what we need + // here. Assume that the width and height are even numbers. + void RGB2YCbCr(vtkImageData *id, th_ycbcr_buffer ycbcr); + // Write the ogg/theora header information + int WriteHeader(); + // Encode a single frame + int EncodeFrame(th_ycbcr_buffer ycbcr, int lastFrame); + + vtkOggTheoraWriter *Writer; + + size_t Off[2]; // offsets of the picture within the frame + th_enc_ctx *thEncContext; // the theora context (has to be freed) + th_ycbcr_buffer thImage; // the Y'CbCr image buffer + ogg_stream_state oggState; // the ogg stream state (has to be cleared) + FILE* outFile; // the output file stream + bool haveImageData; // indicater whether a frame has to be encoded + // (for the leap-frogging) + + int openedFile; + int closedFile; +}; + +//--------------------------------------------------------------------------- +vtkOggTheoraWriterInternal::vtkOggTheoraWriterInternal(vtkOggTheoraWriter *creator) +{ + this->Writer = creator; + this->Dim[0] = 0; + this->Dim[1] = 0; + + this->Off[0] = 0; + this->Off[1] = 0; + this->thEncContext = NULL; + this->outFile = NULL; + this->thImage[0].data = NULL; + this->thImage[1].data = NULL; + this->thImage[2].data = NULL; + + this->openedFile = 0; + this->closedFile = 1; + this->haveImageData = false; + + this->FrameRate = 25; +} + +//--------------------------------------------------------------------------- +vtkOggTheoraWriterInternal::~vtkOggTheoraWriterInternal() +{ + if (!this->closedFile) + { + this->End(); + } +} + +//--------------------------------------------------------------------------- +int vtkOggTheoraWriterInternal::Start() +{ + this->closedFile = 0; + + // ogg information + srand(time(NULL)); + if (ogg_stream_init(&this->oggState,rand())!=0) + { + vtkGenericWarningMacro("Could not initialize ogg stream state."); + return 0; + } + + // fill in theora information + th_info thInfo; + th_info_init(&thInfo); + // frame_width and frame_height must be multiples of 16 + thInfo.frame_width = this->Dim[0]+15&~0xF; + thInfo.frame_height = this->Dim[1]+15&~0xF; + thInfo.pic_width = this->Dim[0]; + thInfo.pic_height = this->Dim[1]; + // force even offsets + // force even offsets of the picture within the frame + this->Off[0] = (thInfo.frame_width-this->Dim[0])>>1&~1; + this->Off[1] = (thInfo.frame_height-this->Dim[1])>>1&~1; + thInfo.pic_x = this->Off[0]; + thInfo.pic_y = this->Off[1]; + thInfo.colorspace = TH_CS_ITU_REC_470BG; + // 4:2:0 subsampling is the only implemented option in libtheora + // as of version 1.0. + thInfo.pixel_fmt = TH_PF_420; + thInfo.target_bitrate = 0; // variable bitrate recording (default) + // allow a variable quality/size tradeoff + //! \todo still have to find appropriate quality parameters though... + //! valid values are in [0,63]. + switch (this->Writer->GetQuality()) + { + case 0: + thInfo.quality = 42; + break; + case 1: + thInfo.quality = 52; + break; + default: + thInfo.quality = 63; + break; + } + thInfo.keyframe_granule_shift = 6; // default value + // the frame rate (as a fraction) + thInfo.fps_numerator = this->FrameRate; + thInfo.fps_denominator = 1; + // pixel ascpect ratio + thInfo.aspect_numerator = 1; + thInfo.aspect_denominator = 1; + + // create the theora encoder context + this->thEncContext = th_encode_alloc(&thInfo); + if (!this->thEncContext) + { + vtkGenericWarningMacro(<< "Could not allocate the theora context."); + return 0; + } + + // create the theora buffer (do not cheat with the frame padding, + // allocate the whole thing!) + for (size_t i=0; i<3; ++i) + { + this->thImage[i].width = thInfo.frame_width; + this->thImage[i].height = thInfo.frame_height; + if (i>0) + { + // Chroma planes are subsampled by a factor of 2 + this->thImage[i].width /= 2; + this->thImage[i].height /= 2; + } + // the stride is in bytes + this->thImage[i].stride = this->thImage[i].width*sizeof(unsigned char); + // make sure there's nothing left lying around... + if (this->thImage[i].data) + delete[] this->thImage[i].data; + // allocate the image plane + size_t siz = this->thImage[i].width * this->thImage[i].height; + this->thImage[i].data = new unsigned char[siz]; + } + + // thInfo is no longer needed + th_info_clear(&thInfo); + + // Finally, open the file and start it off. + this->outFile = fopen(this->Writer->GetFileName(),"wb"); + if (!this->outFile) + { + vtkGenericWarningMacro(<< "Could not open " << this->Writer->GetFileName() << "." ); + return 0; + } + this->openedFile = 1; + + return this->WriteHeader(); + +} + +//--------------------------------------------------------------------------- +// ripped from libtheora-1.0/examples/encoder_example.c +int vtkOggTheoraWriterInternal::WriteHeader() +{ + th_comment thComment; + ogg_packet oggPacket; + ogg_page oggPage; + + th_comment_init(&thComment); + + // first packet will get its own page automatically + if (th_encode_flushheader(this->thEncContext,&thComment,&oggPacket)<=0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + ogg_stream_packetin(&this->oggState,&oggPacket); + if (ogg_stream_pageout(&this->oggState,&oggPage)!=1) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + // remaining theora headers + int ret; + while (true) + { + ret=th_encode_flushheader(this->thEncContext,&thComment,&oggPacket); + if (ret<0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + else if (!ret) + break; + ogg_stream_packetin(&this->oggState,&oggPacket); + } + // Flush the rest of our headers. This ensures + // the actual data in each stream will start + // on a new page, as per spec. + while (true) + { + ret = ogg_stream_flush(&this->oggState,&oggPage); + if (ret<0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + if (ret==0)break; + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + } + + th_comment_clear(&thComment); + + return 1; +} + +//--------------------------------------------------------------------------- +int vtkOggTheoraWriterInternal::Write(vtkImageData *id) +{ + // encode the frame from the last call. + // have to do leap-frogging, because otherwise we can't + // write the EOS page with the last frame in End(). + int ret; + if (this->haveImageData) + { + ret = this->EncodeFrame(this->thImage,0); + this->haveImageData = false; + } + + id->Update(); + + // convert current RGB int YCbCr color space + this->RGB2YCbCr(id,this->thImage); + this->haveImageData = true; + + return ret; +} + +//--------------------------------------------------------------------------- +// ripped from libtheora-1.0/examples/encoder_example.c +int vtkOggTheoraWriterInternal::EncodeFrame(th_ycbcr_buffer ycbcr, int lastFrame) +{ + if (th_encode_ycbcr_in(this->thEncContext,this->thImage)<0) + { + vtkGenericWarningMacro("Error encoding frame."); + return 0; + } + // retrieve and push packets, writing pages as required + ogg_packet oggPacket; + ogg_page oggPage; + int ret; + while (ret=th_encode_packetout(this->thEncContext,lastFrame,&oggPacket)) + { + if(ret<0) + { + vtkGenericWarningMacro("Error retrieving packet from codec."); + return 0; + } + if (ogg_stream_packetin(&this->oggState,&oggPacket)<0) + { + vtkGenericWarningMacro("Error inserting packet into stream."); + return 0; + } + while (ogg_stream_pageout(&this->oggState,&oggPage)) + { + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + } + } + return 1; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriterInternal::End() +{ + // flush remaining frame + if (this->haveImageData) + if (!this->EncodeFrame(this->thImage,1)) + vtkGenericWarningMacro("Failed to finish writing movie"); + this->haveImageData = false; + + // clean up + for (size_t i = 0; i < 3; ++i) + { + if (this->thImage[i].data) + { + delete[] this->thImage[i].data; + this->thImage[i].data = NULL; + } + } + + if (this->thEncContext) + { + th_encode_free(this->thEncContext); + this->thEncContext = NULL; + } + + ogg_stream_clear(&this->oggState); + + if (this->openedFile) + { + fclose(this->outFile); + this->openedFile = 0; + } + this->closedFile = 1; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriterInternal::RGB2YCbCr(vtkImageData *id, + th_ycbcr_buffer ycbcr) +{ + // convenience + typedef unsigned char uchar; + + // + // constant coefficiens + // + + static const uchar OffY = 16, OffCr = 128, OffCb = 128; + // divide by 255, because the formulas use normalized RGB, i.e in [0,1] + static const double ExcurY = 219.0/255, ExcurCr = 224.0/255, ExcurCb = 224.0/255, + Kr = 0.299, Kb = 0.114; + // derived constants + static const double Kg = 1 - Kr - Kb, + Krm1 = Kr - 1, + Kbm1 = Kb - 1; + // stride between rows in the YCbCr image planes, since + // pixels in a row are contigious, but rows need not be + static const int strideRGB = this->Dim[0]*3, + strideY = ycbcr[0].stride/sizeof(uchar), // th_image_plane strides are in bytes + strideCb = ycbcr[1].stride/sizeof(uchar), + strideCr = ycbcr[2].stride/sizeof(uchar); + // + // computation + // + + // the first pixel in the RGB image + uchar *rgbStart = (uchar*)id->GetScalarPointer(); + // pointers to iterate through the RGB image an the Y, Cb and Cr planes + uchar *rgb, *Y, *Cb, *Cr; + // indicators whether we have to handle chroma planes. + bool isXCPlane = false, + isYCPlane = true; // y-flipping + // loop over rows + size_t x, y, yC; + for (y = 0; y < this->Dim[1]; ++y) + { + // reset x indicator and flip y indicator + isXCPlane = false; + isYCPlane = !isYCPlane; + // compute pointers to the first pixel in row y, + // flipping y coordinate + rgb = rgbStart + (this->Dim[1]-y-1) * strideRGB; + Y = ycbcr[0].data + (y+this->Off[1]) * strideY + this->Off[0]; + if (isYCPlane) + { + // compute y on chroma planes + yC = (y+this->Off[1])/2; + // pointers to first pixel in row yC of chroma planes + Cb = ycbcr[1].data + yC * strideCb + this->Off[0]/2; + Cr = ycbcr[2].data + yC * strideCr + this->Off[0]/2; + } + // loop over columns in row y + for (x = 0; x < this->Dim[0]; ++x) + { + // flip indicator + isXCPlane = !isXCPlane; + // do the actual transformation + *Y = uchar((Kr*rgb[0] + Kg*rgb[1] + Kb*rgb[2]) * ExcurY) + OffY; + if (isYCPlane && isXCPlane) + { + /* REMARK: actually, interpolation seems to give worse results... + * just use the associated RGB pixel (a.k.a nearest neighbor). + */ +# if 0 + // interpolate surrounding rgb (subsampling) + // use double in order to not loose too much precision... + double irgb[3]; + for (size_t i = 0; i < 3; ++i) + { + irgb[i] = 0.25 * (rgb[i] + rgb[i+3] + rgb[i+strideRGB] + rgb[i+3+strideRGB]); + } + *Cb = uchar((Kr *irgb[0]+Kg*irgb[1]+Kbm1*irgb[2])/(2*Kbm1)*ExcurCb)+OffCb; + *Cr = uchar((Krm1*irgb[0]+Kg*irgb[1]+Kb *irgb[2])/(2*Krm1)*ExcurCr)+OffCr; +# else + *Cb = uchar((Kr *rgb[0]+Kg*rgb[1]+Kbm1*rgb[2])/(2*Kbm1)*ExcurCb)+OffCb; + *Cr = uchar((Krm1*rgb[0]+Kg*rgb[1]+Kb *rgb[2])/(2*Krm1)*ExcurCr)+OffCr; +# endif + } + // advance to next pixel in row y + ++Y; + rgb += 3; + if (isYCPlane && isXCPlane) + { + ++Cb; + ++Cr; + } + } + } +} + +//--------------------------------------------------------------------------- +vtkStandardNewMacro(vtkOggTheoraWriter); +vtkCxxRevisionMacro(vtkOggTheoraWriter, "$Revision:$"); + +//--------------------------------------------------------------------------- +vtkOggTheoraWriter::vtkOggTheoraWriter() +{ + this->Internals = 0; + this->Quality = 2; + this->Rate = 25; +} + +//--------------------------------------------------------------------------- +vtkOggTheoraWriter::~vtkOggTheoraWriter() +{ + delete this->Internals; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::Start() +{ + this->Error = 1; + + if ( this->Internals ) + { + vtkErrorMacro("Movie already started."); + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + if ( this->GetInput() == NULL ) + { + vtkErrorMacro("Please specify an input."); + this->SetErrorCode(vtkGenericMovieWriter::NoInputError); + return; + } + if (!this->FileName) + { + vtkErrorMacro("Please specify a filename."); + this->SetErrorCode(vtkErrorCode::NoFileNameError); + return; + } + + this->Internals = new vtkOggTheoraWriterInternal(this); + + this->Error = 0; + + this->Initialized = 0; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::Write() +{ + if (this->Error) + { + return; + } + + if ( !this->Internals ) + { + vtkErrorMacro("Movie not started."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + + // get the data + this->GetInput()->UpdateInformation(); + int *wExtent = this->GetInput()->GetWholeExtent(); + this->GetInput()->SetUpdateExtent(wExtent); + this->GetInput()->Update(); + + int dim[4]; + this->GetInput()->GetDimensions(dim); + if ( this->Internals->Dim[0] == 0 && this->Internals->Dim[1] == 0 ) + { + this->Internals->Dim[0] = dim[0]; + this->Internals->Dim[1] = dim[1]; + } + + if (this->Internals->Dim[0]!= dim[0] || this->Internals->Dim[1]!= dim[1]) + { + vtkErrorMacro("Image not of the same size."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::ChangedResolutionError); + return; + } + + if ( !this->Initialized ) + { + this->Internals->FrameRate = this->Rate; + if (!this->Internals->Start()) + { + vtkErrorMacro("Error initializing video stream."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + this->Initialized = 1; + } + + if (!this->Internals->Write(this->GetInput())) + { + vtkErrorMacro("Error storing image."); + this->Error = 1; + this->SetErrorCode(vtkErrorCode::OutOfDiskSpaceError); + } +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::End() +{ + this->Internals->End(); + + delete this->Internals; + this->Internals = 0; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "Quality: " << this->Quality << endl; + os << indent << "Rate: " << this->Rate << endl; +} diff --git a/VTK/IO/vtkOggTheoraWriter.h b/VTK/IO/vtkOggTheoraWriter.h new file mode 100644 index 0000000..0413ef2 --- /dev/null +++ b/VTK/IO/vtkOggTheoraWriter.h @@ -0,0 +1,77 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkOggTheoraWriter.h,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +// .NAME vtkOggTheoraWriter - Uses the ogg and theora libraries to write video +// files. +// .SECTION Description +// vtkOggTheoraWriter is an adapter that allows VTK to use the ogg and theora +// libraries to write movie files. This class creates .ogv files containing +// theora encoded video without audio. +// +// This implementation is based on vtkFFMPEGWriter and uses some code derived +// from the encoder example distributed with libtheora. +// +// .SECTION See Also vtkGenericMovieWriter vtkAVIWriter vtkMPEG2Writer vtkFFMPEGWriter + +#ifndef __vtkOggTheoraWriter_h +#define __vtkOggTheoraWriter_h + +#include "vtkGenericMovieWriter.h" + +class vtkOggTheoraWriterInternal; + +class VTK_IO_EXPORT vtkOggTheoraWriter : public vtkGenericMovieWriter +{ +public: + static vtkOggTheoraWriter *New(); + vtkTypeRevisionMacro(vtkOggTheoraWriter,vtkGenericMovieWriter); + void PrintSelf(ostream& os, vtkIndent indent); + + // Description: + // These methods start writing an Movie file, write a frame to the file + // and then end the writing process. + void Start(); + void Write(); + void End(); + + // Description: + // Set/Get the compression quality. + // 0 means worst quality and smallest file size + // 2 means best quality and largest file size + vtkSetClampMacro(Quality, int, 0, 2); + vtkGetMacro(Quality, int); + + // Description: + // Set/Get the frame rate, in frame/s. + vtkSetClampMacro(Rate, int , 1, 5000); + vtkGetMacro(Rate, int); +protected: + vtkOggTheoraWriter(); + ~vtkOggTheoraWriter(); + + vtkOggTheoraWriterInternal *Internals; + + int Initialized; + int Quality; + int Rate; + +private: + vtkOggTheoraWriter(const vtkOggTheoraWriter&); // Not implemented + void operator=(const vtkOggTheoraWriter&); // Not implemented +}; + +#endif + + + -- 1.6.3.3 ![]() From fc16e8e06e4a6c6225b25634768aed29836b43bb Mon Sep 17 00:00:00 2001 From: Michael Wild <themiwi@users.sourceforge.net> Date: Fri, 17 Jul 2009 11:26:31 +0200 Subject: [PATCH 3/3] ENH: Integrated vtkOggTheoraWriter into ParaView Signed-off-by: Michael Wild <themiwi@users.sourceforge.net> --- Applications/OverView/Core/AboutDialog.cxx | 2 ++ Qt/Components/pqAnimationManager.cxx | 4 ++++ Qt/Components/pqClientAboutDialog.cxx | 2 ++ Servers/Common/vtkPVServerInformation.cxx | 13 +++++++++++++ Servers/Common/vtkPVServerInformation.h | 6 ++++++ .../vtkSMAnimationSceneImageWriter.cxx | 9 +++++++++ 6 files changed, 36 insertions(+), 0 deletions(-) diff --git a/Applications/OverView/Core/AboutDialog.cxx b/Applications/OverView/Core/AboutDialog.cxx index 00ba766..891eec7 100755 --- a/Applications/OverView/Core/AboutDialog.cxx +++ b/Applications/OverView/Core/AboutDialog.cxx @@ -218,6 +218,8 @@ void AboutDialog::AddServerInformation(pqServer* server, QTreeWidget* tree) ::addItem(tree, "Tile Display", "Off"); } + ::addItem(tree, "Write Ogg/Theora Animations", + serverInfo->GetOGVSupport()? "On": "Off"); ::addItem(tree, "Write AVI Animations", serverInfo->GetAVISupport()? "On": "Off"); } diff --git a/Qt/Components/pqAnimationManager.cxx b/Qt/Components/pqAnimationManager.cxx index 4ec1c3b..bbd92ac 100644 --- a/Qt/Components/pqAnimationManager.cxx +++ b/Qt/Components/pqAnimationManager.cxx @@ -427,6 +427,10 @@ bool pqAnimationManager::saveAnimation() } QString filters = ""; + if (serverInfo && serverInfo->GetOGVSupport()) + { + filters += "Ogg/Theora files (*.ogv);;"; + } if (serverInfo && serverInfo->GetAVISupport()) { filters += "AVI files (*.avi);;"; diff --git a/Qt/Components/pqClientAboutDialog.cxx b/Qt/Components/pqClientAboutDialog.cxx index 4beae0c..8d748e6 100644 --- a/Qt/Components/pqClientAboutDialog.cxx +++ b/Qt/Components/pqClientAboutDialog.cxx @@ -218,6 +218,8 @@ void pqClientAboutDialog::AddServerInformation(pqServer* server, QTreeWidget* tr ::addItem(tree, "Tile Display", "Off"); } + ::addItem(tree, "Write Ogg/Theora Animations", + serverInfo->GetOGVSupport()? "On": "Off"); ::addItem(tree, "Write AVI Animations", serverInfo->GetAVISupport()? "On": "Off"); } diff --git a/Servers/Common/vtkPVServerInformation.cxx b/Servers/Common/vtkPVServerInformation.cxx index c5bd771..f7b0aad 100755 --- a/Servers/Common/vtkPVServerInformation.cxx +++ b/Servers/Common/vtkPVServerInformation.cxx @@ -48,6 +48,7 @@ vtkPVServerInformation::vtkPVServerInformation() this->AVISupport = 1; # endif #endif + this->OGVSupport = 1; this->RenderModuleName = NULL; this->MachinesInternals = new vtkPVServerOptionsInternals; @@ -73,6 +74,7 @@ void vtkPVServerInformation::PrintSelf(ostream& os, vtkIndent indent) os << indent << "UseIceT: " << this->UseIceT << endl; os << indent << "RenderModuleName: " << (this->RenderModuleName ? this->RenderModuleName : "(none)") << endl; + os << indent << "OGVSupport: " << this->OGVSupport << endl; os << indent << "AVISupport: " << this->AVISupport << endl; os << indent << "Timeout: " << this->Timeout << endl; } @@ -167,6 +169,11 @@ void vtkPVServerInformation::AddInformation(vtkPVInformation* info) this->Timeout = serverInfo->GetTimeout(); } + if (!serverInfo->GetOGVSupport()) + { + this->OGVSupport = 0; + } + if (!serverInfo->GetAVISupport()) { this->AVISupport = 0; @@ -199,6 +206,7 @@ void vtkPVServerInformation::CopyToStream(vtkClientServerStream* css) *css << this->Timeout; *css << this->UseIceT; *css << this->RenderModuleName; + *css << this->OGVSupport; *css << this->AVISupport; *css << this->GetNumberOfMachines(); unsigned int idx; @@ -257,6 +265,11 @@ void vtkPVServerInformation::CopyFromStream(const vtkClientServerStream* css) return; } this->SetRenderModuleName(rmName); + if (!css->GetArgument(0, 9, &this->OGVSupport)) + { + vtkErrorMacro("Error parsing OGVSupport flag from message."); + return; + } if (!css->GetArgument(0, 9, &this->AVISupport)) { vtkErrorMacro("Error parsing AVISupport flag from message."); diff --git a/Servers/Common/vtkPVServerInformation.h b/Servers/Common/vtkPVServerInformation.h index aafe6d6..cfc2fcd 100755 --- a/Servers/Common/vtkPVServerInformation.h +++ b/Servers/Common/vtkPVServerInformation.h @@ -80,6 +80,11 @@ public: vtkGetStringMacro(RenderModuleName); // Description: + // Get/Set if the server supports saving OGVs. + vtkSetMacro(OGVSupport, int); + vtkGetMacro(OGVSupport, int); + + // Description: // Get/Set if the server supports saving AVIs. vtkSetMacro(AVISupport, int); vtkGetMacro(AVISupport, int); @@ -120,6 +125,7 @@ protected: vtkPVServerInformation(); ~vtkPVServerInformation(); + int OGVSupport; int AVISupport; int RemoteRendering; int TileDimensions[2]; diff --git a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx index f7fa49e..f950588 100644 --- a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx +++ b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx @@ -45,6 +45,7 @@ # include "vtkFFMPEGWriter.h" # endif #endif +#include "vtkOggTheoraWriter.h" vtkStandardNewMacro(vtkSMAnimationSceneImageWriter); vtkCxxRevisionMacro(vtkSMAnimationSceneImageWriter, "$Revision: 1.13 $"); @@ -376,6 +377,14 @@ bool vtkSMAnimationSceneImageWriter::CreateWriter() mwriter = aviwriter; } # endif + else if (extension == ".ogv" || extension == ".ogg") + { + vtkOggTheoraWriter *ogvwriter = vtkOggTheoraWriter::New(); + ogvwriter->SetQuality(this->Quality); + ogvwriter->SetRate( + static_cast<int>(this->GetFrameRate())); + mwriter = ogvwriter; + } #endif else { -- 1.6.3.3 ![]() ![]() From 93f65498f355fe92a19527c395efd673d11263bc Mon Sep 17 00:00:00 2001 From: Michael Wild <themiwi@users.sourceforge.net> Date: Wed, 15 Jul 2009 11:25:50 +0200 Subject: [PATCH 2/3] ENH: Added vtkOggTheoraWriter to VTK Supports both 4:4:4 and 4:2:0 subsampling. The former is the default as it gives better quality, but the 4:2:0 subsampling can still be used by enabling the Subsampling property of the class. Signed-off-by: Michael Wild <themiwi@users.sourceforge.net> --- VTK/IO/CMakeLists.txt | 1 + VTK/IO/Testing/Cxx/CMakeLists.txt | 2 + VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx | 104 +++++ VTK/IO/vtkOggTheoraWriter.cxx | 602 ++++++++++++++++++++++++++++ VTK/IO/vtkOggTheoraWriter.h | 84 ++++ 5 files changed, 793 insertions(+), 0 deletions(-) create mode 100644 VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx create mode 100644 VTK/IO/vtkOggTheoraWriter.cxx create mode 100644 VTK/IO/vtkOggTheoraWriter.h diff --git a/VTK/IO/CMakeLists.txt b/VTK/IO/CMakeLists.txt index 601bae5..3c4d043 100644 --- a/VTK/IO/CMakeLists.txt +++ b/VTK/IO/CMakeLists.txt @@ -81,6 +81,7 @@ vtkMoleculeReaderBase.cxx vtkNetCDFCOARDSReader.cxx vtkNetCDFReader.cxx vtkOBJReader.cxx +vtkOggTheoraWriter.cxx vtkOutputStream.cxx vtkPDBReader.cxx vtkPLOT3DReader.cxx diff --git a/VTK/IO/Testing/Cxx/CMakeLists.txt b/VTK/IO/Testing/Cxx/CMakeLists.txt index 30ab30c..6f99852 100644 --- a/VTK/IO/Testing/Cxx/CMakeLists.txt +++ b/VTK/IO/Testing/Cxx/CMakeLists.txt @@ -58,6 +58,7 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CxxTests.cxx TestXML.cxx TestCompress.cxx TestSQLDatabaseSchema.cxx + TestOggTheoraWriter.cxx ${ConditionalTests} EXTRA_INCLUDE vtkTestDriver.h ) @@ -110,6 +111,7 @@ IF (VTK_LARGE_DATA_ROOT) ENDIF (VTK_USE_DISPLAY AND VTK_USE_RENDERING) ENDIF (VTK_LARGE_DATA_ROOT) +ADD_TEST(TestOggTheoraWriter ${CXX_TEST_PATH}/${KIT}CxxTests TestOggTheoraWriter) ADD_TEST(TestSQLDatabaseSchema ${CXX_TEST_PATH}/${KIT}CxxTests TestSQLDatabaseSchema) IF(WIN32 AND VTK_USE_VIDEO_FOR_WINDOWS) diff --git a/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx b/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx new file mode 100644 index 0000000..684a31c --- /dev/null +++ b/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx @@ -0,0 +1,104 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: TestOggTheoraWriter.cxx,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +// .NAME TestOggTheoraWriter - Tests vtkOggTheoraWriter. +// .SECTION Description +// Creates a scene and uses OggTheoraWriter to generate a movie file. Test passes +// if the file exists and has non zero length. + + +#include "vtkImageCast.h" +#include "vtkImageData.h" +#include "vtkImageMandelbrotSource.h" +#include "vtkImageMapToColors.h" +#include "vtkLookupTable.h" +#include "vtkOggTheoraWriter.h" +#include "vtksys/SystemTools.hxx" + +int TestOggTheoraWriter(int vtkNotUsed(argc), char* vtkNotUsed(argv)[]) +{ + int err = 0; + int cc = 0; + int exists = 0; + unsigned long length = 0; + vtkImageMandelbrotSource* Fractal0 = vtkImageMandelbrotSource::New(); + Fractal0->SetWholeExtent( 0, 247, 0, 247, 0, 0 ); + Fractal0->SetProjectionAxes( 0, 1, 2 ); + Fractal0->SetOriginCX( -1.75, -1.25, 0, 0 ); + Fractal0->SetSizeCX( 2.5, 2.5, 2, 1.5 ); + Fractal0->SetMaximumNumberOfIterations( 100); + + vtkImageCast* cast = vtkImageCast::New(); + cast->SetInputConnection(Fractal0->GetOutputPort()); + cast->SetOutputScalarTypeToUnsignedChar(); + + vtkLookupTable* table = vtkLookupTable::New(); + table->SetTableRange(0, 100); + table->SetNumberOfColors(100); + table->Build(); + table->SetTableValue(99, 0, 0, 0); + + vtkImageMapToColors* colorize = vtkImageMapToColors::New(); + colorize->SetOutputFormatToRGB(); + colorize->SetLookupTable(table); + colorize->SetInputConnection(cast->GetOutputPort()); + + vtkOggTheoraWriter *w = vtkOggTheoraWriter::New(); + w->SetInputConnection(colorize->GetOutputPort()); + w->SetFileName("TestOggTheoraWriter.ogv"); + cout << "Writing file TestOggTheoraWriter.ogv..." << endl; + w->Start(); + for ( cc = 2; cc < 99; cc ++ ) + { + cout << "."; + Fractal0->SetMaximumNumberOfIterations(cc); + table->SetTableRange(0, cc); + table->SetNumberOfColors(cc); + table->ForceBuild(); + table->SetTableValue(cc-1, 0, 0, 0); + w->Write(); + } + w->End(); + cout << endl; + cout << "Done writing file TestOggTheoraWriter.ogv..." << endl; + w->Delete(); + + exists = (int) vtksys::SystemTools::FileExists("TestOggTheoraWriter.ogv"); + length = vtksys::SystemTools::FileLength("TestOggTheoraWriter.ogv"); + cout << "TestOggTheoraWriter.ogv file exists: " << exists << endl; + cout << "TestOggTheoraWriter.ogv file length: " << length << endl; + if (!exists) + { + err = 1; + cerr << "ERROR: 1 - Test failing because TestOggTheoraWriter.ogv file doesn't exist..." << endl; + } + else + { + vtksys::SystemTools::RemoveFile("TestOggTheoraWriter.ogv"); + } + if (0==length) + { + err = 2; + cerr << "ERROR: 2 - Test failing because TestOggTheoraWriter.ogv file has zero length..." << endl; + } + + colorize->Delete(); + table->Delete(); + cast->Delete(); + Fractal0->Delete(); + + // err == 0 means test passes... + // + return err; +} diff --git a/VTK/IO/vtkOggTheoraWriter.cxx b/VTK/IO/vtkOggTheoraWriter.cxx new file mode 100644 index 0000000..2ebb53d --- /dev/null +++ b/VTK/IO/vtkOggTheoraWriter.cxx @@ -0,0 +1,602 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkOggTheoraWriter.cxx,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +#include "vtkOggTheoraWriter.h" + +#include "vtkImageData.h" +#include "vtkObjectFactory.h" +#include "vtkErrorCode.h" + +#include "vtk_oggtheora.h" + +//--------------------------------------------------------------------------- +class vtkOggTheoraWriterInternal +{ +public: + vtkOggTheoraWriterInternal(vtkOggTheoraWriter *creator); + ~vtkOggTheoraWriterInternal(); + + int Start(); + int Write(vtkImageData *id); + void End(); + + int Dim[2]; + int FrameRate; + +private: + + // Helper function to convert an RGB image into the Y'CbCr color space and + // into the data structure required by theora (i.e. 4:4:4 or 4:2:0 + // subsampling will be used). Refer to http://www.theora.org/doc/Theora.pdf + // sections 4.3 and 4.3.2. Actually, the equations are inverted. However, I'm + // not sure whether VTK uses gamma-corrected RGB or not. I assume they are + // not, which is what we need here. Assume that the width and height are even + // numbers. + void RGB2YCbCr(vtkImageData *id, th_ycbcr_buffer ycbcr); + // Write the ogg/theora header information + int WriteHeader(); + // Encode a single frame + int EncodeFrame(th_ycbcr_buffer ycbcr, int lastFrame); + + vtkOggTheoraWriter *Writer; + + size_t Off[2]; // offsets of the picture within the frame + th_enc_ctx *thEncContext; // the theora context (has to be freed) + th_ycbcr_buffer thImage; // the Y'CbCr image buffer + ogg_stream_state oggState; // the ogg stream state (has to be cleared) + FILE* outFile; // the output file stream + bool haveImageData; // indicater whether a frame has to be encoded + // (for the leap-frogging) + + int openedFile; + int closedFile; +}; + +//--------------------------------------------------------------------------- +vtkOggTheoraWriterInternal::vtkOggTheoraWriterInternal(vtkOggTheoraWriter *creator) +{ + this->Writer = creator; + this->Dim[0] = 0; + this->Dim[1] = 0; + + this->Off[0] = 0; + this->Off[1] = 0; + this->thEncContext = NULL; + this->outFile = NULL; + this->thImage[0].data = NULL; + this->thImage[1].data = NULL; + this->thImage[2].data = NULL; + + this->openedFile = 0; + this->closedFile = 1; + this->haveImageData = false; + + this->FrameRate = 25; +} + +//--------------------------------------------------------------------------- +vtkOggTheoraWriterInternal::~vtkOggTheoraWriterInternal() +{ + if (!this->closedFile) + { + this->End(); + } +} + +//--------------------------------------------------------------------------- +int vtkOggTheoraWriterInternal::Start() +{ + this->closedFile = 0; + + // ogg information + srand(time(NULL)); + if (ogg_stream_init(&this->oggState,rand())!=0) + { + vtkGenericWarningMacro("Could not initialize ogg stream state."); + return 0; + } + + // fill in theora information + th_info thInfo; + th_info_init(&thInfo); + // frame_width and frame_height must be multiples of 16 + thInfo.frame_width = this->Dim[0]+15&~0xF; + thInfo.frame_height = this->Dim[1]+15&~0xF; + thInfo.pic_width = this->Dim[0]; + thInfo.pic_height = this->Dim[1]; + // force even offsets + // force even offsets of the picture within the frame + this->Off[0] = (thInfo.frame_width-this->Dim[0])>>1&~1; + this->Off[1] = (thInfo.frame_height-this->Dim[1])>>1&~1; + thInfo.pic_x = this->Off[0]; + thInfo.pic_y = this->Off[1]; + thInfo.colorspace = TH_CS_ITU_REC_470BG; + if (this->Writer->GetSubsampling()) + { + // 4:2:0 subsampling is the only implemented option in libtheora-1.0 + thInfo.pixel_fmt = TH_PF_420; + } + else + { + thInfo.pixel_fmt = TH_PF_444; + } + thInfo.target_bitrate = 0; // variable bitrate recording (default) + // allow a variable quality/size tradeoff + //! \todo still have to find appropriate quality parameters though... + //! valid values are in [0,63]. + switch (this->Writer->GetQuality()) + { + case 0: + thInfo.quality = 42; + break; + case 1: + thInfo.quality = 52; + break; + default: + thInfo.quality = 63; + break; + } + thInfo.keyframe_granule_shift = 6; // default value + // the frame rate (as a fraction) + thInfo.fps_numerator = this->FrameRate; + thInfo.fps_denominator = 1; + // pixel ascpect ratio + thInfo.aspect_numerator = 1; + thInfo.aspect_denominator = 1; + + // create the theora encoder context + this->thEncContext = th_encode_alloc(&thInfo); + if (!this->thEncContext) + { + vtkGenericWarningMacro(<< "Could not allocate the theora context."); + return 0; + } + + // create the theora buffer (do not cheat with the frame padding, + // allocate the whole thing!) + for (size_t i=0; i<3; ++i) + { + this->thImage[i].width = thInfo.frame_width; + this->thImage[i].height = thInfo.frame_height; + if (this->Writer->GetSubsampling() && i>0) + { + // Chroma planes are subsampled by a factor of 2 + this->thImage[i].width /= 2; + this->thImage[i].height /= 2; + } + // the stride is in bytes + this->thImage[i].stride = this->thImage[i].width*sizeof(unsigned char); + // make sure there's nothing left lying around... + if (this->thImage[i].data) + delete[] this->thImage[i].data; + // allocate the image plane + size_t siz = this->thImage[i].width * this->thImage[i].height; + this->thImage[i].data = new unsigned char[siz]; + } + + // thInfo is no longer needed + th_info_clear(&thInfo); + + // Finally, open the file and start it off. + this->outFile = fopen(this->Writer->GetFileName(),"wb"); + if (!this->outFile) + { + vtkGenericWarningMacro(<< "Could not open " << this->Writer->GetFileName() << "." ); + return 0; + } + this->openedFile = 1; + + return this->WriteHeader(); + +} + +//--------------------------------------------------------------------------- +// ripped from libtheora-1.0/examples/encoder_example.c +int vtkOggTheoraWriterInternal::WriteHeader() +{ + th_comment thComment; + ogg_packet oggPacket; + ogg_page oggPage; + + th_comment_init(&thComment); + + // first packet will get its own page automatically + if (th_encode_flushheader(this->thEncContext,&thComment,&oggPacket)<=0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + ogg_stream_packetin(&this->oggState,&oggPacket); + if (ogg_stream_pageout(&this->oggState,&oggPage)!=1) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + // remaining theora headers + int ret; + while (true) + { + ret=th_encode_flushheader(this->thEncContext,&thComment,&oggPacket); + if (ret<0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + else if (!ret) + break; + ogg_stream_packetin(&this->oggState,&oggPacket); + } + // Flush the rest of our headers. This ensures + // the actual data in each stream will start + // on a new page, as per spec. + while (true) + { + ret = ogg_stream_flush(&this->oggState,&oggPage); + if (ret<0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + if (ret==0)break; + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + } + + th_comment_clear(&thComment); + + return 1; +} + +//--------------------------------------------------------------------------- +int vtkOggTheoraWriterInternal::Write(vtkImageData *id) +{ + // encode the frame from the last call. + // have to do leap-frogging, because otherwise we can't + // write the EOS page with the last frame in End(). + int ret; + if (this->haveImageData) + { + ret = this->EncodeFrame(this->thImage,0); + this->haveImageData = false; + } + + id->Update(); + + // convert current RGB int YCbCr color space + this->RGB2YCbCr(id,this->thImage); + this->haveImageData = true; + + return ret; +} + +//--------------------------------------------------------------------------- +// ripped from libtheora-1.0/examples/encoder_example.c +int vtkOggTheoraWriterInternal::EncodeFrame(th_ycbcr_buffer ycbcr, int lastFrame) +{ + if (th_encode_ycbcr_in(this->thEncContext,this->thImage)<0) + { + vtkGenericWarningMacro("Error encoding frame."); + return 0; + } + // retrieve and push packets, writing pages as required + ogg_packet oggPacket; + ogg_page oggPage; + int ret; + while (ret=th_encode_packetout(this->thEncContext,lastFrame,&oggPacket)) + { + if(ret<0) + { + vtkGenericWarningMacro("Error retrieving packet from codec."); + return 0; + } + if (ogg_stream_packetin(&this->oggState,&oggPacket)<0) + { + vtkGenericWarningMacro("Error inserting packet into stream."); + return 0; + } + while (ogg_stream_pageout(&this->oggState,&oggPage)) + { + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + } + } + return 1; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriterInternal::End() +{ + // flush remaining frame + if (this->haveImageData) + if (!this->EncodeFrame(this->thImage,1)) + vtkGenericWarningMacro("Failed to finish writing movie"); + this->haveImageData = false; + + // clean up + for (size_t i = 0; i < 3; ++i) + { + if (this->thImage[i].data) + { + delete[] this->thImage[i].data; + this->thImage[i].data = NULL; + } + } + + if (this->thEncContext) + { + th_encode_free(this->thEncContext); + this->thEncContext = NULL; + } + + ogg_stream_clear(&this->oggState); + + if (this->openedFile) + { + fclose(this->outFile); + this->openedFile = 0; + } + this->closedFile = 1; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriterInternal::RGB2YCbCr(vtkImageData *id, + th_ycbcr_buffer ycbcr) +{ + // convenience + typedef unsigned char uchar; + + // + // constant coefficiens + // + + static const uchar OffY = 16, OffCr = 128, OffCb = 128; + // divide by 255, because the formulas use normalized RGB, i.e in [0,1] + static const double ExcurY = 219.0/255, ExcurCr = 224.0/255, ExcurCb = 224.0/255, + Kr = 0.299, Kb = 0.114; + // derived constants + static const double Kg = 1 - Kr - Kb, + Krm1 = Kr - 1, + Kbm1 = Kb - 1; + // stride between rows in the YCbCr image planes, since + // pixels in a row are contigious, but rows need not be + static const int strideRGB = this->Dim[0]*3, + strideY = ycbcr[0].stride/sizeof(uchar), // th_image_plane strides are in bytes + strideCb = ycbcr[1].stride/sizeof(uchar), + strideCr = ycbcr[2].stride/sizeof(uchar); + // + // computation + // + + // the first pixel in the RGB image + uchar *rgbStart = (uchar*)id->GetScalarPointer(); + // pointers to iterate through the RGB image an the Y, Cb and Cr planes + uchar *rgb, *Y, *Cb, *Cr; + // indicators whether we have to handle chroma planes. + bool isXCPlane = false, + isYCPlane = true; // y-flipping + // loop over rows + size_t x, y, yC; + for (y = 0; y < this->Dim[1]; ++y) + { + if (this->Writer->GetSubsampling()) + { + // reset x indicator and flip y indicator + isXCPlane = false; + isYCPlane = !isYCPlane; + } + // compute pointers to the first pixel in row y, + // flipping y coordinate + rgb = rgbStart + (this->Dim[1]-y-1) * strideRGB; + Y = ycbcr[0].data + (y+this->Off[1]) * strideY + this->Off[0]; + if (!this->Writer->GetSubsampling()) + { + Cb = ycbcr[1].data + (y+this->Off[1]) * strideY + this->Off[0]; + Cr = ycbcr[2].data + (y+this->Off[1]) * strideY + this->Off[0]; + } + else if (isYCPlane) + { + // compute y on chroma planes + yC = (y+this->Off[1])/2; + // pointers to first pixel in row yC of chroma planes + Cb = ycbcr[1].data + yC * strideCb + this->Off[0]/2; + Cr = ycbcr[2].data + yC * strideCr + this->Off[0]/2; + } + // loop over columns in row y + for (x = 0; x < this->Dim[0]; ++x) + { + // do the actual transformation + *Y = uchar((Kr * rgb[0] + Kg * rgb[1] + Kb *rgb[2]) *ExcurY ) + OffY; + if (!this->Writer->GetSubsampling()) + { + *Cb = uchar((Kr * rgb[0] + Kg * rgb[1] + Kbm1 * rgb[2]) / + (2 * Kbm1) * ExcurCb) + OffCb; + *Cr = uchar((Krm1 * rgb[0] + Kg * rgb[1] + Kb * rgb[2]) / + (2 * Krm1) * ExcurCr) + OffCr; + } + else + { + // flip indicator + isXCPlane = !isXCPlane; + if (isYCPlane && isXCPlane) + { + /* REMARK: actually, interpolation seems to give worse results... + * just use the associated RGB pixel (a.k.a nearest neighbor). + */ +#if 0 + // interpolate surrounding rgb (subsampling) + // use double in order to not loose too much precision... + double irgb[3]; + for (size_t i = 0; i < 3; ++i) + { + irgb[i] = 0.25 * (rgb[i] + rgb[i+3] + + rgb[i+strideRGB] + rgb[i+3+strideRGB]); + } + *Cb = uchar((Kr * irgb[0] + Kg * irgb[1] + Kbm1 * irgb[2]) / + (2 * Kbm1) * ExcurCb) + OffCb; + *Cr = uchar((Krm1 * irgb[0] + Kg * irgb[1] + Kb * irgb[2]) / + (2 * Krm1) * ExcurCr) + OffCr; +#else + *Cb = uchar((Kr * rgb[0] + Kg * rgb[1] + Kbm1 * rgb[2]) / + (2 * Kbm1) * ExcurCb) + OffCb; + *Cr = uchar((Krm1 * rgb[0] + Kg * rgb[1] + Kb * rgb[2]) / + (2 * Krm1) * ExcurCr) + OffCr; +#endif + } + } + // advance to next pixel in row y + rgb += 3; + ++Y; + if (!this->Writer->GetSubsampling()) + { + ++Cb; + ++Cr; + } + else if (isYCPlane && isXCPlane) + { + ++Cb; + ++Cr; + } + } + } +} + +//--------------------------------------------------------------------------- +vtkStandardNewMacro(vtkOggTheoraWriter); +vtkCxxRevisionMacro(vtkOggTheoraWriter, "$Revision:$"); + +//--------------------------------------------------------------------------- +vtkOggTheoraWriter::vtkOggTheoraWriter() +{ + this->Internals = 0; + this->Quality = 2; + this->Rate = 25; + this->Subsampling = 0; +} + +//--------------------------------------------------------------------------- +vtkOggTheoraWriter::~vtkOggTheoraWriter() +{ + delete this->Internals; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::Start() +{ + this->Error = 1; + + if ( this->Internals ) + { + vtkErrorMacro("Movie already started."); + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + if ( this->GetInput() == NULL ) + { + vtkErrorMacro("Please specify an input."); + this->SetErrorCode(vtkGenericMovieWriter::NoInputError); + return; + } + if (!this->FileName) + { + vtkErrorMacro("Please specify a filename."); + this->SetErrorCode(vtkErrorCode::NoFileNameError); + return; + } + + this->Internals = new vtkOggTheoraWriterInternal(this); + + this->Error = 0; + + this->Initialized = 0; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::Write() +{ + if (this->Error) + { + return; + } + + if ( !this->Internals ) + { + vtkErrorMacro("Movie not started."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + + // get the data + this->GetInput()->UpdateInformation(); + int *wExtent = this->GetInput()->GetWholeExtent(); + this->GetInput()->SetUpdateExtent(wExtent); + this->GetInput()->Update(); + + int dim[4]; + this->GetInput()->GetDimensions(dim); + if ( this->Internals->Dim[0] == 0 && this->Internals->Dim[1] == 0 ) + { + this->Internals->Dim[0] = dim[0]; + this->Internals->Dim[1] = dim[1]; + } + + if (this->Internals->Dim[0]!= dim[0] || this->Internals->Dim[1]!= dim[1]) + { + vtkErrorMacro("Image not of the same size."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::ChangedResolutionError); + return; + } + + if ( !this->Initialized ) + { + this->Internals->FrameRate = this->Rate; + if (!this->Internals->Start()) + { + vtkErrorMacro("Error initializing video stream."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + this->Initialized = 1; + } + + if (!this->Internals->Write(this->GetInput())) + { + vtkErrorMacro("Error storing image."); + this->Error = 1; + this->SetErrorCode(vtkErrorCode::OutOfDiskSpaceError); + } +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::End() +{ + this->Internals->End(); + + delete this->Internals; + this->Internals = 0; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "Quality: " << this->Quality << endl; + os << indent << "Rate: " << this->Rate << endl; + os << indent << "Subsampling: " << this->Subsampling << endl; +} diff --git a/VTK/IO/vtkOggTheoraWriter.h b/VTK/IO/vtkOggTheoraWriter.h new file mode 100644 index 0000000..b673cd8 --- /dev/null +++ b/VTK/IO/vtkOggTheoraWriter.h @@ -0,0 +1,84 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkOggTheoraWriter.h,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +// .NAME vtkOggTheoraWriter - Uses the ogg and theora libraries to write video +// files. +// .SECTION Description +// vtkOggTheoraWriter is an adapter that allows VTK to use the ogg and theora +// libraries to write movie files. This class creates .ogv files containing +// theora encoded video without audio. +// +// This implementation is based on vtkFFMPEGWriter and uses some code derived +// from the encoder example distributed with libtheora. +// +// .SECTION See Also vtkGenericMovieWriter vtkAVIWriter vtkMPEG2Writer vtkFFMPEGWriter + +#ifndef __vtkOggTheoraWriter_h +#define __vtkOggTheoraWriter_h + +#include "vtkGenericMovieWriter.h" + +class vtkOggTheoraWriterInternal; + +class VTK_IO_EXPORT vtkOggTheoraWriter : public vtkGenericMovieWriter +{ +public: + static vtkOggTheoraWriter *New(); + vtkTypeRevisionMacro(vtkOggTheoraWriter,vtkGenericMovieWriter); + void PrintSelf(ostream& os, vtkIndent indent); + + // Description: + // These methods start writing an Movie file, write a frame to the file + // and then end the writing process. + void Start(); + void Write(); + void End(); + + // Description: + // Set/Get the compression quality. + // 0 means worst quality and smallest file size + // 2 means best quality and largest file size + vtkSetClampMacro(Quality, int, 0, 2); + vtkGetMacro(Quality, int); + + // Description: + // Set/Get the frame rate, in frame/s. + vtkSetClampMacro(Rate, int , 1, 5000); + vtkGetMacro(Rate, int); + + // Description: + // Is the video to be encoded using 4:2:0 subsampling? + vtkSetMacro(Subsampling, int); + vtkGetMacro(Subsampling, int); + vtkBooleanMacro(Subsampling, int); +protected: + vtkOggTheoraWriter(); + ~vtkOggTheoraWriter(); + + vtkOggTheoraWriterInternal *Internals; + + int Initialized; + int Quality; + int Rate; + int Subsampling; + +private: + vtkOggTheoraWriter(const vtkOggTheoraWriter&); // Not implemented + void operator=(const vtkOggTheoraWriter&); // Not implemented +}; + +#endif + + + -- 1.6.3.3 ![]() From 1b8908e77cf8f46ad38b6458c2a5e5ff4b31165b Mon Sep 17 00:00:00 2001 From: Michael Wild <themiwi@users.sourceforge.net> Date: Fri, 17 Jul 2009 11:26:31 +0200 Subject: [PATCH 3/3] ENH: Integrated vtkOggTheoraWriter into ParaView The Subsampling property is propagated to the vtkSMAnimationSceneImageWriter, but so far there is no GUI control. Signed-off-by: Michael Wild <themiwi@users.sourceforge.net> --- Applications/OverView/Core/AboutDialog.cxx | 2 ++ Qt/Components/pqAnimationManager.cxx | 4 ++++ Qt/Components/pqClientAboutDialog.cxx | 2 ++ Servers/Common/vtkPVServerInformation.cxx | 13 +++++++++++++ Servers/Common/vtkPVServerInformation.h | 6 ++++++ .../vtkSMAnimationSceneImageWriter.cxx | 12 ++++++++++++ .../ServerManager/vtkSMAnimationSceneImageWriter.h | 11 ++++++++++- 7 files changed, 49 insertions(+), 1 deletions(-) diff --git a/Applications/OverView/Core/AboutDialog.cxx b/Applications/OverView/Core/AboutDialog.cxx index 00ba766..891eec7 100755 --- a/Applications/OverView/Core/AboutDialog.cxx +++ b/Applications/OverView/Core/AboutDialog.cxx @@ -218,6 +218,8 @@ void AboutDialog::AddServerInformation(pqServer* server, QTreeWidget* tree) ::addItem(tree, "Tile Display", "Off"); } + ::addItem(tree, "Write Ogg/Theora Animations", + serverInfo->GetOGVSupport()? "On": "Off"); ::addItem(tree, "Write AVI Animations", serverInfo->GetAVISupport()? "On": "Off"); } diff --git a/Qt/Components/pqAnimationManager.cxx b/Qt/Components/pqAnimationManager.cxx index 4ec1c3b..bbd92ac 100644 --- a/Qt/Components/pqAnimationManager.cxx +++ b/Qt/Components/pqAnimationManager.cxx @@ -427,6 +427,10 @@ bool pqAnimationManager::saveAnimation() } QString filters = ""; + if (serverInfo && serverInfo->GetOGVSupport()) + { + filters += "Ogg/Theora files (*.ogv);;"; + } if (serverInfo && serverInfo->GetAVISupport()) { filters += "AVI files (*.avi);;"; diff --git a/Qt/Components/pqClientAboutDialog.cxx b/Qt/Components/pqClientAboutDialog.cxx index 4beae0c..8d748e6 100644 --- a/Qt/Components/pqClientAboutDialog.cxx +++ b/Qt/Components/pqClientAboutDialog.cxx @@ -218,6 +218,8 @@ void pqClientAboutDialog::AddServerInformation(pqServer* server, QTreeWidget* tr ::addItem(tree, "Tile Display", "Off"); } + ::addItem(tree, "Write Ogg/Theora Animations", + serverInfo->GetOGVSupport()? "On": "Off"); ::addItem(tree, "Write AVI Animations", serverInfo->GetAVISupport()? "On": "Off"); } diff --git a/Servers/Common/vtkPVServerInformation.cxx b/Servers/Common/vtkPVServerInformation.cxx index 7e42f98..ac64e6f 100755 --- a/Servers/Common/vtkPVServerInformation.cxx +++ b/Servers/Common/vtkPVServerInformation.cxx @@ -48,6 +48,7 @@ vtkPVServerInformation::vtkPVServerInformation() this->AVISupport = 1; # endif #endif + this->OGVSupport = 1; this->RenderModuleName = NULL; this->MachinesInternals = new vtkPVServerOptionsInternals; @@ -73,6 +74,7 @@ void vtkPVServerInformation::PrintSelf(ostream& os, vtkIndent indent) os << indent << "UseIceT: " << this->UseIceT << endl; os << indent << "RenderModuleName: " << (this->RenderModuleName ? this->RenderModuleName : "(none)") << endl; + os << indent << "OGVSupport: " << this->OGVSupport << endl; os << indent << "AVISupport: " << this->AVISupport << endl; os << indent << "Timeout: " << this->Timeout << endl; } @@ -167,6 +169,11 @@ void vtkPVServerInformation::AddInformation(vtkPVInformation* info) this->Timeout = serverInfo->GetTimeout(); } + if (!serverInfo->GetOGVSupport()) + { + this->OGVSupport = 0; + } + if (!serverInfo->GetAVISupport()) { this->AVISupport = 0; @@ -199,6 +206,7 @@ void vtkPVServerInformation::CopyToStream(vtkClientServerStream* css) *css << this->Timeout; *css << this->UseIceT; *css << this->RenderModuleName; + *css << this->OGVSupport; *css << this->AVISupport; *css << this->GetNumberOfMachines(); unsigned int idx; @@ -257,6 +265,11 @@ void vtkPVServerInformation::CopyFromStream(const vtkClientServerStream* css) return; } this->SetRenderModuleName(rmName); + if (!css->GetArgument(0, 9, &this->OGVSupport)) + { + vtkErrorMacro("Error parsing OGVSupport flag from message."); + return; + } if (!css->GetArgument(0, 9, &this->AVISupport)) { vtkErrorMacro("Error parsing AVISupport flag from message."); diff --git a/Servers/Common/vtkPVServerInformation.h b/Servers/Common/vtkPVServerInformation.h index aafe6d6..cfc2fcd 100755 --- a/Servers/Common/vtkPVServerInformation.h +++ b/Servers/Common/vtkPVServerInformation.h @@ -80,6 +80,11 @@ public: vtkGetStringMacro(RenderModuleName); // Description: + // Get/Set if the server supports saving OGVs. + vtkSetMacro(OGVSupport, int); + vtkGetMacro(OGVSupport, int); + + // Description: // Get/Set if the server supports saving AVIs. vtkSetMacro(AVISupport, int); vtkGetMacro(AVISupport, int); @@ -120,6 +125,7 @@ protected: vtkPVServerInformation(); ~vtkPVServerInformation(); + int OGVSupport; int AVISupport; int RemoteRendering; int TileDimensions[2]; diff --git a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx index f7fa49e..10e1c54 100644 --- a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx +++ b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx @@ -45,6 +45,7 @@ # include "vtkFFMPEGWriter.h" # endif #endif +#include "vtkOggTheoraWriter.h" vtkStandardNewMacro(vtkSMAnimationSceneImageWriter); vtkCxxRevisionMacro(vtkSMAnimationSceneImageWriter, "$Revision: 1.13 $"); @@ -58,6 +59,7 @@ vtkSMAnimationSceneImageWriter::vtkSMAnimationSceneImageWriter() this->Magnification = 1; this->ErrorCode = 0; this->Quality = 1; + this->Subsampling = 0; this->ActualSize[0] = this->ActualSize[1] = 0; this->MovieWriter = 0; @@ -377,6 +379,15 @@ bool vtkSMAnimationSceneImageWriter::CreateWriter() } # endif #endif + else if (extension == ".ogv" || extension == ".ogg") + { + vtkOggTheoraWriter *ogvwriter = vtkOggTheoraWriter::New(); + ogvwriter->SetQuality(this->Quality); + ogvwriter->SetRate( + static_cast<int>(this->GetFrameRate())); + ogvwriter->SetSubsampling(this->GetSubsampling()); + mwriter = ogvwriter; + } else { vtkErrorMacro("Unknown extension " << extension.c_str()); @@ -434,6 +445,7 @@ void vtkSMAnimationSceneImageWriter::PrintSelf(ostream& os, vtkIndent indent) this->Superclass::PrintSelf(os, indent); os << indent << "Quality: " << this->Quality << endl; os << indent << "Magnification: " << this->Magnification << endl; + os << indent << "Subsampling: " << this->Subsampling << endl; os << indent << "ErrorCode: " << this->ErrorCode << endl; os << indent << "FrameRate: " << this->FrameRate << endl; os << indent << "BackgroundColor: " << this->BackgroundColor[0] diff --git a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h index 0e89c6e..c0c710a 100644 --- a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h +++ b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h @@ -54,6 +54,15 @@ public: vtkGetMacro(Quality, int); // Description: + // Get/Set the setting whether the movie encoder should use subsampling of + // the chrome planes or not, if applicable. Since the human eye is more + // sensitive to brightness than color variations, subsampling can be + // useful to reduce the bitrate. Default value is 0. + vtkSetMacro(Subsampling, double); + vtkGetMacro(Subsampling, double); + vtkBooleanMacro(Subsampling, double); + + // Description: // Get the error code which is set if there's an error while writing // the images. vtkGetMacro(ErrorCode, int); @@ -74,7 +83,6 @@ public: vtkSetMacro(FrameRate, double); vtkGetMacro(FrameRate, double); - // Description: // Convenience method used to merge a smaller image (\c src) into a // larger one (\c dest). The location of the smaller image in the larger image @@ -119,6 +127,7 @@ protected: int Magnification; int FileCount; int ErrorCode; + int Subsampling; char* Prefix; char* Suffix; -- 1.6.3.3 ![]() ![]() From 66372d8714be081120b2aef0ebecb111c1337a42 Mon Sep 17 00:00:00 2001 From: Michael Wild <themiwi@users.sourceforge.net> Date: Wed, 15 Jul 2009 11:25:50 +0200 Subject: [PATCH 2/3] ENH: Added vtkOggTheoraWriter to VTK Supports both 4:4:4 and 4:2:0 subsampling. The former is the default as it gives better quality, but the 4:2:0 subsampling can still be used by enabling the Subsampling property of the class. Signed-off-by: Michael Wild <themiwi@users.sourceforge.net> --- VTK/IO/CMakeLists.txt | 1 + VTK/IO/Testing/Cxx/CMakeLists.txt | 2 + VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx | 104 +++++ VTK/IO/vtkOggTheoraWriter.cxx | 602 ++++++++++++++++++++++++++++ VTK/IO/vtkOggTheoraWriter.h | 84 ++++ 5 files changed, 793 insertions(+), 0 deletions(-) create mode 100644 VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx create mode 100644 VTK/IO/vtkOggTheoraWriter.cxx create mode 100644 VTK/IO/vtkOggTheoraWriter.h diff --git a/VTK/IO/CMakeLists.txt b/VTK/IO/CMakeLists.txt index 13851ee..0c6a1e5 100644 --- a/VTK/IO/CMakeLists.txt +++ b/VTK/IO/CMakeLists.txt @@ -81,6 +81,7 @@ vtkMoleculeReaderBase.cxx vtkNetCDFCOARDSReader.cxx vtkNetCDFReader.cxx vtkOBJReader.cxx +vtkOggTheoraWriter.cxx vtkOutputStream.cxx vtkOpenFOAMReader.cxx vtkPDBReader.cxx diff --git a/VTK/IO/Testing/Cxx/CMakeLists.txt b/VTK/IO/Testing/Cxx/CMakeLists.txt index 9997ec2..d2ac8bb 100644 --- a/VTK/IO/Testing/Cxx/CMakeLists.txt +++ b/VTK/IO/Testing/Cxx/CMakeLists.txt @@ -65,6 +65,7 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CxxTests.cxx TestXML.cxx TestCompress.cxx TestSQLDatabaseSchema.cxx + TestOggTheoraWriter.cxx ${ConditionalTests} EXTRA_INCLUDE vtkTestDriver.h ) @@ -119,6 +120,7 @@ IF (VTK_LARGE_DATA_ROOT) ENDIF (VTK_USE_DISPLAY AND VTK_USE_RENDERING) ENDIF (VTK_LARGE_DATA_ROOT) +ADD_TEST(TestOggTheoraWriter ${CXX_TEST_PATH}/${KIT}CxxTests TestOggTheoraWriter) ADD_TEST(TestSQLDatabaseSchema ${CXX_TEST_PATH}/${KIT}CxxTests TestSQLDatabaseSchema) IF(WIN32 AND VTK_USE_VIDEO_FOR_WINDOWS) diff --git a/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx b/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx new file mode 100644 index 0000000..684a31c --- /dev/null +++ b/VTK/IO/Testing/Cxx/TestOggTheoraWriter.cxx @@ -0,0 +1,104 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: TestOggTheoraWriter.cxx,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +// .NAME TestOggTheoraWriter - Tests vtkOggTheoraWriter. +// .SECTION Description +// Creates a scene and uses OggTheoraWriter to generate a movie file. Test passes +// if the file exists and has non zero length. + + +#include "vtkImageCast.h" +#include "vtkImageData.h" +#include "vtkImageMandelbrotSource.h" +#include "vtkImageMapToColors.h" +#include "vtkLookupTable.h" +#include "vtkOggTheoraWriter.h" +#include "vtksys/SystemTools.hxx" + +int TestOggTheoraWriter(int vtkNotUsed(argc), char* vtkNotUsed(argv)[]) +{ + int err = 0; + int cc = 0; + int exists = 0; + unsigned long length = 0; + vtkImageMandelbrotSource* Fractal0 = vtkImageMandelbrotSource::New(); + Fractal0->SetWholeExtent( 0, 247, 0, 247, 0, 0 ); + Fractal0->SetProjectionAxes( 0, 1, 2 ); + Fractal0->SetOriginCX( -1.75, -1.25, 0, 0 ); + Fractal0->SetSizeCX( 2.5, 2.5, 2, 1.5 ); + Fractal0->SetMaximumNumberOfIterations( 100); + + vtkImageCast* cast = vtkImageCast::New(); + cast->SetInputConnection(Fractal0->GetOutputPort()); + cast->SetOutputScalarTypeToUnsignedChar(); + + vtkLookupTable* table = vtkLookupTable::New(); + table->SetTableRange(0, 100); + table->SetNumberOfColors(100); + table->Build(); + table->SetTableValue(99, 0, 0, 0); + + vtkImageMapToColors* colorize = vtkImageMapToColors::New(); + colorize->SetOutputFormatToRGB(); + colorize->SetLookupTable(table); + colorize->SetInputConnection(cast->GetOutputPort()); + + vtkOggTheoraWriter *w = vtkOggTheoraWriter::New(); + w->SetInputConnection(colorize->GetOutputPort()); + w->SetFileName("TestOggTheoraWriter.ogv"); + cout << "Writing file TestOggTheoraWriter.ogv..." << endl; + w->Start(); + for ( cc = 2; cc < 99; cc ++ ) + { + cout << "."; + Fractal0->SetMaximumNumberOfIterations(cc); + table->SetTableRange(0, cc); + table->SetNumberOfColors(cc); + table->ForceBuild(); + table->SetTableValue(cc-1, 0, 0, 0); + w->Write(); + } + w->End(); + cout << endl; + cout << "Done writing file TestOggTheoraWriter.ogv..." << endl; + w->Delete(); + + exists = (int) vtksys::SystemTools::FileExists("TestOggTheoraWriter.ogv"); + length = vtksys::SystemTools::FileLength("TestOggTheoraWriter.ogv"); + cout << "TestOggTheoraWriter.ogv file exists: " << exists << endl; + cout << "TestOggTheoraWriter.ogv file length: " << length << endl; + if (!exists) + { + err = 1; + cerr << "ERROR: 1 - Test failing because TestOggTheoraWriter.ogv file doesn't exist..." << endl; + } + else + { + vtksys::SystemTools::RemoveFile("TestOggTheoraWriter.ogv"); + } + if (0==length) + { + err = 2; + cerr << "ERROR: 2 - Test failing because TestOggTheoraWriter.ogv file has zero length..." << endl; + } + + colorize->Delete(); + table->Delete(); + cast->Delete(); + Fractal0->Delete(); + + // err == 0 means test passes... + // + return err; +} diff --git a/VTK/IO/vtkOggTheoraWriter.cxx b/VTK/IO/vtkOggTheoraWriter.cxx new file mode 100644 index 0000000..2ebb53d --- /dev/null +++ b/VTK/IO/vtkOggTheoraWriter.cxx @@ -0,0 +1,602 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkOggTheoraWriter.cxx,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +#include "vtkOggTheoraWriter.h" + +#include "vtkImageData.h" +#include "vtkObjectFactory.h" +#include "vtkErrorCode.h" + +#include "vtk_oggtheora.h" + +//--------------------------------------------------------------------------- +class vtkOggTheoraWriterInternal +{ +public: + vtkOggTheoraWriterInternal(vtkOggTheoraWriter *creator); + ~vtkOggTheoraWriterInternal(); + + int Start(); + int Write(vtkImageData *id); + void End(); + + int Dim[2]; + int FrameRate; + +private: + + // Helper function to convert an RGB image into the Y'CbCr color space and + // into the data structure required by theora (i.e. 4:4:4 or 4:2:0 + // subsampling will be used). Refer to http://www.theora.org/doc/Theora.pdf + // sections 4.3 and 4.3.2. Actually, the equations are inverted. However, I'm + // not sure whether VTK uses gamma-corrected RGB or not. I assume they are + // not, which is what we need here. Assume that the width and height are even + // numbers. + void RGB2YCbCr(vtkImageData *id, th_ycbcr_buffer ycbcr); + // Write the ogg/theora header information + int WriteHeader(); + // Encode a single frame + int EncodeFrame(th_ycbcr_buffer ycbcr, int lastFrame); + + vtkOggTheoraWriter *Writer; + + size_t Off[2]; // offsets of the picture within the frame + th_enc_ctx *thEncContext; // the theora context (has to be freed) + th_ycbcr_buffer thImage; // the Y'CbCr image buffer + ogg_stream_state oggState; // the ogg stream state (has to be cleared) + FILE* outFile; // the output file stream + bool haveImageData; // indicater whether a frame has to be encoded + // (for the leap-frogging) + + int openedFile; + int closedFile; +}; + +//--------------------------------------------------------------------------- +vtkOggTheoraWriterInternal::vtkOggTheoraWriterInternal(vtkOggTheoraWriter *creator) +{ + this->Writer = creator; + this->Dim[0] = 0; + this->Dim[1] = 0; + + this->Off[0] = 0; + this->Off[1] = 0; + this->thEncContext = NULL; + this->outFile = NULL; + this->thImage[0].data = NULL; + this->thImage[1].data = NULL; + this->thImage[2].data = NULL; + + this->openedFile = 0; + this->closedFile = 1; + this->haveImageData = false; + + this->FrameRate = 25; +} + +//--------------------------------------------------------------------------- +vtkOggTheoraWriterInternal::~vtkOggTheoraWriterInternal() +{ + if (!this->closedFile) + { + this->End(); + } +} + +//--------------------------------------------------------------------------- +int vtkOggTheoraWriterInternal::Start() +{ + this->closedFile = 0; + + // ogg information + srand(time(NULL)); + if (ogg_stream_init(&this->oggState,rand())!=0) + { + vtkGenericWarningMacro("Could not initialize ogg stream state."); + return 0; + } + + // fill in theora information + th_info thInfo; + th_info_init(&thInfo); + // frame_width and frame_height must be multiples of 16 + thInfo.frame_width = this->Dim[0]+15&~0xF; + thInfo.frame_height = this->Dim[1]+15&~0xF; + thInfo.pic_width = this->Dim[0]; + thInfo.pic_height = this->Dim[1]; + // force even offsets + // force even offsets of the picture within the frame + this->Off[0] = (thInfo.frame_width-this->Dim[0])>>1&~1; + this->Off[1] = (thInfo.frame_height-this->Dim[1])>>1&~1; + thInfo.pic_x = this->Off[0]; + thInfo.pic_y = this->Off[1]; + thInfo.colorspace = TH_CS_ITU_REC_470BG; + if (this->Writer->GetSubsampling()) + { + // 4:2:0 subsampling is the only implemented option in libtheora-1.0 + thInfo.pixel_fmt = TH_PF_420; + } + else + { + thInfo.pixel_fmt = TH_PF_444; + } + thInfo.target_bitrate = 0; // variable bitrate recording (default) + // allow a variable quality/size tradeoff + //! \todo still have to find appropriate quality parameters though... + //! valid values are in [0,63]. + switch (this->Writer->GetQuality()) + { + case 0: + thInfo.quality = 42; + break; + case 1: + thInfo.quality = 52; + break; + default: + thInfo.quality = 63; + break; + } + thInfo.keyframe_granule_shift = 6; // default value + // the frame rate (as a fraction) + thInfo.fps_numerator = this->FrameRate; + thInfo.fps_denominator = 1; + // pixel ascpect ratio + thInfo.aspect_numerator = 1; + thInfo.aspect_denominator = 1; + + // create the theora encoder context + this->thEncContext = th_encode_alloc(&thInfo); + if (!this->thEncContext) + { + vtkGenericWarningMacro(<< "Could not allocate the theora context."); + return 0; + } + + // create the theora buffer (do not cheat with the frame padding, + // allocate the whole thing!) + for (size_t i=0; i<3; ++i) + { + this->thImage[i].width = thInfo.frame_width; + this->thImage[i].height = thInfo.frame_height; + if (this->Writer->GetSubsampling() && i>0) + { + // Chroma planes are subsampled by a factor of 2 + this->thImage[i].width /= 2; + this->thImage[i].height /= 2; + } + // the stride is in bytes + this->thImage[i].stride = this->thImage[i].width*sizeof(unsigned char); + // make sure there's nothing left lying around... + if (this->thImage[i].data) + delete[] this->thImage[i].data; + // allocate the image plane + size_t siz = this->thImage[i].width * this->thImage[i].height; + this->thImage[i].data = new unsigned char[siz]; + } + + // thInfo is no longer needed + th_info_clear(&thInfo); + + // Finally, open the file and start it off. + this->outFile = fopen(this->Writer->GetFileName(),"wb"); + if (!this->outFile) + { + vtkGenericWarningMacro(<< "Could not open " << this->Writer->GetFileName() << "." ); + return 0; + } + this->openedFile = 1; + + return this->WriteHeader(); + +} + +//--------------------------------------------------------------------------- +// ripped from libtheora-1.0/examples/encoder_example.c +int vtkOggTheoraWriterInternal::WriteHeader() +{ + th_comment thComment; + ogg_packet oggPacket; + ogg_page oggPage; + + th_comment_init(&thComment); + + // first packet will get its own page automatically + if (th_encode_flushheader(this->thEncContext,&thComment,&oggPacket)<=0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + ogg_stream_packetin(&this->oggState,&oggPacket); + if (ogg_stream_pageout(&this->oggState,&oggPage)!=1) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + // remaining theora headers + int ret; + while (true) + { + ret=th_encode_flushheader(this->thEncContext,&thComment,&oggPacket); + if (ret<0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + else if (!ret) + break; + ogg_stream_packetin(&this->oggState,&oggPacket); + } + // Flush the rest of our headers. This ensures + // the actual data in each stream will start + // on a new page, as per spec. + while (true) + { + ret = ogg_stream_flush(&this->oggState,&oggPage); + if (ret<0) + { + vtkGenericWarningMacro("Internal Theora library error."); + return 0; + } + if (ret==0)break; + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + } + + th_comment_clear(&thComment); + + return 1; +} + +//--------------------------------------------------------------------------- +int vtkOggTheoraWriterInternal::Write(vtkImageData *id) +{ + // encode the frame from the last call. + // have to do leap-frogging, because otherwise we can't + // write the EOS page with the last frame in End(). + int ret; + if (this->haveImageData) + { + ret = this->EncodeFrame(this->thImage,0); + this->haveImageData = false; + } + + id->Update(); + + // convert current RGB int YCbCr color space + this->RGB2YCbCr(id,this->thImage); + this->haveImageData = true; + + return ret; +} + +//--------------------------------------------------------------------------- +// ripped from libtheora-1.0/examples/encoder_example.c +int vtkOggTheoraWriterInternal::EncodeFrame(th_ycbcr_buffer ycbcr, int lastFrame) +{ + if (th_encode_ycbcr_in(this->thEncContext,this->thImage)<0) + { + vtkGenericWarningMacro("Error encoding frame."); + return 0; + } + // retrieve and push packets, writing pages as required + ogg_packet oggPacket; + ogg_page oggPage; + int ret; + while (ret=th_encode_packetout(this->thEncContext,lastFrame,&oggPacket)) + { + if(ret<0) + { + vtkGenericWarningMacro("Error retrieving packet from codec."); + return 0; + } + if (ogg_stream_packetin(&this->oggState,&oggPacket)<0) + { + vtkGenericWarningMacro("Error inserting packet into stream."); + return 0; + } + while (ogg_stream_pageout(&this->oggState,&oggPage)) + { + fwrite(oggPage.header,1,oggPage.header_len,this->outFile); + fwrite(oggPage.body,1,oggPage.body_len,this->outFile); + } + } + return 1; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriterInternal::End() +{ + // flush remaining frame + if (this->haveImageData) + if (!this->EncodeFrame(this->thImage,1)) + vtkGenericWarningMacro("Failed to finish writing movie"); + this->haveImageData = false; + + // clean up + for (size_t i = 0; i < 3; ++i) + { + if (this->thImage[i].data) + { + delete[] this->thImage[i].data; + this->thImage[i].data = NULL; + } + } + + if (this->thEncContext) + { + th_encode_free(this->thEncContext); + this->thEncContext = NULL; + } + + ogg_stream_clear(&this->oggState); + + if (this->openedFile) + { + fclose(this->outFile); + this->openedFile = 0; + } + this->closedFile = 1; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriterInternal::RGB2YCbCr(vtkImageData *id, + th_ycbcr_buffer ycbcr) +{ + // convenience + typedef unsigned char uchar; + + // + // constant coefficiens + // + + static const uchar OffY = 16, OffCr = 128, OffCb = 128; + // divide by 255, because the formulas use normalized RGB, i.e in [0,1] + static const double ExcurY = 219.0/255, ExcurCr = 224.0/255, ExcurCb = 224.0/255, + Kr = 0.299, Kb = 0.114; + // derived constants + static const double Kg = 1 - Kr - Kb, + Krm1 = Kr - 1, + Kbm1 = Kb - 1; + // stride between rows in the YCbCr image planes, since + // pixels in a row are contigious, but rows need not be + static const int strideRGB = this->Dim[0]*3, + strideY = ycbcr[0].stride/sizeof(uchar), // th_image_plane strides are in bytes + strideCb = ycbcr[1].stride/sizeof(uchar), + strideCr = ycbcr[2].stride/sizeof(uchar); + // + // computation + // + + // the first pixel in the RGB image + uchar *rgbStart = (uchar*)id->GetScalarPointer(); + // pointers to iterate through the RGB image an the Y, Cb and Cr planes + uchar *rgb, *Y, *Cb, *Cr; + // indicators whether we have to handle chroma planes. + bool isXCPlane = false, + isYCPlane = true; // y-flipping + // loop over rows + size_t x, y, yC; + for (y = 0; y < this->Dim[1]; ++y) + { + if (this->Writer->GetSubsampling()) + { + // reset x indicator and flip y indicator + isXCPlane = false; + isYCPlane = !isYCPlane; + } + // compute pointers to the first pixel in row y, + // flipping y coordinate + rgb = rgbStart + (this->Dim[1]-y-1) * strideRGB; + Y = ycbcr[0].data + (y+this->Off[1]) * strideY + this->Off[0]; + if (!this->Writer->GetSubsampling()) + { + Cb = ycbcr[1].data + (y+this->Off[1]) * strideY + this->Off[0]; + Cr = ycbcr[2].data + (y+this->Off[1]) * strideY + this->Off[0]; + } + else if (isYCPlane) + { + // compute y on chroma planes + yC = (y+this->Off[1])/2; + // pointers to first pixel in row yC of chroma planes + Cb = ycbcr[1].data + yC * strideCb + this->Off[0]/2; + Cr = ycbcr[2].data + yC * strideCr + this->Off[0]/2; + } + // loop over columns in row y + for (x = 0; x < this->Dim[0]; ++x) + { + // do the actual transformation + *Y = uchar((Kr * rgb[0] + Kg * rgb[1] + Kb *rgb[2]) *ExcurY ) + OffY; + if (!this->Writer->GetSubsampling()) + { + *Cb = uchar((Kr * rgb[0] + Kg * rgb[1] + Kbm1 * rgb[2]) / + (2 * Kbm1) * ExcurCb) + OffCb; + *Cr = uchar((Krm1 * rgb[0] + Kg * rgb[1] + Kb * rgb[2]) / + (2 * Krm1) * ExcurCr) + OffCr; + } + else + { + // flip indicator + isXCPlane = !isXCPlane; + if (isYCPlane && isXCPlane) + { + /* REMARK: actually, interpolation seems to give worse results... + * just use the associated RGB pixel (a.k.a nearest neighbor). + */ +#if 0 + // interpolate surrounding rgb (subsampling) + // use double in order to not loose too much precision... + double irgb[3]; + for (size_t i = 0; i < 3; ++i) + { + irgb[i] = 0.25 * (rgb[i] + rgb[i+3] + + rgb[i+strideRGB] + rgb[i+3+strideRGB]); + } + *Cb = uchar((Kr * irgb[0] + Kg * irgb[1] + Kbm1 * irgb[2]) / + (2 * Kbm1) * ExcurCb) + OffCb; + *Cr = uchar((Krm1 * irgb[0] + Kg * irgb[1] + Kb * irgb[2]) / + (2 * Krm1) * ExcurCr) + OffCr; +#else + *Cb = uchar((Kr * rgb[0] + Kg * rgb[1] + Kbm1 * rgb[2]) / + (2 * Kbm1) * ExcurCb) + OffCb; + *Cr = uchar((Krm1 * rgb[0] + Kg * rgb[1] + Kb * rgb[2]) / + (2 * Krm1) * ExcurCr) + OffCr; +#endif + } + } + // advance to next pixel in row y + rgb += 3; + ++Y; + if (!this->Writer->GetSubsampling()) + { + ++Cb; + ++Cr; + } + else if (isYCPlane && isXCPlane) + { + ++Cb; + ++Cr; + } + } + } +} + +//--------------------------------------------------------------------------- +vtkStandardNewMacro(vtkOggTheoraWriter); +vtkCxxRevisionMacro(vtkOggTheoraWriter, "$Revision:$"); + +//--------------------------------------------------------------------------- +vtkOggTheoraWriter::vtkOggTheoraWriter() +{ + this->Internals = 0; + this->Quality = 2; + this->Rate = 25; + this->Subsampling = 0; +} + +//--------------------------------------------------------------------------- +vtkOggTheoraWriter::~vtkOggTheoraWriter() +{ + delete this->Internals; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::Start() +{ + this->Error = 1; + + if ( this->Internals ) + { + vtkErrorMacro("Movie already started."); + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + if ( this->GetInput() == NULL ) + { + vtkErrorMacro("Please specify an input."); + this->SetErrorCode(vtkGenericMovieWriter::NoInputError); + return; + } + if (!this->FileName) + { + vtkErrorMacro("Please specify a filename."); + this->SetErrorCode(vtkErrorCode::NoFileNameError); + return; + } + + this->Internals = new vtkOggTheoraWriterInternal(this); + + this->Error = 0; + + this->Initialized = 0; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::Write() +{ + if (this->Error) + { + return; + } + + if ( !this->Internals ) + { + vtkErrorMacro("Movie not started."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + + // get the data + this->GetInput()->UpdateInformation(); + int *wExtent = this->GetInput()->GetWholeExtent(); + this->GetInput()->SetUpdateExtent(wExtent); + this->GetInput()->Update(); + + int dim[4]; + this->GetInput()->GetDimensions(dim); + if ( this->Internals->Dim[0] == 0 && this->Internals->Dim[1] == 0 ) + { + this->Internals->Dim[0] = dim[0]; + this->Internals->Dim[1] = dim[1]; + } + + if (this->Internals->Dim[0]!= dim[0] || this->Internals->Dim[1]!= dim[1]) + { + vtkErrorMacro("Image not of the same size."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::ChangedResolutionError); + return; + } + + if ( !this->Initialized ) + { + this->Internals->FrameRate = this->Rate; + if (!this->Internals->Start()) + { + vtkErrorMacro("Error initializing video stream."); + this->Error = 1; + this->SetErrorCode(vtkGenericMovieWriter::InitError); + return; + } + this->Initialized = 1; + } + + if (!this->Internals->Write(this->GetInput())) + { + vtkErrorMacro("Error storing image."); + this->Error = 1; + this->SetErrorCode(vtkErrorCode::OutOfDiskSpaceError); + } +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::End() +{ + this->Internals->End(); + + delete this->Internals; + this->Internals = 0; +} + +//--------------------------------------------------------------------------- +void vtkOggTheoraWriter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "Quality: " << this->Quality << endl; + os << indent << "Rate: " << this->Rate << endl; + os << indent << "Subsampling: " << this->Subsampling << endl; +} diff --git a/VTK/IO/vtkOggTheoraWriter.h b/VTK/IO/vtkOggTheoraWriter.h new file mode 100644 index 0000000..b673cd8 --- /dev/null +++ b/VTK/IO/vtkOggTheoraWriter.h @@ -0,0 +1,84 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkOggTheoraWriter.h,v $ + + Copyright (c) Michael Wild, Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +// .NAME vtkOggTheoraWriter - Uses the ogg and theora libraries to write video +// files. +// .SECTION Description +// vtkOggTheoraWriter is an adapter that allows VTK to use the ogg and theora +// libraries to write movie files. This class creates .ogv files containing +// theora encoded video without audio. +// +// This implementation is based on vtkFFMPEGWriter and uses some code derived +// from the encoder example distributed with libtheora. +// +// .SECTION See Also vtkGenericMovieWriter vtkAVIWriter vtkMPEG2Writer vtkFFMPEGWriter + +#ifndef __vtkOggTheoraWriter_h +#define __vtkOggTheoraWriter_h + +#include "vtkGenericMovieWriter.h" + +class vtkOggTheoraWriterInternal; + +class VTK_IO_EXPORT vtkOggTheoraWriter : public vtkGenericMovieWriter +{ +public: + static vtkOggTheoraWriter *New(); + vtkTypeRevisionMacro(vtkOggTheoraWriter,vtkGenericMovieWriter); + void PrintSelf(ostream& os, vtkIndent indent); + + // Description: + // These methods start writing an Movie file, write a frame to the file + // and then end the writing process. + void Start(); + void Write(); + void End(); + + // Description: + // Set/Get the compression quality. + // 0 means worst quality and smallest file size + // 2 means best quality and largest file size + vtkSetClampMacro(Quality, int, 0, 2); + vtkGetMacro(Quality, int); + + // Description: + // Set/Get the frame rate, in frame/s. + vtkSetClampMacro(Rate, int , 1, 5000); + vtkGetMacro(Rate, int); + + // Description: + // Is the video to be encoded using 4:2:0 subsampling? + vtkSetMacro(Subsampling, int); + vtkGetMacro(Subsampling, int); + vtkBooleanMacro(Subsampling, int); +protected: + vtkOggTheoraWriter(); + ~vtkOggTheoraWriter(); + + vtkOggTheoraWriterInternal *Internals; + + int Initialized; + int Quality; + int Rate; + int Subsampling; + +private: + vtkOggTheoraWriter(const vtkOggTheoraWriter&); // Not implemented + void operator=(const vtkOggTheoraWriter&); // Not implemented +}; + +#endif + + + -- 1.6.3.3 ![]() From 18940a0aa41ab601714e71d2d08889dc33c33094 Mon Sep 17 00:00:00 2001 From: Michael Wild <themiwi@users.sourceforge.net> Date: Fri, 17 Jul 2009 11:26:31 +0200 Subject: [PATCH 3/3] ENH: Integrated vtkOggTheoraWriter into ParaView The Subsampling property is propagated to the vtkSMAnimationSceneImageWriter, but so far there is no GUI control. Signed-off-by: Michael Wild <themiwi@users.sourceforge.net> --- Applications/OverView/Core/AboutDialog.cxx | 2 ++ Qt/Components/pqAnimationManager.cxx | 4 ++++ Qt/Components/pqClientAboutDialog.cxx | 2 ++ Servers/Common/vtkPVServerInformation.cxx | 13 +++++++++++++ Servers/Common/vtkPVServerInformation.h | 6 ++++++ .../vtkSMAnimationSceneImageWriter.cxx | 14 +++++++++++++- .../ServerManager/vtkSMAnimationSceneImageWriter.h | 11 ++++++++++- 7 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Applications/OverView/Core/AboutDialog.cxx b/Applications/OverView/Core/AboutDialog.cxx index 00ba766..891eec7 100755 --- a/Applications/OverView/Core/AboutDialog.cxx +++ b/Applications/OverView/Core/AboutDialog.cxx @@ -218,6 +218,8 @@ void AboutDialog::AddServerInformation(pqServer* server, QTreeWidget* tree) ::addItem(tree, "Tile Display", "Off"); } + ::addItem(tree, "Write Ogg/Theora Animations", + serverInfo->GetOGVSupport()? "On": "Off"); ::addItem(tree, "Write AVI Animations", serverInfo->GetAVISupport()? "On": "Off"); } diff --git a/Qt/Components/pqAnimationManager.cxx b/Qt/Components/pqAnimationManager.cxx index 76c40bc..30d1946 100644 --- a/Qt/Components/pqAnimationManager.cxx +++ b/Qt/Components/pqAnimationManager.cxx @@ -457,6 +457,10 @@ bool pqAnimationManager::saveAnimation() } QString filters = ""; + if (serverInfo && serverInfo->GetOGVSupport()) + { + filters += "Ogg/Theora files (*.ogv);;"; + } if (serverInfo && serverInfo->GetAVISupport()) { filters += "AVI files (*.avi);;"; diff --git a/Qt/Components/pqClientAboutDialog.cxx b/Qt/Components/pqClientAboutDialog.cxx index 4beae0c..8d748e6 100644 --- a/Qt/Components/pqClientAboutDialog.cxx +++ b/Qt/Components/pqClientAboutDialog.cxx @@ -218,6 +218,8 @@ void pqClientAboutDialog::AddServerInformation(pqServer* server, QTreeWidget* tr ::addItem(tree, "Tile Display", "Off"); } + ::addItem(tree, "Write Ogg/Theora Animations", + serverInfo->GetOGVSupport()? "On": "Off"); ::addItem(tree, "Write AVI Animations", serverInfo->GetAVISupport()? "On": "Off"); } diff --git a/Servers/Common/vtkPVServerInformation.cxx b/Servers/Common/vtkPVServerInformation.cxx index 7e42f98..ac64e6f 100755 --- a/Servers/Common/vtkPVServerInformation.cxx +++ b/Servers/Common/vtkPVServerInformation.cxx @@ -48,6 +48,7 @@ vtkPVServerInformation::vtkPVServerInformation() this->AVISupport = 1; # endif #endif + this->OGVSupport = 1; this->RenderModuleName = NULL; this->MachinesInternals = new vtkPVServerOptionsInternals; @@ -73,6 +74,7 @@ void vtkPVServerInformation::PrintSelf(ostream& os, vtkIndent indent) os << indent << "UseIceT: " << this->UseIceT << endl; os << indent << "RenderModuleName: " << (this->RenderModuleName ? this->RenderModuleName : "(none)") << endl; + os << indent << "OGVSupport: " << this->OGVSupport << endl; os << indent << "AVISupport: " << this->AVISupport << endl; os << indent << "Timeout: " << this->Timeout << endl; } @@ -167,6 +169,11 @@ void vtkPVServerInformation::AddInformation(vtkPVInformation* info) this->Timeout = serverInfo->GetTimeout(); } + if (!serverInfo->GetOGVSupport()) + { + this->OGVSupport = 0; + } + if (!serverInfo->GetAVISupport()) { this->AVISupport = 0; @@ -199,6 +206,7 @@ void vtkPVServerInformation::CopyToStream(vtkClientServerStream* css) *css << this->Timeout; *css << this->UseIceT; *css << this->RenderModuleName; + *css << this->OGVSupport; *css << this->AVISupport; *css << this->GetNumberOfMachines(); unsigned int idx; @@ -257,6 +265,11 @@ void vtkPVServerInformation::CopyFromStream(const vtkClientServerStream* css) return; } this->SetRenderModuleName(rmName); + if (!css->GetArgument(0, 9, &this->OGVSupport)) + { + vtkErrorMacro("Error parsing OGVSupport flag from message."); + return; + } if (!css->GetArgument(0, 9, &this->AVISupport)) { vtkErrorMacro("Error parsing AVISupport flag from message."); diff --git a/Servers/Common/vtkPVServerInformation.h b/Servers/Common/vtkPVServerInformation.h index aafe6d6..cfc2fcd 100755 --- a/Servers/Common/vtkPVServerInformation.h +++ b/Servers/Common/vtkPVServerInformation.h @@ -80,6 +80,11 @@ public: vtkGetStringMacro(RenderModuleName); // Description: + // Get/Set if the server supports saving OGVs. + vtkSetMacro(OGVSupport, int); + vtkGetMacro(OGVSupport, int); + + // Description: // Get/Set if the server supports saving AVIs. vtkSetMacro(AVISupport, int); vtkGetMacro(AVISupport, int); @@ -120,6 +125,7 @@ protected: vtkPVServerInformation(); ~vtkPVServerInformation(); + int OGVSupport; int AVISupport; int RemoteRendering; int TileDimensions[2]; diff --git a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx index fde7ac2..5212697 100644 --- a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx +++ b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.cxx @@ -45,6 +45,7 @@ # include "vtkFFMPEGWriter.h" # endif #endif +#include "vtkOggTheoraWriter.h" vtkStandardNewMacro(vtkSMAnimationSceneImageWriter); vtkCxxRevisionMacro(vtkSMAnimationSceneImageWriter, "$Revision: 1.14 $"); @@ -57,7 +58,8 @@ vtkSMAnimationSceneImageWriter::vtkSMAnimationSceneImageWriter() { this->Magnification = 1; this->ErrorCode = 0; - this->Quality = 2; // 0 = low, 1 = medium, 2 = high + this->Quality = 2; // 0 = low, 1 = medium, 2 = hig + this->Subsampling = 0; this->ActualSize[0] = this->ActualSize[1] = 0; this->MovieWriter = 0; @@ -377,6 +379,15 @@ bool vtkSMAnimationSceneImageWriter::CreateWriter() } # endif #endif + else if (extension == ".ogv" || extension == ".ogg") + { + vtkOggTheoraWriter *ogvwriter = vtkOggTheoraWriter::New(); + ogvwriter->SetQuality(this->Quality); + ogvwriter->SetRate( + static_cast<int>(this->GetFrameRate())); + ogvwriter->SetSubsampling(this->GetSubsampling()); + mwriter = ogvwriter; + } else { vtkErrorMacro("Unknown extension " << extension.c_str()); @@ -434,6 +445,7 @@ void vtkSMAnimationSceneImageWriter::PrintSelf(ostream& os, vtkIndent indent) this->Superclass::PrintSelf(os, indent); os << indent << "Quality: " << this->Quality << endl; os << indent << "Magnification: " << this->Magnification << endl; + os << indent << "Subsampling: " << this->Subsampling << endl; os << indent << "ErrorCode: " << this->ErrorCode << endl; os << indent << "FrameRate: " << this->FrameRate << endl; os << indent << "BackgroundColor: " << this->BackgroundColor[0] diff --git a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h index 819510d..0b1c4a3 100644 --- a/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h +++ b/Servers/ServerManager/vtkSMAnimationSceneImageWriter.h @@ -56,6 +56,15 @@ public: vtkGetMacro(Quality, int); // Description: + // Get/Set the setting whether the movie encoder should use subsampling of + // the chrome planes or not, if applicable. Since the human eye is more + // sensitive to brightness than color variations, subsampling can be + // useful to reduce the bitrate. Default value is 0. + vtkSetMacro(Subsampling, double); + vtkGetMacro(Subsampling, double); + vtkBooleanMacro(Subsampling, double); + + // Description: // Get the error code which is set if there's an error while writing // the images. vtkGetMacro(ErrorCode, int); @@ -76,7 +85,6 @@ public: vtkSetMacro(FrameRate, double); vtkGetMacro(FrameRate, double); - // Description: // Convenience method used to merge a smaller image (\c src) into a // larger one (\c dest). The location of the smaller image in the larger image @@ -121,6 +129,7 @@ protected: int Magnification; int FileCount; int ErrorCode; + int Subsampling; char* Prefix; char* Suffix; -- 1.6.3.3 | ||||||||
Relationships | |
Relationships |
Notes | |
(0017117) Michael Wild (reporter) 2009-08-13 10:47 |
Attached an updated set of patches (suffixed with '_updated' to disambiguate them from the old ones). The first three patches attached to this report are obsolete and should not be used except for reference. libtheora has been updated to version 1.1beta2, now supporting 4:4:4 subsampling (i.e. "no" subsampling at all). vtkOggTheoraWriter now has a property 'Subsampling': if it is enabled, 4:2:0 subsampling will be used, otherwise 4:4:4 will be used. The default is 4:4:4. The same property has been added to vtkSMAnimationSceneImageWriter. However, as for the Quality setting, there is not GUI control. |
(0018055) Michael Wild (reporter) 2009-10-11 14:51 |
Updated the first patch to use the newly released libtheora-1.1.1 instead of 1.1beta2. Also updated the patches to work against current ParaView-CVS. The new version are suffixed by _v3. |
(0018357) Michael Wild (reporter) 2009-11-06 02:37 |
Instead of attaching new files every time I update the patches (which gets to be very confusing), I will maintain them in my GIT repository: http://github.com/themiwi/ParaView/commits/patches/OggTheoraWriter [^] |
(0018879) Dave DeMarle (administrator) 2009-12-14 16:22 |
The patch is in. Thanks for the contribution! |
Notes |
Issue History | |||
Date Modified | Username | Field | Change |
2009-07-17 09:27 | Michael Wild | New Issue | |
2009-07-17 09:27 | Michael Wild | File Added: 0001-ENH-Added-vtkoggtheora-library-Ogg-Theora-to-VTK.patch | |
2009-07-17 09:27 | Michael Wild | File Added: 0002-ENH-Added-vtkOggTheoraWriter-to-VTK.patch | |
2009-07-17 09:27 | Michael Wild | File Added: 0003-ENH-Integrated-vtkOggTheoraWriter-into-ParaView.patch | |
2009-08-13 10:41 | Michael Wild | File Added: 0001-ENH-Added-vtkoggtheora-library-Ogg-Theora-to-VTK_updated.patch | |
2009-08-13 10:41 | Michael Wild | File Added: 0002-ENH-Added-vtkOggTheoraWriter-to-VTK_updated.patch | |
2009-08-13 10:41 | Michael Wild | File Added: 0003-ENH-Integrated-vtkOggTheoraWriter-into-ParaView_updated.patch | |
2009-08-13 10:47 | Michael Wild | Note Added: 0017117 | |
2009-10-11 14:49 | Michael Wild | File Added: 0001-ENH-Added-vtkoggtheora-library-Ogg-Theora-to-VTK_v3.patch | |
2009-10-11 14:49 | Michael Wild | File Added: 0002-ENH-Added-vtkOggTheoraWriter-to-VTK_v3.patch | |
2009-10-11 14:49 | Michael Wild | File Added: 0003-ENH-Integrated-vtkOggTheoraWriter-into-ParaView_v3.patch | |
2009-10-11 14:51 | Michael Wild | Note Added: 0018055 | |
2009-11-06 02:37 | Michael Wild | Note Added: 0018357 | |
2009-12-14 16:22 | Dave DeMarle | Note Added: 0018879 | |
2009-12-14 16:24 | Dave DeMarle | Status | backlog => closed |
2011-06-16 13:10 | Zack Galbreath | Category | Feature Request => Feature |
Issue History |
Copyright © 2000 - 2018 MantisBT Team |