forked from mia/Aegisub
Move YUV -> RGB conversion to VideoProviderYUV4MPEG and remove support for formats other than RGB32 from AegiVideoFrame
Originally committed to SVN as r5079.
This commit is contained in:
parent
833e69b09f
commit
824294078f
10 changed files with 171 additions and 332 deletions
|
@ -112,21 +112,15 @@ void CSRISubtitlesProvider::DrawSubtitles(AegiVideoFrame &dst,double time) {
|
||||||
|
|
||||||
// Load data into frame
|
// Load data into frame
|
||||||
csri_frame frame;
|
csri_frame frame;
|
||||||
for (int i=0;i<4;i++) {
|
if (dst.flipped) {
|
||||||
if (dst.flipped) {
|
frame.planes[0] = dst.data + (dst.h-1) * dst.pitch;
|
||||||
frame.planes[i] = dst.data[i] + (dst.h-1) * dst.pitch[i];
|
frame.strides[0] = -(signed)dst.pitch;
|
||||||
frame.strides[i] = -(signed)dst.pitch[i];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
frame.planes[i] = dst.data[i];
|
|
||||||
frame.strides[i] = dst.pitch[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
switch (dst.format) {
|
else {
|
||||||
case FORMAT_RGB32: frame.pixfmt = CSRI_F_BGR_; break;
|
frame.planes[0] = dst.data;
|
||||||
case FORMAT_RGB24: frame.pixfmt = CSRI_F_BGR; break;
|
frame.strides[0] = dst.pitch;
|
||||||
default: frame.pixfmt = CSRI_F_BGR_;
|
|
||||||
}
|
}
|
||||||
|
frame.pixfmt = CSRI_F_BGR_;
|
||||||
|
|
||||||
// Set format
|
// Set format
|
||||||
csri_fmt format;
|
csri_fmt format;
|
||||||
|
|
|
@ -208,11 +208,11 @@ void LibassSubtitlesProvider::DrawSubtitles(AegiVideoFrame &frame,double time) {
|
||||||
|
|
||||||
// Prepare copy
|
// Prepare copy
|
||||||
int src_stride = img->stride;
|
int src_stride = img->stride;
|
||||||
int dst_stride = frame.pitch[0];
|
int dst_stride = frame.pitch;
|
||||||
int dst_delta = dst_stride - img->w*4;
|
int dst_delta = dst_stride - img->w*4;
|
||||||
//int stride = std::min(src_stride,dst_stride);
|
//int stride = std::min(src_stride,dst_stride);
|
||||||
const unsigned char *src = img->bitmap;
|
const unsigned char *src = img->bitmap;
|
||||||
unsigned char *dst = frame.data[0] + (img->dst_y * dst_stride + img->dst_x * 4);
|
unsigned char *dst = frame.data + (img->dst_y * dst_stride + img->dst_x * 4);
|
||||||
unsigned int k,ck,t;
|
unsigned int k,ck,t;
|
||||||
|
|
||||||
// Copy image to destination frame
|
// Copy image to destination frame
|
||||||
|
|
|
@ -89,21 +89,6 @@ template<typename T> inline T mid(T a, T b, T c) { return std::max(a, std::min(b
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// @brief Code taken from http://bob.allegronetwork.com/prog/tricks.html#clamp Clamp integer to range
|
|
||||||
/// @param x
|
|
||||||
/// @param min
|
|
||||||
/// @param max
|
|
||||||
///
|
|
||||||
static FORCEINLINE int ClampSignedInteger32(int x,int min,int max) {
|
|
||||||
x -= min;
|
|
||||||
x &= (~x) >> 31;
|
|
||||||
x += min;
|
|
||||||
x -= max;
|
|
||||||
x &= x >> 31;
|
|
||||||
x += max;
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct delete_ptr {
|
struct delete_ptr {
|
||||||
template<class T>
|
template<class T>
|
||||||
void operator()(T* ptr) const {
|
void operator()(T* ptr) const {
|
||||||
|
|
|
@ -39,29 +39,20 @@
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "video_frame.h"
|
#include "video_frame.h"
|
||||||
|
|
||||||
|
|
||||||
/// @brief Reset values to the defaults
|
|
||||||
///
|
|
||||||
/// Note that this function DOES NOT unallocate memory.
|
|
||||||
/// Use Clear() for that
|
|
||||||
void AegiVideoFrame::Reset() {
|
void AegiVideoFrame::Reset() {
|
||||||
// Zero variables
|
// Zero variables
|
||||||
for (int i=0;i<4;i++) {
|
data = 0;
|
||||||
data[i] = NULL;
|
pitch = 0;
|
||||||
pitch[i] = 0;
|
|
||||||
}
|
|
||||||
memSize = 0;
|
memSize = 0;
|
||||||
w = 0;
|
w = 0;
|
||||||
h = 0;
|
h = 0;
|
||||||
|
|
||||||
// Set properties
|
// Set properties
|
||||||
format = FORMAT_NONE;
|
|
||||||
flipped = false;
|
flipped = false;
|
||||||
invertChannels = true;
|
invertChannels = true;
|
||||||
ownMem = true;
|
ownMem = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Constructor
|
|
||||||
AegiVideoFrame::AegiVideoFrame() {
|
AegiVideoFrame::AegiVideoFrame() {
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
@ -69,217 +60,90 @@ AegiVideoFrame::AegiVideoFrame() {
|
||||||
/// @brief Create a solid black frame of the request size and format
|
/// @brief Create a solid black frame of the request size and format
|
||||||
/// @param width
|
/// @param width
|
||||||
/// @param height
|
/// @param height
|
||||||
/// @param fmt
|
AegiVideoFrame::AegiVideoFrame(unsigned int width, unsigned int height) {
|
||||||
AegiVideoFrame::AegiVideoFrame(int width,int height,VideoFrameFormat fmt) {
|
assert(width > 0 && width < 10000);
|
||||||
|
assert(height > 0 && height < 10000);
|
||||||
|
|
||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
// Set format
|
// Set format
|
||||||
format = fmt;
|
|
||||||
w = width;
|
w = width;
|
||||||
h = height;
|
h = height;
|
||||||
pitch[0] = w * GetBpp();
|
pitch = w * GetBpp();
|
||||||
if (fmt == FORMAT_YV12) {
|
|
||||||
pitch[1] = w/2;
|
|
||||||
pitch[2] = w/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
Allocate();
|
Allocate();
|
||||||
|
memset(data, 0, pitch * height);
|
||||||
// Clear data
|
|
||||||
int size = pitch[0]*height + (pitch[1]+pitch[2])*height/2;
|
|
||||||
memset(data[0],0,size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Allocate memory if needed
|
|
||||||
void AegiVideoFrame::Allocate() {
|
void AegiVideoFrame::Allocate() {
|
||||||
// Check for sanity
|
assert(pitch > 0 && pitch < 10000);
|
||||||
wxASSERT(pitch[0] > 0 && pitch[0] < 10000);
|
assert(w > 0 && w < 10000);
|
||||||
wxASSERT(w > 0 && w < 10000);
|
assert(h > 0 && h < 10000);
|
||||||
wxASSERT(h > 0 && h < 10000);
|
|
||||||
wxASSERT(format != FORMAT_NONE);
|
|
||||||
|
|
||||||
// Get size
|
unsigned int size = pitch * h;
|
||||||
int height = h;
|
|
||||||
unsigned int size;
|
|
||||||
if (format == FORMAT_YV12) {
|
|
||||||
wxASSERT(pitch[1] > 0 && pitch[1] < 10000);
|
|
||||||
wxASSERT(pitch[2] > 0 && pitch[2] < 10000);
|
|
||||||
size = pitch[0]*height + (pitch[1]+pitch[2])*height/2;
|
|
||||||
}
|
|
||||||
else size = pitch[0] * height;
|
|
||||||
|
|
||||||
// Reallocate, if necessary
|
// Reallocate, if necessary
|
||||||
if (memSize != size || !ownMem) {
|
if (memSize != size || !ownMem) {
|
||||||
if (data[0] && ownMem) {
|
if (ownMem) {
|
||||||
delete[] data[0];
|
delete[] data;
|
||||||
}
|
}
|
||||||
data[0] = new unsigned char[size];
|
data = new unsigned char[size];
|
||||||
for (int i=1;i<4;i++) data[i] = NULL;
|
|
||||||
memSize = size;
|
memSize = size;
|
||||||
|
|
||||||
// Planar
|
|
||||||
if (format == FORMAT_YV12) {
|
|
||||||
data[1] = data[0] + (pitch[0]*height);
|
|
||||||
data[2] = data[0] + (pitch[0]*height+pitch[1]*height/2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ownMem = true;
|
ownMem = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Clear
|
|
||||||
void AegiVideoFrame::Clear() {
|
void AegiVideoFrame::Clear() {
|
||||||
if (ownMem) delete[] data[0];
|
if (ownMem) delete[] data;
|
||||||
|
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Copy from an AegiVideoFrame
|
|
||||||
/// @param source The frame to copy from
|
|
||||||
void AegiVideoFrame::CopyFrom(const AegiVideoFrame &source) {
|
void AegiVideoFrame::CopyFrom(const AegiVideoFrame &source) {
|
||||||
w = source.w;
|
w = source.w;
|
||||||
h = source.h;
|
h = source.h;
|
||||||
format = source.format;
|
pitch = source.pitch;
|
||||||
for (int i=0;i<4;i++) pitch[i] = source.pitch[i];
|
|
||||||
Allocate();
|
Allocate();
|
||||||
memcpy(data[0],source.data[0],memSize);
|
memcpy(data, source.data, memSize);
|
||||||
flipped = source.flipped;
|
flipped = source.flipped;
|
||||||
invertChannels = source.invertChannels;
|
invertChannels = source.invertChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Set the frame to an externally allocated block of memory
|
void AegiVideoFrame::SetTo(const unsigned char *source, unsigned int width, unsigned int height, unsigned int pitch) {
|
||||||
/// @param source Target frame data
|
assert(pitch > 0 && pitch < 10000);
|
||||||
/// @param width The frame width in pixels
|
assert(width > 0 && width < 10000);
|
||||||
/// @param height The frame height in pixels
|
assert(height > 0 && height < 10000);
|
||||||
/// @param pitch The frame's pitch
|
|
||||||
/// @param format The fram'e format
|
|
||||||
void AegiVideoFrame::SetTo(const unsigned char *const source[], int width, int height, const int pitch[4], VideoFrameFormat format) {
|
|
||||||
wxASSERT(pitch[0] > 0 && pitch[0] < 10000);
|
|
||||||
wxASSERT(width > 0 && width < 10000);
|
|
||||||
wxASSERT(height > 0 && height < 10000);
|
|
||||||
wxASSERT(format != FORMAT_NONE);
|
|
||||||
|
|
||||||
ownMem = false;
|
ownMem = false;
|
||||||
w = width;
|
w = width;
|
||||||
h = height;
|
h = height;
|
||||||
this->format = format;
|
// Note that despite this cast, the contents of data should still never be modified
|
||||||
|
data = const_cast<unsigned char*>(source);
|
||||||
for (int i = 0; i < 4; i++) {
|
this->pitch = pitch;
|
||||||
// Note that despite this cast, the contents of data should still never be modified
|
|
||||||
data[i] = const_cast<unsigned char*>(source[i]);
|
|
||||||
this->pitch[i] = pitch[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Get wxImage
|
|
||||||
/// @return
|
|
||||||
wxImage AegiVideoFrame::GetImage() const {
|
wxImage AegiVideoFrame::GetImage() const {
|
||||||
if (format == FORMAT_RGB32 || format == FORMAT_RGB24) {
|
unsigned char *buf = (unsigned char*)malloc(w*h*3);
|
||||||
// Create
|
if (!buf) throw std::bad_alloc();
|
||||||
unsigned char *buf = (unsigned char*)malloc(w*h*3);
|
const unsigned char *src = data;
|
||||||
if (!buf) throw std::bad_alloc();
|
unsigned char *dst = buf;
|
||||||
const unsigned char *src = data[0];
|
|
||||||
unsigned char *dst = buf;
|
|
||||||
|
|
||||||
int Bpp = GetBpp();
|
int Bpp = GetBpp();
|
||||||
|
|
||||||
// Convert
|
// Convert
|
||||||
for (unsigned int y=0;y<h;y++) {
|
for (unsigned int y=0;y<h;y++) {
|
||||||
dst = buf + y*w*3;
|
dst = buf + y*w*3;
|
||||||
if (flipped) src = data[0] + (h-y-1)*pitch[0];
|
if (flipped) src = data + (h-y-1)*pitch;
|
||||||
else src = data[0] + y*pitch[0];
|
else src = data + y*pitch;
|
||||||
for (unsigned int x=0;x<w;x++) {
|
for (unsigned int x=0;x<w;x++) {
|
||||||
*dst++ = *(src+2);
|
*dst++ = *(src+2);
|
||||||
*dst++ = *(src+1);
|
*dst++ = *(src+1);
|
||||||
*dst++ = *(src);
|
*dst++ = *(src);
|
||||||
src += Bpp;
|
src += Bpp;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make image
|
|
||||||
wxImage img(w,h);
|
|
||||||
img.SetData(buf);
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
else if (format == FORMAT_YV12) {
|
|
||||||
AegiVideoFrame temp;
|
|
||||||
temp.ConvertFrom(*this);
|
|
||||||
return temp.GetImage();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return wxImage(w,h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Get bytes per pixel for the current frame format
|
|
||||||
/// @param plane
|
|
||||||
/// @return
|
|
||||||
int AegiVideoFrame::GetBpp(int plane) const {
|
|
||||||
switch (format) {
|
|
||||||
case FORMAT_RGB32: return 4;
|
|
||||||
case FORMAT_RGB24: return 3;
|
|
||||||
case FORMAT_YUY2: return 2;
|
|
||||||
case FORMAT_YV12:
|
|
||||||
if (plane == 0) return 1;
|
|
||||||
else return 0;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Convert from another frame
|
|
||||||
/// @param source The frame to convert from
|
|
||||||
/// @param newFormat The format to convert to
|
|
||||||
void AegiVideoFrame::ConvertFrom(const AegiVideoFrame &source, VideoFrameFormat newFormat) {
|
|
||||||
if (newFormat != FORMAT_RGB32) throw _T("AegiVideoFrame::ConvertFrom: Unsupported destination format.");
|
|
||||||
if (source.format != FORMAT_YV12) throw _T("AegiVideoFrame::ConvertFrom: Unsupported source format.");
|
|
||||||
|
|
||||||
w = source.w;
|
|
||||||
h = source.h;
|
|
||||||
format = newFormat;
|
|
||||||
pitch[0] = w * 4;
|
|
||||||
|
|
||||||
Allocate();
|
|
||||||
|
|
||||||
// Set up pointers
|
|
||||||
const unsigned char *src_y = source.data[0];
|
|
||||||
const unsigned char *src_u = source.data[1];
|
|
||||||
const unsigned char *src_v = source.data[2];
|
|
||||||
unsigned char *dst = data[0];
|
|
||||||
|
|
||||||
// Set up pitches
|
|
||||||
const int src_delta1 = source.pitch[0]-w;
|
|
||||||
const int src_delta2 = source.pitch[1]-w/2;
|
|
||||||
const int src_delta3 = source.pitch[2]-w/2;
|
|
||||||
const int dst_delta = pitch[0]-w*4;
|
|
||||||
register int y,u,v;
|
|
||||||
|
|
||||||
// Loop
|
|
||||||
for (unsigned int py=0;py<h;py++) {
|
|
||||||
for (unsigned int px=0;px<w/2;px++) {
|
|
||||||
u = *src_u++ - 128;
|
|
||||||
v = *src_v++ - 128;
|
|
||||||
for (unsigned int i=0;i<2;i++) {
|
|
||||||
y = (*src_y++ - 16) * 298;
|
|
||||||
|
|
||||||
// Assign
|
|
||||||
*dst++ = ClampSignedInteger32((y + 516 * u + 128) >> 8,0,255); // Blue
|
|
||||||
*dst++ = ClampSignedInteger32((y - 100 * u - 208 * v + 128) >> 8,0,255); // Green
|
|
||||||
*dst++ = ClampSignedInteger32((y + 409 * v + 128) >> 8,0,255); // Red
|
|
||||||
*dst++ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase pointers
|
|
||||||
src_y += src_delta1;
|
|
||||||
src_u += src_delta2;
|
|
||||||
src_v += src_delta3;
|
|
||||||
dst += dst_delta;
|
|
||||||
|
|
||||||
// Roll back u/v on even lines
|
|
||||||
if (!(py & 1)) {
|
|
||||||
src_u -= source.pitch[1];
|
|
||||||
src_v -= source.pitch[2];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxImage img(w,h);
|
||||||
|
img.SetData(buf);
|
||||||
|
return img;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,63 +34,35 @@
|
||||||
/// @ingroup video
|
/// @ingroup video
|
||||||
///
|
///
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifndef AGI_PRE
|
#ifndef AGI_PRE
|
||||||
#include <wx/image.h>
|
#include <wx/image.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/// DOCME
|
|
||||||
enum VideoFrameFormat {
|
|
||||||
/// DOCME
|
|
||||||
FORMAT_NONE = 0x0000,
|
|
||||||
|
|
||||||
/// RGB, interleaved
|
|
||||||
FORMAT_RGB24 = 0x0001,
|
|
||||||
|
|
||||||
/// RGBA, interleaved
|
|
||||||
FORMAT_RGB32 = 0x0002,
|
|
||||||
|
|
||||||
/// YCbCr 4:2:2, planar
|
|
||||||
FORMAT_YUY2 = 0x0004,
|
|
||||||
|
|
||||||
/// YCbCr 4:2:0, planar
|
|
||||||
FORMAT_YV12 = 0x0008,
|
|
||||||
|
|
||||||
/// YCbCr 4:4:4, planar
|
|
||||||
FORMAT_YUV444 = 0x0010,
|
|
||||||
|
|
||||||
/// YCbCr 4:4:4 plus alpha, planar
|
|
||||||
FORMAT_YUV444A = 0x0020,
|
|
||||||
|
|
||||||
/// Y only (greyscale)
|
|
||||||
FORMAT_YUVMONO = 0x0040,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// DOCME
|
/// DOCME
|
||||||
/// @class AegiVideoFrame
|
/// @class AegiVideoFrame
|
||||||
/// @brief DOCME
|
/// @brief DOCME
|
||||||
///
|
///
|
||||||
/// DOCME
|
/// DOCME
|
||||||
class AegiVideoFrame {
|
class AegiVideoFrame {
|
||||||
private:
|
|
||||||
/// Whether the object owns its buffer. If this is false, **data should never be modified
|
/// Whether the object owns its buffer. If this is false, **data should never be modified
|
||||||
bool ownMem;
|
bool ownMem;
|
||||||
|
/// @brief Reset values to the defaults
|
||||||
|
///
|
||||||
|
/// Note that this function DOES NOT deallocate memory.
|
||||||
|
/// Use Clear() for that
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/// @brief Allocate memory if needed
|
||||||
void Allocate();
|
void Allocate();
|
||||||
|
|
||||||
unsigned int memSize; /// The size in bytes of the frame buffer
|
/// The size in bytes of the frame buffer
|
||||||
|
unsigned int memSize;
|
||||||
|
|
||||||
/// Pointers to the data planes. Interleaved formats only use data[0]
|
/// Pointer to the data planes
|
||||||
unsigned char *data[4];
|
unsigned char *data;
|
||||||
|
|
||||||
/// Data format
|
|
||||||
VideoFrameFormat format;
|
|
||||||
|
|
||||||
/// Width in pixels
|
/// Width in pixels
|
||||||
unsigned int w;
|
unsigned int w;
|
||||||
|
@ -99,25 +71,33 @@ public:
|
||||||
unsigned int h;
|
unsigned int h;
|
||||||
|
|
||||||
// Pitch, that is, the number of bytes used by each row.
|
// Pitch, that is, the number of bytes used by each row.
|
||||||
unsigned int pitch[4];
|
unsigned int pitch;
|
||||||
|
|
||||||
/// First row is actually the bottom one
|
/// First row is actually the bottom one
|
||||||
bool flipped;
|
bool flipped;
|
||||||
|
|
||||||
/// Swap Red and Blue channels or U and V planes (controls RGB versus BGR ordering etc)
|
/// Swap Red and Blue channels (controls RGB versus BGR ordering etc)
|
||||||
bool invertChannels;
|
bool invertChannels;
|
||||||
|
|
||||||
AegiVideoFrame();
|
AegiVideoFrame();
|
||||||
AegiVideoFrame(int width,int height,VideoFrameFormat format=FORMAT_RGB32);
|
AegiVideoFrame(unsigned int width, unsigned int height);
|
||||||
|
|
||||||
|
// @brief Clear this frame, freeing its memory if nessesary
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
|
/// @brief Copy from an AegiVideoFrame
|
||||||
|
/// @param source The frame to copy from
|
||||||
void CopyFrom(const AegiVideoFrame &source);
|
void CopyFrom(const AegiVideoFrame &source);
|
||||||
void ConvertFrom(const AegiVideoFrame &source, VideoFrameFormat newFormat=FORMAT_RGB32);
|
|
||||||
void SetTo(const unsigned char *const source[], int width, int height, const int pitch[4], VideoFrameFormat format);
|
|
||||||
|
|
||||||
|
/// @brief Set the frame to an externally allocated block of memory
|
||||||
|
/// @param source Target frame data
|
||||||
|
/// @param width The frame width in pixels
|
||||||
|
/// @param height The frame height in pixels
|
||||||
|
/// @param pitch The frame's pitch
|
||||||
|
/// @param format The frame's format
|
||||||
|
void SetTo(const unsigned char *source, unsigned int width, unsigned int height, unsigned int pitch);
|
||||||
|
|
||||||
|
/// @brief Get this frame as a wxImage
|
||||||
wxImage GetImage() const;
|
wxImage GetImage() const;
|
||||||
int GetBpp(int plane=0) const;
|
int GetBpp() const { return 4; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -279,7 +279,7 @@ void VideoOutGL::UploadFrameData(const AegiVideoFrame& frame) {
|
||||||
if (frame.h == 0 || frame.w == 0) return;
|
if (frame.h == 0 || frame.w == 0) return;
|
||||||
|
|
||||||
GLuint format = frame.invertChannels ? GL_BGRA_EXT : GL_RGBA;
|
GLuint format = frame.invertChannels ? GL_BGRA_EXT : GL_RGBA;
|
||||||
InitTextures(frame.w, frame.h, format, frame.GetBpp(0), frame.flipped);
|
InitTextures(frame.w, frame.h, format, frame.GetBpp(), frame.flipped);
|
||||||
|
|
||||||
// Set the row length, needed to be able to upload partial rows
|
// Set the row length, needed to be able to upload partial rows
|
||||||
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.w));
|
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.w));
|
||||||
|
@ -289,7 +289,7 @@ void VideoOutGL::UploadFrameData(const AegiVideoFrame& frame) {
|
||||||
|
|
||||||
CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, ti.textureID));
|
CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, ti.textureID));
|
||||||
CHECK_ERROR(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ti.sourceW,
|
CHECK_ERROR(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ti.sourceW,
|
||||||
ti.sourceH, format, GL_UNSIGNED_BYTE, frame.data[0] + ti.dataOffset));
|
ti.sourceH, format, GL_UNSIGNED_BYTE, frame.data + ti.dataOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
|
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
|
||||||
|
|
|
@ -262,12 +262,11 @@ const AegiVideoFrame AvisynthVideoProvider::GetFrame(int n) {
|
||||||
|
|
||||||
// Aegisub's video frame
|
// Aegisub's video frame
|
||||||
AegiVideoFrame &final = iframe;
|
AegiVideoFrame &final = iframe;
|
||||||
final.format = FORMAT_RGB32;
|
|
||||||
final.flipped = true;
|
final.flipped = true;
|
||||||
final.invertChannels = true;
|
final.invertChannels = true;
|
||||||
|
|
||||||
// Set size properties
|
// Set size properties
|
||||||
final.pitch[0] = frame->GetPitch();
|
final.pitch = frame->GetPitch();
|
||||||
final.w = frame->GetRowSize() / Bpp;
|
final.w = frame->GetRowSize() / Bpp;
|
||||||
final.h = frame->GetHeight();
|
final.h = frame->GetHeight();
|
||||||
|
|
||||||
|
@ -275,7 +274,7 @@ const AegiVideoFrame AvisynthVideoProvider::GetFrame(int n) {
|
||||||
final.Allocate();
|
final.Allocate();
|
||||||
|
|
||||||
// Copy
|
// Copy
|
||||||
memcpy(final.data[0],frame->GetReadPtr(),final.pitch[0] * final.h);
|
memcpy(final.data,frame->GetReadPtr(),final.pitch * final.h);
|
||||||
|
|
||||||
// Set last number
|
// Set last number
|
||||||
last_fnum = n;
|
last_fnum = n;
|
||||||
|
|
|
@ -58,8 +58,8 @@ void DummyVideoProvider::Create(double _fps, int frames, int _width, int _height
|
||||||
width = _width;
|
width = _width;
|
||||||
height = _height;
|
height = _height;
|
||||||
|
|
||||||
frame = AegiVideoFrame(width,height,FORMAT_RGB32);
|
frame = AegiVideoFrame(width,height);
|
||||||
unsigned char *dst = frame.data[0];
|
unsigned char *dst = frame.data;
|
||||||
unsigned char r = colour.Red(), g = colour.Green(), b = colour.Blue();
|
unsigned char r = colour.Red(), g = colour.Green(), b = colour.Blue();
|
||||||
|
|
||||||
unsigned char h, s, l, lr, lg, lb; // light variants
|
unsigned char h, s, l, lr, lg, lb; // light variants
|
||||||
|
@ -69,7 +69,7 @@ void DummyVideoProvider::Create(double _fps, int frames, int _width, int _height
|
||||||
hsl_to_rgb(h, s, l, &lr, &lg, &lb);
|
hsl_to_rgb(h, s, l, &lr, &lg, &lb);
|
||||||
|
|
||||||
if (pattern) {
|
if (pattern) {
|
||||||
int ppitch = frame.pitch[0] / frame.GetBpp();
|
int ppitch = frame.pitch / frame.GetBpp();
|
||||||
for (unsigned int y = 0; y < frame.h; ++y) {
|
for (unsigned int y = 0; y < frame.h; ++y) {
|
||||||
if ((y / 8) & 1) {
|
if ((y / 8) & 1) {
|
||||||
for (int x = 0; x < ppitch; ++x) {
|
for (int x = 0; x < ppitch; ++x) {
|
||||||
|
@ -106,7 +106,7 @@ void DummyVideoProvider::Create(double _fps, int frames, int _width, int _height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i=frame.pitch[0]*frame.h/frame.GetBpp();--i>=0;) {
|
for (int i=frame.pitch*frame.h/frame.GetBpp();--i>=0;) {
|
||||||
*dst++ = b;
|
*dst++ = b;
|
||||||
*dst++ = g;
|
*dst++ = g;
|
||||||
*dst++ = r;
|
*dst++ = r;
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
#include "aegisub_endian.h"
|
#include "aegisub_endian.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "utils.h"
|
||||||
#include "video_context.h"
|
#include "video_context.h"
|
||||||
#include "video_provider_ffmpegsource.h"
|
#include "video_provider_ffmpegsource.h"
|
||||||
|
|
||||||
|
@ -273,23 +274,16 @@ void FFmpegSourceVideoProvider::Close() {
|
||||||
/// @param _n
|
/// @param _n
|
||||||
/// @return
|
/// @return
|
||||||
///
|
///
|
||||||
const AegiVideoFrame FFmpegSourceVideoProvider::GetFrame(int _n) {
|
const AegiVideoFrame FFmpegSourceVideoProvider::GetFrame(int n) {
|
||||||
// don't try to seek to insane places
|
FrameNumber = mid(0, n, GetFrameCount() - 1);
|
||||||
int n = _n;
|
|
||||||
if (n < 0)
|
|
||||||
n = 0;
|
|
||||||
if (n >= GetFrameCount())
|
|
||||||
n = GetFrameCount()-1;
|
|
||||||
// set position
|
|
||||||
FrameNumber = n;
|
|
||||||
|
|
||||||
// decode frame
|
// decode frame
|
||||||
const FFMS_Frame *SrcFrame = FFMS_GetFrame(VideoSource, n, &ErrInfo);
|
const FFMS_Frame *SrcFrame = FFMS_GetFrame(VideoSource, FrameNumber, &ErrInfo);
|
||||||
if (SrcFrame == NULL) {
|
if (SrcFrame == NULL) {
|
||||||
throw VideoDecodeError(std::string("Failed to retrieve frame:") + ErrInfo.Buffer);
|
throw VideoDecodeError(std::string("Failed to retrieve frame:") + ErrInfo.Buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
CurFrame.SetTo(SrcFrame->Data, Width, Height, SrcFrame->Linesize, FORMAT_RGB32);
|
CurFrame.SetTo(SrcFrame->Data[0], Width, Height, SrcFrame->Linesize[0]);
|
||||||
return CurFrame;
|
return CurFrame;
|
||||||
}
|
}
|
||||||
#endif /* WITH_FFMPEGSOURCE */
|
#endif /* WITH_FFMPEGSOURCE */
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include <libaegisub/log.h>
|
#include <libaegisub/log.h>
|
||||||
|
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
|
#include "utils.h"
|
||||||
#include "video_provider_yuv4mpeg.h"
|
#include "video_provider_yuv4mpeg.h"
|
||||||
|
|
||||||
// All of this cstdio bogus is because of one reason and one reason only:
|
// All of this cstdio bogus is because of one reason and one reason only:
|
||||||
|
@ -69,7 +70,7 @@ YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(wxString fname)
|
||||||
wxString filename = wxFileName(fname).GetShortPath();
|
wxString filename = wxFileName(fname).GetShortPath();
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
sf = _wfopen(filename.wc_str(), _T("rb"));
|
sf = _wfopen(filename.wc_str(), L"rb");
|
||||||
#else
|
#else
|
||||||
sf = fopen(filename.utf8_str(), "rb");
|
sf = fopen(filename.utf8_str(), "rb");
|
||||||
#endif
|
#endif
|
||||||
|
@ -195,7 +196,7 @@ std::vector<wxString> YUV4MPEGVideoProvider::ReadHeader(int64_t startpos, bool r
|
||||||
void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
|
void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
|
||||||
if (tags.size() <= 1)
|
if (tags.size() <= 1)
|
||||||
throw VideoOpenError("ParseFileHeader: contentless header");
|
throw VideoOpenError("ParseFileHeader: contentless header");
|
||||||
if (tags.front().Cmp(_T("YUV4MPEG2")))
|
if (tags.front().Cmp("YUV4MPEG2"))
|
||||||
throw VideoOpenError("ParseFileHeader: malformed header (bad magic)");
|
throw VideoOpenError("ParseFileHeader: malformed header (bad magic)");
|
||||||
|
|
||||||
// temporary stuff
|
// temporary stuff
|
||||||
|
@ -207,52 +208,54 @@ void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
|
||||||
Y4M_PixelFormat t_pixfmt = Y4M_PIXFMT_NONE;
|
Y4M_PixelFormat t_pixfmt = Y4M_PIXFMT_NONE;
|
||||||
|
|
||||||
for (unsigned i = 1; i < tags.size(); i++) {
|
for (unsigned i = 1; i < tags.size(); i++) {
|
||||||
wxString tag = _T("");
|
wxString tag;
|
||||||
long tmp_long1 = 0;
|
long tmp_long1 = 0;
|
||||||
long tmp_long2 = 0;
|
long tmp_long2 = 0;
|
||||||
|
|
||||||
if (tags.at(i).StartsWith(_T("W"), &tag)) {
|
if (tags[i].StartsWith("W", &tag)) {
|
||||||
if (!tag.ToLong(&tmp_long1))
|
if (!tag.ToLong(&tmp_long1))
|
||||||
throw VideoOpenError("ParseFileHeader: invalid width");
|
throw VideoOpenError("ParseFileHeader: invalid width");
|
||||||
t_w = (int)tmp_long1;
|
t_w = (int)tmp_long1;
|
||||||
}
|
}
|
||||||
else if (tags.at(i).StartsWith(_T("H"), &tag)) {
|
else if (tags[i].StartsWith("H", &tag)) {
|
||||||
if (!tag.ToLong(&tmp_long1))
|
if (!tag.ToLong(&tmp_long1))
|
||||||
throw VideoOpenError("ParseFileHeader: invalid height");
|
throw VideoOpenError("ParseFileHeader: invalid height");
|
||||||
t_h = (int)tmp_long1;
|
t_h = (int)tmp_long1;
|
||||||
}
|
}
|
||||||
else if (tags.at(i).StartsWith(_T("F"), &tag)) {
|
else if (tags[i].StartsWith("F", &tag)) {
|
||||||
if (!(tag.BeforeFirst(':')).ToLong(&tmp_long1) && tag.AfterFirst(':').ToLong(&tmp_long2))
|
if (!(tag.BeforeFirst(':')).ToLong(&tmp_long1) && tag.AfterFirst(':').ToLong(&tmp_long2))
|
||||||
throw VideoOpenError("ParseFileHeader: invalid framerate");
|
throw VideoOpenError("ParseFileHeader: invalid framerate");
|
||||||
t_fps_num = (int)tmp_long1;
|
t_fps_num = (int)tmp_long1;
|
||||||
t_fps_den = (int)tmp_long2;
|
t_fps_den = (int)tmp_long2;
|
||||||
}
|
}
|
||||||
else if (tags.at(i).StartsWith(_T("C"), &tag)) {
|
else if (tags[i].StartsWith("C", &tag)) {
|
||||||
// technically this should probably be case sensitive,
|
// technically this should probably be case sensitive,
|
||||||
// but being liberal in what you accept doesn't hurt
|
// but being liberal in what you accept doesn't hurt
|
||||||
if (!tag.CmpNoCase(_T("420"))) t_pixfmt = Y4M_PIXFMT_420JPEG; // is this really correct?
|
tag.MakeLower();
|
||||||
else if (!tag.CmpNoCase(_T("420jpeg"))) t_pixfmt = Y4M_PIXFMT_420JPEG;
|
if (tag == "420") t_pixfmt = Y4M_PIXFMT_420JPEG; // is this really correct?
|
||||||
else if (!tag.CmpNoCase(_T("420mpeg2"))) t_pixfmt = Y4M_PIXFMT_420MPEG2;
|
else if (tag == "420jpeg") t_pixfmt = Y4M_PIXFMT_420JPEG;
|
||||||
else if (!tag.CmpNoCase(_T("420paldv"))) t_pixfmt = Y4M_PIXFMT_420PALDV;
|
else if (tag == "420mpeg2") t_pixfmt = Y4M_PIXFMT_420MPEG2;
|
||||||
else if (!tag.CmpNoCase(_T("411"))) t_pixfmt = Y4M_PIXFMT_411;
|
else if (tag == "420paldv") t_pixfmt = Y4M_PIXFMT_420PALDV;
|
||||||
else if (!tag.CmpNoCase(_T("422"))) t_pixfmt = Y4M_PIXFMT_422;
|
else if (tag == "411") t_pixfmt = Y4M_PIXFMT_411;
|
||||||
else if (!tag.CmpNoCase(_T("444"))) t_pixfmt = Y4M_PIXFMT_444;
|
else if (tag == "422") t_pixfmt = Y4M_PIXFMT_422;
|
||||||
else if (!tag.CmpNoCase(_T("444alpha"))) t_pixfmt = Y4M_PIXFMT_444ALPHA;
|
else if (tag == "444") t_pixfmt = Y4M_PIXFMT_444;
|
||||||
else if (!tag.CmpNoCase(_T("mono"))) t_pixfmt = Y4M_PIXFMT_MONO;
|
else if (tag == "444alpha") t_pixfmt = Y4M_PIXFMT_444ALPHA;
|
||||||
|
else if (tag == "mono") t_pixfmt = Y4M_PIXFMT_MONO;
|
||||||
else
|
else
|
||||||
throw VideoOpenError("ParseFileHeader: invalid or unknown colorspace");
|
throw VideoOpenError("ParseFileHeader: invalid or unknown colorspace");
|
||||||
}
|
}
|
||||||
else if (tags.at(i).StartsWith(_T("I"), &tag)) {
|
else if (tags[i].StartsWith("I", &tag)) {
|
||||||
if (!tag.CmpNoCase(_T("p"))) t_imode = Y4M_ILACE_PROGRESSIVE;
|
tag.MakeLower();
|
||||||
else if (!tag.CmpNoCase(_T("t"))) t_imode = Y4M_ILACE_TFF;
|
if (tag == "p") t_imode = Y4M_ILACE_PROGRESSIVE;
|
||||||
else if (!tag.CmpNoCase(_T("b"))) t_imode = Y4M_ILACE_BFF;
|
else if (tag == "t") t_imode = Y4M_ILACE_TFF;
|
||||||
else if (!tag.CmpNoCase(_T("m"))) t_imode = Y4M_ILACE_MIXED;
|
else if (tag == "b") t_imode = Y4M_ILACE_BFF;
|
||||||
else if (!tag.CmpNoCase(_T("?"))) t_imode = Y4M_ILACE_UNKNOWN;
|
else if (tag == "m") t_imode = Y4M_ILACE_MIXED;
|
||||||
|
else if (tag == "?") t_imode = Y4M_ILACE_UNKNOWN;
|
||||||
else
|
else
|
||||||
throw VideoOpenError("ParseFileHeader: invalid or unknown interlacing mode");
|
throw VideoOpenError("ParseFileHeader: invalid or unknown interlacing mode");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LOG_D("provider/video/yuv4mpeg") << "Unparsed tag: " << tags.at(i).c_str();
|
LOG_D("provider/video/yuv4mpeg") << "Unparsed tag: " << tags[i].c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The point of all this is to allow multiple YUV4MPEG2 headers in a single file
|
// The point of all this is to allow multiple YUV4MPEG2 headers in a single file
|
||||||
|
@ -317,11 +320,11 @@ int YUV4MPEGVideoProvider::IndexFile() {
|
||||||
break; // no more headers
|
break; // no more headers
|
||||||
|
|
||||||
Y4M_FrameFlags flags = Y4M_FFLAG_NOTSET;
|
Y4M_FrameFlags flags = Y4M_FFLAG_NOTSET;
|
||||||
if (!tags.front().Cmp(_T("YUV4MPEG2"))) {
|
if (!tags.front().Cmp("YUV4MPEG2")) {
|
||||||
ParseFileHeader(tags);
|
ParseFileHeader(tags);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (!tags.front().Cmp(_T("FRAME")))
|
else if (!tags.front().Cmp("FRAME"))
|
||||||
flags = ParseFrameHeader(tags);
|
flags = ParseFrameHeader(tags);
|
||||||
|
|
||||||
if (flags == Y4M_FFLAG_NONE) {
|
if (flags == Y4M_FFLAG_NONE) {
|
||||||
|
@ -329,7 +332,7 @@ int YUV4MPEGVideoProvider::IndexFile() {
|
||||||
seek_table.push_back(curpos);
|
seek_table.push_back(curpos);
|
||||||
// seek to next frame header start position
|
// seek to next frame header start position
|
||||||
if (fseeko(sf, frame_sz, SEEK_CUR))
|
if (fseeko(sf, frame_sz, SEEK_CUR))
|
||||||
throw VideoOpenError(STD_STR(wxString::Format(_T("IndexFile: failed seeking to position %d"), curpos + frame_sz)));
|
throw VideoOpenError(STD_STR(wxString::Format("IndexFile: failed seeking to position %d", curpos + frame_sz)));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/// @todo implement rff flags etc
|
/// @todo implement rff flags etc
|
||||||
|
@ -339,60 +342,80 @@ int YUV4MPEGVideoProvider::IndexFile() {
|
||||||
return framecount;
|
return framecount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://bob.allegronetwork.com/prog/tricks.html#clamp
|
||||||
|
static FORCEINLINE int clamp(int x) {
|
||||||
|
x &= (~x) >> 31;
|
||||||
|
x -= 255;
|
||||||
|
x &= x >> 31;
|
||||||
|
x += 255;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Gets a given frame
|
/// @brief Gets a given frame
|
||||||
/// @param n The frame number to return
|
/// @param n The frame number to return
|
||||||
/// @return The video frame
|
/// @return The video frame
|
||||||
const AegiVideoFrame YUV4MPEGVideoProvider::GetFrame(int n) {
|
const AegiVideoFrame YUV4MPEGVideoProvider::GetFrame(int n) {
|
||||||
// don't try to seek to insane places
|
cur_fn = mid(0, n, num_frames - 1);
|
||||||
if (n < 0)
|
|
||||||
n = 0;
|
|
||||||
if (n >= num_frames)
|
|
||||||
n = num_frames-1;
|
|
||||||
// set position
|
|
||||||
cur_fn = n;
|
|
||||||
|
|
||||||
VideoFrameFormat src_fmt, dst_fmt;
|
int uv_width = w / 2;
|
||||||
dst_fmt = FORMAT_RGB32;
|
|
||||||
int uv_width;
|
|
||||||
switch (pixfmt) {
|
switch (pixfmt) {
|
||||||
case Y4M_PIXFMT_420JPEG:
|
case Y4M_PIXFMT_420JPEG:
|
||||||
case Y4M_PIXFMT_420MPEG2:
|
case Y4M_PIXFMT_420MPEG2:
|
||||||
case Y4M_PIXFMT_420PALDV:
|
case Y4M_PIXFMT_420PALDV:
|
||||||
src_fmt = FORMAT_YV12; uv_width = w / 2; break;
|
break;
|
||||||
case Y4M_PIXFMT_422:
|
|
||||||
src_fmt = FORMAT_YUY2; uv_width = w / 2; break;
|
|
||||||
/// @todo add support for more pixel formats
|
/// @todo add support for more pixel formats
|
||||||
default:
|
default:
|
||||||
throw _T("YUV4MPEG video provider: GetFrame: Unsupported source colorspace");
|
throw "YUV4MPEG video provider: GetFrame: Unsupported source colorspace";
|
||||||
}
|
}
|
||||||
|
|
||||||
AegiVideoFrame tmp_frame;
|
|
||||||
|
|
||||||
tmp_frame.format = src_fmt;
|
std::vector<uint8_t> planes[3];
|
||||||
tmp_frame.w = w;
|
planes[0].resize(luma_sz);
|
||||||
tmp_frame.h = h;
|
planes[1].resize(chroma_sz);
|
||||||
tmp_frame.invertChannels = false;
|
planes[2].resize(chroma_sz);
|
||||||
tmp_frame.pitch[0] = w;
|
|
||||||
for (int i=1;i<=2;i++)
|
|
||||||
tmp_frame.pitch[i] = uv_width;
|
|
||||||
tmp_frame.Allocate();
|
|
||||||
|
|
||||||
fseeko(sf, seek_table[n], SEEK_SET);
|
fseeko(sf, seek_table[n], SEEK_SET);
|
||||||
size_t ret;
|
size_t ret;
|
||||||
ret = fread(tmp_frame.data[0], luma_sz, 1, sf);
|
ret = fread(&planes[0][0], luma_sz, 1, sf);
|
||||||
if (ret != 1 || feof(sf) || ferror(sf))
|
if (ret != 1 || feof(sf) || ferror(sf))
|
||||||
throw _T("YUV4MPEG video provider: GetFrame: failed to read luma plane");
|
throw "YUV4MPEG video provider: GetFrame: failed to read luma plane";
|
||||||
for (int i = 1; i <= 2; i++) {
|
for (int i = 1; i <= 2; i++) {
|
||||||
ret = fread(tmp_frame.data[i], chroma_sz, 1, sf);
|
ret = fread(&planes[i][0], chroma_sz, 1, sf);
|
||||||
if (ret != 1 || feof(sf) || ferror(sf))
|
if (ret != 1 || feof(sf) || ferror(sf))
|
||||||
throw _T("YUV4MPEG video provider: GetFrame: failed to read chroma planes");
|
throw "YUV4MPEG video provider: GetFrame: failed to read chroma planes";
|
||||||
}
|
}
|
||||||
|
|
||||||
AegiVideoFrame dst_frame;
|
AegiVideoFrame dst_frame;
|
||||||
dst_frame.invertChannels = true;
|
dst_frame.invertChannels = true;
|
||||||
dst_frame.ConvertFrom(tmp_frame, dst_fmt);
|
dst_frame.w = w;
|
||||||
|
dst_frame.h = h;
|
||||||
|
dst_frame.pitch = w * 4;
|
||||||
|
dst_frame.Allocate();
|
||||||
|
|
||||||
tmp_frame.Clear();
|
const unsigned char *src_y = &planes[0][0];
|
||||||
|
const unsigned char *src_u = &planes[1][0];
|
||||||
|
const unsigned char *src_v = &planes[2][0];
|
||||||
|
unsigned char *dst = dst_frame.data;
|
||||||
|
|
||||||
|
for (int py = 0; py < h; ++py) {
|
||||||
|
for (int px = 0; px < w / 2; ++px) {
|
||||||
|
const int u = *src_u++ - 128;
|
||||||
|
const int v = *src_v++ - 128;
|
||||||
|
for (unsigned int i = 0; i < 2; ++i) {
|
||||||
|
const int y = (*src_y++ - 16) * 298;
|
||||||
|
|
||||||
|
*dst++ = clamp((y + 516 * u + 128) >> 8); // Blue
|
||||||
|
*dst++ = clamp((y - 100 * u - 208 * v + 128) >> 8); // Green
|
||||||
|
*dst++ = clamp((y + 409 * v + 128) >> 8); // Red
|
||||||
|
*dst++ = 0; // Alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roll back u/v on even lines
|
||||||
|
if (!(py & 1)) {
|
||||||
|
src_u -= uv_width;
|
||||||
|
src_v -= uv_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dst_frame;
|
return dst_frame;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue