working on examples, editing, and export

master
Jacob Gustafson 2016-08-06 11:21:27 -04:00
parent 4445c70600
commit 7a8add81f5
9 changed files with 311 additions and 124 deletions

View File

@ -6,20 +6,24 @@ This is a manual rotoscoping (frame by frame painting) application. Rotoscoping
### Why aren't there more rotoscoping applications?
Possibly, rotoscoping applications are not considered commercially viable since there is unavoidable lag situations that can't be reasonably blamed on developers but inevitably are (see "Caveats" below). Unavoidable lag and video format complexity issues could be reasons for the discontinuation of such programs as Ulead ® VideoPaint ® (this project is not affiliated with Corel ® or Ulead ®). Another reason could be that rotoscoping is highly dependent on the source frame remaining the same, whereas MPEG (variants of which are used almost everywhere) has inherently inexact frame seeking. These issues may be partially or completely resolved by advanced caching (for the lag issue) and advanced frame seeking algorithms (for the accuracy issue), either of which are not easily achieved but both of which are needed in a professional video application (since video, which has slow seeking would be required, and MPEG, which is normally inaccurate would be required). Even if image sequences are used to resolve the seek lag and seek accuracy issues, seek lag normally remains simply because the edits have to be re-applied or re-loaded (basically, a multi-layer image project needs to be loaded each time seeking to a different frame). Leaving rotoscoping out entirely is often seen as the only way to avoid these issues. This program aims to implement rotoscoping regardless of the possibility of seek lag or requiring image sequences (to avoid seek accuracy issues). In this project, rotoscoping (the core feature) is considered to be an indispensable part of video editing, regardless of the fact that meeting the expectations of normal consumers (primarily expectations for speed and format support) may be impractical to be achieved by volunteer programmers, or may be impossible for technical reasons described above.
## Changes
* (2016-08-05) detect format instead of ever using the strings "png" or ".png" during loading (but continue to use png for all saved image data, as nondestructive layers)
## Caveats
* Lag during frame loading cannot be avoided, since each video frame must be loaded at full quality, which at 1080p takes up an unavoidable 8100kb per layer. Maximum performance could be achieved when one or more frames in either direction of the current frame are cached, in their edited form. However, upon editing, the cache will have to be updated and the image, redrawn. To prevent further lag in that situation, the source frame (base layer) could be cached so that editing layers can be applied without reloading the frame from the source video file.
* At this time, image sequences are required. MPEG-derived formats may or may not ever be added, since MPEG-style frame seeking is inexact and rotoscoping is highly dependent on the source frame remaining the same.
## Known Issues
* not yet done first working version (expected August 2016)
* initially will only input and output image sequences.
* detect format instead of ever using the strings "png" or ".png"
* layer cache (purpose for unused variable cacheMaxMB) is not yet implemented
* Implement blocker layer type
* complete first working version
## Planned Features
* implement alpha.png
* Implement blocker layer type (make an animated object that seems to "undo" previous edits, such as to reveal parts of characters under the effect, without permanently erasing any part of the effect)
* use alpha.png for reducing opacity of parts of background layer
* allow blocker layer type (make an animated object that seems to "undo" previous edits, such as to reveal parts of characters under the effect, without permanently erasing any part of the effect)
* use layer cache (purpose for unused variable cacheMaxMB)
## Low-priority Known Issues
* drawLineTo (this is used for painting) should draw line instead of last point
## Developer Notes
The RotoCanvas class is modular, with hopes that it can be used by various video editing applications in the future. The recommended use of RotoCanvas in a video editing application is for applying effects (primarily manual rotoscoping) to source videos (as frame sequences) as a preprocessing step before they are trimmed or other effects are added, since rotoscoping is highly dependent on the source frame (base layer) remaining the same. Using the RotoCanvas as a post-processing effect is possible, but accurate frame seeking must be assured somehow (such as by a frame-accurate video editing engine), and further edits to the previous layers will in some cases cause the rotoscoped parts (parts of the image edited by RotoCanvas) to no longer make sense (such as, if a lens pinch effect is added to a scene where there was a layer order error that has been rotoscoped out, instead of the error being rotoscoped out, there will be both the error and a corrected blotch that is the error's original position & shape), which in such cases would require redoing the rotoscoping.

View File

@ -0,0 +1,8 @@
Content-Type: text/blnk
Type:Directory
NoDisplay:true
Terminal:false
Name:ImageSequenceExamples - Shortcut
Encoding:UTF-8
Comment:File or folder shortcut generated by lnk-to-blnk
Exec:C:\Users\Owner\Videos\ImageSequenceExamples

BIN
examples/examples.blend Normal file

Binary file not shown.

View File

@ -52,7 +52,8 @@ void MainWindow::open()
{
if (maybeSave()) {
QString path=QDir::currentPath();
QString tryPath="C:\\Users\\Owner\\Videos\\NTWAOG (Music Video)\\Media\\Sequence 00092 hovering\\00092a";
//QString tryPath="C:\\Users\\Owner\\Videos\\NTWAOG (Music Video)\\Media\\Sequence 00092 hovering\\00092a";
QString tryPath="C:\\Users\\Owner\\Videos\\ImageSequenceExamples";
QDir defaultDir=QDir(tryPath);
if (defaultDir.exists()) path=tryPath;
QString fileName = QFileDialog::getOpenFileName(this,
@ -69,7 +70,7 @@ void MainWindow::save()
{
QAction *action = qobject_cast<QAction *>(sender());
QByteArray fileFormat = action->data().toByteArray();
saveFile(fileFormat);
saveFrame();//fileFormat);
}
//! [6]
@ -141,6 +142,10 @@ void MainWindow::createActions()
openAct->setShortcuts(QKeySequence::Open);
connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
saveFrameAct = new QAction(tr("&Save..."), this);
saveFrameAct->setShortcut(QKeySequence::Save);
connect(saveFrameAct, SIGNAL(triggered()), this, SLOT(save()));
foreach (QByteArray format, QImageWriter::supportedImageFormats()) {
QString text = tr("%1...").arg(QString(format).toUpper());
@ -167,7 +172,7 @@ void MainWindow::createActions()
connect(brushHardnessAct, SIGNAL(triggered()), this, SLOT(askBrushHardness()));
brushOpacityAct = new QAction(tr("Brush &Opacity..."), this);
connect(brushOpacityAct, SIGNAL(triggered()), this, SLOT(askOpacity()));
connect(brushOpacityAct, SIGNAL(triggered()), this, SLOT(askBrushOpacity()));
clearScreenAct = new QAction(tr("&Clear Screen"), this);
clearScreenAct->setShortcut(tr("Ctrl+L"));
@ -192,6 +197,7 @@ void MainWindow::createMenus()
fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(openAct);
fileMenu->addAction(saveFrameAct);
fileMenu->addMenu(saveAsMenu);
fileMenu->addAction(printAct);
fileMenu->addSeparator();
@ -226,7 +232,7 @@ bool MainWindow::maybeSave()
QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel);
if (ret == QMessageBox::Save) {
return saveFile("png");
return saveFrame();
} else if (ret == QMessageBox::Cancel) {
return false;
}
@ -236,20 +242,37 @@ bool MainWindow::maybeSave()
//! [18]
//! [19]
bool MainWindow::saveFile(const QByteArray &fileFormat)
bool MainWindow::saveFrame() // const QByteArray &fileFormat)
//! [19] //! [20]
{
QString initialPath = QDir::currentPath() + "/untitled." + fileFormat;
return rotocanvas->saveFrame();
}
QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
initialPath,
tr("%1 Files (*.%2);;All Files (*)")
.arg(QString::fromLatin1(fileFormat.toUpper()))
.arg(QString::fromLatin1(fileFormat)));
if (fileName.isEmpty()) {
return false;
} else {
return rotocanvas->saveImage(fileName, fileFormat.constData());
}
bool MainWindow::exportSequence()
{
QByteArray fileFormat("png");
QString initialPath = QDir::currentPath() + "untitled.png"; //"/untitled." + fileFormat;
QString fileName = QFileDialog::getSaveFileName(this, tr("Export Image Sequence (#s will be added)"),
initialPath,
tr("%1 Files (*.%2);;All Files (*)")
.arg(QString::fromLatin1(fileFormat.toUpper()))
.arg(QString::fromLatin1(fileFormat)));
if (fileName.isEmpty()) {
return false;
} else {
QFileInfo frameFI(fileName);
QString sequenceName=frameFI.completeBaseName(); //baseName gets name such as file from file.tar.gz, so use completeBaseName
int resultCount=rotocanvas->exportFrames(frameFI.dir(), sequenceName, fileFormat);//(fileName, fileFormat.constData());
QString msg="Exported "+QString::number(resultCount)+" frame(s).";
if (resultCount<1) msg="ERROR: "+msg;
QMessageBox msgBox;
msgBox.setText("Finished Exporting Image Sequence");
msgBox.setInformativeText(msg);
msgBox.setStandardButtons(QMessageBox::Ok); //msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
int thisDialogResult=msgBox.exec();
//if (thisDialogResult==QMessageBox::OK)
}
}
//! [20]

View File

@ -32,7 +32,8 @@ private:
void createActions();
void createMenus();
bool maybeSave();
bool saveFile(const QByteArray &fileFormat);
bool saveFrame(); // const QByteArray &fileFormat);
bool exportSequence();
QVBoxLayout *vBoxLayout;
@ -45,6 +46,7 @@ private:
QMenu *helpMenu;
QAction *openAct;
QAction *saveFrameAct;
QList<QAction *> saveAsActs;
QAction *exitAct;
QAction *brushColorAct;

View File

@ -11,9 +11,10 @@ RotoCanvas::RotoCanvas(QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_StaticContents);
isModified = false;
//isModified = false;
isToolActive = false;
selectedLayerIndex = -1;
layerCount = 1; // always have at least one layer (layer 0, the nondestructive edits to the background)
//frameNumber = -1;
//minDigitCount = 4;
cacheMaxMB = 800.0; //NOTE: 1920*1080*4 = 8294400 = ~7.91 MB, so 800MB is around 100 HD frames
@ -87,7 +88,7 @@ QString RotoCanvas::getFolderPath(QFileInfo frameFI)
QString RotoCanvas::getFolderPath()
{
if (this->loadedFI!=nullptr) RotoCanvas::getFolderPath(*this->loadedFI);
if (this->loadedFI!=nullptr) return RotoCanvas::getFolderPath(*this->loadedFI);
else return "";
}
@ -108,7 +109,7 @@ QString RotoCanvas::getSeqPaddedFrameNumber(QFileInfo frameFI)
QString RotoCanvas::getSeqPaddedFrameNumber()
{
if (this->loadedFI!=nullptr) RotoCanvas::getSeqPaddedFrameNumber(*this->loadedFI);
if (this->loadedFI!=nullptr) return RotoCanvas::getSeqPaddedFrameNumber(*this->loadedFI);
else return "";
}
@ -156,35 +157,76 @@ QString RotoCanvas::getLayersFolderPath(int frameNumber, bool createEnable)
else return "";
}
QString RotoCanvas::getLayerImagePathMostRecent(QString framePath, int frameNumber, int layerNumber)
QString RotoCanvas::getLayerImagePathMostRecent(QString framePath, int frameNumber, int layerNumber, int* returnFrameNumber)
{
//QString result;
QFileInfo frameFI(framePath);
return RotoCanvas::getLayerImagePathMostRecent(frameFI, frameNumber, createEnable);
return RotoCanvas::getLayerImagePathMostRecent(frameFI, frameNumber, layerNumber, returnFrameNumber);
}
QString RotoCanvas::getLayerImagePathMostRecent(QFileInfo frameFI, int frameNumber, int layerNumber)
QString RotoCanvas::getLayerImagePathMostRecent(QFileInfo frameFI, int frameNumber, int layerNumber, int* returnFrameNumber)
{
QString result="";
QString layersPath=RotoCanvas::getLayersFolderPath(frameFI,frameNumber,false);
QDir layersDir(layersPath);
QString thisLayerImagePath=layersDir.filePath(QString::number(layerNumber)+".png");
QString thisLayerImagePath=layersDir.filePath(QString::number(layerNumber)+"."+RotoCanvas::getSeqFormatString(frameFI));
QFileInfo thisLayerImageFI(thisLayerImagePath);
int thisFrameNumber=frameNumber;
while (thisFrameNumber>=0 && !thisLayerImageFI.exists()) {
layersPath=RotoCanvas::getLayersFolderPath(frameFI,frameNumber,false);
layersDir=QDir(layersPath);
thisLayerImagePath=layersDir.filePath(QString::number(layerNumber)+".png");
thisLayerImagePath=layersDir.filePath(QString::number(layerNumber)+"."+RotoCanvas::getSeqFormatString(frameFI));
thisLayerImageFI=QFileInfo(thisLayerImagePath);
thisFrameNumber--;
}
if (thisLayerImageFI.exists()) result=thisLayerImageFI.path();
thisFrameNumber++; // go back to last used value
if (thisLayerImageFI.exists()) {
result=thisLayerImageFI.path();
if (returnFrameNumber!=nullptr) *returnFrameNumber=thisFrameNumber;
}
else {
if (returnFrameNumber!=nullptr) *returnFrameNumber=-1;
}
return result;
}
QString RotoCanvas::getLayerImagePathMostRecent(int frameNumber, int layerNumber)
QString RotoCanvas::getLayerImagePathMostRecent(int frameNumber, int layerNumber, int* returnFrameNumber)
{
if (this->loadedFI!=nullptr) return RotoCanvas::getLayerImagePathMostRecent(*this->loadedFI, frameNumber, layerNumber);
if (this->loadedFI!=nullptr) return RotoCanvas::getLayerImagePathMostRecent(*this->loadedFI, frameNumber, layerNumber, returnFrameNumber);
else return "";
}
QString RotoCanvas::getFrameName(QString framePath, int frameNumber)
{
QFileInfo frameFI(framePath);
return RotoCanvas::getFrameName(frameFI, frameNumber);
}
QString RotoCanvas::getFrameName(QFileInfo frameFI, int frameNumber)
{
return RotoCanvas::getSeqName(frameFI) + RotoCanvas::getZeroPadded(frameNumber, RotoCanvas::getSeqDigitCount(frameFI)) + "." + frameFI.suffix();
}
QString RotoCanvas::getFrameName(int frameNumber)
{
if (this->loadedFI!=nullptr) return RotoCanvas::getFrameName(*this->loadedFI, frameNumber);
else return "";
}
QString RotoCanvas::getFramePath(QString framePath, int frameNumber)
{
QFileInfo frameFI(framePath);
return RotoCanvas::getFramePath(frameFI, frameNumber);
}
QString RotoCanvas::getFramePath(QFileInfo frameFI, int frameNumber)
{
return frameFI.dir().filePath(RotoCanvas::getFrameName(frameFI, frameNumber));
}
QString RotoCanvas::getFramePath(int frameNumber)
{
if (this->loadedFI!=nullptr) return RotoCanvas::getFramePath(*this->loadedFI, frameNumber);
else return "";
}
@ -252,17 +294,17 @@ int RotoCanvas::getSeqDigitCount()
else return -1;
}
QString RotoCanvas::getSeqFramePath(QString folderPath, QString seqName, int frameNumber, int minDigitCount, QString format)
QString RotoCanvas::getFramePath(QString folderPath, QString seqName, int frameNumber, int minDigitCount, QString format)
{
QString fileName=seqName+RotoCanvas::getZeroPadded(frameNumber,minDigitCount)+"."+format;
return QDir::cleanPath(folderPath+QDir::separator()+fileName);
}
QString RotoCanvas::getSeqFramePath(int frameNumber)
{
if (this->loadedFI!=nullptr) return RotoCanvas::getSeqFramePath(this->loadedFI->dir().path(), getSeqName(),getSeqDigitCount(),getSeqFormatString());
else return "";
}
//QString RotoCanvas::getFramePath(int frameNumber)
//{
// if (this->loadedFI!=nullptr) return RotoCanvas::getFramePath(this->loadedFI->dir().path(), getSeqName(),frameNumber,getSeqDigitCount(),getSeqFormatString());
// else return "";
//}
QString RotoCanvas::getZeroPadded(int frameNumber, int minDigitCount)
{
@ -273,12 +315,45 @@ QString RotoCanvas::getZeroPadded(int frameNumber, int minDigitCount)
}
return result;
}
void RotoCanvas::drawAlphaPix(QImage *destImage, int x, int y, QColor sourceColor, double this_opacity)
{
QColor thisColor;
if (destImage!=nullptr) {
QColor destColor = destImage->pixelColor(x,y);
//do alpha formula (byte)((source-dest)*alpha/255.0f+dest+.5f)
int r = (int)((sourceColor.red()-destColor.red())*this_opacity+destColor.red()+.5); //+.5 for rounding
int g = (int)((sourceColor.green()-destColor.green())*this_opacity+destColor.green()+.5); //+.5 for rounding
int b = (int)((sourceColor.blue()-destColor.blue())*this_opacity+destColor.blue()+.5); //+.5 for rounding
int a = (int)((sourceColor.alpha()-destColor.alpha())*this_opacity+destColor.alpha()+.5); //+.5 for rounding
thisColor.setRed(r);
thisColor.setGreen(g);
thisColor.setBlue(b);
thisColor.setAlpha(a);
destImage->setPixelColor(x, y, thisColor);
//painter.setOpacity(this_opacity);
//painter.drawPoint(x,y);
}
}
bool RotoCanvas::getIsModified()
{
bool result=false;
for (int i=0; i<layerPtrs.length(); i++) {
if (layerPtrs[i]!=nullptr && layerPtrs[i]->isModified) {
result=true;
break;
}
}
return result;
}
//! [0]
//! [1]
bool RotoCanvas::openImage(const QString &fileName)
//! [1] //! [2]
{
QImage loadedImage;
if (!loadedImage.load(fileName))
return false;
@ -292,42 +367,53 @@ bool RotoCanvas::openImage(const QString &fileName)
//this->formatString = loadedFI.suffix();
QSize newSize = loadedImage.size().expandedTo(size());
//resizeImage(&loadedImage, this->outputSize);
resizeImage(&displayImage, newSize); //resizeImage(&)
//backgroundImage = loadedImage;
backgroundImage = QImage(loadedImage.size(),QImage::Format_RGB32);
resizeImage(&panelImage, newSize); //resizeImage(&)
fillCheckered(&panelImage);
backgroundImage.fill(Qt::transparent);
QPainter bgPainter(&backgroundImage);
bgPainter.drawImage(QPoint(0,0),loadedImage);
/// Load the source frame as originalImage:
originalImage = QImage(loadedImage.size(),QImage::Format_ARGB32);
originalImage.fill(qRgba(0,0,0,0));
QPainter bgPainter(&originalImage);
bgPainter.drawImage(QPoint(0,0),loadedImage);//originalImage = loadedImage;
QPainter painter(&displayImage);
QPainter displayPainter(&panelImage);
isModified = false;
//isModified = false;
while (layerPtrs.length()>0) {
RotoCanvasLayer* thisLayer=layerPtrs.takeLast();
if (thisLayer!=nullptr) delete thisLayer;
}
QString layersPath=getLayersFolderPath(getSeqFrameNumber(),false);
QDir layersDir=QDir(layersPath);
int thisLayerNumber=0;
QString thisLayerPath=layersDir.filePath(QString::number(thisLayerNumber)+".png");
QFileInfo thisLayerFI=QFileInfo(thisLayerPath);
int thisLayerFrameNumber=getSeqFrameNumber();
while (thisLayerFI.exists()) {
qInfo()<<"Found layer file: "<<thisLayerPath;
//QImage newImage(loadedImage.size(), QImage::Format_RGB32);
//QImage* thisLayerImagePtr=new QImage();
//thisLayerImagePtr->load(thisLayerPath);
RotoCanvasLayer* thisLayerPtr=new RotoCanvasLayer();
thisLayerPtr->frameNumber=thisLayerFrameNumber;
thisLayerPtr->image.load(thisLayerPath);
//TODO: seek backward to get image from previous keyframe
layerPtrs.append(thisLayerPtr);
painter.drawImage(QPoint(0, 0), thisLayerPtr->image);
thisLayerNumber++;
thisLayerPath=layersDir.filePath(QString::number(thisLayerNumber));
thisLayerFI=QFileInfo(thisLayerPath);
for (int thisLayerNumber=0; thisLayerNumber<layerCount; thisLayerNumber++) {
//QString thisLayerPath=layersDir.filePath(QString::number(thisLayerNumber)+"."+RotoCanvas::getSeqFormatString(fileName));
int thisLayerFrameNumber=-1;
QString thisLayerPath=getLayerImagePathMostRecent(getSeqFrameNumber(),thisLayerNumber,&thisLayerFrameNumber);
QFileInfo thisLayerFI=QFileInfo(thisLayerPath);
if (thisLayerPath!="" && thisLayerFI.exists()) {
//while (thisLayerFI.exists()) {
qInfo()<<"Found layer file: "<<thisLayerPath;
//QImage newImage(loadedImage.size(), QImage::Format_ARGB32);
//QImage* thisLayerImagePtr=new QImage();
//thisLayerImagePtr->load(thisLayerPath);
RotoCanvasLayer* thisLayerPtr=new RotoCanvasLayer();
thisLayerPtr->frameNumber=thisLayerFrameNumber;
thisLayerPtr->isModified=false;
thisLayerPtr->image.load(thisLayerPath);
//TODO: seek backward to get image from previous keyframe
layerPtrs.append(thisLayerPtr);
displayPainter.drawImage(QPoint(0, 0), thisLayerPtr->image);
//thisLayerNumber++;
//thisLayerPath=layersDir.filePath(QString::number(thisLayerNumber));
//thisLayerFI=QFileInfo(thisLayerPath);
//}
}
else {
layerPtrs.append((RotoCanvasLayer*)nullptr);
qInfo()<<"No layer file: "<<thisLayerPath;
}
}
//bgPainter.drawImage(QPoint(0,0), displayImage);
this->selectedLayerIndex=0;
update();
return true;
@ -335,18 +421,82 @@ bool RotoCanvas::openImage(const QString &fileName)
//! [2]
//! [3]
bool RotoCanvas::saveImage(const QString &fileName, const char *fileFormat)
bool RotoCanvas::saveFrame() //saveImage(const QString &fileName, const char *fileFormat)
//! [3] //! [4]
{
QImage visibleImage = displayImage;
resizeImage(&visibleImage, size());
// QImage visibleImage = displayImage;
// resizeImage(&visibleImage, size());
if (visibleImage.save(fileName, fileFormat)) {
isModified = false;
return true;
} else {
return false;
// if (visibleImage.save(fileName, fileFormat)) {
// isModified = false;
// return true;
// } else {
// return false;
// }
int thisFrameNumber=getSeqFrameNumber();
QString layersPath=getLayersFolderPath(thisFrameNumber,true);
QDir layersDir;
bool result=true;
for (int i=0; i<layerCount; i++) {
if (i<layerPtrs.length()) {
if (layerPtrs[i]!=nullptr) {
if (layerPtrs[i]->isModified) {
layerPtrs[i]->frameNumber=thisFrameNumber; // add a keyframe
if (!layerPtrs[i]->image.save(layersDir.filePath(QString::number(i)+".png"),"png")) {
result=false;
}
layerPtrs[i]->isModified=false;
}
}
}
}
return result;
}
bool RotoCanvas::exportFrame(QDir destinationDir, const QString &sequenceName, const char *fileFormat, int frameNumber)
{
bool result=false;
QString formatString=QString(fileFormat).toLower();
if (this->loadedFI!=nullptr) {
QString destPath=getFramePath(frameNumber);//destinationDir.filePath(sequenceName+RotoCanvas::getZeroPadded(frameNumber, this->getSeqDigitCount())+"."+QString(fileFormat));
QImage destImage(originalImage.size(), QImage::Format_ARGB32);
destImage.fill(qRgba(0,0,0,0));
QPainter destPainter(&destImage);
for (int i=0; i<layerCount; i++) {
if (i<layerPtrs.length()) {
if (layerPtrs[i]!=nullptr) {
destPainter.drawImage(0,0,layerPtrs[i]->image);
}
}
}
//(formatString=="png")?QImage::Format_ARGB32:QImage::Format_RGB888
result=destImage.save(destPath, fileFormat);
}
return result;
}
int RotoCanvas::exportFrames(QDir destinationDir, const QString &sequenceName, const char *fileFormat)
{
bool result=false;
int resultCount=0;
if (this->loadedFI!=nullptr) {
int frameNumber=0;
QString framePath=getFramePath(frameNumber);
QFileInfo frameFI(framePath);
if (frameFI.exists()) {
if (!exportFrame(destinationDir, sequenceName, fileFormat, frameNumber)) result=false;
else resultCount++;
}
frameNumber++; //manually advance to 1 in case starts at 1 instead of 0
while (QFileInfo(getFramePath(frameNumber)).exists()) {
//while there is an original frame, export (edited) frame
QFileInfo frameFI=QFileInfo(getFramePath(frameNumber));
if (!exportFrame(destinationDir, sequenceName, fileFormat, frameNumber)) result=false;
else resultCount++;
frameNumber++;
}
}
return resultCount;
}
//! [4]
@ -386,8 +536,7 @@ void RotoCanvas::setBrushOpacity(double new_value)
void RotoCanvas::clearImage()
//! [9] //! [10]
{
displayImage.fill(qRgb(255, 255, 255));
isModified = true;
panelImage.fill(qRgba(255, 255, 255, 255));
update();
}
//! [10]
@ -422,7 +571,7 @@ void RotoCanvas::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QRect dirtyRect = event->rect();
painter.drawImage(dirtyRect, displayImage, dirtyRect);
painter.drawImage(dirtyRect, panelImage, dirtyRect);
}
//! [14]
@ -430,10 +579,10 @@ void RotoCanvas::paintEvent(QPaintEvent *event)
void RotoCanvas::resizeEvent(QResizeEvent *event)
//! [15] //! [16]
{
if (width() > displayImage.width() || height() > displayImage.height()) {
int newWidth = qMax(width() + 128, displayImage.width());
int newHeight = qMax(height() + 128, displayImage.height());
resizeImage(&displayImage, QSize(newWidth, newHeight));
if (width() > panelImage.width() || height() > panelImage.height()) {
int newWidth = qMax(width() + 128, panelImage.width());
int newHeight = qMax(height() + 128, panelImage.height());
resizeImage(&panelImage, QSize(newWidth, newHeight));
update();
}
QWidget::resizeEvent(event);
@ -449,17 +598,17 @@ void RotoCanvas::drawLineTo(const QPoint &endPoint)
QString layersPath=getLayersFolderPath(getSeqFrameNumber(),true);
QDir layersDir=QDir(layersPath);
int thisLayerNumber=selectedLayerIndex;
QString thisLayerPath=layersDir.filePath(QString::number(thisLayerNumber)+".png");
QString thisLayerPath=layersDir.filePath(QString::number(thisLayerNumber)+"."+RotoCanvas::getSeqFormatString(*this->loadedFI));
QFileInfo thisLayerFI=QFileInfo(thisLayerPath);
while (layerPtrs.length()<selectedLayerIndex+1) {
RotoCanvasLayer* newLayer;
newLayer = new RotoCanvasLayer;
newLayer->frameNumber=getSeqFrameNumber();
newLayer->image=QImage(outputSize, QImage::Format_RGB32);
newLayer->image=QImage(outputSize, QImage::Format_ARGB32);
layerPtrs.append(newLayer);
}
QPainter painter(&displayImage);
QPainter painter(&panelImage);
int rad = (brushRadius) + 2;
QRect rectBrush = QRect(lastPoint, endPoint).normalized()
.adjusted(-rad, -rad, +rad, +rad);
@ -483,23 +632,15 @@ void RotoCanvas::drawLineTo(const QPoint &endPoint)
double this_opacity = (fade_length>0.0) ? ((brushRadius-this_distance) / fade_length) : ((brushRadius-this_distance) / 0.000001) ;
if (this_opacity>1.0) this_opacity=1.0;
else if (this_opacity<0.0) this_opacity=0.0;
QColor destColor = displayImage.pixelColor(x,y);
//do alpha formula (byte)((source-dest)*alpha/255.0f+dest+.5f)
int r = (int)((this->brushColor.red()-destColor.red())*this_opacity+destColor.red()+.5); //+.5 for rounding
int g = (int)((this->brushColor.green()-destColor.green())*this_opacity+destColor.green()+.5); //+.5 for rounding
int b = (int)((this->brushColor.blue()-destColor.blue())*this_opacity+destColor.blue()+.5); //+.5 for rounding
int a = (int)((this->brushColor.alpha()-destColor.alpha())*this_opacity+destColor.alpha()+.5); //+.5 for rounding
thisColor.setRed(r);
thisColor.setGreen(g);
thisColor.setBlue(b);
thisColor.setAlpha(a);
displayImage.setPixelColor(x, y, thisColor);
//painter.setOpacity(this_opacity);
//painter.drawPoint(x,y);
if (selectedLayerIndex>=0&&layerPtrs[selectedLayerIndex]!=nullptr) {
RotoCanvas::drawAlphaPix(&panelImage, x, y, this->brushColor, this_opacity);
RotoCanvas::drawAlphaPix(&layerPtrs[selectedLayerIndex]->image, x, y, this->brushColor, this_opacity);
layerPtrs[selectedLayerIndex]->isModified=true;
}
}
}
}
isModified = true;
//isModified = true;
update(rectBrush);
lastPoint = endPoint;
}
@ -525,7 +666,7 @@ void RotoCanvas::fillCheckered(QImage *thisImage)
}
}
}
else QDebug("fillCheckered Error: thisImage is nullptr");
else qDebug()<<"fillCheckered Error: thisImage is nullptr";
}
//! [18]
@ -536,8 +677,8 @@ void RotoCanvas::resizeImage(QImage *image, const QSize &newSize)
if (image->size() == newSize)
return;
QImage newImage(newSize, QImage::Format_RGB32);
fillCheckered(&newImage);
QImage newImage(newSize, QImage::Format_ARGB32);
//fillCheckered(&newImage);
QPainter painter(&newImage);
painter.drawImage(QPoint(0, 0), *image);
@ -563,11 +704,11 @@ void RotoCanvas::print()
if (printDialog.exec() == QDialog::Accepted) {
QPainter painter(&printer);
QRect rect = painter.viewport();
QSize size = displayImage.size();
QSize size = panelImage.size();
size.scale(rect.size(), Qt::KeepAspectRatio);
painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
painter.setWindow(displayImage.rect());
painter.drawImage(0, 0, displayImage);
painter.setWindow(panelImage.rect());
painter.drawImage(0, 0, panelImage);
}
#endif // QT_NO_PRINTER
}

