mysteryx93 / VapourSynthViewer.NET

VapourSynth Video Script Viewer API for .NET
Other
14 stars 3 forks source link

[migrate] I migrate the project to support vs R66 #2

Open emako opened 4 months ago

emako commented 4 months ago

I change some codes like following:

Bmp = new WriteableBitmap(vi.Width, vi.Height, 96, 96, PixelFormats.Rgb24, null);

VsHelper.BitBlt(Bmp.BackBuffer, Bmp.BackBufferStride, plane.Ptr, plane.Stride, plane.Width * 3, plane.Height);

https://www.vapoursynth.com/2021/09/r55-audio-support-and-improved-performance/ Because COMPATBGR32 was removed so that I set the COMPATBGR32.vpy file to empty, and use vs.RGB24 to instead .

example vpy script here:

import vapoursynth as vs

src = vs.core.lsmas.LibavSMASHSource(u'D:/Program Files/Qvs/example.mp4', fpsnum=0, fpsden=1, format='', decoder='')
src = vs.core.resize.Bicubic(src, format=vs.RGB24, matrix_in_s='709')
src.set_output()

but vapoursynthviewer.net rendering incorrectly: image

vsedit is rendering good: image

Do you have any idea what's going on?

emako commented 4 months ago

test codes of VapourSynthViewer.NET.zip test script.zip

mysteryx93 commented 4 months ago

I haven't touched that code in 5 years.

If COMPATBGR32 was removed, that's the format expected by the display image buffer. It looks like it's not getting data in the expected format. It would require some code rewrite.

emako commented 4 months ago

Could you fix this error?

mysteryx93 commented 4 months ago

I honestly don't have time to work on this for now.

But if someone fixes it, I'll gladly merge the PR.

emako commented 4 months ago

Find the sample code from preview_dialog.cpp in VapourSynth-Editor

QPixmap PreviewDialog::pixmapFromRGB(
    const VSFrame * a_cpFrame)
{
    if((!m_cpVSAPI) || (!a_cpFrame))
        return QPixmap();

    const VSVideoFormat * cpFormat = m_cpVSAPI->getVideoFrameFormat(a_cpFrame);
    Q_ASSERT(cpFormat);
    int wwidth = m_cpVSAPI->getFrameWidth(a_cpFrame, 0);

    if((cpFormat->colorFamily != cfGray)
        || (cpFormat->bitsPerSample != 8)
        || (wwidth % 4))
    {
        QString errorString = tr("Error forming pixmap from frame. "
            "Expected format Gray8 with width divisible by 4. ");
        emit signalWriteLogMessage(mtCritical, errorString);
        return QPixmap();
    }

    const VSMap *props = m_cpVSAPI->getFramePropertiesRO(a_cpFrame);
    enum p2p_packing packing_fmt = static_cast<p2p_packing>(
        m_cpVSAPI->mapGetInt(props, "PackingFormat", 0, nullptr));
    bool is_10_bits;
    if (packing_fmt == p2p_rgb30)
    {
        is_10_bits = true;
    }
    else if (packing_fmt == p2p_argb32)
    {
        is_10_bits = false;
    }
    else
    {
        QString errorString = tr("Error forming pixmap from frame. "
            "Expected frame being packed from RGB24 or RGB30.");
        emit signalWriteLogMessage(mtCritical, errorString);
        return QPixmap();
    }

    int width = wwidth / 4;
    int height = m_cpVSAPI->getFrameHeight(a_cpFrame, 0);
    int stride = m_cpVSAPI->getStride(a_cpFrame, 0);

    const uint8_t * pData = m_cpVSAPI->getReadPtr(a_cpFrame, 0);
    QImage frameImage(reinterpret_cast<const uchar *>(pData),
        width, height, stride, is_10_bits ?
        QImage::Format_RGB30 : QImage::Format_ARGB32);
    QPixmap framePixmap = QPixmap::fromImage(frameImage, Qt::NoFormatConversion);
    return framePixmap;
}
emako commented 4 months ago

old vsedit code from https://github.com/YomikoR/VapourSynth-Editor/blob/r19/vsedit/src/preview/preview_dialog.cpp

