2011-10-11 08:44:27 -05:00
|
|
|
/**
|
2013-09-09 23:32:16 -05:00
|
|
|
* @file
|
2013-09-12 17:52:10 -05:00
|
|
|
* @brief Source file for Frame class
|
|
|
|
|
* @author Jonathan Thomas <jonathan@openshot.org>
|
|
|
|
|
*
|
2019-06-09 08:31:04 -04:00
|
|
|
* @ref License
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* LICENSE
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2019-06-11 06:48:32 -04:00
|
|
|
* Copyright (c) 2008-2019 OpenShot Studios, LLC
|
2014-03-29 18:49:22 -05:00
|
|
|
* <http://www.openshotstudios.com/>. This file is part of
|
|
|
|
|
* OpenShot Library (libopenshot), an open-source project dedicated to
|
|
|
|
|
* delivering high quality video editing and animation solutions to the
|
|
|
|
|
* world. For more information visit <http://www.openshot.org/>.
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2014-03-29 18:49:22 -05:00
|
|
|
* OpenShot Library (libopenshot) is free software: you can redistribute it
|
2014-07-11 16:52:14 -05:00
|
|
|
* and/or modify it under the terms of the GNU Lesser General Public License
|
2014-03-29 18:49:22 -05:00
|
|
|
* as published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
* License, or (at your option) any later version.
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2014-03-29 18:49:22 -05:00
|
|
|
* OpenShot Library (libopenshot) is distributed in the hope that it will be
|
|
|
|
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2014-07-11 16:52:14 -05:00
|
|
|
* GNU Lesser General Public License for more details.
|
2013-09-12 17:52:10 -05:00
|
|
|
*
|
2014-07-11 16:52:14 -05:00
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
2014-03-29 18:49:22 -05:00
|
|
|
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
|
2011-10-11 08:44:27 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "../include/Frame.h"
|
2020-09-13 16:28:31 -04:00
|
|
|
#include "JuceHeader.h"
|
|
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QImage>
|
|
|
|
|
#include <QPixmap>
|
|
|
|
|
#include <QBitmap>
|
|
|
|
|
#include <QColor>
|
|
|
|
|
#include <QString>
|
|
|
|
|
#include <QVector>
|
|
|
|
|
#include <QPainter>
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
#include <QWidget>
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QPointF>
|
|
|
|
|
#include <QWidget>
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
using namespace openshot;
|
|
|
|
|
|
2020-08-20 20:40:55 -04:00
|
|
|
// Constructor - blank frame
|
2018-01-21 23:49:07 -06:00
|
|
|
Frame::Frame() : number(1), pixel_ratio(1,1), channels(2), width(1), height(1), color("#000000"),
|
2018-05-30 03:20:31 -05:00
|
|
|
channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false),
|
|
|
|
|
max_audio_sample(0)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2020-08-20 20:40:55 -04:00
|
|
|
// Allocate and zero (fill with silence) the audio buffer
|
2020-08-20 16:50:12 -04:00
|
|
|
audio = std::make_shared<juce::AudioSampleBuffer>(channels, 0);
|
2011-10-11 08:44:27 -05:00
|
|
|
audio->clear();
|
2019-12-27 01:01:48 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2020-08-20 20:40:55 -04:00
|
|
|
// Constructor - image only
|
2019-08-04 15:13:40 -04:00
|
|
|
Frame::Frame(int64_t number, int width, int height, std::string color)
|
2018-01-21 23:49:07 -06:00
|
|
|
: number(number), pixel_ratio(1,1), channels(2), width(width), height(height), color(color),
|
2018-05-30 03:20:31 -05:00
|
|
|
channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false),
|
|
|
|
|
max_audio_sample(0)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2020-08-20 20:40:55 -04:00
|
|
|
// Allocate and zero (fill with silence) the audio buffer
|
2020-08-20 16:50:12 -04:00
|
|
|
audio = std::make_shared<juce::AudioSampleBuffer>(channels, 0);
|
2011-10-11 08:44:27 -05:00
|
|
|
audio->clear();
|
2019-12-27 01:01:48 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2020-08-20 20:40:55 -04:00
|
|
|
// Constructor - audio only
|
2017-09-28 16:03:01 -05:00
|
|
|
Frame::Frame(int64_t number, int samples, int channels) :
|
2018-01-21 23:49:07 -06:00
|
|
|
number(number), pixel_ratio(1,1), channels(channels), width(1), height(1), color("#000000"),
|
2018-05-30 03:20:31 -05:00
|
|
|
channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false),
|
|
|
|
|
max_audio_sample(0)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2020-08-20 20:40:55 -04:00
|
|
|
// Allocate and zero (fill with silence) the audio buffer
|
2020-08-20 16:50:12 -04:00
|
|
|
audio = std::make_shared<juce::AudioSampleBuffer>(channels, samples);
|
2011-10-11 08:44:27 -05:00
|
|
|
audio->clear();
|
2019-12-27 01:01:48 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Constructor - image & audio
|
2019-08-04 15:13:40 -04:00
|
|
|
Frame::Frame(int64_t number, int width, int height, std::string color, int samples, int channels)
|
2018-01-21 23:49:07 -06:00
|
|
|
: number(number), pixel_ratio(1,1), channels(channels), width(width), height(height), color(color),
|
2018-05-30 03:20:31 -05:00
|
|
|
channel_layout(LAYOUT_STEREO), sample_rate(44100), qbuffer(NULL), has_audio_data(false), has_image_data(false),
|
|
|
|
|
max_audio_sample(0)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2020-08-20 20:40:55 -04:00
|
|
|
// Allocate and zero (fill with silence) the audio buffer
|
2020-08-20 16:50:12 -04:00
|
|
|
audio = std::make_shared<juce::AudioSampleBuffer>(channels, samples);
|
2011-10-11 08:44:27 -05:00
|
|
|
audio->clear();
|
2019-12-27 01:01:48 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// Copy constructor
|
|
|
|
|
Frame::Frame ( const Frame &other )
|
|
|
|
|
{
|
|
|
|
|
// copy pointers and data
|
|
|
|
|
DeepCopy(other);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-21 23:49:07 -06:00
|
|
|
// Assignment operator
|
|
|
|
|
Frame& Frame::operator= (const Frame& other)
|
|
|
|
|
{
|
|
|
|
|
// copy pointers and data
|
|
|
|
|
DeepCopy(other);
|
|
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Copy data and pointers from another Frame instance
|
|
|
|
|
void Frame::DeepCopy(const Frame& other)
|
|
|
|
|
{
|
|
|
|
|
number = other.number;
|
2012-07-02 00:51:10 -05:00
|
|
|
channels = other.channels;
|
2018-01-21 23:49:07 -06:00
|
|
|
width = other.width;
|
|
|
|
|
height = other.height;
|
2015-02-05 00:11:55 -06:00
|
|
|
channel_layout = other.channel_layout;
|
2019-12-27 10:51:02 -05:00
|
|
|
has_audio_data = other.has_audio_data;
|
2016-01-05 01:59:50 -06:00
|
|
|
has_image_data = other.has_image_data;
|
|
|
|
|
sample_rate = other.sample_rate;
|
2018-01-21 23:49:07 -06:00
|
|
|
pixel_ratio = Fraction(other.pixel_ratio.num, other.pixel_ratio.den);
|
|
|
|
|
color = other.color;
|
2020-01-02 17:15:49 -06:00
|
|
|
max_audio_sample = other.max_audio_sample;
|
2016-01-05 01:59:50 -06:00
|
|
|
|
2018-01-21 23:49:07 -06:00
|
|
|
if (other.image)
|
2020-08-20 16:50:12 -04:00
|
|
|
image = std::make_shared<QImage>(*(other.image));
|
2018-01-21 23:49:07 -06:00
|
|
|
if (other.audio)
|
2020-08-20 16:50:12 -04:00
|
|
|
audio = std::make_shared<juce::AudioSampleBuffer>(*(other.audio));
|
2012-10-14 02:36:05 -05:00
|
|
|
if (other.wave_image)
|
2020-08-20 16:50:12 -04:00
|
|
|
wave_image = std::make_shared<QImage>(*(other.wave_image));
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2019-05-08 14:53:23 -07:00
|
|
|
// Destructor
|
2013-01-12 12:45:55 -06:00
|
|
|
Frame::~Frame() {
|
|
|
|
|
// Clear all pointers
|
|
|
|
|
image.reset();
|
|
|
|
|
audio.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Display the frame image to the screen (primarily used for debugging reasons)
|
|
|
|
|
void Frame::Display()
|
|
|
|
|
{
|
2016-02-23 00:27:03 -06:00
|
|
|
if (!QApplication::instance()) {
|
|
|
|
|
// Only create the QApplication once
|
|
|
|
|
static int argc = 1;
|
|
|
|
|
static char* argv[1] = {NULL};
|
2020-08-20 16:50:12 -04:00
|
|
|
previewApp = std::make_shared<QApplication>(argc, argv);
|
2016-02-23 00:27:03 -06:00
|
|
|
}
|
2011-12-11 20:42:50 -06:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Get preview image
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<QImage> previewImage = GetImage();
|
2016-02-23 00:27:03 -06:00
|
|
|
|
|
|
|
|
// Update the image to reflect the correct pixel aspect ration (i.e. to fix non-squar pixels)
|
|
|
|
|
if (pixel_ratio.num != 1 || pixel_ratio.den != 1)
|
|
|
|
|
{
|
|
|
|
|
// Calculate correct DAR (display aspect ratio)
|
|
|
|
|
int new_width = previewImage->size().width();
|
|
|
|
|
int new_height = previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble();
|
|
|
|
|
|
|
|
|
|
// Resize to fix DAR
|
2020-08-20 16:50:12 -04:00
|
|
|
previewImage = std::make_shared<QImage>(previewImage->scaled(
|
|
|
|
|
new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
2016-02-23 00:27:03 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create window
|
|
|
|
|
QWidget previewWindow;
|
|
|
|
|
previewWindow.setStyleSheet("background-color: #000000;");
|
|
|
|
|
QHBoxLayout layout;
|
|
|
|
|
|
|
|
|
|
// Create label with current frame's image
|
|
|
|
|
QLabel previewLabel;
|
|
|
|
|
previewLabel.setPixmap(QPixmap::fromImage(*previewImage));
|
|
|
|
|
previewLabel.setMask(QPixmap::fromImage(*previewImage).mask());
|
|
|
|
|
layout.addWidget(&previewLabel);
|
|
|
|
|
|
|
|
|
|
// Show the window
|
|
|
|
|
previewWindow.setLayout(&layout);
|
|
|
|
|
previewWindow.show();
|
|
|
|
|
previewApp->exec();
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2012-08-22 17:31:12 -05:00
|
|
|
// Get an audio waveform image
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<QImage> Frame::GetWaveform(int width, int height, int Red, int Green, int Blue, int Alpha)
|
2011-10-24 17:32:26 -05:00
|
|
|
{
|
2012-08-22 17:31:12 -05:00
|
|
|
// Clear any existing waveform image
|
|
|
|
|
ClearWaveform();
|
2011-10-24 17:32:26 -05:00
|
|
|
|
|
|
|
|
// Init a list of lines
|
2015-06-01 00:20:14 -07:00
|
|
|
QVector<QPointF> lines;
|
|
|
|
|
QVector<QPointF> labels;
|
2011-10-24 17:32:26 -05:00
|
|
|
|
2015-06-01 00:20:14 -07:00
|
|
|
// Calculate width of an image based on the # of samples
|
2018-05-30 03:20:31 -05:00
|
|
|
int total_samples = GetAudioSamplesCount();
|
2012-08-22 17:31:12 -05:00
|
|
|
if (total_samples > 0)
|
2011-10-24 17:32:26 -05:00
|
|
|
{
|
2011-10-27 11:27:44 -05:00
|
|
|
// If samples are present...
|
2012-08-22 17:31:12 -05:00
|
|
|
int new_height = 200 * audio->getNumChannels();
|
2011-10-27 11:27:44 -05:00
|
|
|
int height_padding = 20 * (audio->getNumChannels() - 1);
|
2012-08-22 17:31:12 -05:00
|
|
|
int total_height = new_height + height_padding;
|
|
|
|
|
int total_width = 0;
|
2011-10-24 17:32:26 -05:00
|
|
|
|
2011-10-27 11:27:44 -05:00
|
|
|
// Loop through each audio channel
|
|
|
|
|
int Y = 100;
|
|
|
|
|
for (int channel = 0; channel < audio->getNumChannels(); channel++)
|
2011-10-24 17:32:26 -05:00
|
|
|
{
|
2012-08-22 17:31:12 -05:00
|
|
|
int X = 0;
|
|
|
|
|
|
2011-10-27 11:27:44 -05:00
|
|
|
// Get audio for this channel
|
2015-02-05 00:11:55 -06:00
|
|
|
const float *samples = audio->getReadPointer(channel);
|
2011-10-24 17:32:26 -05:00
|
|
|
|
2018-05-30 03:20:31 -05:00
|
|
|
for (int sample = 0; sample < GetAudioSamplesCount(); sample++, X++)
|
2011-10-27 11:27:44 -05:00
|
|
|
{
|
|
|
|
|
// Sample value (scaled to -100 to 100)
|
|
|
|
|
float value = samples[sample] * 100;
|
2011-10-24 17:32:26 -05:00
|
|
|
|
2011-10-27 11:27:44 -05:00
|
|
|
// Append a line segment for each sample
|
2015-06-01 00:20:14 -07:00
|
|
|
if (value != 0.0) {
|
2011-10-27 11:27:44 -05:00
|
|
|
// LINE
|
2015-06-01 00:20:14 -07:00
|
|
|
lines.push_back(QPointF(X,Y));
|
|
|
|
|
lines.push_back(QPointF(X,Y-value));
|
|
|
|
|
}
|
|
|
|
|
else {
|
2011-10-27 11:27:44 -05:00
|
|
|
// DOT
|
2015-06-01 00:20:14 -07:00
|
|
|
lines.push_back(QPointF(X,Y));
|
|
|
|
|
lines.push_back(QPointF(X,Y));
|
|
|
|
|
}
|
2011-10-24 17:32:26 -05:00
|
|
|
}
|
2011-10-27 11:27:44 -05:00
|
|
|
|
2015-06-01 00:20:14 -07:00
|
|
|
// Add Channel Label Coordinate
|
|
|
|
|
labels.push_back(QPointF(5, Y - 5));
|
2011-10-27 11:27:44 -05:00
|
|
|
|
|
|
|
|
// Increment Y
|
|
|
|
|
Y += (200 + height_padding);
|
2012-08-22 17:31:12 -05:00
|
|
|
total_width = X;
|
2011-10-24 17:32:26 -05:00
|
|
|
}
|
|
|
|
|
|
2015-06-01 00:20:14 -07:00
|
|
|
// Create blank image
|
2020-08-20 16:50:12 -04:00
|
|
|
wave_image = std::make_shared<QImage>(
|
|
|
|
|
total_width, total_height, QImage::Format_RGBA8888);
|
2015-11-25 23:54:10 -06:00
|
|
|
wave_image->fill(QColor(0,0,0,0));
|
2015-06-01 00:20:14 -07:00
|
|
|
|
|
|
|
|
// Load QPainter with wave_image device
|
|
|
|
|
QPainter painter(wave_image.get());
|
|
|
|
|
|
|
|
|
|
// Set pen color
|
|
|
|
|
painter.setPen(QColor(Red, Green, Blue, Alpha));
|
2012-08-22 17:31:12 -05:00
|
|
|
|
2011-10-27 11:27:44 -05:00
|
|
|
// Draw the waveform
|
2015-06-01 00:20:14 -07:00
|
|
|
painter.drawLines(lines);
|
|
|
|
|
painter.end();
|
|
|
|
|
|
|
|
|
|
// Loop through the channels labels (and draw the text)
|
|
|
|
|
// TODO: Configure Fonts in Qt5 correctly, so the drawText method does not crash
|
|
|
|
|
// painter.setFont(QFont(QString("Arial"), 16, 1, false));
|
|
|
|
|
// for (int channel = 0; channel < labels.size(); channel++) {
|
|
|
|
|
// stringstream label;
|
|
|
|
|
// label << "Channel " << channel;
|
|
|
|
|
// painter.drawText(labels.at(channel), QString::fromStdString(label.str()));
|
|
|
|
|
// }
|
2011-10-27 11:27:44 -05:00
|
|
|
|
|
|
|
|
// Resize Image (if requested)
|
2015-06-01 00:20:14 -07:00
|
|
|
if (width != total_width || height != total_height) {
|
|
|
|
|
QImage scaled_wave_image = wave_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::FastTransformation);
|
2020-08-20 16:50:12 -04:00
|
|
|
wave_image = std::make_shared<QImage>(scaled_wave_image);
|
2012-08-22 17:31:12 -05:00
|
|
|
}
|
2011-10-27 11:27:44 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// No audio samples present
|
2020-08-20 16:50:12 -04:00
|
|
|
wave_image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888);
|
2015-06-01 00:20:14 -07:00
|
|
|
wave_image->fill(QColor(QString::fromStdString("#000000")));
|
2011-10-24 17:32:26 -05:00
|
|
|
}
|
|
|
|
|
|
2012-08-22 17:31:12 -05:00
|
|
|
// Return new image
|
|
|
|
|
return wave_image;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-14 09:26:56 -07:00
|
|
|
// Clear the waveform image (and deallocate its memory)
|
2012-08-22 17:31:12 -05:00
|
|
|
void Frame::ClearWaveform()
|
|
|
|
|
{
|
|
|
|
|
if (wave_image)
|
2012-10-17 09:57:02 -05:00
|
|
|
wave_image.reset();
|
2012-08-22 17:31:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get an audio waveform image pixels
|
2015-06-01 00:20:14 -07:00
|
|
|
const unsigned char* Frame::GetWaveformPixels(int width, int height, int Red, int Green, int Blue, int Alpha)
|
2012-08-22 17:31:12 -05:00
|
|
|
{
|
|
|
|
|
// Get audio wave form image
|
2015-06-01 00:20:14 -07:00
|
|
|
wave_image = GetWaveform(width, height, Red, Green, Blue, Alpha);
|
2012-08-22 17:31:12 -05:00
|
|
|
|
|
|
|
|
// Return array of pixel packets
|
2019-11-26 00:56:13 +01:00
|
|
|
return wave_image->constBits();
|
2012-08-22 17:31:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Display the wave form
|
|
|
|
|
void Frame::DisplayWaveform()
|
|
|
|
|
{
|
|
|
|
|
// Get audio wave form image
|
2015-06-01 00:20:14 -07:00
|
|
|
GetWaveform(720, 480, 0, 123, 255, 255);
|
2012-08-22 17:31:12 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
if (!QApplication::instance()) {
|
|
|
|
|
// Only create the QApplication once
|
|
|
|
|
static int argc = 1;
|
|
|
|
|
static char* argv[1] = {NULL};
|
2020-08-20 16:50:12 -04:00
|
|
|
previewApp = std::make_shared<QApplication>(argc, argv);
|
2015-06-01 00:20:14 -07:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Create window
|
|
|
|
|
QWidget previewWindow;
|
|
|
|
|
previewWindow.setStyleSheet("background-color: #000000;");
|
|
|
|
|
QHBoxLayout layout;
|
|
|
|
|
|
|
|
|
|
// Create label with current frame's waveform image
|
|
|
|
|
QLabel previewLabel;
|
|
|
|
|
previewLabel.setPixmap(QPixmap::fromImage(*wave_image));
|
|
|
|
|
previewLabel.setMask(QPixmap::fromImage(*wave_image).mask());
|
|
|
|
|
layout.addWidget(&previewLabel);
|
|
|
|
|
|
|
|
|
|
// Show the window
|
|
|
|
|
previewWindow.setLayout(&layout);
|
|
|
|
|
previewWindow.show();
|
|
|
|
|
previewApp->exec();
|
2012-08-22 17:31:12 -05:00
|
|
|
|
|
|
|
|
// Deallocate waveform image
|
|
|
|
|
ClearWaveform();
|
2011-10-24 17:32:26 -05:00
|
|
|
}
|
|
|
|
|
|
2016-04-22 02:43:06 -05:00
|
|
|
// Get magnitude of range of samples (if channel is -1, return average of all channels for that sample)
|
|
|
|
|
float Frame::GetAudioSample(int channel, int sample, int magnitude_range)
|
|
|
|
|
{
|
|
|
|
|
if (channel > 0) {
|
|
|
|
|
// return average magnitude for a specific channel/sample range
|
|
|
|
|
return audio->getMagnitude(channel, sample, magnitude_range);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// Return average magnitude for all channels
|
|
|
|
|
return audio->getMagnitude(sample, magnitude_range);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-09 01:22:11 -05:00
|
|
|
// Get an array of sample data
|
|
|
|
|
float* Frame::GetAudioSamples(int channel)
|
|
|
|
|
{
|
|
|
|
|
// return JUCE audio data for this channel
|
2015-02-05 00:11:55 -06:00
|
|
|
return audio->getWritePointer(channel);
|
2012-07-09 01:22:11 -05:00
|
|
|
}
|
|
|
|
|
|
2015-02-05 00:11:55 -06:00
|
|
|
// Get a planar array of sample data, using any sample rate
|
|
|
|
|
float* Frame::GetPlanarAudioSamples(int new_sample_rate, AudioResampler* resampler, int* sample_count)
|
2012-07-24 12:50:17 -05:00
|
|
|
{
|
2012-07-30 02:37:19 -05:00
|
|
|
float *output = NULL;
|
2019-10-27 03:56:13 -04:00
|
|
|
juce::AudioSampleBuffer *buffer(audio.get());
|
2012-07-30 02:37:19 -05:00
|
|
|
int num_of_channels = audio->getNumChannels();
|
2018-05-30 03:20:31 -05:00
|
|
|
int num_of_samples = GetAudioSamplesCount();
|
2012-07-30 02:37:19 -05:00
|
|
|
|
|
|
|
|
// Resample to new sample rate (if needed)
|
2015-02-05 00:11:55 -06:00
|
|
|
if (new_sample_rate != sample_rate)
|
2012-07-30 02:37:19 -05:00
|
|
|
{
|
|
|
|
|
// YES, RESAMPLE AUDIO
|
2015-02-05 00:11:55 -06:00
|
|
|
resampler->SetBuffer(audio.get(), sample_rate, new_sample_rate);
|
|
|
|
|
|
|
|
|
|
// Resample data, and return new buffer pointer
|
|
|
|
|
buffer = resampler->GetResampledBuffer();
|
|
|
|
|
|
|
|
|
|
// Update num_of_samples
|
|
|
|
|
num_of_samples = buffer->getNumSamples();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// INTERLEAVE all samples together (channel 1 + channel 2 + channel 1 + channel 2, etc...)
|
|
|
|
|
output = new float[num_of_channels * num_of_samples];
|
|
|
|
|
int position = 0;
|
|
|
|
|
|
|
|
|
|
// Loop through samples in each channel (combining them)
|
|
|
|
|
for (int channel = 0; channel < num_of_channels; channel++)
|
|
|
|
|
{
|
|
|
|
|
for (int sample = 0; sample < num_of_samples; sample++)
|
|
|
|
|
{
|
|
|
|
|
// Add sample to output array
|
|
|
|
|
output[position] = buffer->getReadPointer(channel)[sample];
|
|
|
|
|
|
|
|
|
|
// increment position
|
|
|
|
|
position++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update sample count (since it might have changed due to resampling)
|
|
|
|
|
*sample_count = num_of_samples;
|
|
|
|
|
|
|
|
|
|
// return combined array
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get an array of sample data (all channels interleaved together), using any sample rate
|
|
|
|
|
float* Frame::GetInterleavedAudioSamples(int new_sample_rate, AudioResampler* resampler, int* sample_count)
|
|
|
|
|
{
|
|
|
|
|
float *output = NULL;
|
2019-10-27 03:56:13 -04:00
|
|
|
juce::AudioSampleBuffer *buffer(audio.get());
|
2015-02-05 00:11:55 -06:00
|
|
|
int num_of_channels = audio->getNumChannels();
|
2018-05-30 03:20:31 -05:00
|
|
|
int num_of_samples = GetAudioSamplesCount();
|
2015-02-05 00:11:55 -06:00
|
|
|
|
|
|
|
|
// Resample to new sample rate (if needed)
|
2015-06-01 00:20:14 -07:00
|
|
|
if (new_sample_rate != sample_rate && resampler)
|
2015-02-05 00:11:55 -06:00
|
|
|
{
|
|
|
|
|
// YES, RESAMPLE AUDIO
|
|
|
|
|
resampler->SetBuffer(audio.get(), sample_rate, new_sample_rate);
|
2012-07-30 02:37:19 -05:00
|
|
|
|
2012-08-05 15:17:37 -05:00
|
|
|
// Resample data, and return new buffer pointer
|
|
|
|
|
buffer = resampler->GetResampledBuffer();
|
2012-07-30 02:37:19 -05:00
|
|
|
|
|
|
|
|
// Update num_of_samples
|
2012-08-05 15:17:37 -05:00
|
|
|
num_of_samples = buffer->getNumSamples();
|
2012-07-30 02:37:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// INTERLEAVE all samples together (channel 1 + channel 2 + channel 1 + channel 2, etc...)
|
|
|
|
|
output = new float[num_of_channels * num_of_samples];
|
2012-07-24 12:50:17 -05:00
|
|
|
int position = 0;
|
|
|
|
|
|
|
|
|
|
// Loop through samples in each channel (combining them)
|
2012-07-30 02:37:19 -05:00
|
|
|
for (int sample = 0; sample < num_of_samples; sample++)
|
2012-07-24 12:50:17 -05:00
|
|
|
{
|
2012-07-30 02:37:19 -05:00
|
|
|
for (int channel = 0; channel < num_of_channels; channel++)
|
2012-07-24 12:50:17 -05:00
|
|
|
{
|
|
|
|
|
// Add sample to output array
|
2015-02-05 00:11:55 -06:00
|
|
|
output[position] = buffer->getReadPointer(channel)[sample];
|
2012-07-30 02:37:19 -05:00
|
|
|
|
2012-07-24 12:50:17 -05:00
|
|
|
// increment position
|
|
|
|
|
position++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-30 02:37:19 -05:00
|
|
|
// Update sample count (since it might have changed due to resampling)
|
|
|
|
|
*sample_count = num_of_samples;
|
|
|
|
|
|
2012-07-24 12:50:17 -05:00
|
|
|
// return combined array
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-09 01:22:11 -05:00
|
|
|
// Get number of audio channels
|
|
|
|
|
int Frame::GetAudioChannelsCount()
|
|
|
|
|
{
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingAudioSection);
|
2017-07-19 16:05:07 -05:00
|
|
|
if (audio)
|
|
|
|
|
return audio->getNumChannels();
|
|
|
|
|
else
|
|
|
|
|
return 0;
|
2012-07-09 01:22:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get number of audio samples
|
|
|
|
|
int Frame::GetAudioSamplesCount()
|
|
|
|
|
{
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingAudioSection);
|
2018-05-30 03:20:31 -05:00
|
|
|
return max_audio_sample;
|
2012-07-09 01:22:11 -05:00
|
|
|
}
|
|
|
|
|
|
2014-01-27 19:03:46 +08:00
|
|
|
juce::AudioSampleBuffer *Frame::GetAudioSampleBuffer()
|
|
|
|
|
{
|
|
|
|
|
return audio.get();
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-11 17:30:32 -05:00
|
|
|
// Get the size in bytes of this frame (rough estimate)
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t Frame::GetBytes()
|
2012-10-11 17:30:32 -05:00
|
|
|
{
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t total_bytes = 0;
|
2012-10-11 17:30:32 -05:00
|
|
|
if (image)
|
2012-10-26 00:27:44 -05:00
|
|
|
total_bytes += (width * height * sizeof(char) * 4);
|
2016-06-29 02:42:00 -05:00
|
|
|
if (audio) {
|
|
|
|
|
// approximate audio size (sample rate / 24 fps)
|
|
|
|
|
total_bytes += (sample_rate / 24.0) * sizeof(float);
|
|
|
|
|
}
|
2012-10-11 17:30:32 -05:00
|
|
|
|
|
|
|
|
// return size of this frame
|
|
|
|
|
return total_bytes;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Get pixel data (as packets)
|
2015-06-01 00:20:14 -07:00
|
|
|
const unsigned char* Frame::GetPixels()
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2015-08-05 23:40:58 -05:00
|
|
|
// Check for blank image
|
|
|
|
|
if (!image)
|
|
|
|
|
// Fill with black
|
2018-01-21 23:49:07 -06:00
|
|
|
AddColor(width, height, color);
|
2015-08-05 23:40:58 -05:00
|
|
|
|
2012-08-20 14:26:49 -05:00
|
|
|
// Return array of pixel packets
|
2019-11-26 00:56:13 +01:00
|
|
|
return image->constBits();
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get pixel data (for only a single scan-line)
|
2015-06-01 00:20:14 -07:00
|
|
|
const unsigned char* Frame::GetPixels(int row)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2020-06-06 01:55:52 -05:00
|
|
|
// Check for blank image
|
|
|
|
|
if (!image)
|
|
|
|
|
// Fill with black
|
|
|
|
|
AddColor(width, height, color);
|
|
|
|
|
|
2012-08-20 14:26:49 -05:00
|
|
|
// Return array of pixel packets
|
2019-11-26 00:56:13 +01:00
|
|
|
return image->constScanLine(row);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2019-05-31 19:02:28 -05:00
|
|
|
// Check a specific pixel color value (returns True/False)
|
2019-06-08 12:21:56 -05:00
|
|
|
bool Frame::CheckPixel(int row, int col, int red, int green, int blue, int alpha, int threshold) {
|
2019-05-31 19:02:28 -05:00
|
|
|
int col_pos = col * 4; // Find column array position
|
|
|
|
|
if (!image || row < 0 || row >= (height - 1) ||
|
|
|
|
|
col_pos < 0 || col_pos >= (width - 1) ) {
|
|
|
|
|
// invalid row / col
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Check pixel color
|
|
|
|
|
const unsigned char* pixels = GetPixels(row);
|
2019-06-08 12:21:56 -05:00
|
|
|
if (pixels[col_pos + 0] >= (red - threshold) && pixels[col_pos + 0] <= (red + threshold) &&
|
2019-06-08 12:52:35 -05:00
|
|
|
pixels[col_pos + 1] >= (green - threshold) && pixels[col_pos + 1] <= (green + threshold) &&
|
|
|
|
|
pixels[col_pos + 2] >= (blue - threshold) && pixels[col_pos + 2] <= (blue + threshold) &&
|
|
|
|
|
pixels[col_pos + 3] >= (alpha - threshold) && pixels[col_pos + 3] <= (alpha + threshold)) {
|
2019-05-31 19:02:28 -05:00
|
|
|
// Pixel color matches successfully
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
// Pixel color does not match
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-11 20:42:50 -06:00
|
|
|
// Set Pixel Aspect Ratio
|
|
|
|
|
void Frame::SetPixelRatio(int num, int den)
|
|
|
|
|
{
|
|
|
|
|
pixel_ratio.num = num;
|
|
|
|
|
pixel_ratio.den = den;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-10 21:16:46 -06:00
|
|
|
// Set frame number
|
2017-09-28 16:03:01 -05:00
|
|
|
void Frame::SetFrameNumber(int64_t new_number)
|
2013-02-10 21:16:46 -06:00
|
|
|
{
|
|
|
|
|
number = new_number;
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-28 17:17:38 -06:00
|
|
|
// Calculate the # of samples per video frame (for a specific frame number and frame rate)
|
2017-09-28 16:03:01 -05:00
|
|
|
int Frame::GetSamplesPerFrame(int64_t number, Fraction fps, int sample_rate, int channels)
|
2011-12-11 20:42:50 -06:00
|
|
|
{
|
2014-01-28 17:17:38 -06:00
|
|
|
// Get the total # of samples for the previous frame, and the current frame (rounded)
|
|
|
|
|
double fps_rate = fps.Reciprocal().ToDouble();
|
2015-03-08 21:42:53 -05:00
|
|
|
|
|
|
|
|
// Determine previous samples total, and make sure it's evenly divisible by the # of channels
|
|
|
|
|
double previous_samples = (sample_rate * fps_rate) * (number - 1);
|
|
|
|
|
double previous_samples_remainder = fmod(previous_samples, (double)channels); // subtract the remainder to the total (to make it evenly divisible)
|
|
|
|
|
previous_samples -= previous_samples_remainder;
|
|
|
|
|
|
|
|
|
|
// Determine the current samples total, and make sure it's evenly divisible by the # of channels
|
|
|
|
|
double total_samples = (sample_rate * fps_rate) * number;
|
|
|
|
|
double total_samples_remainder = fmod(total_samples, (double)channels); // subtract the remainder to the total (to make it evenly divisible)
|
|
|
|
|
total_samples -= total_samples_remainder;
|
2014-01-28 17:17:38 -06:00
|
|
|
|
|
|
|
|
// Subtract the previous frame's total samples with this frame's total samples. Not all sample rates can
|
|
|
|
|
// be evenly divided into frames, so each frame can have have different # of samples.
|
2015-03-08 21:42:53 -05:00
|
|
|
int samples_per_frame = round(total_samples - previous_samples);
|
2018-09-17 00:27:30 -05:00
|
|
|
if (samples_per_frame < 0)
|
|
|
|
|
samples_per_frame = 0;
|
2014-01-28 17:17:38 -06:00
|
|
|
return samples_per_frame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the # of samples per video frame (for the current frame number)
|
2015-03-08 21:42:53 -05:00
|
|
|
int Frame::GetSamplesPerFrame(Fraction fps, int sample_rate, int channels)
|
2014-01-28 17:17:38 -06:00
|
|
|
{
|
2015-03-08 21:42:53 -05:00
|
|
|
return GetSamplesPerFrame(number, fps, sample_rate, channels);
|
2011-12-11 20:42:50 -06:00
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Get height of image
|
|
|
|
|
int Frame::GetHeight()
|
|
|
|
|
{
|
2013-02-10 21:16:46 -06:00
|
|
|
return height;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get height of image
|
|
|
|
|
int Frame::GetWidth()
|
|
|
|
|
{
|
2013-02-10 21:16:46 -06:00
|
|
|
return width;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2015-02-05 00:11:55 -06:00
|
|
|
// Get the original sample rate of this frame's audio data
|
|
|
|
|
int Frame::SampleRate()
|
|
|
|
|
{
|
|
|
|
|
return sample_rate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the original sample rate of this frame's audio data
|
|
|
|
|
ChannelLayout Frame::ChannelsLayout()
|
|
|
|
|
{
|
|
|
|
|
return channel_layout;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-26 23:02:21 -06:00
|
|
|
|
2012-08-05 16:43:09 -05:00
|
|
|
// Save the frame image to the specified path. The image format is determined from the extension (i.e. image.PNG, image.JPEG)
|
2019-08-04 15:13:40 -04:00
|
|
|
void Frame::Save(std::string path, float scale, std::string format, int quality)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2016-02-23 00:27:03 -06:00
|
|
|
// Get preview image
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<QImage> previewImage = GetImage();
|
2012-08-05 16:43:09 -05:00
|
|
|
|
|
|
|
|
// scale image if needed
|
2020-03-02 23:50:10 -06:00
|
|
|
if (fabs(scale) > 1.001 || fabs(scale) < 0.999)
|
2012-08-05 16:43:09 -05:00
|
|
|
{
|
2016-02-23 00:27:03 -06:00
|
|
|
int new_width = width;
|
|
|
|
|
int new_height = height;
|
|
|
|
|
|
|
|
|
|
// Update the image to reflect the correct pixel aspect ration (i.e. to fix non-squar pixels)
|
|
|
|
|
if (pixel_ratio.num != 1 || pixel_ratio.den != 1)
|
|
|
|
|
{
|
|
|
|
|
// Calculate correct DAR (display aspect ratio)
|
|
|
|
|
int new_width = previewImage->size().width();
|
|
|
|
|
int new_height = previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble();
|
|
|
|
|
|
|
|
|
|
// Resize to fix DAR
|
2020-08-20 16:50:12 -04:00
|
|
|
previewImage = std::make_shared<QImage>(previewImage->scaled(
|
|
|
|
|
new_width, new_height,
|
|
|
|
|
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
2016-02-23 00:27:03 -06:00
|
|
|
}
|
|
|
|
|
|
2012-08-05 16:43:09 -05:00
|
|
|
// Resize image
|
2020-08-20 16:50:12 -04:00
|
|
|
previewImage = std::make_shared<QImage>(previewImage->scaled(
|
|
|
|
|
new_width * scale, new_height * scale,
|
|
|
|
|
Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
2012-08-05 16:43:09 -05:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Save image
|
|
|
|
|
previewImage->save(QString::fromStdString(path), format.c_str(), quality);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2014-05-20 00:57:27 -05:00
|
|
|
// Thumbnail the frame image to the specified path. The image format is determined from the extension (i.e. image.PNG, image.JPEG)
|
2019-08-04 15:13:40 -04:00
|
|
|
void Frame::Thumbnail(std::string path, int new_width, int new_height, std::string mask_path, std::string overlay_path,
|
|
|
|
|
std::string background_color, bool ignore_aspect, std::string format, int quality, float rotate) {
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Create blank thumbnail image & fill background color
|
2020-08-20 16:50:12 -04:00
|
|
|
auto thumbnail = std::make_shared<QImage>(
|
|
|
|
|
new_width, new_height, QImage::Format_RGBA8888);
|
2016-02-23 00:27:03 -06:00
|
|
|
thumbnail->fill(QColor(QString::fromStdString(background_color)));
|
|
|
|
|
|
2018-02-03 01:57:18 -06:00
|
|
|
// Create painter
|
2016-02-23 00:27:03 -06:00
|
|
|
QPainter painter(thumbnail.get());
|
|
|
|
|
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true);
|
2015-06-01 00:20:14 -07:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Get preview image
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<QImage> previewImage = GetImage();
|
2014-05-20 00:34:20 -05:00
|
|
|
|
|
|
|
|
// Update the image to reflect the correct pixel aspect ration (i.e. to fix non-squar pixels)
|
2016-02-23 00:27:03 -06:00
|
|
|
if (pixel_ratio.num != 1 || pixel_ratio.den != 1)
|
2014-05-20 00:34:20 -05:00
|
|
|
{
|
2016-02-23 00:27:03 -06:00
|
|
|
// Calculate correct DAR (display aspect ratio)
|
|
|
|
|
int aspect_width = previewImage->size().width();
|
|
|
|
|
int aspect_height = previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble();
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Resize to fix DAR
|
2020-08-20 16:50:12 -04:00
|
|
|
previewImage = std::make_shared<QImage>(previewImage->scaled(
|
|
|
|
|
aspect_width, aspect_height,
|
|
|
|
|
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
2014-05-20 00:34:20 -05:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Resize frame image
|
|
|
|
|
if (ignore_aspect)
|
|
|
|
|
// Ignore aspect ratio
|
2020-08-20 16:50:12 -04:00
|
|
|
previewImage = std::make_shared<QImage>(previewImage->scaled(
|
|
|
|
|
new_width, new_height,
|
|
|
|
|
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
2016-02-23 00:27:03 -06:00
|
|
|
else
|
|
|
|
|
// Maintain aspect ratio
|
2020-08-20 16:50:12 -04:00
|
|
|
previewImage = std::make_shared<QImage>(previewImage->scaled(
|
|
|
|
|
new_width, new_height,
|
|
|
|
|
Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Composite frame image onto background (centered)
|
|
|
|
|
int x = (new_width - previewImage->size().width()) / 2.0; // center
|
|
|
|
|
int y = (new_height - previewImage->size().height()) / 2.0; // center
|
|
|
|
|
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
2018-02-03 01:57:18 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create transform and rotate (if needed)
|
|
|
|
|
QTransform transform;
|
|
|
|
|
float origin_x = previewImage->width() / 2.0;
|
|
|
|
|
float origin_y = previewImage->height() / 2.0;
|
|
|
|
|
transform.translate(origin_x, origin_y);
|
|
|
|
|
transform.rotate(rotate);
|
|
|
|
|
transform.translate(-origin_x,-origin_y);
|
|
|
|
|
painter.setTransform(transform);
|
|
|
|
|
|
|
|
|
|
// Draw image onto QImage
|
2016-02-23 00:27:03 -06:00
|
|
|
painter.drawImage(x, y, *previewImage);
|
2014-05-20 00:34:20 -05:00
|
|
|
|
|
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Overlay Image (if any)
|
2014-05-20 00:34:20 -05:00
|
|
|
if (overlay_path != "") {
|
2016-02-23 00:27:03 -06:00
|
|
|
// Open overlay
|
2020-08-20 16:50:12 -04:00
|
|
|
auto overlay = std::make_shared<QImage>();
|
2016-02-23 00:27:03 -06:00
|
|
|
overlay->load(QString::fromStdString(overlay_path));
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Set pixel format
|
2020-08-20 16:50:12 -04:00
|
|
|
overlay = std::make_shared<QImage>(
|
|
|
|
|
overlay->convertToFormat(QImage::Format_RGBA8888));
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Resize to fit
|
2020-08-20 16:50:12 -04:00
|
|
|
overlay = std::make_shared<QImage>(overlay->scaled(
|
|
|
|
|
new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Composite onto thumbnail
|
|
|
|
|
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
|
painter.drawImage(0, 0, *overlay);
|
2014-05-20 00:34:20 -05:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
|
|
|
|
|
// Mask Image (if any)
|
2014-05-20 00:34:20 -05:00
|
|
|
if (mask_path != "") {
|
2016-02-23 00:27:03 -06:00
|
|
|
// Open mask
|
2020-08-20 16:50:12 -04:00
|
|
|
auto mask = std::make_shared<QImage>();
|
2016-02-23 00:27:03 -06:00
|
|
|
mask->load(QString::fromStdString(mask_path));
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Set pixel format
|
2020-08-20 16:50:12 -04:00
|
|
|
mask = std::make_shared<QImage>(
|
|
|
|
|
mask->convertToFormat(QImage::Format_RGBA8888));
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Resize to fit
|
2020-08-20 16:50:12 -04:00
|
|
|
mask = std::make_shared<QImage>(mask->scaled(
|
|
|
|
|
new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Negate mask
|
|
|
|
|
mask->invertPixels();
|
2014-05-20 00:34:20 -05:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
// Get pixels
|
|
|
|
|
unsigned char *pixels = (unsigned char *) thumbnail->bits();
|
2019-11-27 23:57:58 +01:00
|
|
|
const unsigned char *mask_pixels = (const unsigned char *) mask->constBits();
|
2016-02-23 00:27:03 -06:00
|
|
|
|
|
|
|
|
// Convert the mask image to grayscale
|
|
|
|
|
// Loop through pixels
|
|
|
|
|
for (int pixel = 0, byte_index=0; pixel < new_width * new_height; pixel++, byte_index+=4)
|
2014-05-20 00:34:20 -05:00
|
|
|
{
|
2016-02-23 00:27:03 -06:00
|
|
|
// Get the RGB values from the pixel
|
|
|
|
|
int gray_value = qGray(mask_pixels[byte_index], mask_pixels[byte_index] + 1, mask_pixels[byte_index] + 2);
|
|
|
|
|
int Frame_Alpha = pixels[byte_index + 3];
|
|
|
|
|
int Mask_Value = constrain(Frame_Alpha - gray_value);
|
|
|
|
|
|
|
|
|
|
// Set all alpha pixels to gray value
|
|
|
|
|
pixels[byte_index + 3] = Mask_Value;
|
2014-05-20 00:34:20 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
|
|
|
|
|
// End painter
|
|
|
|
|
painter.end();
|
|
|
|
|
|
|
|
|
|
// Save image
|
|
|
|
|
thumbnail->save(QString::fromStdString(path), format.c_str(), quality);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Constrain a color value from 0 to 255
|
|
|
|
|
int Frame::constrain(int color_value)
|
|
|
|
|
{
|
|
|
|
|
// Constrain new color from 0 to 255
|
|
|
|
|
if (color_value < 0)
|
|
|
|
|
color_value = 0;
|
|
|
|
|
else if (color_value > 255)
|
|
|
|
|
color_value = 255;
|
|
|
|
|
|
|
|
|
|
return color_value;
|
2014-05-20 00:34:20 -05:00
|
|
|
}
|
|
|
|
|
|
2012-10-12 00:54:53 -05:00
|
|
|
// Add (or replace) pixel data to the frame (based on a solid color)
|
2019-08-04 15:13:40 -04:00
|
|
|
void Frame::AddColor(int new_width, int new_height, std::string new_color)
|
2012-10-12 00:54:53 -05:00
|
|
|
{
|
2018-01-21 23:49:07 -06:00
|
|
|
// Set color
|
|
|
|
|
color = new_color;
|
|
|
|
|
|
2012-10-12 00:54:53 -05:00
|
|
|
// Create new image object, and fill with pixel data
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingImageSection);
|
2017-11-10 23:36:13 -06:00
|
|
|
#pragma omp critical (AddImage)
|
|
|
|
|
{
|
2020-08-20 16:50:12 -04:00
|
|
|
image = std::make_shared<QImage>(new_width, new_height, QImage::Format_RGBA8888);
|
2012-11-08 04:35:21 -06:00
|
|
|
|
2017-11-10 23:36:13 -06:00
|
|
|
// Fill with solid color
|
|
|
|
|
image->fill(QColor(QString::fromStdString(color)));
|
|
|
|
|
}
|
2012-10-26 00:27:44 -05:00
|
|
|
// Update height and width
|
2015-06-01 00:20:14 -07:00
|
|
|
width = image->width();
|
|
|
|
|
height = image->height();
|
2016-01-05 01:59:50 -06:00
|
|
|
has_image_data = true;
|
2012-10-12 00:54:53 -05:00
|
|
|
}
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Add (or replace) pixel data to the frame
|
2016-09-07 00:40:01 -05:00
|
|
|
void Frame::AddImage(int new_width, int new_height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_)
|
2011-10-24 08:22:21 -05:00
|
|
|
{
|
2015-06-01 00:20:14 -07:00
|
|
|
// Create new buffer
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingImageSection);
|
2016-09-07 00:40:01 -05:00
|
|
|
int buffer_size = new_width * new_height * bytes_per_pixel;
|
2015-06-01 00:20:14 -07:00
|
|
|
qbuffer = new unsigned char[buffer_size]();
|
|
|
|
|
|
|
|
|
|
// Copy buffer data
|
|
|
|
|
memcpy((unsigned char*)qbuffer, pixels_, buffer_size);
|
|
|
|
|
|
2011-10-24 08:22:21 -05:00
|
|
|
// Create new image object, and fill with pixel data
|
2017-11-10 23:36:13 -06:00
|
|
|
#pragma omp critical (AddImage)
|
|
|
|
|
{
|
2020-08-20 16:50:12 -04:00
|
|
|
image = std::make_shared<QImage>(
|
|
|
|
|
qbuffer, new_width, new_height, new_width * bytes_per_pixel, type, (QImageCleanupFunction) &openshot::Frame::cleanUpBuffer, (void*) qbuffer);
|
2012-10-26 00:27:44 -05:00
|
|
|
|
2017-11-10 23:36:13 -06:00
|
|
|
// Always convert to RGBA8888 (if different)
|
|
|
|
|
if (image->format() != QImage::Format_RGBA8888)
|
2020-08-20 16:50:12 -04:00
|
|
|
*image = image->convertToFormat(QImage::Format_RGBA8888);
|
2012-11-08 04:35:21 -06:00
|
|
|
|
2017-11-10 23:36:13 -06:00
|
|
|
// Update height and width
|
|
|
|
|
width = image->width();
|
|
|
|
|
height = image->height();
|
|
|
|
|
has_image_data = true;
|
|
|
|
|
}
|
2011-10-24 08:22:21 -05:00
|
|
|
}
|
|
|
|
|
|
2012-08-29 15:29:15 -05:00
|
|
|
// Add (or replace) pixel data to the frame
|
2017-08-20 17:37:39 -05:00
|
|
|
void Frame::AddImage(std::shared_ptr<QImage> new_image)
|
2012-08-29 15:29:15 -05:00
|
|
|
{
|
2015-06-01 00:20:14 -07:00
|
|
|
// Ignore blank images
|
|
|
|
|
if (!new_image)
|
|
|
|
|
return;
|
|
|
|
|
|
2012-08-29 15:29:15 -05:00
|
|
|
// assign image data
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingImageSection);
|
2017-11-10 23:36:13 -06:00
|
|
|
#pragma omp critical (AddImage)
|
|
|
|
|
{
|
|
|
|
|
image = new_image;
|
2015-06-01 00:20:14 -07:00
|
|
|
|
2017-11-10 23:36:13 -06:00
|
|
|
// Always convert to RGBA8888 (if different)
|
|
|
|
|
if (image->format() != QImage::Format_RGBA8888)
|
2018-03-04 23:59:14 -06:00
|
|
|
*image = image->convertToFormat(QImage::Format_RGBA8888);
|
2012-10-26 00:27:44 -05:00
|
|
|
|
2017-11-10 23:36:13 -06:00
|
|
|
// Update height and width
|
|
|
|
|
width = image->width();
|
|
|
|
|
height = image->height();
|
|
|
|
|
has_image_data = true;
|
|
|
|
|
}
|
2012-08-29 15:29:15 -05:00
|
|
|
}
|
|
|
|
|
|
2012-10-18 02:58:09 -05:00
|
|
|
// Add (or replace) pixel data to the frame (for only the odd or even lines)
|
2017-08-20 17:37:39 -05:00
|
|
|
void Frame::AddImage(std::shared_ptr<QImage> new_image, bool only_odd_lines)
|
2012-10-18 02:58:09 -05:00
|
|
|
{
|
2015-06-01 00:20:14 -07:00
|
|
|
// Ignore blank new_image
|
|
|
|
|
if (!new_image)
|
|
|
|
|
return;
|
2012-10-18 02:58:09 -05:00
|
|
|
|
2015-06-01 00:20:14 -07:00
|
|
|
// Check for blank source image
|
2015-08-05 23:40:58 -05:00
|
|
|
if (!image) {
|
2015-06-01 00:20:14 -07:00
|
|
|
// Replace the blank source image
|
|
|
|
|
AddImage(new_image);
|
2012-10-18 02:58:09 -05:00
|
|
|
|
2015-08-05 23:40:58 -05:00
|
|
|
} else {
|
2012-10-18 02:58:09 -05:00
|
|
|
|
2015-08-05 23:40:58 -05:00
|
|
|
// Ignore image of different sizes or formats
|
2017-11-10 23:36:13 -06:00
|
|
|
bool ret=false;
|
|
|
|
|
#pragma omp critical (AddImage)
|
2020-01-21 10:56:57 -05:00
|
|
|
{
|
|
|
|
|
if (image == new_image || image->size() != new_image->size()) {
|
|
|
|
|
ret = true;
|
|
|
|
|
}
|
|
|
|
|
else if (new_image->format() != image->format()) {
|
2020-08-20 16:50:12 -04:00
|
|
|
new_image = std::make_shared<QImage>(
|
|
|
|
|
new_image->convertToFormat(image->format()));
|
2020-01-21 10:56:57 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (ret) {
|
2015-08-05 23:40:58 -05:00
|
|
|
return;
|
2020-01-21 10:56:57 -05:00
|
|
|
}
|
2020-05-19 06:33:30 -04:00
|
|
|
|
2015-08-05 23:40:58 -05:00
|
|
|
// Get the frame's image
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingImageSection);
|
2017-11-10 23:36:13 -06:00
|
|
|
#pragma omp critical (AddImage)
|
|
|
|
|
{
|
2020-01-21 10:56:57 -05:00
|
|
|
unsigned char *pixels = image->bits();
|
2019-11-26 00:56:13 +01:00
|
|
|
const unsigned char *new_pixels = new_image->constBits();
|
2015-08-05 23:40:58 -05:00
|
|
|
|
2017-11-10 23:36:13 -06:00
|
|
|
// Loop through the scanlines of the image (even or odd)
|
|
|
|
|
int start = 0;
|
|
|
|
|
if (only_odd_lines)
|
|
|
|
|
start = 1;
|
2019-07-30 13:15:31 -04:00
|
|
|
|
|
|
|
|
for (int row = start; row < image->height(); row += 2) {
|
2020-01-21 10:56:57 -05:00
|
|
|
int offset = row * image->bytesPerLine();
|
|
|
|
|
memcpy(pixels + offset, new_pixels + offset, image->bytesPerLine());
|
2019-07-30 13:15:31 -04:00
|
|
|
}
|
2017-11-10 23:36:13 -06:00
|
|
|
|
|
|
|
|
// Update height and width
|
|
|
|
|
height = image->height();
|
2020-01-21 10:56:57 -05:00
|
|
|
width = image->width();
|
2017-11-10 23:36:13 -06:00
|
|
|
has_image_data = true;
|
2015-08-05 23:40:58 -05:00
|
|
|
}
|
2012-10-18 02:58:09 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-29 01:47:39 -05:00
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Resize audio container to hold more (or less) samples and channels
|
|
|
|
|
void Frame::ResizeAudio(int channels, int length, int rate, ChannelLayout layout)
|
|
|
|
|
{
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingAudioSection);
|
2017-07-19 16:05:07 -05:00
|
|
|
|
|
|
|
|
// Resize JUCE audio buffer
|
2015-03-01 22:36:39 -06:00
|
|
|
audio->setSize(channels, length, true, true, false);
|
|
|
|
|
channel_layout = layout;
|
|
|
|
|
sample_rate = rate;
|
2018-05-30 03:20:31 -05:00
|
|
|
|
|
|
|
|
// Calculate max audio sample added
|
|
|
|
|
max_audio_sample = length;
|
2015-03-01 22:36:39 -06:00
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Add audio samples to a specific channel
|
2017-07-19 16:05:07 -05:00
|
|
|
void Frame::AddAudio(bool replaceSamples, int destChannel, int destStartSample, const float* source, int numSamples, float gainToApplyToSource = 1.0f) {
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingAudioSection);
|
2017-07-19 16:05:07 -05:00
|
|
|
#pragma omp critical (adding_audio)
|
|
|
|
|
{
|
2018-06-06 03:56:02 -05:00
|
|
|
// Clamp starting sample to 0
|
|
|
|
|
int destStartSampleAdjusted = max(destStartSample, 0);
|
|
|
|
|
|
2017-07-19 16:05:07 -05:00
|
|
|
// Extend audio container to hold more (or less) samples and channels.. if needed
|
2018-06-06 03:56:02 -05:00
|
|
|
int new_length = destStartSampleAdjusted + numSamples;
|
2017-07-19 16:05:07 -05:00
|
|
|
int new_channel_length = audio->getNumChannels();
|
|
|
|
|
if (destChannel >= new_channel_length)
|
|
|
|
|
new_channel_length = destChannel + 1;
|
|
|
|
|
if (new_length > audio->getNumSamples() || new_channel_length > audio->getNumChannels())
|
|
|
|
|
audio->setSize(new_channel_length, new_length, true, true, false);
|
2012-11-08 04:35:21 -06:00
|
|
|
|
2017-07-19 16:05:07 -05:00
|
|
|
// Clear the range of samples first (if needed)
|
|
|
|
|
if (replaceSamples)
|
2018-06-06 03:56:02 -05:00
|
|
|
audio->clear(destChannel, destStartSampleAdjusted, numSamples);
|
2012-10-21 05:29:29 -05:00
|
|
|
|
2017-07-19 16:05:07 -05:00
|
|
|
// Add samples to frame's audio buffer
|
2018-06-11 12:02:21 -07:00
|
|
|
audio->addFrom(destChannel, destStartSampleAdjusted, source, numSamples, gainToApplyToSource);
|
2017-07-19 16:05:07 -05:00
|
|
|
has_audio_data = true;
|
2018-05-30 03:20:31 -05:00
|
|
|
|
|
|
|
|
// Calculate max audio sample added
|
|
|
|
|
if (new_length > max_audio_sample)
|
|
|
|
|
max_audio_sample = new_length;
|
2017-07-19 16:05:07 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2012-11-29 16:32:48 -06:00
|
|
|
// Apply gain ramp (i.e. fading volume)
|
|
|
|
|
void Frame::ApplyGainRamp(int destChannel, int destStartSample, int numSamples, float initial_gain = 0.0f, float final_gain = 1.0f)
|
|
|
|
|
{
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingAudioSection);
|
2017-07-19 16:05:07 -05:00
|
|
|
|
|
|
|
|
// Apply gain ramp
|
2012-11-29 16:32:48 -06:00
|
|
|
audio->applyGainRamp(destChannel, destStartSample, numSamples, initial_gain, final_gain);
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-29 16:07:47 -05:00
|
|
|
// Get pointer to Magick++ image object
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<QImage> Frame::GetImage()
|
2012-08-29 16:07:47 -05:00
|
|
|
{
|
2015-06-01 00:20:14 -07:00
|
|
|
// Check for blank image
|
|
|
|
|
if (!image)
|
|
|
|
|
// Fill with black
|
2018-01-21 23:49:07 -06:00
|
|
|
AddColor(width, height, color);
|
2015-06-01 00:20:14 -07:00
|
|
|
|
2012-08-29 16:07:47 -05:00
|
|
|
return image;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
#ifdef USE_IMAGEMAGICK
|
2015-06-01 00:20:14 -07:00
|
|
|
// Get pointer to ImageMagick image object
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<Magick::Image> Frame::GetMagickImage()
|
2015-03-16 13:59:11 -05:00
|
|
|
{
|
2015-06-01 00:20:14 -07:00
|
|
|
// Check for blank image
|
|
|
|
|
if (!image)
|
|
|
|
|
// Fill with black
|
|
|
|
|
AddColor(width, height, "#000000");
|
|
|
|
|
|
|
|
|
|
// Get the pixels from the frame image
|
2019-11-27 23:57:58 +01:00
|
|
|
const QRgb *tmpBits = (const QRgb*)image->constBits();
|
2015-06-01 00:20:14 -07:00
|
|
|
|
|
|
|
|
// Create new image object, and fill with pixel data
|
2020-08-20 16:50:12 -04:00
|
|
|
auto magick_image = std::make_shared<Magick::Image>(
|
|
|
|
|
image->width(), image->height(),"RGBA", Magick::CharPixel, tmpBits);
|
2015-06-01 00:20:14 -07:00
|
|
|
|
|
|
|
|
// Give image a transparent background color
|
|
|
|
|
magick_image->backgroundColor(Magick::Color("none"));
|
|
|
|
|
magick_image->virtualPixelMethod(Magick::TransparentVirtualPixelMethod);
|
Add backwards-compatible Imagemagick 7 support (#252)
* Add ImageMagick 7 compatibility
A new header, `imclude/MagickUtilities.h`, is created to hold the
compatibility `#define`s.
The image-conversion code in `src/Frame.cpp` received the only
major changes — instead of doing the export by hand (and having
to account for changes in the underlying API), it uses the
`MagickCore::ExportImagePixels()` function which does basically
the same work, but accounts for all of the API changes for us.
The API of that function is _unchanged_ from IM6 to IM7.
TODO: `MagickCore::ExportImagePixels()` will return an `exception`
struct if it encounters any problems. Currently the code ignores
that, which it should not.
* Add ImageMagick 7 compatibility
A new header, `imclude/MagickUtilities.h`, is created to hold the
compatibility `#define`s.
The image-conversion code in `src/Frame.cpp` received the only
major changes — instead of doing the export by hand (and having
to account for changes in the underlying API), it uses the
`MagickCore::ExportImagePixels()` function which does basically
the same work, but accounts for all of the API changes for us.
The API of that function is _unchanged_ from IM6 to IM7.
TODO: `MagickCore::ExportImagePixels()` will return an `exception`
struct if it encounters any problems. Currently the code ignores
that, which it should not.
Thanks @ferdnyc
2019-06-21 01:07:49 -04:00
|
|
|
MAGICK_IMAGE_ALPHA(magick_image, true);
|
2015-06-01 00:20:14 -07:00
|
|
|
|
|
|
|
|
return magick_image;
|
|
|
|
|
}
|
2016-02-23 00:27:03 -06:00
|
|
|
#endif
|
2015-06-01 00:20:14 -07:00
|
|
|
|
2016-02-23 00:27:03 -06:00
|
|
|
#ifdef USE_IMAGEMAGICK
|
2015-06-01 00:20:14 -07:00
|
|
|
// Get pointer to QImage of frame
|
2017-08-20 17:37:39 -05:00
|
|
|
void Frame::AddMagickImage(std::shared_ptr<Magick::Image> new_image)
|
2015-06-01 00:20:14 -07:00
|
|
|
{
|
|
|
|
|
const int BPP = 4;
|
|
|
|
|
const std::size_t bufferSize = new_image->columns() * new_image->rows() * BPP;
|
2015-03-16 13:59:11 -05:00
|
|
|
|
|
|
|
|
/// Use realloc for fast memory allocation.
|
|
|
|
|
/// TODO: consider locking the buffer for mt safety
|
|
|
|
|
//qbuffer = reinterpret_cast<unsigned char*>(realloc(qbuffer, bufferSize));
|
|
|
|
|
qbuffer = new unsigned char[bufferSize]();
|
2015-06-01 00:20:14 -07:00
|
|
|
unsigned char *buffer = (unsigned char*)qbuffer;
|
2015-03-16 13:59:11 -05:00
|
|
|
|
Add backwards-compatible Imagemagick 7 support (#252)
* Add ImageMagick 7 compatibility
A new header, `imclude/MagickUtilities.h`, is created to hold the
compatibility `#define`s.
The image-conversion code in `src/Frame.cpp` received the only
major changes — instead of doing the export by hand (and having
to account for changes in the underlying API), it uses the
`MagickCore::ExportImagePixels()` function which does basically
the same work, but accounts for all of the API changes for us.
The API of that function is _unchanged_ from IM6 to IM7.
TODO: `MagickCore::ExportImagePixels()` will return an `exception`
struct if it encounters any problems. Currently the code ignores
that, which it should not.
* Add ImageMagick 7 compatibility
A new header, `imclude/MagickUtilities.h`, is created to hold the
compatibility `#define`s.
The image-conversion code in `src/Frame.cpp` received the only
major changes — instead of doing the export by hand (and having
to account for changes in the underlying API), it uses the
`MagickCore::ExportImagePixels()` function which does basically
the same work, but accounts for all of the API changes for us.
The API of that function is _unchanged_ from IM6 to IM7.
TODO: `MagickCore::ExportImagePixels()` will return an `exception`
struct if it encounters any problems. Currently the code ignores
that, which it should not.
Thanks @ferdnyc
2019-06-21 01:07:49 -04:00
|
|
|
MagickCore::ExceptionInfo exception;
|
|
|
|
|
// TODO: Actually do something, if we get an exception here
|
|
|
|
|
MagickCore::ExportImagePixels(new_image->constImage(), 0, 0, new_image->columns(), new_image->rows(), "RGBA", Magick::CharPixel, buffer, &exception);
|
2015-03-16 13:59:11 -05:00
|
|
|
|
Add backwards-compatible Imagemagick 7 support (#252)
* Add ImageMagick 7 compatibility
A new header, `imclude/MagickUtilities.h`, is created to hold the
compatibility `#define`s.
The image-conversion code in `src/Frame.cpp` received the only
major changes — instead of doing the export by hand (and having
to account for changes in the underlying API), it uses the
`MagickCore::ExportImagePixels()` function which does basically
the same work, but accounts for all of the API changes for us.
The API of that function is _unchanged_ from IM6 to IM7.
TODO: `MagickCore::ExportImagePixels()` will return an `exception`
struct if it encounters any problems. Currently the code ignores
that, which it should not.
* Add ImageMagick 7 compatibility
A new header, `imclude/MagickUtilities.h`, is created to hold the
compatibility `#define`s.
The image-conversion code in `src/Frame.cpp` received the only
major changes — instead of doing the export by hand (and having
to account for changes in the underlying API), it uses the
`MagickCore::ExportImagePixels()` function which does basically
the same work, but accounts for all of the API changes for us.
The API of that function is _unchanged_ from IM6 to IM7.
TODO: `MagickCore::ExportImagePixels()` will return an `exception`
struct if it encounters any problems. Currently the code ignores
that, which it should not.
Thanks @ferdnyc
2019-06-21 01:07:49 -04:00
|
|
|
// Create QImage of frame data
|
2020-08-20 16:50:12 -04:00
|
|
|
image = std::make_shared<QImage>(
|
|
|
|
|
qbuffer, width, height, width * BPP, QImage::Format_RGBA8888,
|
|
|
|
|
(QImageCleanupFunction) &cleanUpBuffer, (void*) qbuffer);
|
2015-03-16 13:59:11 -05:00
|
|
|
|
2015-06-01 00:20:14 -07:00
|
|
|
// Update height and width
|
|
|
|
|
width = image->width();
|
|
|
|
|
height = image->height();
|
2016-01-05 01:59:50 -06:00
|
|
|
has_image_data = true;
|
2015-03-16 13:59:11 -05:00
|
|
|
}
|
2016-02-23 00:27:03 -06:00
|
|
|
#endif
|
2015-03-16 13:59:11 -05:00
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Play audio samples for this frame
|
2015-02-05 00:11:55 -06:00
|
|
|
void Frame::Play()
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2011-10-27 11:27:44 -05:00
|
|
|
// Check if samples are present
|
2018-05-30 03:20:31 -05:00
|
|
|
if (!GetAudioSamplesCount())
|
2011-10-27 11:27:44 -05:00
|
|
|
return;
|
|
|
|
|
|
2019-10-27 03:56:13 -04:00
|
|
|
juce::AudioDeviceManager deviceManager;
|
2020-05-19 06:33:30 -04:00
|
|
|
juce::String error = deviceManager.initialise (
|
|
|
|
|
0, /* number of input channels */
|
2012-06-17 23:15:47 -05:00
|
|
|
2, /* number of output channels */
|
2011-10-11 08:44:27 -05:00
|
|
|
0, /* no XML settings.. */
|
|
|
|
|
true /* select default device on failure */);
|
2019-04-04 00:55:47 -05:00
|
|
|
|
|
|
|
|
// Output error (if any)
|
|
|
|
|
if (error.isNotEmpty()) {
|
2020-08-07 01:49:35 -04:00
|
|
|
cout << "Error on initialise(): " << error << endl;
|
2019-04-04 00:55:47 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2019-10-27 03:56:13 -04:00
|
|
|
juce::AudioSourcePlayer audioSourcePlayer;
|
2011-10-11 08:44:27 -05:00
|
|
|
deviceManager.addAudioCallback (&audioSourcePlayer);
|
|
|
|
|
|
2020-05-19 06:32:35 -04:00
|
|
|
std::unique_ptr<AudioBufferSource> my_source;
|
|
|
|
|
my_source.reset (new AudioBufferSource (audio.get()));
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2012-06-17 23:15:47 -05:00
|
|
|
// Create TimeSliceThread for audio buffering
|
2019-10-27 03:56:13 -04:00
|
|
|
juce::TimeSliceThread my_thread("Audio buffer thread");
|
2012-06-17 23:15:47 -05:00
|
|
|
|
|
|
|
|
// Start thread
|
|
|
|
|
my_thread.startThread();
|
|
|
|
|
|
2020-05-19 06:33:30 -04:00
|
|
|
juce::AudioTransportSource transport1;
|
2020-05-19 06:32:35 -04:00
|
|
|
transport1.setSource (my_source.get(),
|
2011-10-11 08:44:27 -05:00
|
|
|
5000, // tells it to buffer this many samples ahead
|
2012-06-17 23:15:47 -05:00
|
|
|
&my_thread,
|
|
|
|
|
(double) sample_rate,
|
|
|
|
|
audio->getNumChannels()); // sample rate of source
|
2011-10-11 08:44:27 -05:00
|
|
|
transport1.setPosition (0);
|
2018-06-11 23:36:23 -07:00
|
|
|
transport1.setGain(1.0);
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2012-06-17 23:15:47 -05:00
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Create MIXER
|
2019-10-27 03:56:13 -04:00
|
|
|
juce::MixerAudioSource mixer;
|
2011-10-11 08:44:27 -05:00
|
|
|
mixer.addInputSource(&transport1, false);
|
|
|
|
|
audioSourcePlayer.setSource (&mixer);
|
|
|
|
|
|
|
|
|
|
// Start transports
|
|
|
|
|
transport1.start();
|
|
|
|
|
|
|
|
|
|
while (transport1.isPlaying())
|
|
|
|
|
{
|
|
|
|
|
cout << "playing" << endl;
|
2016-04-04 23:09:18 -05:00
|
|
|
usleep(1000000);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cout << "DONE!!!" << endl;
|
|
|
|
|
|
|
|
|
|
transport1.stop();
|
|
|
|
|
transport1.setSource (0);
|
|
|
|
|
audioSourcePlayer.setSource (0);
|
2012-06-17 23:15:47 -05:00
|
|
|
my_thread.stopThread(500);
|
2011-10-11 08:44:27 -05:00
|
|
|
deviceManager.removeAudioCallback (&audioSourcePlayer);
|
|
|
|
|
deviceManager.closeAudioDevice();
|
|
|
|
|
deviceManager.removeAllChangeListeners();
|
|
|
|
|
deviceManager.dispatchPendingMessages();
|
|
|
|
|
|
|
|
|
|
cout << "End of Play()" << endl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2015-06-01 00:20:14 -07:00
|
|
|
|
|
|
|
|
// Clean up buffer after QImage is deleted
|
|
|
|
|
void Frame::cleanUpBuffer(void *info)
|
|
|
|
|
{
|
|
|
|
|
if (info)
|
|
|
|
|
{
|
|
|
|
|
// Remove buffer since QImage tells us to
|
|
|
|
|
unsigned char* ptr_to_qbuffer = (unsigned char*) info;
|
2016-07-27 13:18:55 -05:00
|
|
|
delete[] ptr_to_qbuffer;
|
2015-06-01 00:20:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
2015-08-02 20:32:33 -05:00
|
|
|
|
|
|
|
|
// Add audio silence
|
|
|
|
|
void Frame::AddAudioSilence(int numSamples)
|
|
|
|
|
{
|
2019-10-27 03:56:13 -04:00
|
|
|
const GenericScopedLock<juce::CriticalSection> lock(addingAudioSection);
|
2017-07-19 16:05:07 -05:00
|
|
|
|
|
|
|
|
// Resize audio container
|
2015-08-02 20:32:33 -05:00
|
|
|
audio->setSize(channels, numSamples, false, true, false);
|
|
|
|
|
audio->clear();
|
2016-01-05 01:59:50 -06:00
|
|
|
has_audio_data = true;
|
2018-05-30 03:20:31 -05:00
|
|
|
|
|
|
|
|
// Calculate max audio sample added
|
|
|
|
|
if (numSamples > max_audio_sample)
|
|
|
|
|
max_audio_sample = numSamples;
|
2015-08-02 20:32:33 -05:00
|
|
|
}
|