Custom icons do not shown anymore / custom icons upload fails

Please use template to report bugs and problems. Post here your questions when not sure where else to post
Only for bugs in the Domoticz application! other problems go in different subforums!

Moderators: leecollings, remb0

Forum rules
Before posting here, make sure you are on the latest Beta or Stable version.
If you have problems related to the web gui, clear your browser cache + appcache first.

Use the following template when posting here:

Version: xxxx
Platform: xxxx
Plugin/Hardware: xxxx
Description:
.....

If you are having problems with scripts/blockly, always post the script (in a spoiler or code tag) or screenshots of your blockly

If you are replying, please do not quote images/code from the first post

Please mark your topic as Solved when the problem is solved.
User avatar
frank666
Posts: 21
Joined: Monday 07 December 2020 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: San Marino

Re: Custom icons do not shown anymore / custom icons upload fails

Post by frank666 »

Same problem domoticz2025.1 (Build 16675) in container, I tried to import icons created with https://domoticz- icon.aurelien-ve.fr but it gives me error :
error in loading the icon set: icon file: tower.png is to small or issue with extraction.
Domoticz running on Docker,Orange Pi Zero Plus
User avatar
gizmocuz
Posts: 2536
Joined: Thursday 11 July 2013 18:59
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Top of the world
Contact:

Re: Custom icons do not shown anymore / custom icons upload fails

Post by gizmocuz »

RonkA wrote: Tuesday 10 June 2025 13:30 The icon in question is a custom icon i created called 'domoticz_custom_icon_Inverter.zip' ,a picture of my Solar-Edge inverter, but the icon that's being shown is not mine anymore. It looks like some sort of Victron gizmo..
Ahh, sorry about that, but I added an 'Inverter' icon a long time ago... I think your Icons had the same name (Inveter, Inverter48_On/Off)
It's an Inverter, does it really matter what brand it is? You got a much better one now :mrgreen:

All custom icons are stored inside the database.

They are reloaded/stored on disk at Domoticz startup

It will check if the file exists, and if it does, it will not overwrite it

It will probably also find that the file exists when it is 0 bytes

I have changed this in beta xx, it will not try to overwrite 0 length files

When uploading a new custom image

But this is not an error, but for you an unfortunate event... You can create a new custom icon with the names SolarEdge
Quality outlives Quantity!
User avatar
gizmocuz
Posts: 2536
Joined: Thursday 11 July 2013 18:59
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Top of the world
Contact:

Re: Custom icons do not shown anymore / custom icons upload fails

Post by gizmocuz »

frank666 wrote: Tuesday 10 June 2025 15:25 Same problem domoticz2025.1 (Build 16675) in container, I tried to import icons created with https://domoticz- icon.aurelien-ve.fr but it gives me error :
error in loading the icon set: icon file: tower.png is to small or issue with extraction.
That's great, but you really have to attach the zip file with the custom icon so we can have a look at it...

It is always easy to say it's a bug

Try it with this nice flour.... No issues here at all, also running Domoticz inside a docker compose container
flour.zip
(6.66 KiB) Downloaded 140 times
Quality outlives Quantity!
User avatar
frank666
Posts: 21
Joined: Monday 07 December 2020 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: San Marino

Re: Custom icons do not shown anymore / custom icons upload fails

Post by frank666 »

gizmo thanks for the example zip, I have now succeeded; the problem is https://domoticz- icon.aurelien-ve.fr there seems to be some problem as it creates a file in the zip with the extension 0 .
I enclose the files the first constructed manually
towerpower.zip
(8.45 KiB) Downloaded 119 times

the second built via the site
domoticz_custom_icon_towerpowerfailed.zip
(7.66 KiB) Downloaded 118 times
result :
image_2025-06-11_163841412.png
image_2025-06-11_163841412.png (62.68 KiB) Viewed 325 times
👍👍👍
Domoticz running on Docker,Orange Pi Zero Plus
User avatar
RonkA
Posts: 115
Joined: Tuesday 14 June 2022 12:57
Target OS: NAS (Synology & others)
Domoticz version: 2025.1
Location: Harlingen
Contact:

Re: Custom icons do not shown anymore / custom icons upload fails

Post by RonkA »