QPixmap PreviewDialog::pixmapFromCompatBGR32(
    const VSFrameRef * a_cpFrameRef)
{
    if((!m_cpVSAPI) || (!a_cpFrameRef))
        return QPixmap();

    const VSFormat * cpFormat = m_cpVSAPI->getFrameFormat(a_cpFrameRef);
    Q_ASSERT(cpFormat);

    if(cpFormat->id != pfCompatBGR32)
    {
        QString errorString = trUtf8("Error forming pixmap from frame. "
            "Expected format CompatBGR32. Instead got \'%1\'.")
            .arg(cpFormat->name);
        emit signalWriteLogMessage(mtCritical, errorString);
        return QPixmap();
    }

    int width = m_cpVSAPI->getFrameWidth(a_cpFrameRef, 0);
    int height = m_cpVSAPI->getFrameHeight(a_cpFrameRef, 0);
    const void * pData = m_cpVSAPI->getReadPtr(a_cpFrameRef, 0);
    int stride = m_cpVSAPI->getStride(a_cpFrameRef, 0);
    QImage frameImage((const uchar *)pData, width, height,
        stride, QImage::Format_RGB32);
    QImage flippedImage = frameImage.mirrored();
    QPixmap framePixmap = QPixmap::fromImage(flippedImage);
    return framePixmap;
}
mysteryx93 commented 4 months ago

This is C++ code. Converting it to .NET is not that simple. Won't be using the same libraries to do the pixel conversion work. I don't know if there's a simple solution other than the defunct 'compat' mode.

Also I wrote this for Windows-only and I'm now on Linux. Modernizing it would require considerable work.

I haven't followed other developments in the community. What is this VS viewer doing better than other alternatives? I wonder how many people have been using it.

emako commented 4 months ago

The latest vsedit use #include <vapoursynth/VapourSynth4.h> instead of #include <vapoursynth/VapourSynth.h>. The v4 api, added the audio APIs to struct VSAPI, such as https://github.com/vapoursynth/vapoursynth/blob/master/include/VapourSynth4.h Maybe I can't migrate the c++ code from the latest version of vsedit.

emako commented 4 months ago

https://github.com/vapoursynth/vapoursynth/blob/master/sdk/vsscript_example.c

SDK example

for (int n = 0; n < vi->numFrames; n++) {
        const VSFrame *frame = vsapi->getFrame(n, node, errMsg, sizeof(errMsg));

        if (!frame) { // Check if an error happened when getting the frame
            error = 1;
            break;
        }

        // Loop over every row of every plane write to the file
        for (int p = 0; p < vi->format.numPlanes; p++) {
            ptrdiff_t stride = vsapi->getStride(frame, p);
            const uint8_t *readPtr = vsapi->getReadPtr(frame, p);
            int rowSize = vsapi->getFrameWidth(frame, p) * vi->format.bytesPerSample;
            int height = vsapi->getFrameHeight(frame, p);

            for (int y = 0; y < height; y++) {
                // You should probably handle any fwrite errors here as well
                fwrite(readPtr, rowSize, 1, outFile);
                readPtr += stride;
            }
        }

        vsapi->freeFrame(frame);
    }
emako commented 4 months ago

and then

VsVideoInfo vi = e.Frame.GetVideoInfo();
WriteableBitmap Bmp = new WriteableBitmap(vi.Width, vi.Height, 96, 96, PixelFormats.Rgb24, null);
nint writePtr = Bmp.BackBuffer;

Bmp.Lock();
for (int p = 0; p < vi.Format.NumPlanes; p++)
{
    VsPlane plane = e.Frame.GetPlane(p);

    int stride = plane.Stride;
    nint readPtr = plane.Ptr;
    int rowSize = plane.GetFrameWidth(p) * vi.Format.BytesPerSample;
    int height = plane.GetFrameHeight(p);

    for (int y = 0; y < height; y++)
    {
        memcpy(writePtr, readPtr, rowSize);
        readPtr = IntPtr.Add(readPtr, stride);
        writePtr = IntPtr.Add(writePtr, stride);
    }
}
Bmp.AddDirtyRect(new Int32Rect(0, 0, vi.Width, vi.Height));
Bmp.Unlock();

Preview image

emako commented 4 months ago

https://github.com/vapoursynth/vapoursynth/issues/1076

mysteryx93 commented 4 months ago

btw came upon this code that is similar to what's needed to convert frame data.

https://github.com/0xC0000054/libheif-sharp-samples/blob/bf473ba3c18726204cc4bab11ce2317df68d0738/src/decoder/Program.cs#L335

I don't whether doing it manually like that is the only option; the issue is that we then have to support every input format. Whereas with the COMPAT type, VapourSynth handles all formats automatically.

Also doing it manually doesn't have assembly optimization and will be considerably slower than properly-optimized frame conversion code.

mysteryx93 commented 4 months ago

Possibly SixLabors.ImageSharp would allow doing the frame conversions we need

https://docs.sixlabors.com/articles/imagesharp/gettingstarted.html

Indeed once you have it in an Image object, can convert it to the desired format.

image.CloneAs<Rgba32>();