Ehsan
Ehsan

Reputation: 3

Issues with Positioning Objects on an Image in a Blazor Component

I'm working on a Blazor project where I need to place objects (like bread, watermelon) on a background image. The problem is that when I move the objects, they appear underneath the image rather than on top of it where I want them. Here's an overview of my implementation and the problem:

Problem

I have a Blazor component where users can drag and drop objects onto a background image. However, the objects are not placed directly on the image but seem to appear underneath it when moved. I want the objects to be positioned exactly on the image, not beneath it.

Code

Here is the relevant part of my code:

@page "/planDisplay"
@using siteplan_Projekt.Data
@inject FileService fileService
@inject IJSRuntime JsRuntime
@rendermode InteractiveServer

<PageTitle>Plan-Display</PageTitle>

@if (errors.Count > 0)
{
    <ul class="text-danger">
        @foreach (var error in errors)
        {
            <li>@error</li>
        }
    </ul>
}

<!-- siteplan-Projekt/Components/Pages/SitePlan/PlanDisplay.razor -->
<MudDropContainer T="DropItem" Items="_items" ApplyDropClassesOnDragStarted="_applyDropClassesOnDragStarted" ItemsSelector="@((item, dropzone) => item.Place == dropzone)" ItemDropped="ItemUpdated" Class="d-flex flex-column flex-grow-1">
    <ChildContent>
        <div class="d-flex flex-wrap justify-space-between" @onmousemove="OnMouseMove">
            <MudDropZone T="DropItem" Identifier="ObjBox" CanDrop="@(item => false)" Class="rounded-lg border-2 border-solid mud-border-lines-default pa-6 ma-8">
                <MudText Typo="Typo.h6" Class="mb-4">Objekt</MudText>
            </MudDropZone>
            <div class="image-wrapper" style="position: relative; width: 800px; height: 600px;">
                <MudDropZone T="DropItem" Identifier="Image" CanDrop="@(item => item.IsPicked == false && item.IsRotten == false)" Class="site-plan-dropzone">
                    <img src="@imageUpload[0].Url" alt="Site plan" class="site-plan" @onclick="onImageClick" style="width: 100%; height: 100%;"/>
                </MudDropZone>
                <svg class="grid-overlay" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
                    @if (selectedPoints.Count == 1 && tempPoint != null)
                    {
                    <line x1="@selectedPoints[0].X" y1="@selectedPoints[0].Y" x2="@tempPoint.Value.X" y2="@tempPoint.Value.Y" stroke="red" stroke-width="3"/>
                    }
                    @if (selectedPoints.Count == 2)
                    {
                    <line x1="@selectedPoints[0].X" y1="@selectedPoints[0].Y" x2="@selectedPoints[1].X" y2="@selectedPoints[1].Y" stroke="red" stroke-width="3"/>
                    }

                    @if (selectedPoints.Count <= 2)
                    {
                    @foreach (var point in selectedPoints)
                    {
                    <circle cx="@point.X" cy="@point.Y" r="5" fill="blue"></circle>
                    }
                    }
                </svg>
            </div>
        </div>
    </ChildContent>
    <ItemRenderer>
        <MudPaper Height="54px" Width="54px" Class="pa-2 icon-over-image" Elevation="0">
            <MudIcon Icon="@context.Icon"/>
        </MudPaper>
    </ItemRenderer>
</MudDropContainer>

@if (selectedPoints.Count == 2)
{
    <MudItem xs="12">
        <MudTextField @bind-Value="realDistance" Label="Real Distance (meters)"/>
        <MudButton OnClick="calculateScale">Calculate Scale</MudButton>
    </MudItem>
}
<MudItem xs="12">
    <MudText>Scale: @scaleManager.getScale() pixels per meter</MudText>
</MudItem>
<MudToolBar>
    <MudSpacer/>
    <MudButton OnClick="Reset">Reset</MudButton>
</MudToolBar>
using System.Drawing;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using MudBlazor;
using siteplan_Projekt.Data;
using siteplan_Projekt.Models;

namespace siteplan_Projekt.Components.Pages.SitePlan;

/// <summary>
///     This class is responsible for displaying the plan.
///     It initializes and manages the display of uploaded images,
///     and handles rendering behavior to ensure proper initialization.
/// </summary>
public partial class PlanDisplay
{
    #region Protected

    /// <summary>
    ///     Called when the component is initialized.
    ///     Populates the `imageUpload` list with files retrieved from the `fileService`.
    /// </summary>
    protected override void OnInitialized()
    {
        try
        {
            imageUpload = fileService.GetUploadedFiles();
            var dimensions = fileService.GetImageDimensions(imageUpload[0].Name);
            imageWidth = dimensions.width;
            imageHeight = dimensions.height;
        }
        catch (Exception e)
        {
            Console.WriteLine($"Error: {e.Message}");
        }
    }

    /// <summary>
    ///     Asynchronously called after the component has rendered.
    ///     Executes actions that should only occur during the first render.
    ///     If `firstRender` is true and `isFirstRender` is true,
    ///     sets `isFirstRender` to `false` to prevent the first render
    ///     logic from running again.
    /// </summary>
    /// ///
    /// <param name="CamBox">
    ///     True if this is the first time the component is rendered;
    ///     false if it has been rendered before.
    /// </param>
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && isFirstRender) isFirstRender = false;
    }

    #endregion

    #region Private

    private readonly List<PointF> selectedPoints = new();
    private readonly Dictionary<string, object> svgAttributes = new();
    private readonly List<PointF> objectPoints = new();

    /// <summary>
    ///     A list to store any errors encountered during processing.
    /// </summary>
    private readonly List<string> errors = new();


    private readonly bool _applyDropClassesOnDragStarted = false;

    private readonly List<DropItem> _items = new()
    {
        new DropItem { Icon = Icons.Custom.Uncategorized.ChessKing, IsRotten = false, Place = "ObjBox" },
        new DropItem { Icon = Icons.Custom.Uncategorized.Baguette, IsRotten = false, Place = "ObjBox" },
        new DropItem { Icon = Icons.Custom.Uncategorized.Sausage, IsRotten = true, Place = "ObjBox" },
        new DropItem { Icon = Icons.Custom.Uncategorized.WaterMelon, IsRotten = false, Place = "ObjBox" },
        new DropItem { Icon = Icons.Custom.Uncategorized.Fish, IsRotten = true, Place = "ObjBox" }
    };

    private PointF? tempPoint { get; set; }
    private string? imageUrl { get; set; }
    private double realDistance { get; set; }

    [Inject] private ScaleManager scaleManager { get; set; }

    private IBrowserFile? iBrowserFile;
    private int imageWidth;
    private int imageHeight;

    /// <summary>
    ///     Indicates whether this is the first time the component is rendering.
    ///     Initially set to true, it will be set to false after the first render.
    /// </summary>
    private bool isFirstRender = true;

    /// <summary>
    ///     A list of image upload services used to manage uploaded images.
    /// </summary>
    private List<UploadedImageService> imageUpload = new();

    private async Task onImageClick(MouseEventArgs e)
    {
        try
        {
            if (selectedPoints.Count < 2)
            {
                selectedPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY));
            }
            else
            {
                selectedPoints.RemoveAt(0);
                selectedPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY));
            }

            if (selectedPoints.Count == 2)
            {
                // Calculate the pixel distance between the two points
                var pixelDistance = calculatePixelDistance(selectedPoints[0], selectedPoints[1]);
                // Output the points for debugging purposes
                await JsRuntime.InvokeVoidAsync("console.log",
                    $"Point 1: {selectedPoints[0]}, Point 2: {selectedPoints[1]}, Distance: {pixelDistance}px");
            }

            objectPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY));
            StateHasChanged();
        }
        catch (Exception ex)
        {
            await JsRuntime.InvokeVoidAsync("console.error", $"Error: {ex.Message}");
        }
    }

    private double calculatePixelDistance(PointF point1, PointF point2)
    {
        return Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2));
    }

    private void calculateScale()
    {
        if (selectedPoints.Count == 2 && realDistance > 0)
        {
            var pixelDistance = calculatePixelDistance(selectedPoints[0], selectedPoints[1]);
            scaleManager.scale = pixelDistance / realDistance;
        }
    }

    private async Task onFileSelected(IBrowserFile file)
    {
        iBrowserFile = file;
        var buffer = new byte[iBrowserFile.Size];
        await iBrowserFile.OpenReadStream().ReadAsync(buffer);

        // Convert the uploaded image to a base64 string
        imageUrl = $"data:image/png;base64,{Convert.ToBase64String(buffer)}";
    }

    private void OnMouseMove(MouseEventArgs e)
    {
        if (selectedPoints.Count == 1)
        {
            tempPoint = new PointF((float)e.OffsetX, (float)e.OffsetY);
            StateHasChanged();
        }
    }

    private void Reset()
    {
        foreach (var item in _items)
        {
            item.Place = "ObjBox";
            item.IsPicked = false;
        }
    }

    private void ItemUpdated(MudItemDropInfo<DropItem> dropItem)
    {
        dropItem.Item.IsPicked = true;
        dropItem.Item.Place = dropItem.DropzoneIdentifier;
    }

    #endregion
}

public class DropItem
{
    #region Public

    public string Icon { get; init; }
    public bool IsRotten { get; set; }
    public bool IsPicked { get; set; }
    public string Place { get; set; }

    #endregion
}

Questions

  1. Why are the objects appearing underneath the image rather than directly on it?

  2. How can I ensure that the objects are correctly positioned on the image?

I would greatly appreciate any help or guidance on how to resolve this issue. Thanks in advance!

Upvotes: 0

Views: 49

Answers (0)

Related Questions