Here is a python-script that generates the right icons from the generated ones by domoticz- icon.aurelien-ve.fr:

Code: Select all

import sys
import os
import zipfile
from PIL import Image
from io import BytesIO

def crop_icon(image_data):
    with Image.open(BytesIO(image_data)) as img:
        if img.size == (50, 50):
            cropped = img.crop((1, 1, 49, 49))  # Remove 1 pixel from each edge
            output = BytesIO()
            cropped.save(output, format="PNG")
            return output.getvalue(), cropped
        return image_data, img.copy()

def resize_to_16x16(image: Image.Image):
    resized = image.resize((16, 16), Image.LANCZOS)
    output = BytesIO()
    resized.save(output, format="PNG")
    return output.getvalue()

def process_zip(zip_path):
    zip_dir = os.path.dirname(zip_path)
    zip_name = os.path.basename(zip_path)
    new_zip_path = os.path.join(zip_dir, f"processed_{zip_name}")

    icons_txt = None
    cropped_icons = {}
    resized_icons = {}

    with zipfile.ZipFile(zip_path, 'r') as zin:
        for item in zin.infolist():
            data = zin.read(item.filename)

            # Keep only icons.txt
            if item.filename.lower() == "icons.txt":
                icons_txt = (item.filename, data)

            # Process only 48_Off and 48_On icons
            elif item.filename.endswith("48_Off.png") or item.filename.endswith("48_On.png"):
                cropped_data, cropped_img = crop_icon(data)
                cropped_icons[item.filename] = cropped_data

                # Create 16x16 icon from *_48_On.png only
                if item.filename.endswith("48_On.png"):
                    base_name = item.filename.replace("48_On", "")
                    resized_data = resize_to_16x16(cropped_img)
                    resized_icons[base_name] = resized_data

    # Create the new ZIP file
    with zipfile.ZipFile(new_zip_path, 'w') as zout:
        if icons_txt:
            zout.writestr(icons_txt[0], icons_txt[1])

        for filename, data in cropped_icons.items():
            zout.writestr(filename, data)

        for filename, data in resized_icons.items():
            zout.writestr(filename, data)

    print(f"Processed ZIP file saved as: {new_zip_path}")

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: python Iconcrop.py <file.zip>")
        sys.exit(1)

    zip_file_path = sys.argv[1]
    if not os.path.isfile(zip_file_path) or not zip_file_path.endswith(".zip"):
        print("Please provide a valid .zip file.")
        sys.exit(1)

    process_zip(zip_file_path)

save code as Iconcrop.py

I copied all custom icon zipfiles from the download-folder from windows into a new folder, also placed the python script and from commandprompt do for example

Code: Select all

C:\Users\Ron\domoticz icons>python Iconcrop.py domoticz_custom_icon_Kliko.zip
the reply should be:

Code: Select all

Nieuw ZIP-file created as: processed_domoticz_custom_icon_Kliko.zip
SolarEdge ModbusTCP - Kaku - Synology NAS - Watermeter - ESPEasy - DS18b20
Work in progress = Life in general..
User avatar
frank666
Posts: 21
Joined: Monday 07 December 2020 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: San Marino

Re: Custom icons do not shown anymore / custom icons upload fails

Post by frank666 »

tnx Ronka 👍

