OpenShot Library | libopenshot 0.5.0
Loading...
Searching...
No Matches
Frame.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2019 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include <thread> // for std::this_thread::sleep_for
14#include <chrono> // for std::chrono::milliseconds
15#include <iomanip>
16
17#include "Frame.h"
18#include "AudioBufferSource.h"
19#include "AudioResampler.h"
20#include "QtUtilities.h"
21
22#include <AppConfig.h>
23#include <juce_audio_basics/juce_audio_basics.h>
24#include <juce_audio_devices/juce_audio_devices.h>
25
26#include <QApplication>
27#include <QImage>
28#include <QPixmap>
29#include <QBitmap>
30#include <QColor>
31#include <QString>
32#include <QVector>
33#include <QPainter>
34#include <QHBoxLayout>
35#include <QWidget>
36#include <QLabel>
37#include <QPointF>
38#include <QWidget>
39
40using namespace std;
41using namespace openshot;
42
43// Constructor - image & audio
44Frame::Frame(int64_t number, int width, int height, std::string color, int samples, int channels)
45 : audio(std::make_shared<juce::AudioBuffer<float>>(channels, samples)),
46 number(number), width(width), height(height),
47 pixel_ratio(1,1), color(color),
48 channels(channels), channel_layout(LAYOUT_STEREO),
49 sample_rate(44100),
50 has_audio_data(false), has_image_data(false),
51 max_audio_sample(0), audio_is_increasing(true)
52{
53 // zero (fill with silence) the audio buffer
54 audio->clear();
55}
56
57// Delegating Constructor - blank frame
58Frame::Frame() : Frame::Frame(1, 1, 1, "#000000", 0, 2) {}
59
60// Delegating Constructor - image only
61Frame::Frame(int64_t number, int width, int height, std::string color)
62 : Frame::Frame(number, width, height, color, 0, 2) {}
63
64// Delegating Constructor - audio only
65Frame::Frame(int64_t number, int samples, int channels)
66 : Frame::Frame(number, 1, 1, "#000000", samples, channels) {}
67
68
69// Copy constructor
70Frame::Frame ( const Frame &other )
71{
72 // copy pointers and data
73 DeepCopy(other);
74}
75
76// Assignment operator
78{
79 // copy pointers and data
80 DeepCopy(other);
81
82 return *this;
83}
84
85// Copy data and pointers from another Frame instance
86void Frame::DeepCopy(const Frame& other)
87{
88 number = other.number;
89 channels = other.channels;
90 width = other.width;
91 height = other.height;
92 channel_layout = other.channel_layout;
95 sample_rate = other.sample_rate;
96 pixel_ratio = Fraction(other.pixel_ratio.num, other.pixel_ratio.den);
97 color = other.color;
98 max_audio_sample = other.max_audio_sample;
99 audio_is_increasing = other.audio_is_increasing;
100
101 if (other.image)
102 image = std::make_shared<QImage>(*(other.image));
103 if (other.audio)
104 audio = std::make_shared<juce::AudioBuffer<float>>(*(other.audio));
105 if (other.wave_image)
106 wave_image = std::make_shared<QImage>(*(other.wave_image));
107}
108
109// Destructor
111 // Clear all pointers
112 image.reset();
113 audio.reset();
114 #ifdef USE_OPENCV
115 imagecv.release();
116 #endif
117}
118
119// Display the frame image to the screen (primarily used for debugging reasons)
121{
122 if (!QApplication::instance()) {
123 // Only create the QApplication once
124 static int argc = 1;
125 static char* argv[1] = {NULL};
126 previewApp = std::make_shared<QApplication>(argc, argv);
127 }
128
129 // Get preview image
130 std::shared_ptr<QImage> previewImage = GetImage();
131
132 // Update the image to reflect the correct pixel aspect ration (i.e. to fix non-square pixels)
133 if (pixel_ratio.num != 1 || pixel_ratio.den != 1)
134 {
135 // Resize to fix DAR
136 previewImage = std::make_shared<QImage>(previewImage->scaled(
137 previewImage->size().width(), previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble(),
138 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
139 }
140
141 // Create window
142 QWidget previewWindow;
143 previewWindow.setStyleSheet("background-color: #000000;");
144 QHBoxLayout layout;
145
146 // Create label with current frame's image
147 QLabel previewLabel;
148 previewLabel.setPixmap(QPixmap::fromImage(*previewImage));
149 previewLabel.setMask(QPixmap::fromImage(*previewImage).mask());
150 layout.addWidget(&previewLabel);
151
152 // Show the window
153 previewWindow.setLayout(&layout);
154 previewWindow.show();
155 previewApp->exec();
156}
157
158// Get an audio waveform image
159std::shared_ptr<QImage> Frame::GetWaveform(int width, int height, int Red, int Green, int Blue, int Alpha)
160{
161 // Clear any existing waveform image
163
164 // Init a list of lines
165 QVector<QPointF> lines;
166 QVector<QPointF> labels;
167
168 // Calculate width of an image based on the # of samples
169 int total_samples = GetAudioSamplesCount();
170 if (total_samples > 0)
171 {
172 // If samples are present...
173 int new_height = 200 * audio->getNumChannels();
174 int height_padding = 20 * (audio->getNumChannels() - 1);
175 int total_height = new_height + height_padding;
176 int total_width = 0;
177 float zero_height = 1.0; // Used to clamp near-zero vales to this value to prevent gaps
178
179 // Loop through each audio channel
180 float Y = 100.0;
181 for (int channel = 0; channel < audio->getNumChannels(); channel++)
182 {
183 float X = 0.0;
184
185 // Get audio for this channel
186 const float *samples = audio->getReadPointer(channel);
187
188 for (int sample = 0; sample < GetAudioSamplesCount(); sample++, X++)
189 {
190 // Sample value (scaled to -100 to 100)
191 float value = samples[sample] * 100.0;
192
193 // Set threshold near zero (so we don't allow near-zero values)
194 // This prevents empty gaps from appearing in the waveform
195 if (value > -zero_height && value < 0.0) {
196 value = -zero_height;
197 } else if (value > 0.0 && value < zero_height) {
198 value = zero_height;
199 }
200
201 // Append a line segment for each sample
202 lines.push_back(QPointF(X, Y));
203 lines.push_back(QPointF(X, Y - value));
204 }
205
206 // Add Channel Label Coordinate
207 labels.push_back(QPointF(5.0, Y - 5.0));
208
209 // Increment Y
210 Y += (200 + height_padding);
211 total_width = X;
212 }
213
214 // Create blank image
215 wave_image = std::make_shared<QImage>(
216 total_width, total_height, QImage::Format_RGBA8888_Premultiplied);
217 wave_image->fill(QColor(0,0,0,0));
218
219 // Load QPainter with wave_image device
220 QPainter painter(wave_image.get());
221
222 // Set pen color
223 QPen pen;
224 pen.setColor(QColor(Red, Green, Blue, Alpha));
225 pen.setWidthF(1.0);
226 pen.setStyle(Qt::SolidLine);
227 painter.setPen(pen);
228
229 // Draw the waveform
230 painter.drawLines(lines);
231 painter.end();
232 }
233 else
234 {
235 // No audio samples present
236 wave_image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
237 wave_image->fill(QColor(QString::fromStdString("#000000")));
238 }
239
240 // Resize Image (if needed)
241 if (wave_image->width() != width || wave_image->height() != height) {
242 QImage scaled_wave_image = wave_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
243 wave_image = std::make_shared<QImage>(scaled_wave_image);
244 }
245
246 // Return new image
247 return wave_image;
248}
249
250// Clear the waveform image (and deallocate its memory)
252{
253 if (wave_image)
254 wave_image.reset();
255}
256
257// Get an audio waveform image pixels
258const unsigned char* Frame::GetWaveformPixels(int width, int height, int Red, int Green, int Blue, int Alpha)
259{
260 // Get audio wave form image
261 wave_image = GetWaveform(width, height, Red, Green, Blue, Alpha);
262
263 // Return array of pixel packets
264 return wave_image->constBits();
265}
266
267// Display the wave form
269{
270 // Get audio wave form image
271 GetWaveform(720, 480, 0, 123, 255, 255);
272
273 if (!QApplication::instance()) {
274 // Only create the QApplication once
275 static int argc = 1;
276 static char* argv[1] = {NULL};
277 previewApp = std::make_shared<QApplication>(argc, argv);
278 }