<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Box Packaging Design Selector</title>
<style>
    body, html {
        margin: 0;
        overflow: hidden;
        height: 100%;
        display: flex;
        flex-direction: column;
        font-family: Arial, sans-serif;
    }
    #toolbar {
        display: flex;
        align-items: center;
        padding: 5px 10px;
        background-color: #333;
        color: #fff;
        flex-wrap: wrap;
    }
    #toolbar button {
        background-color: #555;
        color: #fff;
        border: none;
        padding: 5px 10px;
        margin-right: 10px;
        cursor: pointer;
    }
    #toolbar button:hover {
        background-color: #666;
    }
    #faceList {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        margin-top: 10px;
    }
    .face-item {
        display: flex;
        align-items: center;
        gap: 5px;
        background-color: #444;
        padding: 5px;
        border-radius: 5px;
    }
    .face-label {
        width: 50px;
        color: #fff;
        font-weight: bold;
    }
    .face-item button {
        padding: 3px 5px;
        background-color: #666;
        border: none;
        color: #fff;
        cursor: pointer;
    }
    .face-item button:disabled {
        background-color: #555;
        cursor: not-allowed;
    }
    .face-item button:hover:enabled {
        background-color: #777;
    }
    .face-options {
        display: flex;
        gap: 5px;
        align-items: center;
    }
    .face-options select {
        padding: 2px;
    }
    .face-options label {
        margin-left: 3px;
        color: #fff;
    }
    .face-options input {
        margin-left: 5px;
    }
    #canvasContainer {
        flex: 1;
        position: relative;
        background-color: #222;
    }
    #imageCanvas {
        width: 100%;
        height: 100%;
        background-color: #ccc;
        cursor: crosshair;
    }
    .handle {
        position: absolute;
        width: 10px;
        height: 10px;
        background: red;
        border: 1px solid #fff;
        border-radius: 50%;
        cursor: pointer;
    }
    .selection-label {
        position: absolute;
        background-color: rgba(0, 0, 0, 0.5);
        color: #fff;
        padding: 2px 5px;
        border-radius: 3px;
        pointer-events: none;
        font-weight: bold;
    }
</style>
</head>
<body>

<div id="toolbar">
    <button id="importImageButton">Import Image</button>
    <input type="file" id="imageLoader" accept="image/*" style="display:none"/>
    <button id="importUVButton">Import .uv1 File</button>
    <input type="file" id="uvLoader" accept=".uv1" style="display:none"/>
    <button id="generateTextureButton">Generate Texture</button>
    <div id="faceList">
        <!-- Faces will be dynamically added here -->
    </div>
</div>

<div id="canvasContainer">
    <canvas id="imageCanvas"></canvas>
</div>

<script>
// JavaScript code

// Get references to DOM elements
const importImageButton = document.getElementById('importImageButton');
const imageLoader = document.getElementById('imageLoader');
const importUVButton = document.getElementById('importUVButton');
const uvLoader = document.getElementById('uvLoader');
const generateTextureButton = document.getElementById('generateTextureButton');
const canvas = document.getElementById('imageCanvas');
const faceList = document.getElementById('faceList');
const canvasContainer = document.getElementById('canvasContainer');
const ctx = canvas.getContext('2d');

// Variables for canvas manipulation
let image = new Image();
let imgScale = 1;
let imgPos = { x: 0, y: 0 };
let isPanning = false;
let panStart = { x: 0, y: 0 };
let mouseStart = { x: 0, y: 0 };

// Variables for selection
let faces = ['front', 'back', 'right', 'left', 'top', 'bottom'];
let faceColors = {
    front: 'red',
    back: 'green',
    right: 'blue',
    left: 'yellow',
    top: 'cyan',
    bottom: 'magenta'
};
let selections = {};
let currentFace = null;
let isDrawing = false;
let isEditing = false;
let editHandle = null;
let hoverHandle = null;
let handles = [];
const handleSize = 10;

// Added variables for UV mapping
let uvData = {};
let outputImageSize = { width: 1000, height: 1000 };

// Initialize canvas size
function resizeCanvas() {
    canvas.width = canvasContainer.clientWidth;
    canvas.height = canvasContainer.clientHeight;
    draw();
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();

// Import image button click
importImageButton.addEventListener('click', () => {
    imageLoader.click();
});

// Import UV button click
importUVButton.addEventListener('click', () => {
    uvLoader.click();
});

// Load selected image
imageLoader.addEventListener('change', (e) => {
    const file = e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (event) => {
        image.src = event.target.result;
        image.onload = () => {
            imgScale = 1;
            imgPos = { x: (canvas.width - image.width) / 2, y: (canvas.height - image.height) / 2 };
            draw();
        };
    };
    reader.readAsDataURL(file);
});

// Load UV file
uvLoader.addEventListener('change', (e) => {
    const file = e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (event) => {
        parseUVData(event.target.result);
        alert('UV data loaded successfully!');
    };
    reader.readAsText(file);
});

// Parse UV data
function parseUVData(data) {
    const lines = data.split('\n').map(line => line.trim()).filter(line => line);
    uvData = {};
    lines.forEach((line, index) => {
        const parts = line.split(',');
        if (index === 0 && parts[0] === 'size') {
            outputImageSize = {
                width: parseFloat(parts[1]),
                height: parseFloat(parts[2])
            };
        } else {
            const faceName = parts[0];
            uvData[faceName] = {
                x: parseFloat(parts[1]),
                y: parseFloat(parts[2]),
                width: parseFloat(parts[3]),
                height: parseFloat(parts[4])
            };
        }
    });
}

// Set up faces list
faces.forEach(faceName => {
    // Create face item
    const faceItem = document.createElement('div');
    faceItem.className = 'face-item';

    // Add elements to face item
    const faceLabel = document.createElement('span');
    faceLabel.textContent = faceName;
    faceLabel.className = 'face-label';

    const addButton = document.createElement('button');
    addButton.textContent = 'Add';
    addButton.disabled = false;

    const deleteButton = document.createElement('button');
    deleteButton.textContent = 'Delete';
    deleteButton.disabled = true;

    const editButton = document.createElement('button');
    editButton.textContent = 'Edit';
    editButton.disabled = true;

    // Rotation and flip options
    const faceOptions = document.createElement('div');
    faceOptions.className = 'face-options';

    const rotationSelect = document.createElement('select');
    ['0', '90', '-90', '180'].forEach(angle => {
        const option = document.createElement('option');
        option.value = angle;
        option.textContent = angle + '°';
        rotationSelect.appendChild(option);
    });

    const flipHCheckbox = document.createElement('input');
    flipHCheckbox.type = 'checkbox';
    flipHCheckbox.id = 'flipH_' + faceName;
    const flipHLabel = document.createElement('label');
    flipHLabel.textContent = 'Flip H';
    flipHLabel.style.color = '#fff';
    flipHLabel.setAttribute('for', 'flipH_' + faceName);

    const flipVCheckbox = document.createElement('input');
    flipVCheckbox.type = 'checkbox';
    flipVCheckbox.id = 'flipV_' + faceName;
    const flipVLabel = document.createElement('label');
    flipVLabel.textContent = 'Flip V';
    flipVLabel.style.color = '#fff';
    flipVLabel.setAttribute('for', 'flipV_' + faceName);

    faceOptions.appendChild(rotationSelect);
    faceOptions.appendChild(flipHCheckbox);
    faceOptions.appendChild(flipHLabel);
    faceOptions.appendChild(flipVCheckbox);
    faceOptions.appendChild(flipVLabel);

    faceItem.appendChild(faceLabel);
    faceItem.appendChild(addButton);
    faceItem.appendChild(deleteButton);
    faceItem.appendChild(editButton);
    faceItem.appendChild(faceOptions);
    faceList.appendChild(faceItem);

    // Event listeners for buttons
    addButton.addEventListener('click', () => {
        currentFace = faceName;
        isDrawing = true;
        addButton.disabled = true;
        deleteButton.disabled = false;
        editButton.disabled = false;
        selections[faceName] = selections[faceName] || {};
        selections[faceName].rotation = rotationSelect.value;
        selections[faceName].flipH = flipHCheckbox.checked;
        selections[faceName].flipV = flipVCheckbox.checked;
    });

    deleteButton.addEventListener('click', () => {
        delete selections[faceName];
        addButton.disabled = false;
        deleteButton.disabled = true;
        editButton.disabled = true;
        draw();
    });

    editButton.addEventListener('click', () => {
        currentFace = faceName;
        isEditing = !isEditing;
        editButton.textContent = isEditing ? 'Done' : 'Edit';
        draw();
    });

    // Event listeners for rotation and flip options
    rotationSelect.addEventListener('change', () => {
        if (selections[faceName]) {
            selections[faceName].rotation = rotationSelect.value;
            draw();
        }
    });

    flipHCheckbox.addEventListener('change', () => {
        if (selections[faceName]) {
            selections[faceName].flipH = flipHCheckbox.checked;
            draw();
        }
    });

    flipVCheckbox.addEventListener('change', () => {
        if (selections[faceName]) {
            selections[faceName].flipV = flipVCheckbox.checked;
            draw();
        }
    });
});

// Canvas event handlers
canvas.addEventListener('mousedown', (e) => {
    const rect = canvas.getBoundingClientRect();
    const mouseX = (e.clientX - rect.left);
    const mouseY = (e.clientY - rect.top);

    const imgMouseX = (mouseX - imgPos.x) / imgScale;
    const imgMouseY = (mouseY - imgPos.y) / imgScale;

    if (e.button === 1 || (e.button === 0 && e.ctrlKey)) {
        // Middle mouse button or Ctrl+Left click for panning
        isPanning = true;
        panStart = { x: imgPos.x, y: imgPos.y };
        mouseStart = { x: e.clientX, y: e.clientY };
    } else if (isDrawing && currentFace) {
        // Start drawing selection rectangle
        selections[currentFace] = selections[currentFace] || {};
        selections[currentFace].x = imgMouseX;
        selections[currentFace].y = imgMouseY;
        selections[currentFace].width = 0;
        selections[currentFace].height = 0;
        isDrawing = true;
    } else if (isEditing && editHandle && selections[currentFace]) {
        // Editing handle already selected
    } else if (isEditing && selections[currentFace]) {
        // Check if clicked on handle
        const handle = getHandleUnderMouse(mouseX, mouseY);
        if (handle) {
            editHandle = handle;
        } else if (isPointInSelection(mouseX, mouseY, selections[currentFace])) {
            // Start moving the selection
            editHandle = 'move';
            panStart = { x: selections[currentFace].x, y: selections[currentFace].y };
            mouseStart = { x: imgMouseX, y: imgMouseY };
        }
    }
});

canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    const mouseX = (e.clientX - rect.left);
    const mouseY = (e.clientY - rect.top);

    const imgMouseX = (mouseX - imgPos.x) / imgScale;
    const imgMouseY = (mouseY - imgPos.y) / imgScale;

    if (isPanning) {
        // Panning
        imgPos.x = panStart.x + (e.clientX - mouseStart.x);
        imgPos.y = panStart.y + (e.clientY - mouseStart.y);
        draw();
    } else if (isDrawing && selections[currentFace]) {
        // Update selection rectangle size
        const sel = selections[currentFace];
        sel.width = imgMouseX - sel.x;
        sel.height = imgMouseY - sel.y;
        draw();
    } else if (isEditing && editHandle && selections[currentFace]) {
        // Edit selection rectangle
        const sel = selections[currentFace];
        if (editHandle === 'move') {
            sel.x = panStart.x + (imgMouseX - mouseStart.x);
            sel.y = panStart.y + (imgMouseY - mouseStart.y);
        } else {
            // Adjust selection based on handle
            adjustSelection(sel, editHandle, imgMouseX, imgMouseY);
        }
        draw();
    } else {
        // Update hover state for handles
        hoverHandle = getHandleUnderMouse(mouseX, mouseY);
        canvas.style.cursor = hoverHandle ? 'pointer' : 'default';
    }
});

canvas.addEventListener('mouseup', (e) => {
    isPanning = false;
    isDrawing = false;
    editHandle = null;
});

canvas.addEventListener('wheel', (e) => {
    // Zooming
    e.preventDefault();
    const delta = e.deltaY < 0 ? 1.1 : 0.9;
    const rect = canvas.getBoundingClientRect();
    const mouseX = (e.clientX - rect.left);
    const mouseY = (e.clientY - rect.top);

    const imgMouseX = (mouseX - imgPos.x) / imgScale;
    const imgMouseY = (mouseY - imgPos.y) / imgScale;

    imgScale *= delta;
    imgPos.x = mouseX - imgMouseX * imgScale;
    imgPos.y = mouseY - imgMouseY * imgScale;

    draw();
});

canvas.addEventListener('contextmenu', (e) => {
    e.preventDefault();
});

function draw() {
    // Clear canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Draw image
    if (image.src) {
        ctx.drawImage(image, imgPos.x, imgPos.y, image.width * imgScale, image.height * imgScale);
    }

    // Draw selections
    for (const faceName in selections) {
        const sel = selections[faceName];
        drawSelection(sel, faceName, faceName === currentFace && isEditing);
    }
}

function drawSelection(sel, label, showHandles) {
    ctx.save();
    ctx.strokeStyle = faceColors[label] || 'red';
    ctx.lineWidth = 2;
    ctx.strokeRect(
        sel.x * imgScale + imgPos.x,
        sel.y * imgScale + imgPos.y,
        sel.width * imgScale,
        sel.height * imgScale
    );

    // Draw label
    ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
    ctx.font = 'bold 14px Arial';
    const textWidth = ctx.measureText(label).width;
    ctx.fillRect(
        (sel.x + sel.width / 2) * imgScale + imgPos.x - textWidth / 2 - 5,
        (sel.y + sel.height / 2) * imgScale + imgPos.y - 15,
        textWidth + 10,
        20
    );
    ctx.fillStyle = 'white';
    ctx.fillText(
        label,
        (sel.x + sel.width / 2) * imgScale + imgPos.x - textWidth / 2,
        (sel.y + sel.height / 2) * imgScale + imgPos.y
    );

    // Draw handles if editing
    if (showHandles) {
        updateHandles(sel);
        handles.forEach(handle => {
            ctx.fillStyle = 'red';
            ctx.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
        });
    }

    ctx.restore();
}

function updateHandles(sel) {
    handles = [];
    const points = [
        { x: sel.x, y: sel.y }, // Top-left
        { x: sel.x + sel.width, y: sel.y }, // Top-right
        { x: sel.x, y: sel.y + sel.height }, // Bottom-left
        { x: sel.x + sel.width, y: sel.y + sel.height } // Bottom-right
    ];
    points.forEach((pt, idx) => {
        handles.push({
            x: pt.x * imgScale + imgPos.x,
            y: pt.y * imgScale + imgPos.y,
            pos: ['nw', 'ne', 'sw', 'se'][idx]
        });
    });
}

function getHandleUnderMouse(mouseX, mouseY) {
    return handles.find(handle => {
        return (
            mouseX >= handle.x - handleSize / 2 &&
            mouseX <= handle.x + handleSize / 2 &&
            mouseY >= handle.y - handleSize / 2 &&
            mouseY <= handle.y + handleSize / 2
        );
    })?.pos;
}

function isPointInSelection(mouseX, mouseY, sel) {
    const x = sel.x * imgScale + imgPos.x;
    const y = sel.y * imgScale + imgPos.y;
    const width = sel.width * imgScale;
    const height = sel.height * imgScale;
    return (
        mouseX >= x && mouseX <= x + width &&
        mouseY >= y && mouseY <= y + height
    );
}

function adjustSelection(sel, handlePos, imgMouseX, imgMouseY) {
    switch (handlePos) {
        case 'nw':
            sel.width += sel.x - imgMouseX;
            sel.height += sel.y - imgMouseY;
            sel.x = imgMouseX;
            sel.y = imgMouseY;
            break;
        case 'ne':
            sel.width = imgMouseX - sel.x;
            sel.height += sel.y - imgMouseY;
            sel.y = imgMouseY;
            break;
        case 'sw':
            sel.width += sel.x - imgMouseX;
            sel.x = imgMouseX;
            sel.height = imgMouseY - sel.y;
            break;
        case 'se':
            sel.width = imgMouseX - sel.x;
            sel.height = imgMouseY - sel.y;
            break;
    }
}

generateTextureButton.addEventListener('click', () => {
    if (!image.src) {
        alert('Please load an image first.');
        return;
    }
    if (!uvData || Object.keys(uvData).length === 0) {
        alert('Please load a .uv1 file first.');
        return;
    }
    generateFinalTexture();
});

function generateFinalTexture() {
    // Create off-screen canvas
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = outputImageSize.width;
    outputCanvas.height = outputImageSize.height;
    const outputCtx = outputCanvas.getContext('2d');

    for (const faceName in uvData) {
        const uv = uvData[faceName];
        const sel = selections[faceName];
        if (!sel) {
            continue; // Skip if no selection is made for this face
        }

        // Get the selected area from the original image
        const sx = sel.x;
        const sy = sel.y;
        const sw = sel.width;
        const sh = sel.height;

        // Create an off-screen canvas to apply transformations
        const faceCanvas = document.createElement('canvas');
        faceCanvas.width = sw;
        faceCanvas.height = sh;
        const faceCtx = faceCanvas.getContext('2d');

        // Draw the selected area onto the face canvas
        faceCtx.drawImage(
            image,
            sx,
            sy,
            sw,
            sh,
            0,
            0,
            sw,
            sh
        );

        // Apply flips
        if (sel.flipH || sel.flipV) {
            faceCtx.translate(sel.flipH ? sw : 0, sel.flipV ? sh : 0);
            faceCtx.scale(sel.flipH ? -1 : 1, sel.flipV ? -1 : 1);
            faceCtx.drawImage(faceCanvas, 0, 0);
        }

        // Apply rotation
        let rotationAngle = parseInt(sel.rotation);
        if (rotationAngle !== 0) {
            const rotatedCanvas = document.createElement('canvas');
            if (Math.abs(rotationAngle) === 90) {
                rotatedCanvas.width = sh;
                rotatedCanvas.height = sw;
            } else {
                rotatedCanvas.width = sw;
                rotatedCanvas.height = sh;
            }
            const rotatedCtx = rotatedCanvas.getContext('2d');
            rotatedCtx.translate(rotatedCanvas.width / 2, rotatedCanvas.height / 2);
            rotatedCtx.rotate((rotationAngle * Math.PI) / 180);
            rotatedCtx.drawImage(faceCanvas, -sw / 2, -sh / 2);
            faceCanvas.width = rotatedCanvas.width;
            faceCanvas.height = rotatedCanvas.height;
            faceCtx.clearRect(0, 0, faceCanvas.width, faceCanvas.height);
            faceCtx.drawImage(rotatedCanvas, 0, 0);
        }

        // Draw the transformed face onto the output canvas at the UV-specified position
        outputCtx.drawImage(
            faceCanvas,
            0,
            0,
            faceCanvas.width,
            faceCanvas.height,
            uv.x,
            uv.y,
            uv.width,
            uv.height
        );
    }

    // Open a new window to display the final texture
    const dataURL = outputCanvas.toDataURL('image/png');
    const newWindow = window.open();
    if (newWindow) {
        newWindow.document.write(`<img src="${dataURL}" alt="Final Texture"/>`);
    }
}
</script>

</body>
</html>


------------------
add middle edge handles also to modify the face rectangle
add a visual (maybe an arow with deferent left and right sides) identifier on the top left corner inside the face rectangle so the user can tell how the face orientation are
add the option to chouse the result texture background color so the user can write a hex color code or pic the color from the image directly
add a choice to save the image as png and jpg