Code: Select all

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Domoticz Icon Generator</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body {
            background: #f3f4f6; /* Soft light gray (grigio tenue) */
        }
        .container {
            transition: transform 0.3s ease-in-out;
        }
        .container:hover {
            transform: scale(1.02);
        }
        .glow {
            box-shadow: 0 0 10px rgba(34, 197, 94, 0.5);
        }
    </style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen">
    <div class="container bg-white p-10 rounded-2xl shadow-md w-full max-w-lg mx-4">
        <h1 class="text-3xl font-extrabold text-center text-indigo-700 mb-6">Domoticz Icon Generator</h1>
        <div class="mb-6">
            <label class="block text-sm font-medium text-gray-900 mb-2">Upload Icon (Any PNG)</label>
            <input type="file" id="iconInput" accept="image/png" class="block w-full text-sm text-gray-900 file:mr-4 file:py-3 file:px-5 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 transition duration-200">
        </div>
        <button id="generateZip" class="glow w-full py-3 px-4 bg-green-500 text-white text-lg font-semibold rounded-lg hover:bg-green-600 focus:ring-4 focus:ring-green-300 transition duration-200">Generate ZIP</button>
        <p id="status" class="mt-4 text-sm text-gray-900 text-center"></p>
    </div>

    <script>
        const iconInput = document.getElementById('iconInput');
        const generateZip = document.getElementById('generateZip');
        const status = document.getElementById('status');

        // Image processing functions
        function resizeTo48x48(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        function cropIcon(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 1, 1, 48, 48, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        function resizeTo16x16(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 16;
            canvas.height = 16;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, 16, 16);
            canvas.toBlob(callback, 'image/png');
        }

        function createOffIcon(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.filter = 'grayscale(100%) opacity(50%)';
            ctx.drawImage(img, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        // ZIP generation
        generateZip.addEventListener('click', async () => {
            if (!iconInput.files[0]) {
                status.textContent = 'Please upload a PNG file.';
                return;
            }

            status.textContent = 'Processing...';
            const zip = new JSZip();

            // Determine icon name
            let iconName = iconInput.files[0].name.replace(/\.[^/.]+$/, '');
            const iconNameCapitalized = iconName.charAt(0).toUpperCase() + iconName.slice(1);

            let img = new Image();
            let source = URL.createObjectURL(iconInput.files[0]);

            img.onload = async () => {
                // Resize input image to 48x48
                const resizedBlob = await new Promise(resolve => resizeTo48x48(img, resolve));
                const resizedImg = new Image();
                resizedImg.src = URL.createObjectURL(resizedBlob);

                resizedImg.onload = async () => {
                    // Crop the "On" icon
                    const croppedOnBlob = await new Promise(resolve => cropIcon(resizedImg, resolve));
                    zip.file(`${iconName}48_On.png`, croppedOnBlob);

                    // Create and crop the "Off" icon
                    const offImg = await new Promise(resolve => {
                        createOffIcon(resizedImg, blob => {
                            const offImg = new Image();
                            offImg.onload = () => resolve(offImg);
                            offImg.src = URL.createObjectURL(blob);
                        });
                    });
                    const croppedOffBlob = await new Promise(resolve => cropIcon(offImg, resolve));
                    zip.file(`${iconName}48_Off.png`, croppedOffBlob);

                    // Resize cropped "On" icon to 16x16
                    const croppedOnImg = new Image();
                    croppedOnImg.src = URL.createObjectURL(croppedOnBlob);
                    croppedOnImg.onload = async () => {
                        const resizedBlob = await new Promise(resolve => resizeTo16x16(croppedOnImg, resolve));
                        zip.file(`${iconName}.png`, resizedBlob);

                        // Add icons.txt with name;Name;Name format
                        zip.file('icons.txt', `${iconName};${iconNameCapitalized};${iconNameCapitalized}`);

                        // Generate and download ZIP
                        const content = await zip.generateAsync({ type: 'blob' });
                        const link = document.createElement('a');
                        link.href = URL.createObjectURL(content);
                        link.download = `${iconName}_icons.zip`;
                        link.click();
                        status.textContent = 'ZIP file downloaded!';
                    };
                };
            };
            img.onerror = () => status.textContent = 'Error loading image.';
            img.src = source;
        });
    </script>
</body>
</html>
image_2025-06-12_165737833.png
image_2025-06-12_165737833.png (17.64 KiB) Viewed 245 times
I made this , it is to be saved in .html and used on a server works well .
Domoticz running on Docker,Orange Pi Zero Plus
User avatar
gizmocuz
Posts: 2536
Joined: Thursday 11 July 2013 18:59
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Top of the world
Contact:

Re: Custom icons do not shown anymore / custom icons upload fails

Post by gizmocuz »

Thanks for the feedback. Maybe you could let the author of this tool know that it sometimes generates icons with 0 byte length
Quality outlives Quantity!
HvdW
Posts: 612
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Custom icons do not shown anymore / custom icons upload fails

Post by HvdW »

Which reminds me to say thank you to the Domoticz developers for the new icons in the latest release.
Bugs bug me.
User avatar
RonkA
Posts: 115
Joined: Tuesday 14 June 2022 12:57
Target OS: NAS (Synology & others)
Domoticz version: 2025.1
Location: Harlingen
Contact:

Re: Custom icons do not shown anymore / custom icons upload fails

Post by RonkA »

Maybe you could let the author of this tool know that it sometimes generates icons with 0 byte length
I couldn't reach him, i don't have twitter/X, his GitHub is 404, and i don't have LinkedIn..
I made this , it is to be saved in .html and used on a server works well
Also Nice, saves a lot of time making new icons!!
SolarEdge ModbusTCP - Kaku - Synology NAS - Watermeter - ESPEasy - DS18b20
Work in progress = Life in general..
Filip
Posts: 110
Joined: Thursday 03 November 2016 10:12
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Custom icons do not shown anymore / custom icons upload fails

Post by Filip »

frank666 wrote: Wednesday 11 June 2025 20:54 tnx Ronka 👍

Code: Select all

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Domoticz Icon Generator</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body {
            background: #f3f4f6; /* Soft light gray (grigio tenue) */
        }
        .container {
            transition: transform 0.3s ease-in-out;
        }
        .container:hover {
            transform: scale(1.02);
        }
        .glow {
            box-shadow: 0 0 10px rgba(34, 197, 94, 0.5);
        }
    </style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen">
    <div class="container bg-white p-10 rounded-2xl shadow-md w-full max-w-lg mx-4">
        <h1 class="text-3xl font-extrabold text-center text-indigo-700 mb-6">Domoticz Icon Generator</h1>
        <div class="mb-6">
            <label class="block text-sm font-medium text-gray-900 mb-2">Upload Icon (Any PNG)</label>
            <input type="file" id="iconInput" accept="image/png" class="block w-full text-sm text-gray-900 file:mr-4 file:py-3 file:px-5 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 transition duration-200">
        </div>
        <button id="generateZip" class="glow w-full py-3 px-4 bg-green-500 text-white text-lg font-semibold rounded-lg hover:bg-green-600 focus:ring-4 focus:ring-green-300 transition duration-200">Generate ZIP</button>
        <p id="status" class="mt-4 text-sm text-gray-900 text-center"></p>
    </div>

    <script>
        const iconInput = document.getElementById('iconInput');
        const generateZip = document.getElementById('generateZip');
        const status = document.getElementById('status');

        // Image processing functions
        function resizeTo48x48(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        function cropIcon(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 1, 1, 48, 48, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        function resizeTo16x16(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 16;
            canvas.height = 16;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, 16, 16);
            canvas.toBlob(callback, 'image/png');
        }

        function createOffIcon(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.filter = 'grayscale(100%) opacity(50%)';
            ctx.drawImage(img, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        // ZIP generation
        generateZip.addEventListener('click', async () => {
            if (!iconInput.files[0]) {
                status.textContent = 'Please upload a PNG file.';
                return;
            }

            status.textContent = 'Processing...';
            const zip = new JSZip();

            // Determine icon name
            let iconName = iconInput.files[0].name.replace(/\.[^/.]+$/, '');
            const iconNameCapitalized = iconName.charAt(0).toUpperCase() + iconName.slice(1);

            let img = new Image();
            let source = URL.createObjectURL(iconInput.files[0]);

            img.onload = async () => {
                // Resize input image to 48x48
                const resizedBlob = await new Promise(resolve => resizeTo48x48(img, resolve));
                const resizedImg = new Image();
                resizedImg.src = URL.createObjectURL(resizedBlob);

                resizedImg.onload = async () => {
                    // Crop the "On" icon
                    const croppedOnBlob = await new Promise(resolve => cropIcon(resizedImg, resolve));
                    zip.file(`${iconName}48_On.png`, croppedOnBlob);

                    // Create and crop the "Off" icon
                    const offImg = await new Promise(resolve => {
                        createOffIcon(resizedImg, blob => {
                            const offImg = new Image();
                            offImg.onload = () => resolve(offImg);
                            offImg.src = URL.createObjectURL(blob);
                        });
                    });
                    const croppedOffBlob = await new Promise(resolve => cropIcon(offImg, resolve));
                    zip.file(`${iconName}48_Off.png`, croppedOffBlob);

                    // Resize cropped "On" icon to 16x16
                    const croppedOnImg = new Image();
                    croppedOnImg.src = URL.createObjectURL(croppedOnBlob);
                    croppedOnImg.onload = async () => {
                        const resizedBlob = await new Promise(resolve => resizeTo16x16(croppedOnImg, resolve));
                        zip.file(`${iconName}.png`, resizedBlob);

                        // Add icons.txt with name;Name;Name format
                        zip.file('icons.txt', `${iconName};${iconNameCapitalized};${iconNameCapitalized}`);

                        // Generate and download ZIP
                        const content = await zip.generateAsync({ type: 'blob' });
                        const link = document.createElement('a');
                        link.href = URL.createObjectURL(content);
                        link.download = `${iconName}_icons.zip`;
                        link.click();
                        status.textContent = 'ZIP file downloaded!';
                    };
                };
            };
            img.onerror = () => status.textContent = 'Error loading image.';
            img.src = source;
        });
    </script>
</body>
</html>
image_2025-06-12_165737833.png
I made this , it is to be saved in .html and used on a server works well .
I like this tool? Just some suggestions: I would prefer the possibility to upload 2 different files; one for ON and one for OFF. Because sometime the OFF icon is not just a "gray" version of the ON one...
And would be good to have it somewhere in the domoticz git...
Thanks!
User avatar
frank666
Posts: 21
Joined: Monday 07 December 2020 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: San Marino

Re: Custom icons do not shown anymore / custom icons upload fails

Post by frank666 »

@Filip I have modified as suggested here :

Code: Select all

<script type="text/javascript">
        var gk_isXlsx = false;
        var gk_xlsxFileLookup = {};
        var gk_fileData = {};
        function filledCell(cell) {
          return cell !== '' && cell != null;
        }
        function loadFileData(filename) {
        if (gk_isXlsx && gk_xlsxFileLookup[filename]) {
            try {
                var workbook = XLSX.read(gk_fileData[filename], { type: 'base64' });
                var firstSheetName = workbook.SheetNames[0];
                var worksheet = workbook.Sheets[firstSheetName];

                // Convert sheet to JSON to filter blank rows
                var jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false, defval: '' });
                // Filter out blank rows (rows where all cells are empty, null, or undefined)
                var filteredData = jsonData.filter(row => row.some(filledCell));

                // Heuristic to find the header row by ignoring rows with fewer filled cells than the next row
                var headerRowIndex = filteredData.findIndex((row, index) =>
                  row.filter(filledCell).length >= filteredData[index + 1]?.filter(filledCell).length
                );
                // Fallback
                if (headerRowIndex === -1 || headerRowIndex > 25) {
                  headerRowIndex = 0;
                }

                // Convert filtered JSON back to CSV
                var csv = XLSX.utils.aoa_to_sheet(filteredData.slice(headerRowIndex)); // Create a new sheet from filtered array of arrays
                csv = XLSX.utils.sheet_to_csv(csv, { header: 1 });
                return csv;
            } catch (e) {
                console.error(e);
                return "";
            }
        }
        return gk_fileData[filename] || "";
        }
        </script><!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Domoticz Icon Generator</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body {
            background: #1f2937; /* Dark gray background */
        }
        .container {
            transition: transform 0.3s ease-in-out;
        }
        .container:hover {
            transform: scale(1.02);
        }
        .glow {
            box-shadow: 0 0 10px rgba(34, 197, 94, 0.5);
        }
    </style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen">
    <div class="container bg-gray-300 p-10 rounded-2xl shadow-md w-full max-w-lg mx-4">
        <h1 class="text-3xl font-extrabold text-center text-indigo-700 mb-6">Domoticz Icon Generator</h1>
        <div class="mb-6">
            <label class="block text-sm font-medium text-gray-900 mb-2">Upload On Icon (PNG)</label>
            <input type="file" id="onIconInput" accept="image/png" class="block w-full text-sm text-gray-900 file:mr-4 file:py-3 file:px-5 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 transition duration-200">
        </div>
        <div class="mb-6">
            <label class="block text-sm font-medium text-gray-900 mb-2">Upload Off Icon (PNG)</label>
            <input type="file" id="offIconInput" accept="image/png" class="block w-full text-sm text-gray-900 file:mr-4 file:py-3 file:px-5 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 transition duration-200">
        </div>
        <button id="generateZip" class="glow w-full py-3 px-4 bg-green-500 text-white text-lg font-semibold rounded-lg hover:bg-green-600 focus:ring-4 focus:ring-green-300 transition duration-200">Generate ZIP</button>
        <p id="status" class="mt-4 text-sm text-gray-900 text-center"></p>
    </div>

    <script>
        const onIconInput = document.getElementById('onIconInput');
        const offIconInput = document.getElementById('offIconInput');
        const generateZip = document.getElementById('generateZip');
        const status = document.getElementById('status');

        // Image processing functions
        function resizeTo48x48(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        function cropIcon(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 48;
            canvas.height = 48;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 1, 1, 48, 48, 0, 0, 48, 48);
            canvas.toBlob(callback, 'image/png');
        }

        function resizeTo16x16(img, callback) {
            const canvas = document.createElement('canvas');
            canvas.width = 16;
            canvas.height = 16;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0, 16, 16);
            canvas.toBlob(callback, 'image/png');
        }

        // ZIP generation
        generateZip.addEventListener('click', async () => {
            if (!onIconInput.files[0] || !offIconInput.files[0]) {
                status.textContent = 'Please upload both On and Off PNG files.';
                return;
            }

            status.textContent = 'Processing...';
            const zip = new JSZip();

            // Determine icon name from On icon
            let iconName = onIconInput.files[0].name.replace(/\.[^/.]+$/, '');
            const iconNameCapitalized = iconName.charAt(0).toUpperCase() + iconName.slice(1);

            // Process On icon
            let onImg = new Image();
            onImg.src = URL.createObjectURL(onIconInput.files[0]);

            onImg.onload = async () => {
                // Resize On image to 48x48
                const resizedOnBlob = await new Promise(resolve => resizeTo48x48(onImg, resolve));
                const resizedOnImg = new Image();
                resizedOnImg.src = URL.createObjectURL(resizedOnBlob);

                resizedOnImg.onload = async () => {
                    // Crop the On icon
                    const croppedOnBlob = await new Promise(resolve => cropIcon(resizedOnImg, resolve));
                    zip.file(`${iconName}48_On.png`, croppedOnBlob);

                    // Resize cropped On icon to 16x16
                    const croppedOnImg = new Image();
                    croppedOnImg.src = URL.createObjectURL(croppedOnBlob);
                    croppedOnImg.onload = async () => {
                        const resized16Blob = await new Promise(resolve => resizeTo16x16(croppedOnImg, resolve));
                        zip.file(`${iconName}.png`, resized16Blob);

                        // Process Off icon
                        let offImg = new Image();
                        offImg.src = URL.createObjectURL(offIconInput.files[0]);

                        offImg.onload = async () => {
                            // Resize Off image to 48x48
                            const resizedOffBlob = await new Promise(resolve => resizeTo48x48(offImg, resolve));
                            const resizedOffImg = new Image();
                            resizedOffImg.src = URL.createObjectURL(resizedOffBlob);

                            resizedOffImg.onload = async () => {
                                // Crop the Off icon
                                const croppedOffBlob = await new Promise(resolve => cropIcon(resizedOffImg, resolve));
                                zip.file(`${iconName}48_Off.png`, croppedOffBlob);

                                // Add icons.txt with name;Name;Name format
                                zip.file('icons.txt', `${iconName};${iconNameCapitalized};${iconNameCapitalized}`);

                                // Generate and download ZIP
                                const content = await zip.generateAsync({ type: 'blob' });
                                const link = document.createElement('a');
                                link.href = URL.createObjectURL(content);
                                link.download = `${iconName}_icons.zip`;
                                link.click();
                                status.textContent = 'ZIP file downloaded!';
                            };
                        };
                        offImg.onerror = () => status.textContent = 'Error loading Off image.';
                    };
                };
            };
            onImg.onerror = () => status.textContent = 'Error loading On image.';
        });
    </script>
</body>
</html>
Domoticz running on Docker,Orange Pi Zero Plus
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest