You've already forked libopenshot
mirror of
https://github.com/OpenShot/libopenshot.git
synced 2026-03-02 08:53:52 -08:00
- Refactoring all Timeline drawing code into the Clip class
- Making Clip a proper Reader (so it can be used directly, instead of a Timeline)
This commit is contained in:
@@ -92,7 +92,7 @@ namespace openshot {
|
||||
* c2.alpha.AddPoint(384, 1.0); // Animate the alpha to visible (between frame #360 and frame #384)
|
||||
* @endcode
|
||||
*/
|
||||
class Clip : public openshot::ClipBase {
|
||||
class Clip : public openshot::ClipBase, public openshot::ReaderBase {
|
||||
protected:
|
||||
/// Section lock for multiple threads
|
||||
juce::CriticalSection getFrameCriticalSection;
|
||||
@@ -100,6 +100,7 @@ namespace openshot {
|
||||
private:
|
||||
bool waveform; ///< Should a waveform be used instead of the clip's image
|
||||
std::list<openshot::EffectBase*> effects; ///<List of clips on this timeline
|
||||
bool is_open; ///> Is Reader opened
|
||||
|
||||
// Audio resampler (if time mapping)
|
||||
openshot::AudioResampler *resampler;
|
||||
@@ -117,6 +118,9 @@ namespace openshot {
|
||||
/// Apply effects to the source frame (if any)
|
||||
std::shared_ptr<openshot::Frame> apply_effects(std::shared_ptr<openshot::Frame> frame);
|
||||
|
||||
/// Apply keyframes to the source frame (if any)
|
||||
std::shared_ptr<openshot::Frame> apply_keyframes(std::shared_ptr<openshot::Frame> frame);
|
||||
|
||||
/// Get file extension
|
||||
std::string get_file_extension(std::string path);
|
||||
|
||||
@@ -132,6 +136,9 @@ namespace openshot {
|
||||
/// Update default rotation from reader
|
||||
void init_reader_rotation();
|
||||
|
||||
/// Compare 2 floating point numbers
|
||||
bool isEqual(double a, double b);
|
||||
|
||||
/// Sort effects by order
|
||||
void sort_effects();
|
||||
|
||||
@@ -159,6 +166,18 @@ namespace openshot {
|
||||
/// Destructor
|
||||
virtual ~Clip();
|
||||
|
||||
|
||||
/// Get the cache object used by this reader (always returns NULL for this object)
|
||||
CacheMemory* GetCache() override { return NULL; };
|
||||
|
||||
/// Determine if reader is open or closed
|
||||
bool IsOpen() override { return is_open; };
|
||||
|
||||
/// Return the type name of the class
|
||||
std::string Name() override { return "Clip"; };
|
||||
|
||||
|
||||
|
||||
/// @brief Add an effect to the clip
|
||||
/// @param effect Add an effect to the clip. An effect can modify the audio or video of an openshot::Frame.
|
||||
void AddEffect(openshot::EffectBase* effect);
|
||||
|
||||
299
src/Clip.cpp
299
src/Clip.cpp
@@ -134,14 +134,14 @@ void Clip::init_reader_rotation() {
|
||||
}
|
||||
|
||||
// Default Constructor for a clip
|
||||
Clip::Clip() : resampler(NULL), reader(NULL), allocated_reader(NULL)
|
||||
Clip::Clip() : resampler(NULL), reader(NULL), allocated_reader(NULL), is_open(false)
|
||||
{
|
||||
// Init all default settings
|
||||
init_settings();
|
||||
}
|
||||
|
||||
// Constructor with reader
|
||||
Clip::Clip(ReaderBase* new_reader) : resampler(NULL), reader(new_reader), allocated_reader(NULL)
|
||||
Clip::Clip(ReaderBase* new_reader) : resampler(NULL), reader(new_reader), allocated_reader(NULL), is_open(false)
|
||||
{
|
||||
// Init all default settings
|
||||
init_settings();
|
||||
@@ -158,7 +158,7 @@ Clip::Clip(ReaderBase* new_reader) : resampler(NULL), reader(new_reader), alloca
|
||||
}
|
||||
|
||||
// Constructor with filepath
|
||||
Clip::Clip(std::string path) : resampler(NULL), reader(NULL), allocated_reader(NULL)
|
||||
Clip::Clip(std::string path) : resampler(NULL), reader(NULL), allocated_reader(NULL), is_open(false)
|
||||
{
|
||||
// Init all default settings
|
||||
init_settings();
|
||||
@@ -262,6 +262,10 @@ void Clip::Open()
|
||||
{
|
||||
// Open the reader
|
||||
reader->Open();
|
||||
is_open = true;
|
||||
|
||||
// Copy Reader info to Clip
|
||||
info = reader->info;
|
||||
|
||||
// Set some clip properties from the file reader
|
||||
if (end == 0.0)
|
||||
@@ -275,6 +279,7 @@ void Clip::Open()
|
||||
// Close the internal reader
|
||||
void Clip::Close()
|
||||
{
|
||||
is_open = false;
|
||||
if (reader) {
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::Close");
|
||||
|
||||
@@ -311,6 +316,10 @@ float Clip::End() const
|
||||
// Get an openshot::Frame object for a specific frame number of this reader.
|
||||
std::shared_ptr<Frame> Clip::GetFrame(int64_t requested_frame)
|
||||
{
|
||||
// Check for open reader (or throw exception)
|
||||
if (!is_open)
|
||||
throw ReaderClosed("The Clip is closed. Call Open() before calling this method", "N/A");
|
||||
|
||||
if (reader)
|
||||
{
|
||||
// Adjust out of bounds frame number
|
||||
@@ -360,6 +369,9 @@ std::shared_ptr<Frame> Clip::GetFrame(int64_t requested_frame)
|
||||
// Apply effects to the frame (if any)
|
||||
apply_effects(frame);
|
||||
|
||||
// Apply keyframe / transforms
|
||||
apply_keyframes(frame);
|
||||
|
||||
// Return processed 'frame'
|
||||
return frame;
|
||||
}
|
||||
@@ -1042,3 +1054,284 @@ std::shared_ptr<Frame> Clip::apply_effects(std::shared_ptr<Frame> frame)
|
||||
// Return modified frame
|
||||
return frame;
|
||||
}
|
||||
|
||||
// Compare 2 floating point numbers for equality
|
||||
bool Clip::isEqual(double a, double b)
|
||||
{
|
||||
return fabs(a - b) < 0.000001;
|
||||
}
|
||||
|
||||
|
||||
// Apply keyframes to the source frame (if any)
|
||||
std::shared_ptr<Frame> Clip::apply_keyframes(std::shared_ptr<Frame> frame)
|
||||
{
|
||||
// Get actual frame image data
|
||||
std::shared_ptr<QImage> source_image = frame->GetImage();
|
||||
|
||||
/* REPLACE IMAGE WITH WAVEFORM IMAGE (IF NEEDED) */
|
||||
if (Waveform())
|
||||
{
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::apply_keyframes (Generate Waveform Image)", "frame->number", frame->number, "Waveform()", Waveform());
|
||||
|
||||
// Get the color of the waveform
|
||||
int red = wave_color.red.GetInt(frame->number);
|
||||
int green = wave_color.green.GetInt(frame->number);
|
||||
int blue = wave_color.blue.GetInt(frame->number);
|
||||
int alpha = wave_color.alpha.GetInt(frame->number);
|
||||
|
||||
// Generate Waveform Dynamically (the size of the timeline)
|
||||
source_image = frame->GetWaveform(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, red, green, blue, alpha);
|
||||
frame->AddImage(std::shared_ptr<QImage>(source_image));
|
||||
}
|
||||
|
||||
/* ALPHA & OPACITY */
|
||||
if (alpha.GetValue(frame->number) != 1.0)
|
||||
{
|
||||
float alpha_value = alpha.GetValue(frame->number);
|
||||
|
||||
// Get source image's pixels
|
||||
unsigned char *pixels = (unsigned char *) source_image->bits();
|
||||
|
||||
// Loop through pixels
|
||||
for (int pixel = 0, byte_index=0; pixel < source_image->width() * source_image->height(); pixel++, byte_index+=4)
|
||||
{
|
||||
// Apply alpha to pixel
|
||||
pixels[byte_index + 3] *= alpha_value;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::apply_keyframes (Set Alpha & Opacity)", "alpha_value", alpha_value, "frame->number", frame->number);
|
||||
}
|
||||
|
||||
/* RESIZE SOURCE IMAGE - based on scale type */
|
||||
QSize source_size = source_image->size();
|
||||
switch (scale)
|
||||
{
|
||||
case (SCALE_FIT): {
|
||||
// keep aspect ratio
|
||||
source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::KeepAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Scale: SCALE_FIT)", "frame->number", frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
case (SCALE_STRETCH): {
|
||||
// ignore aspect ratio
|
||||
source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::IgnoreAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Scale: SCALE_STRETCH)", "frame->number", frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
case (SCALE_CROP): {
|
||||
QSize width_size(Settings::Instance()->MAX_WIDTH, round(Settings::Instance()->MAX_WIDTH / (float(source_size.width()) / float(source_size.height()))));
|
||||
QSize height_size(round(Settings::Instance()->MAX_HEIGHT / (float(source_size.height()) / float(source_size.width()))), Settings::Instance()->MAX_HEIGHT);
|
||||
|
||||
// respect aspect ratio
|
||||
if (width_size.width() >= Settings::Instance()->MAX_WIDTH && width_size.height() >= Settings::Instance()->MAX_HEIGHT)
|
||||
source_size.scale(width_size.width(), width_size.height(), Qt::KeepAspectRatio);
|
||||
else
|
||||
source_size.scale(height_size.width(), height_size.height(), Qt::KeepAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Scale: SCALE_CROP)", "frame->number", frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
case (SCALE_NONE): {
|
||||
// Calculate ratio of source size to project size
|
||||
// Even with no scaling, previews need to be adjusted correctly
|
||||
// (otherwise NONE scaling draws the frame image outside of the preview)
|
||||
float source_width_ratio = source_size.width() / float(Settings::Instance()->MAX_WIDTH);
|
||||
float source_height_ratio = source_size.height() / float(Settings::Instance()->MAX_HEIGHT);
|
||||
source_size.scale(Settings::Instance()->MAX_WIDTH * source_width_ratio, Settings::Instance()->MAX_HEIGHT * source_height_ratio, Qt::KeepAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Scale: SCALE_NONE)", "frame->number", frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float crop_x_value = crop_x.GetValue(frame->number);
|
||||
float crop_y_value = crop_y.GetValue(frame->number);
|
||||
float crop_w_value = crop_width.GetValue(frame->number);
|
||||
float crop_h_value = crop_height.GetValue(frame->number);
|
||||
switch(crop_gravity)
|
||||
{
|
||||
case (GRAVITY_TOP_LEFT):
|
||||
// This is only here to prevent unused-enum warnings
|
||||
break;
|
||||
case (GRAVITY_TOP):
|
||||
crop_x_value += 0.5;
|
||||
break;
|
||||
case (GRAVITY_TOP_RIGHT):
|
||||
crop_x_value += 1.0;
|
||||
break;
|
||||
case (GRAVITY_LEFT):
|
||||
crop_y_value += 0.5;
|
||||
break;
|
||||
case (GRAVITY_CENTER):
|
||||
crop_x_value += 0.5;
|
||||
crop_y_value += 0.5;
|
||||
break;
|
||||
case (GRAVITY_RIGHT):
|
||||
crop_x_value += 1.0;
|
||||
crop_y_value += 0.5;
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_LEFT):
|
||||
crop_y_value += 1.0;
|
||||
break;
|
||||
case (GRAVITY_BOTTOM):
|
||||
crop_x_value += 0.5;
|
||||
crop_y_value += 1.0;
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_RIGHT):
|
||||
crop_x_value += 1.0;
|
||||
crop_y_value += 1.0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* GRAVITY LOCATION - Initialize X & Y to the correct values (before applying location curves) */
|
||||
float x = 0.0; // left
|
||||
float y = 0.0; // top
|
||||
|
||||
// Adjust size for scale x and scale y
|
||||
float sx = scale_x.GetValue(frame->number); // percentage X scale
|
||||
float sy = scale_y.GetValue(frame->number); // percentage Y scale
|
||||
float scaled_source_width = source_size.width() * sx;
|
||||
float scaled_source_height = source_size.height() * sy;
|
||||
|
||||
switch (gravity)
|
||||
{
|
||||
case (GRAVITY_TOP_LEFT):
|
||||
// This is only here to prevent unused-enum warnings
|
||||
break;
|
||||
case (GRAVITY_TOP):
|
||||
x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_TOP_RIGHT):
|
||||
x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right
|
||||
break;
|
||||
case (GRAVITY_LEFT):
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_CENTER):
|
||||
x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_RIGHT):
|
||||
x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_LEFT):
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom
|
||||
break;
|
||||
case (GRAVITY_BOTTOM):
|
||||
x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_RIGHT):
|
||||
x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom
|
||||
break;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Gravity)", "frame->number", frame->number, "source_clip->gravity", gravity, "scaled_source_width", scaled_source_width, "scaled_source_height", scaled_source_height);
|
||||
|
||||
/* LOCATION, ROTATION, AND SCALE */
|
||||
float r = rotation.GetValue(frame->number); // rotate in degrees
|
||||
x += (Settings::Instance()->MAX_WIDTH * location_x.GetValue(frame->number)); // move in percentage of final width
|
||||
y += (Settings::Instance()->MAX_HEIGHT * location_y.GetValue(frame->number)); // move in percentage of final height
|
||||
float shear_x_value = shear_x.GetValue(frame->number);
|
||||
float shear_y_value = shear_y.GetValue(frame->number);
|
||||
float origin_x_value = origin_x.GetValue(frame->number);
|
||||
float origin_y_value = origin_y.GetValue(frame->number);
|
||||
|
||||
bool transformed = false;
|
||||
QTransform transform;
|
||||
|
||||
// Transform source image (if needed)
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Build QTransform - if needed)", "frame->number", frame->number, "x", x, "y", y, "r", r, "sx", sx, "sy", sy);
|
||||
|
||||
if (!isEqual(x, 0) || !isEqual(y, 0)) {
|
||||
// TRANSLATE/MOVE CLIP
|
||||
transform.translate(x, y);
|
||||
transformed = true;
|
||||
}
|
||||
|
||||
if (!isEqual(r, 0) || !isEqual(shear_x_value, 0) || !isEqual(shear_y_value, 0)) {
|
||||
// ROTATE CLIP (around origin_x, origin_y)
|
||||
float origin_x_offset = (scaled_source_width * origin_x_value);
|
||||
float origin_y_offset = (scaled_source_height * origin_y_value);
|
||||
transform.translate(origin_x_offset, origin_y_offset);
|
||||
transform.rotate(r);
|
||||
transform.shear(shear_x_value, shear_y_value);
|
||||
transform.translate(-origin_x_offset,-origin_y_offset);
|
||||
transformed = true;
|
||||
}
|
||||
|
||||
// SCALE CLIP (if needed)
|
||||
float source_width_scale = (float(source_size.width()) / float(source_image->width())) * sx;
|
||||
float source_height_scale = (float(source_size.height()) / float(source_image->height())) * sy;
|
||||
|
||||
if (!isEqual(source_width_scale, 1.0) || !isEqual(source_height_scale, 1.0)) {
|
||||
transform.scale(source_width_scale, source_height_scale);
|
||||
transformed = true;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Transform: Composite Image Layer: Prepare)", "frame->number", frame->number, "transformed", transformed);
|
||||
|
||||
/* COMPOSITE SOURCE IMAGE (LAYER) ONTO FINAL IMAGE */
|
||||
std::shared_ptr<QImage> new_image;
|
||||
new_image = std::shared_ptr<QImage>(new QImage(*source_image));
|
||||
new_image->fill(QColor(QString::fromStdString("#00000000")));
|
||||
|
||||
// Load timeline's new frame image into a QPainter
|
||||
QPainter painter(new_image.get());
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true);
|
||||
|
||||
// Apply transform (translate, rotate, scale)... if any
|
||||
if (transformed)
|
||||
painter.setTransform(transform);
|
||||
|
||||
// Composite a new layer onto the image
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
painter.drawImage(0, 0, *source_image, crop_x_value * source_image->width(), crop_y_value * source_image->height(), crop_w_value * source_image->width(), crop_h_value * source_image->height());
|
||||
|
||||
// Draw frame #'s on top of image (if needed)
|
||||
if (display != FRAME_DISPLAY_NONE) {
|
||||
std::stringstream frame_number_str;
|
||||
switch (display)
|
||||
{
|
||||
case (FRAME_DISPLAY_NONE):
|
||||
// This is only here to prevent unused-enum warnings
|
||||
break;
|
||||
|
||||
case (FRAME_DISPLAY_CLIP):
|
||||
frame_number_str << frame->number;
|
||||
break;
|
||||
|
||||
case (FRAME_DISPLAY_TIMELINE):
|
||||
frame_number_str << "N/A";
|
||||
break;
|
||||
|
||||
case (FRAME_DISPLAY_BOTH):
|
||||
frame_number_str << "N/A" << " (" << frame->number << ")";
|
||||
break;
|
||||
}
|
||||
|
||||
// Draw frame number on top of image
|
||||
painter.setPen(QColor("#ffffff"));
|
||||
painter.drawText(20, 20, QString(frame_number_str.str().c_str()));
|
||||
}
|
||||
|
||||
painter.end();
|
||||
|
||||
// Add new QImage to frame
|
||||
frame->AddImage(new_image);
|
||||
|
||||
// Return modified frame
|
||||
return frame;
|
||||
}
|
||||
|
||||
261
src/Timeline.cpp
261
src/Timeline.cpp
@@ -409,25 +409,6 @@ void Timeline::add_layer(std::shared_ptr<Frame> new_frame, Clip* source_clip, in
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer", "new_frame->number", new_frame->number, "clip_frame_number", clip_frame_number, "timeline_frame_number", timeline_frame_number);
|
||||
|
||||
/* REPLACE IMAGE WITH WAVEFORM IMAGE (IF NEEDED) */
|
||||
if (source_clip->Waveform())
|
||||
{
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Generate Waveform Image)", "source_frame->number", source_frame->number, "source_clip->Waveform()", source_clip->Waveform(), "clip_frame_number", clip_frame_number);
|
||||
|
||||
// Get the color of the waveform
|
||||
int red = source_clip->wave_color.red.GetInt(clip_frame_number);
|
||||
int green = source_clip->wave_color.green.GetInt(clip_frame_number);
|
||||
int blue = source_clip->wave_color.blue.GetInt(clip_frame_number);
|
||||
int alpha = source_clip->wave_color.alpha.GetInt(clip_frame_number);
|
||||
|
||||
// Generate Waveform Dynamically (the size of the timeline)
|
||||
std::shared_ptr<QImage> source_image;
|
||||
#pragma omp critical (T_addLayer)
|
||||
source_image = source_frame->GetWaveform(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, red, green, blue, alpha);
|
||||
source_frame->AddImage(std::shared_ptr<QImage>(source_image));
|
||||
}
|
||||
|
||||
/* Apply effects to the source frame (if any). If multiple clips are overlapping, only process the
|
||||
* effects on the top clip. */
|
||||
if (is_top_clip && source_frame) {
|
||||
@@ -498,7 +479,6 @@ void Timeline::add_layer(std::shared_ptr<Frame> new_frame, Clip* source_clip, in
|
||||
else
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (No Audio Copied - Wrong # of Channels)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), "info.channels", info.channels, "clip_frame_number", clip_frame_number, "timeline_frame_number", timeline_frame_number);
|
||||
|
||||
}
|
||||
|
||||
// Skip out if video was disabled or only an audio frame (no visualisation in use)
|
||||
@@ -513,253 +493,26 @@ void Timeline::add_layer(std::shared_ptr<Frame> new_frame, Clip* source_clip, in
|
||||
// Get actual frame image data
|
||||
source_image = source_frame->GetImage();
|
||||
|
||||
/* ALPHA & OPACITY */
|
||||
if (source_clip->alpha.GetValue(clip_frame_number) != 1.0)
|
||||
{
|
||||
float alpha = source_clip->alpha.GetValue(clip_frame_number);
|
||||
|
||||
// Get source image's pixels
|
||||
unsigned char *pixels = (unsigned char *) source_image->bits();
|
||||
|
||||
// Loop through pixels
|
||||
for (int pixel = 0, byte_index=0; pixel < source_image->width() * source_image->height(); pixel++, byte_index+=4)
|
||||
{
|
||||
// Apply alpha to pixel
|
||||
pixels[byte_index + 3] *= alpha;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Set Alpha & Opacity)", "alpha", alpha, "source_frame->number", source_frame->number, "clip_frame_number", clip_frame_number);
|
||||
}
|
||||
|
||||
/* RESIZE SOURCE IMAGE - based on scale type */
|
||||
QSize source_size = source_image->size();
|
||||
switch (source_clip->scale)
|
||||
{
|
||||
case (SCALE_FIT): {
|
||||
// keep aspect ratio
|
||||
source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::KeepAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_FIT)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
case (SCALE_STRETCH): {
|
||||
// ignore aspect ratio
|
||||
source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::IgnoreAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_STRETCH)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
case (SCALE_CROP): {
|
||||
QSize width_size(Settings::Instance()->MAX_WIDTH, round(Settings::Instance()->MAX_WIDTH / (float(source_size.width()) / float(source_size.height()))));
|
||||
QSize height_size(round(Settings::Instance()->MAX_HEIGHT / (float(source_size.height()) / float(source_size.width()))), Settings::Instance()->MAX_HEIGHT);
|
||||
|
||||
// respect aspect ratio
|
||||
if (width_size.width() >= Settings::Instance()->MAX_WIDTH && width_size.height() >= Settings::Instance()->MAX_HEIGHT)
|
||||
source_size.scale(width_size.width(), width_size.height(), Qt::KeepAspectRatio);
|
||||
else
|
||||
source_size.scale(height_size.width(), height_size.height(), Qt::KeepAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_CROP)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
case (SCALE_NONE): {
|
||||
// Calculate ratio of source size to project size
|
||||
// Even with no scaling, previews need to be adjusted correctly
|
||||
// (otherwise NONE scaling draws the frame image outside of the preview)
|
||||
float source_width_ratio = source_size.width() / float(info.width);
|
||||
float source_height_ratio = source_size.height() / float(info.height);
|
||||
source_size.scale(Settings::Instance()->MAX_WIDTH * source_width_ratio, Settings::Instance()->MAX_HEIGHT * source_height_ratio, Qt::KeepAspectRatio);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_NONE)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float crop_x = source_clip->crop_x.GetValue(clip_frame_number);
|
||||
float crop_y = source_clip->crop_y.GetValue(clip_frame_number);
|
||||
float crop_w = source_clip->crop_width.GetValue(clip_frame_number);
|
||||
float crop_h = source_clip->crop_height.GetValue(clip_frame_number);
|
||||
switch(source_clip->crop_gravity)
|
||||
{
|
||||
case (GRAVITY_TOP_LEFT):
|
||||
// This is only here to prevent unused-enum warnings
|
||||
break;
|
||||
case (GRAVITY_TOP):
|
||||
crop_x += 0.5;
|
||||
break;
|
||||
case (GRAVITY_TOP_RIGHT):
|
||||
crop_x += 1.0;
|
||||
break;
|
||||
case (GRAVITY_LEFT):
|
||||
crop_y += 0.5;
|
||||
break;
|
||||
case (GRAVITY_CENTER):
|
||||
crop_x += 0.5;
|
||||
crop_y += 0.5;
|
||||
break;
|
||||
case (GRAVITY_RIGHT):
|
||||
crop_x += 1.0;
|
||||
crop_y += 0.5;
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_LEFT):
|
||||
crop_y += 1.0;
|
||||
break;
|
||||
case (GRAVITY_BOTTOM):
|
||||
crop_x += 0.5;
|
||||
crop_y += 1.0;
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_RIGHT):
|
||||
crop_x += 1.0;
|
||||
crop_y += 1.0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* GRAVITY LOCATION - Initialize X & Y to the correct values (before applying location curves) */
|
||||
float x = 0.0; // left
|
||||
float y = 0.0; // top
|
||||
|
||||
// Adjust size for scale x and scale y
|
||||
float sx = source_clip->scale_x.GetValue(clip_frame_number); // percentage X scale
|
||||
float sy = source_clip->scale_y.GetValue(clip_frame_number); // percentage Y scale
|
||||
float scaled_source_width = source_size.width() * sx;
|
||||
float scaled_source_height = source_size.height() * sy;
|
||||
|
||||
switch (source_clip->gravity)
|
||||
{
|
||||
case (GRAVITY_TOP_LEFT):
|
||||
// This is only here to prevent unused-enum warnings
|
||||
break;
|
||||
case (GRAVITY_TOP):
|
||||
x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_TOP_RIGHT):
|
||||
x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right
|
||||
break;
|
||||
case (GRAVITY_LEFT):
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_CENTER):
|
||||
x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_RIGHT):
|
||||
x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_LEFT):
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom
|
||||
break;
|
||||
case (GRAVITY_BOTTOM):
|
||||
x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom
|
||||
break;
|
||||
case (GRAVITY_BOTTOM_RIGHT):
|
||||
x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right
|
||||
y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom
|
||||
break;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Gravity)", "source_frame->number", source_frame->number, "source_clip->gravity", source_clip->gravity, "info.width", info.width, "scaled_source_width", scaled_source_width, "info.height", info.height, "scaled_source_height", scaled_source_height);
|
||||
|
||||
/* LOCATION, ROTATION, AND SCALE */
|
||||
float r = source_clip->rotation.GetValue(clip_frame_number); // rotate in degrees
|
||||
x += (Settings::Instance()->MAX_WIDTH * source_clip->location_x.GetValue(clip_frame_number)); // move in percentage of final width
|
||||
y += (Settings::Instance()->MAX_HEIGHT * source_clip->location_y.GetValue(clip_frame_number)); // move in percentage of final height
|
||||
float shear_x = source_clip->shear_x.GetValue(clip_frame_number);
|
||||
float shear_y = source_clip->shear_y.GetValue(clip_frame_number);
|
||||
float origin_x = source_clip->origin_x.GetValue(clip_frame_number);
|
||||
float origin_y = source_clip->origin_y.GetValue(clip_frame_number);
|
||||
|
||||
bool transformed = false;
|
||||
QTransform transform;
|
||||
|
||||
// Transform source image (if needed)
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Build QTransform - if needed)", "source_frame->number", source_frame->number, "x", x, "y", y, "r", r, "sx", sx, "sy", sy);
|
||||
|
||||
if (!isEqual(x, 0) || !isEqual(y, 0)) {
|
||||
// TRANSLATE/MOVE CLIP
|
||||
transform.translate(x, y);
|
||||
transformed = true;
|
||||
}
|
||||
|
||||
if (!isEqual(r, 0) || !isEqual(shear_x, 0) || !isEqual(shear_y, 0)) {
|
||||
// ROTATE CLIP (around origin_x, origin_y)
|
||||
float origin_x_value = (scaled_source_width * origin_x);
|
||||
float origin_y_value = (scaled_source_height * origin_y);
|
||||
transform.translate(origin_x_value, origin_y_value);
|
||||
transform.rotate(r);
|
||||
transform.shear(shear_x, shear_y);
|
||||
transform.translate(-origin_x_value,-origin_y_value);
|
||||
transformed = true;
|
||||
}
|
||||
|
||||
// SCALE CLIP (if needed)
|
||||
float source_width_scale = (float(source_size.width()) / float(source_image->width())) * sx;
|
||||
float source_height_scale = (float(source_size.height()) / float(source_image->height())) * sy;
|
||||
|
||||
if (!isEqual(source_width_scale, 1.0) || !isEqual(source_height_scale, 1.0)) {
|
||||
transform.scale(source_width_scale, source_height_scale);
|
||||
transformed = true;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: Composite Image Layer: Prepare)", "source_frame->number", source_frame->number, "new_frame->GetImage()->width()", new_frame->GetImage()->width(), "transformed", transformed);
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: Composite Image Layer: Prepare)", "source_frame->number", source_frame->number, "new_frame->GetImage()->width()", new_frame->GetImage()->width(), "source_image->width()", source_image->width());
|
||||
|
||||
/* COMPOSITE SOURCE IMAGE (LAYER) ONTO FINAL IMAGE */
|
||||
std::shared_ptr<QImage> new_image;
|
||||
#pragma omp critical (T_addLayer)
|
||||
new_image = new_frame->GetImage();
|
||||
new_image = new_frame->GetImage();
|
||||
|
||||
// Load timeline's new frame image into a QPainter
|
||||
QPainter painter(new_image.get());
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true);
|
||||
|
||||
// Apply transform (translate, rotate, scale)... if any
|
||||
if (transformed)
|
||||
painter.setTransform(transform);
|
||||
|
||||
// Composite a new layer onto the image
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
painter.drawImage(0, 0, *source_image, crop_x * source_image->width(), crop_y * source_image->height(), crop_w * source_image->width(), crop_h * source_image->height());
|
||||
|
||||
// Draw frame #'s on top of image (if needed)
|
||||
if (source_clip->display != FRAME_DISPLAY_NONE) {
|
||||
std::stringstream frame_number_str;
|
||||
switch (source_clip->display)
|
||||
{
|
||||
case (FRAME_DISPLAY_NONE):
|
||||
// This is only here to prevent unused-enum warnings
|
||||
break;
|
||||
|
||||
case (FRAME_DISPLAY_CLIP):
|
||||
frame_number_str << clip_frame_number;
|
||||
break;
|
||||
|
||||
case (FRAME_DISPLAY_TIMELINE):
|
||||
frame_number_str << timeline_frame_number;
|
||||
break;
|
||||
|
||||
case (FRAME_DISPLAY_BOTH):
|
||||
frame_number_str << timeline_frame_number << " (" << clip_frame_number << ")";
|
||||
break;
|
||||
}
|
||||
|
||||
// Draw frame number on top of image
|
||||
painter.setPen(QColor("#ffffff"));
|
||||
painter.drawText(20, 20, QString(frame_number_str.str().c_str()));
|
||||
}
|
||||
|
||||
painter.drawImage(0, 0, *source_image, 0, 0, source_image->width(), source_image->height());
|
||||
painter.end();
|
||||
|
||||
// Add new QImage to frame
|
||||
new_frame->AddImage(new_image);
|
||||
|
||||
// Debug output
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: Composite Image Layer: Completed)", "source_frame->number", source_frame->number, "new_frame->GetImage()->width()", new_frame->GetImage()->width(), "transformed", transformed);
|
||||
ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: Composite Image Layer: Completed)", "source_frame->number", source_frame->number, "new_frame->GetImage()->width()", new_frame->GetImage()->width());
|
||||
}
|
||||
|
||||
// Update the list of 'opened' clips
|
||||
|
||||
Reference in New Issue
Block a user