multitheftauto / mtasa-blue

Multi Theft Auto is a game engine that incorporates an extendable network play element into a proprietary commercial single-player game.
https://multitheftauto.com
GNU General Public License v3.0
1.42k stars 437 forks source link

Convert SVG bitmap to BGRA unpremultiplied #3829

Open Lpsd opened 1 month ago

Lpsd commented 1 month ago

Resolves #3828

image

local w,h = 600, 450

local svg_bg = svgCreate(w, h, [[
    <svg width="604" height="454" viewBox="0 0 604 454" fill="none" xmlns="http://www.w3.org/2000/svg">
        <rect x="2" y="2" width="600" height="450" rx="10" fill="#21323A" fill-opacity="0.6"/>
        <rect x="1" y="1" width="602" height="452" rx="11" stroke="#30444D" stroke-opacity="0.6" stroke-width="2"/>
    </svg>
]])

dxSetTextureEdge(svg_bg, 'clamp')
local tex_bg = dxCreateTexture('bg.png', 'argb', false, 'clamp')

addEventHandler('onClientRender', root, function()
    dxDrawImage(150, 200, w, h, svg_bg, 0, 0, 0, 0xFFFFFFFF)
    dxDrawImage(150 + w + 100, 200, w, h, tex_bg, 0, 0, 0, 0xFFFFFFFF)
end)

bg.png bg

TheNormalnij commented 1 month ago

The next lunasvg version has no convert method. Maybe we can optimize and prepare the solution for the future update:

Faster convert function

void CClientVectorGraphicDisplay::UnpremultiplyBitmap(Bitmap &bitmap)
{
    auto width = bitmap.width();
    auto height = bitmap.height();
    auto stride = bitmap.stride();
    auto rowData = bitmap.data();

    for (std::uint32_t y = 0; y < height; y++)
    {
        auto data = rowData;
        for (std::uint32_t x = 0; x < width; x++)
        {
            auto &b = data[0];
            auto &g = data[1];
            auto &r = data[2];
            auto &a = data[3];

            if (a != 0)
            {
                r = (r * 255) / a;
                g = (g * 255) / a;
                b = (b * 255) / a;
            }

            data += 4;
        }

        rowData += stride;
    }
}

Avoid additional memory allocation and memcpy

    IDirect3DSurface9* surface = m_pVectorGraphic->GetRenderItem()->m_pD3DRenderTargetSurface;
    if (!surface)
        return;

    // Lock surface
    D3DLOCKED_RECT LockedRect;
    if (SUCCEEDED(surface->LockRect(&LockedRect, nullptr, D3DLOCK_DISCARD)))
    {
        auto surfaceData = static_cast<std::uint8_t*>(LockedRect.pBits);
        auto stride = static_cast<std::uint32_t>(LockedRect.Pitch);

        Bitmap bitmap{surfaceData, pVectorGraphicItem->m_uiSizeX, pVectorGraphicItem->m_uiSizeY, stride};
        svgDocument->render(bitmap);
        UnpremultiplyBitmap(bitmap);

        // Unlock surface
        surface->UnlockRect();
    }
Lpsd commented 3 weeks ago

Just making sure status is known, as this was discussed on Discord - I tried the above code but unfortunately the size of the resulting texture seems to be slightly off, by a few pixels? I'll get some screenshots later, but in this case it looks like drawing a 103x103 rect to a 100x100 canvas at 0x0.

Maybe there are differences in texture generation from render compared to renderToBitmap, I don't see anything wrong in the calculations for UnpremultiplyBitmap or how it is being drawn to the device.