From 000c9d6d4c4b0cc5cd7ba924507152f3fe73f989 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 26 Aug 2020 13:12:42 -0500 Subject: [PATCH] - Refactoring all Timeline drawing code into the Clip class - Making Clip a proper Reader (so it can be used directly, instead of a Timeline) --- include/Clip.h | 21 +++- src/Clip.cpp | 299 ++++++++++++++++++++++++++++++++++++++++++++++- src/Timeline.cpp | 261 ++--------------------------------------- 3 files changed, 323 insertions(+), 258 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index 0c98526a0..a609cb85c 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -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 effects; /// 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 apply_effects(std::shared_ptr frame); + /// Apply keyframes to the source frame (if any) + std::shared_ptr apply_keyframes(std::shared_ptr 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); diff --git a/src/Clip.cpp b/src/Clip.cpp index 1f7382828..8e1435477 100644 --- a/src/Clip.cpp +++ b/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 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 @@ -363,6 +372,9 @@ std::shared_ptr 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; } @@ -1057,3 +1069,284 @@ std::shared_ptr Clip::apply_effects(std::shared_ptr 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 Clip::apply_keyframes(std::shared_ptr frame) +{ + // Get actual frame image data + std::shared_ptr 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(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 new_image; + new_image = std::shared_ptr(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; +} diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 9ccf9a84e..42009a22f 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -482,25 +482,6 @@ void Timeline::add_layer(std::shared_ptr 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 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(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) { @@ -571,7 +552,6 @@ void Timeline::add_layer(std::shared_ptr 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) @@ -586,253 +566,26 @@ void Timeline::add_layer(std::shared_ptr 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 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