footage: support multi-layered images

Fixes #982
This commit is contained in:
itsmattkc
2022-05-04 09:27:34 -07:00
parent a3c52cafaa
commit 5f13a83009
9 changed files with 132 additions and 26 deletions
+50 -22
View File
@@ -72,22 +72,44 @@ FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt*
return desc;
}
VideoParams video_params;
bool stream_enabled = true;
video_params.set_stream_index(0);
video_params.set_width(in->spec().width);
video_params.set_height(in->spec().height);
video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(in->spec().format.basetype)));
video_params.set_channel_count(in->spec().nchannels);
video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec()));
video_params.set_video_type(VideoParams::kVideoTypeStill);
for (int i=0; in->seek_subimage(i, 0); i++) {
VideoParams video_params;
// OIIO automatically premultiplies alpha
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
// likely reduces the fidelity?
video_params.set_premultiplied_alpha(true);
OIIO::ImageSpec spec = in->spec();
desc.AddVideoStream(video_params);
video_params.set_stream_index(i);
video_params.set_width(spec.width);
video_params.set_height(spec.height);
video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(spec.format.basetype)));
video_params.set_channel_count(spec.nchannels);
video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(spec));
video_params.set_video_type(VideoParams::kVideoTypeStill);
if (i > 1) {
// This is a multilayer image and this image might have an offset
OIIO::ImageSpec root_spec = in->spec(0);
float norm_x = spec.x + float(spec.width)*0.5f - float(root_spec.width)*0.5f;
float norm_y = spec.y + float(spec.height)*0.5f - float(root_spec.height)*0.5f;
video_params.set_x(norm_x);
video_params.set_y(norm_y);
}
// By default, only enable the first subimage (presumably the combined image). Later we will
// ask the user if they want to enable the layers instead.
video_params.set_enabled(stream_enabled);
stream_enabled = false;
// OIIO automatically premultiplies alpha
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
// likely reduces the fidelity?
video_params.set_premultiplied_alpha(true);
desc.AddVideoStream(video_params);
}
// If we're here, we have a successful image open
in->close();
@@ -98,7 +120,7 @@ FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt*
bool OIIODecoder::OpenInternal()
{
// If we can open the filename provided, assume everything is working
return OpenImageHandler(stream().filename());
return OpenImageHandler(stream().filename(), stream().stream());
}
FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams &divider, const QAtomicInt *cancelled)
@@ -108,13 +130,15 @@ FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const Retr
FramePtr frame = Frame::Create();
frame->set_video_params(VideoParams(buffer_->spec().width,
buffer_->spec().height,
pix_fmt_,
channel_count_,
OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()),
VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us?
divider.divider));
VideoParams vp(buffer_->spec().width,
buffer_->spec().height,
pix_fmt_,
channel_count_,
OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()),
VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us?
divider.divider);
frame->set_video_params(vp);
frame->allocate();
if (divider.divider == 1) {
@@ -167,7 +191,7 @@ bool OIIODecoder::FileTypeIsSupported(const QString& fn)
return true;
}
bool OIIODecoder::OpenImageHandler(const QString &fn)
bool OIIODecoder::OpenImageHandler(const QString &fn, int subimage)
{
image_ = OIIO::ImageInput::open(fn.toStdString());
@@ -175,6 +199,10 @@ bool OIIODecoder::OpenImageHandler(const QString &fn)
return false;
}
if (!image_->seek_subimage(subimage, 0)) {
return false;
}
// Check if we can work with this pixel format
const OIIO::ImageSpec& spec = image_->spec();
+1 -1
View File
@@ -52,7 +52,7 @@ private:
static bool FileTypeIsSupported(const QString& fn);
bool OpenImageHandler(const QString& fn);
bool OpenImageHandler(const QString& fn, int subimage);
void CloseImageHandle();
+43
View File
@@ -533,6 +533,49 @@ void Core::ImportTaskComplete(Task* task)
MultiUndoCommand *command = import_task->GetCommand();
foreach (Footage *f, import_task->GetImportedFootage()) {
// Look for multi-layer images
if (f->GetAudioStreamCount() == 0 && f->GetVideoStreamCount() > 1) {
bool all_stills = true;
for (int i=0; i<f->GetVideoStreamCount(); i++) {
const VideoParams &vs = f->GetVideoParams(i);
if (!(vs.video_type() == VideoParams::kVideoTypeStill && vs.enabled() == (i == 0))) {
all_stills = false;
}
}
if (all_stills) {
QMessageBox d(main_window());
d.setIcon(QMessageBox::Question);
d.setWindowTitle(tr("Multi-Layer Image"));
d.setText(tr("The file '%1' has multiple layers. Would you like these layers to be "
"separated across multiple tracks or merged into a single image?").arg(f->filename()));
auto multi_btn = d.addButton(tr("Multiple Layers"), QMessageBox::YesRole);
auto single_btn = d.addButton(tr("Single Layer"), QMessageBox::NoRole);
auto cancel_btn = d.addButton(QMessageBox::Cancel);
d.exec();
if (d.clickedButton() == multi_btn) {
for (int i=0; i<f->GetVideoStreamCount(); i++) {
VideoParams vs = f->GetVideoParams(i);
vs.set_enabled(!vs.enabled());
f->SetVideoParams(vs, i);
}
} else if (d.clickedButton() == single_btn) {
// Do nothing, footage will already be set up this way
} else if (d.clickedButton() == cancel_btn) {
// Cancel import
delete command;
return;
}
}
}
}
if (import_task->HasInvalidFiles()) {
ProjectImportErrorDialog d(import_task->GetInvalidFiles(), main_window_);
d.exec();
@@ -318,7 +318,7 @@ void TransformDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardM
}
}
QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat, const QVector2D &sequence_res, const QVector2D &texture_res, AutoScaleType autoscale_type)
QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat, const QVector2D &sequence_res, const QVector2D &texture_res, const QVector2D &offset, AutoScaleType autoscale_type)
{
// First, create an identity matrix
QMatrix4x4 adjusted_matrix;
@@ -326,6 +326,9 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat
// Scale it to a square based on the sequence's resolution
adjusted_matrix.scale(2.0 / sequence_res.x(), 2.0 / sequence_res.y(), 1.0);
// Apply offset if applicable
adjusted_matrix.translate(offset);
// Adjust by the matrix we generated earlier
adjusted_matrix *= mat;
@@ -378,6 +381,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
// GizmoTraverser just returns the sizes of the textures and no other data
VideoParams tex_params = tex->params();
QVector2D tex_sz(tex_params.square_pixel_width(), tex_params.height());
QVector2D tex_offset = tex_params.offset();
// Retrieve autoscale value
AutoScaleType autoscale = static_cast<AutoScaleType>(row[kAutoscaleInput].data().toInt());
@@ -388,6 +392,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, false),
sequence_res,
tex_sz,
tex_offset,
autoscale);
// Create rect and transform it
@@ -407,6 +412,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, true, false, false),
sequence_res,
tex_sz,
tex_offset,
autoscale);
anchor_gizmo_->SetPoint(anchor_matrix.toTransform().map(QPointF(0, 0)) + sequence_half_res_pt);
@@ -422,7 +428,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
// Use offsets to make the appearance of values that start in the top left, even though we
// really anchor around the center
SetInputProperty(kPositionInput, QStringLiteral("offset"), sequence_half_res);
SetInputProperty(kPositionInput, QStringLiteral("offset"), sequence_half_res + tex_offset);
SetInputProperty(kAnchorInput, QStringLiteral("offset"), tex_sz * 0.5);
}
@@ -440,6 +446,7 @@ QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& gene
return AdjustMatrixByResolutions(generated_matrix,
sequence_res,
texture_res,
texture_params.offset(),
autoscale);
}
@@ -82,6 +82,7 @@ public:
static QMatrix4x4 AdjustMatrixByResolutions(const QMatrix4x4& mat,
const QVector2D& sequence_res,
const QVector2D& texture_res,
const QVector2D& offset,
AutoScaleType autoscale_type = kAutoScaleNone);
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
+1
View File
@@ -363,6 +363,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt
QMatrix4x4 adjusted_matrix = TransformDistortNode::AdjustMatrixByResolutions(number_val.data().value<QMatrix4x4>(),
sequence_res,
texture->params().offset(),
texture_res);
if (operation != kOpMultiply || adjusted_matrix.isIdentity()) {
+8 -1
View File
@@ -475,7 +475,9 @@ void ViewerOutput::set_parameters_from_footage(const QVector<ViewerOutput *> foo
QVector<VideoParams> video_streams = f->GetEnabledVideoStreams();
QVector<AudioParams> audio_streams = f->GetEnabledAudioStreams();
foreach (const VideoParams& s, video_streams) {
for (int i=0; i<video_streams.size(); i++) {
const VideoParams& s = video_streams.at(i);
bool found_video_params = false;
rational using_timebase;
@@ -483,6 +485,11 @@ void ViewerOutput::set_parameters_from_footage(const QVector<ViewerOutput *> foo
// If this is a still image, we'll use it's resolution but won't set
// `found_video_params` in case something with a frame rate comes along which we'll
// prioritize
if (i > 0) {
// Ignore still images past stream 0
continue;
}
using_timebase = GetVideoParams().time_base();
} else {
using_timebase = s.frame_rate_as_time_base();
+10
View File
@@ -259,6 +259,8 @@ void VideoParams::set_defaults_for_footage()
start_time_ = 0;
duration_ = 0;
premultiplied_alpha_ = false;
x_ = 0;
y_ = 0;
}
void VideoParams::calculate_square_pixel_width()
@@ -327,6 +329,8 @@ QByteArray VideoParams::toBytes() const
hasher.addData(reinterpret_cast<const char*>(&interlacing_), sizeof(interlacing_));
hasher.addData(reinterpret_cast<const char*>(&divider_), sizeof(divider_));
hasher.addData(reinterpret_cast<const char*>(&enabled_), sizeof(enabled_));
hasher.addData(reinterpret_cast<const char*>(&x_), sizeof(x_));
hasher.addData(reinterpret_cast<const char*>(&y_), sizeof(y_));
hasher.addData(reinterpret_cast<const char*>(&stream_index_), sizeof(stream_index_));
hasher.addData(reinterpret_cast<const char*>(&video_type_), sizeof(video_type_));
hasher.addData(reinterpret_cast<const char*>(&frame_rate_), sizeof(frame_rate_));
@@ -370,6 +374,10 @@ void VideoParams::Load(QXmlStreamReader *reader)
set_divider(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("enabled")) {
set_enabled(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("x")) {
set_x(reader->readElementText().toFloat());
} else if (reader->name() == QStringLiteral("y")) {
set_y(reader->readElementText().toFloat());
} else if (reader->name() == QStringLiteral("streamindex")) {
set_stream_index(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("videotype")) {
@@ -402,6 +410,8 @@ void VideoParams::Save(QXmlStreamWriter *writer) const
writer->writeTextElement(QStringLiteral("interlacing"), QString::number(interlacing_));
writer->writeTextElement(QStringLiteral("divider"), QString::number(divider_));
writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_));
writer->writeTextElement(QStringLiteral("x"), QString::number(x_));
writer->writeTextElement(QStringLiteral("y"), QString::number(y_));
writer->writeTextElement(QStringLiteral("streamindex"), QString::number(stream_index_));
writer->writeTextElement(QStringLiteral("videotype"), QString::number(video_type_));
writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString());
+9
View File
@@ -21,6 +21,7 @@
#ifndef VIDEOPARAMS_H
#define VIDEOPARAMS_H
#include <QVector2D>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
@@ -273,6 +274,12 @@ public:
enabled_ = e;
}
float x() const { return x_; }
void set_x(float x) { x_ = x; }
float y() const { return y_; }
void set_y(float y) { y_ = y; }
QVector2D offset() const { return QVector2D(x_, y_); }
int stream_index() const
{
return stream_index_;
@@ -387,6 +394,8 @@ private:
int64_t duration_;
bool premultiplied_alpha_;
QString colorspace_;
float x_;
float y_;
};