View File

@ -37,23 +37,31 @@ public:
static QString getLayersFolderPath(QString framePath, int frameNumber, bool createEnable);
static QString getLayersFolderPath(QFileInfo frameFI, int frameNumber, bool createEnable);
QString getLayersFolderPath(int frameNumber, bool createEnable);
static QString getLayerImagePathMostRecent(QString framePath, int frameNumber, int layerNumber);
static QString getLayerImagePathMostRecent(QFileInfo frameFI, int frameNumber, int layerNumber);
QString getLayerImagePathMostRecent(int frameNumber, int layerNumber);
static QString getLayerImagePathMostRecent(QString framePath, int frameNumber, int layerNumber, int* returnFrameNumber);
static QString getLayerImagePathMostRecent(QFileInfo frameFI, int frameNumber, int layerNumber, int* returnFrameNumber);
QString getLayerImagePathMostRecent(int frameNumber, int layerNumber, int* returnFrameNumber);
static QString getFrameName(QString framePath, int frameNumber);
static QString getFrameName(QFileInfo frameFI, int frameNumber);
QString getFrameName(int frameNumber);
static QString getFramePath(QString framePath, int frameNumber);
static QString getFramePath(QFileInfo frameFI, int frameNumber);
QString getFramePath(int frameNumber);
static QString getSeqFramePath(QString folderPath, QString seqName, int frameNumber, int minDigitCount, QString format);
QString getSeqFramePath(int frameNumber);
static QString getFramePath(QString folderPath, QString seqName, int frameNumber, int minDigitCount, QString format);
//QString getFramePath(int frameNumber);
static QString getZeroPadded(int frameNumber, int minDigitCount);
static void drawAlphaPix(QImage* destImage, int x, int y, QColor sourceColor, double this_opacity);
bool getIsModified();
bool openImage(const QString &fileName);
bool saveImage(const QString &fileName, const char *fileFormat);
bool saveFrame();//const QString &fileName, const char *fileFormat);
bool exportFrame(QDir destinationDir, const QString &sequenceName, const char *fileFormat, int frameNumber);
int exportFrames(QDir destinationDir, const QString &sequenceName, const char *fileFormat);
void setBrushColor(const QColor &newColor);
void setBrushRadius(double newWidth);
void setBrushHardness(double newHardness);
void setBrushOpacity(double newOpacity);
bool getIsModified() const { return isModified; }
QColor getBrushColor() const { return brushColor; }
double getBrushRadius() const { return brushRadius; }
double getBrushHardness() const { return (brushRadius-brushHardRadius)/brushRadius; }
@ -76,21 +84,22 @@ private:
void resizeImage(QImage *thisImage, const QSize &newSize);
QImage getCacheableImage(QString filePath);
bool isModified;
//bool isModified;
bool isToolActive;
int selectedLayerIndex;
double cacheMaxMB;
int layerCount; // PROJECT INFO
double cacheMaxMB; // SETTINGS
//int minDigitCount; // use getSeqDigitCount; formerly PROJECT INFO
double brushRadius; // PROJECT INFO
double brushHardRadius; // PROJECT INFO
double brushOpacity; // PROJECT INFO
QFileInfo* loadedFI; // PROJECT INFO; STATE INFO: background layer
double brushRadius; // PROJECT INFO
double brushHardRadius; // PROJECT INFO
double brushOpacity; // PROJECT INFO
QFileInfo* loadedFI; // PROJECT INFO; STATE INFO: background layer
QSize outputSize; // PROJECT INFO size of video (same as layers, but different from image)
QColor brushColor; // PROJECT INFO
QColor checkerDarkColor; // PROJECT INFO
QColor checkerLightColor; // PROJECT INFO
QImage backgroundImage;
QImage displayImage; //just for display (not saved anywhere)--includes matte, canvas (checkerboard), and interface
QColor brushColor; // PROJECT INFO
QColor checkerDarkColor; // PROJECT INFO
QColor checkerLightColor; // PROJECT INFO
QImage originalImage; // the actual source frame (nondestructive edits are on layerPtrs)
QImage panelImage; // just for display (not saved anywhere)--includes matte, canvas (checkerboard), and interface
QList<RotoCanvasLayer*> layerPtrs;
QPoint lastPoint;