|
Поговорим о...
|
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { width: 100%; margin-bottom: 20px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; width: 350px; }
.group-columns-container { display: flex; flex-wrap: wrap; gap: 15px; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Создаем все колонки для отображения const allColumns = []; // Добавляем колонки групп Object.keys(groups).sort().forEach(groupName => { const groupIcons = groups[groupName]; const columns = splitIconsIntoColumns(groupIcons); columns.forEach((columnIcons, index) => { const column = createIconColumn(columnIcons); // Для первой колонки группы добавляем заголовок if (index === 0) { const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; allColumns.push({ element: header, isHeader: true }); } allColumns.push({ element: column, isHeader: false }); }); }); // Добавляем прочие иконки if (noGroup.length > 0 && Object.keys(groups).length > 0) { const columns = splitIconsIntoColumns(noGroup); // Добавляем заголовок для прочих иконок const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; allColumns.push({ element: header, isHeader: true }); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allColumns.push({ element: column, isHeader: false }); }); } else if (noGroup.length > 0) { // Если групп нет, просто отображаем прочие иконки const columns = splitIconsIntoColumns(noGroup); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allColumns.push({ element: column, isHeader: false }); }); } // Добавляем все элементы в контейнер allColumns.forEach(item => { container.appendChild(item.element); }); }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { display: contents; /* Убираем обертку группы, чтобы элементы располагались свободно */ }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; width: 350px; height: fit-content; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Создаем все элементы для отображения const allElements = []; // Добавляем группы Object.keys(groups).sort().forEach(groupName => { const groupIcons = groups[groupName]; const columns = splitIconsIntoColumns(groupIcons); // Добавляем заголовок группы const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; allElements.push(header); // Добавляем колонки группы columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); }); // Добавляем прочие иконки if (noGroup.length > 0) { const columns = splitIconsIntoColumns(noGroup); // Добавляем заголовок для прочих иконок только если есть другие группы if (Object.keys(groups).length > 0) { const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; allElements.push(header); } // Добавляем колонки прочих иконок columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); } // Добавляем все элементы в контейнер allElements.forEach(element => { container.appendChild(element); }); }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-block { display: contents; /* Разрешаем элементам группы участвовать в общем потоке */ }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; width: 350px; flex-shrink: 0; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; flex-shrink: 0; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIz IDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Создаем все элементы для отображения const allElements = []; // Добавляем группы Object.keys(groups).sort().forEach(groupName => { const groupIcons = groups[groupName]; const columns = splitIconsIntoColumns(groupIcons); // Создаем контейнер для группы const groupBlock = document.createElement('div'); groupBlock.className = 'group-block'; // Добавляем заголовок группы const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; allElements.push(header); // Добавляем колонки группы columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); }); // Добавляем прочие иконки if (noGroup.length > 0) { const columns = splitIconsIntoColumns(noGroup); // Если есть другие группы, добавляем заголовок для прочих иконок if (Object.keys(groups).length > 0) { const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; allElements.push(header); } // Добавляем колонки прочих иконок columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); } // Добавляем все элементы в контейнер allElements.forEach(element => { container.appendChild(element); }); }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-block { display: contents; /* Разрешаем элементам группы участвовать в общем потоке */ }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; width: 350px; flex-shrink: 0; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; flex-shrink: 0; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIz IDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Создаем все элементы для отображения const allElements = []; // Добавляем группы Object.keys(groups).sort().forEach(groupName => { const groupIcons = groups[groupName]; const columns = splitIconsIntoColumns(groupIcons); // Создаем контейнер для группы const groupBlock = document.createElement('div'); groupBlock.className = 'group-block'; // Добавляем заголовок группы const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; allElements.push(header); // Добавляем колонки группы columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); }); // Добавляем прочие иконки if (noGroup.length > 0) { const columns = splitIconsIntoColumns(noGroup); // Если есть другие группы, добавляем заголовок для прочих иконок if (Object.keys(groups).length > 0) { const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; allElements.push(header); } // Добавляем колонки прочих иконок columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); } // Добавляем все элементы в контейнер allElements.forEach(element => { container.appendChild(element); }); }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-block { display: contents; /* Элементы группы участвуют в общем потоке */ }
.group-header-container { width: 100%; flex-basis: 100%; margin-bottom: 8px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; font-weight: bold; color: #495057; font-size: 13px; display: inline-block; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; flex-shrink: 0; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getImageData('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Создаем все элементы для отображения const allElements = []; // Добавляем группы Object.keys(groups).sort().forEach(groupName => { const groupIcons = groups[groupName]; const columns = splitIconsIntoColumns(groupIcons); // Добавляем заголовок группы (занимает всю ширину) const headerContainer = document.createElement('div'); headerContainer.className = 'group-header-container'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; headerContainer.appendChild(header); allElements.push(headerContainer); // Добавляем колонки группы columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); }); // Добавляем прочие иконки if (noGroup.length > 0) { const columns = splitIconsIntoColumns(noGroup); // Если есть другие группы, добавляем заголовок для прочих иконок if (Object.keys(groups).length > 0) { const headerContainer = document.createElement('div'); headerContainer.className = 'group-header-container'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; headerContainer.appendChild(header); allElements.push(headerContainer); } // Добавляем колонки прочих иконок columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); allElements.push(column); }); } // Добавляем все элементы в контейнер allElements.forEach(element => { container.appendChild(element); }); }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.groups-container { display: flex; flex-direction: column; gap: 20px; }
.group-section { display: flex; flex-direction: column; gap: 8px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; font-weight: bold; color: #495057; font-size: 13px; width: fit-content; }
.columns-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; }
.no-results { text-align: center; padding: 40px; color: #666; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; const isChromaKey = r === 255 && g === 0 && b === 255; const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; } } ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => resolve({ element: processedImg, processed: true }); processedImg.src = canvas.toDataURL('image/png'); } catch (error) { resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { if (src.toLowerCase().endsWith('.bmp')) { try { const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { resolve({ element: img, processed: false }); } } else { resolve({ element: img, processed: false }); } }; img.onerror = function() { const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); const columns = splitIconsIntoColumns(sortedIcons); const columnsContainer = document.createElement('div'); columnsContainer.className = 'columns-container'; columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); container.appendChild(columnsContainer); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; const { groups, noGroup } = groupIcons(filteredIcons); const groupsContainer = document.createElement('div'); groupsContainer.className = 'groups-container'; // Отображаем группы Object.keys(groups).sort().forEach(groupName => { const groupSection = document.createElement('div'); groupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; groupSection.appendChild(header); const columnsContainer = document.createElement('div'); columnsContainer.className = 'columns-container'; const columns = splitIconsIntoColumns(groups[groupName]); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); groupSection.appendChild(columnsContainer); groupsContainer.appendChild(groupSection); }); // Отображаем прочие иконки if (noGroup.length > 0) { const groupSection = document.createElement('div'); groupSection.className = 'group-section'; if (Object.keys(groups).length > 0) { const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; groupSection.appendChild(header); } const columnsContainer = document.createElement('div'); columnsContainer.className = 'columns-container'; const columns = splitIconsIntoColumns(noGroup); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); groupSection.appendChild(columnsContainer); groupsContainer.appendChild(groupSection); } container.appendChild(groupsContainer); }
// Функция для фильтрации иконок function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Удаление фона изображений</title> <style> body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background-color: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { text-align: center; color: #333; margin-bottom: 20px; } .upload-area { border: 2px dashed #ccc; border-radius: 8px; padding: 30px; text-align: center; margin-bottom: 20px; cursor: pointer; } .upload-area p { margin: 0; color: #666; } #fileInput { display: none; } .preview-container { display: flex; justify-content: space-around; margin-top: 20px; } .preview-box { text-align: center; } .preview-box h3 { margin-bottom: 10px; color: #444; } .preview-image { border: 1px solid #ddd; border-radius: 4px; background-color: #f9f9f9; padding: 10px; width: 160px; height: 160px; image-rendering: pixelated; object-fit: contain; } .transparent-bg { background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%), linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; } .error { color: #d32f2f; text-align: center; margin-top: 10px; padding: 10px; background-color: #ffebee; border-radius: 4px; } .info { color: #666; font-size: 14px; margin-top: 20px; text-align: center; } .file-info { background-color: #e8f5e9; padding: 10px; border-radius: 4px; margin-top: 10px; text-align: center; } .warning { color: #ff9800; text-align: center; margin-top: 10px; padding: 10px; background-color: #fff3e0; border-radius: 4px; } .debug-info { background-color: #e3f2fd; padding: 10px; border-radius: 4px; margin-top: 10px; font-size: 12px; font-family: monospace; max-height: 100px; overflow-y: auto; } .controls { text-align: center; margin: 20px 0; } .download-btn { background-color: #4CAF50; color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } .download-btn:hover { background-color: #45a049; } .download-btn:disabled { background-color: #cccccc; cursor: not-allowed; } .filename-info { margin-top: 10px; font-size: 14px; color: #666; } </style> </head> <body> <div class="container"> <h1>Удаление розового фона с изображений</h1> <div class="upload-area" id="uploadArea"> <p>Нажмите здесь или перетащите файл изображения</p> <input type="file" id="fileInput" accept=".bmp,.png,.jpg,.jpeg,.gif,.ico"> </div> <div id="fileInfo" class="file-info" style="display: none;"></div> <div id="debugInfo" class="debug-info" style="display: none;"></div> <div class="preview-container" id="previewContainer" style="display: none;"> <div class="preview-box"> <h3>Исходное изображение</h3> <img id="originalImage" class="preview-image"> </div> <div class="preview-box"> <h3>Без фона</h3> <canvas id="processedCanvas" class="preview-image transparent-bg"></canvas> </div> </div> <div class="controls" id="controls" style="display: none;"> <button id="downloadBtn" class="download-btn"> 💾 Скачать без фона </button> <div id="filenameInfo" class="filename-info"></div> </div> <div id="errorMessage" class="error" style="display: none;"></div> <div id="warningMessage" class="warning" style="display: none;"></div> <div class="info"> <p>Ярко-розовый фон (RGB: 255, 0, 255) будет заменён на прозрачный.</p> <p>Поддерживаются BMP, PNG, JPG, GIF и другие форматы.</p> </div> </div>
<script> const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const previewContainer = document.getElementById('previewContainer'); const originalImage = document.getElementById('originalImage'); const processedCanvas = document.getElementById('processedCanvas'); const errorMessage = document.getElementById('errorMessage'); const warningMessage = document.getElementById('warningMessage'); const fileInfo = document.getElementById('fileInfo'); const debugInfo = document.getElementById('debugInfo'); const controls = document.getElementById('controls'); const downloadBtn = document.getElementById('downloadBtn'); const filenameInfo = document.getElementById('filenameInfo'); let currentFile = null; let processedImageData = null; // Обработчик клика по области загрузки uploadArea.addEventListener('click', () => { fileInput.click(); }); // Обработчик перетаскивания файлов uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); if (e.dataTransfer.files.length) { handleFile(e.dataTransfer.files[0]); } }); // Обработчик выбора файла через input fileInput.addEventListener('change', (e) => { if (e.target.files.length) { handleFile(e.target.files[0]); } }); // Обработчик кнопки скачивания downloadBtn.addEventListener('click', downloadProcessedImage); function handleFile(file) { // Скрываем сообщения errorMessage.style.display = 'none'; warningMessage.style.display = 'none'; debugInfo.style.display = 'none'; controls.style.display = 'none'; currentFile = file; // Показываем информацию о файле fileInfo.textContent = `Файл: ${file.name} (${formatFileSize(file.size)})`; fileInfo.style.display = 'block'; const reader = new FileReader(); reader.onload = function(e) { try { // Сначала пробуем открыть как обычное изображение openAsStandardImage(file, e.target.result); } catch (error) { console.warn('Не удалось открыть как стандартное изображение:', error); // Если не получилось, пробуем как BMP try { openAsBMP(file, e.target.result); } catch (bmpError) { showError('Не удалось открыть файл: ' + bmpError.message); console.error('Ошибка открытия BMP:', bmpError); } } }; reader.onerror = function() { showError('Ошибка при чтении файла'); }; reader.readAsArrayBuffer(file); } function openAsStandardImage(file, arrayBuffer) { // Создаем Blob и URL для стандартного отображения const blob = new Blob([arrayBuffer], { type: file.type }); const url = URL.createObjectURL(blob); originalImage.onload = function() { URL.revokeObjectURL(url); // Отображаем исходное изображение originalImage.style.display = 'block'; // Создаем canvas для обработки const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); tempCanvas.width = originalImage.naturalWidth; tempCanvas.height = originalImage.naturalHeight; tempCtx.drawImage(originalImage, 0, 0); const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height); // Обрабатываем изображение processedImageData = removePinkBackgroundFromImageData(imageData); displayProcessedImage(processedImageData); // Показываем контейнер с превью previewContainer.style.display = 'flex'; controls.style.display = 'block'; // Показываем информацию о имени файла для скачивания const exportFilename = getExportFilename(currentFile.name); filenameInfo.textContent = `Будет сохранено как: ${exportFilename}`; // Добавляем отладочную информацию debugInfo.innerHTML = ` Формат: Стандартное изображение<br> Размер: ${tempCanvas.width}×${tempCanvas.height}<br> Пикселей: ${imageData.data.length / 4} `; debugInfo.style.display = 'block'; }; originalImage.onerror = function() { URL.revokeObjectURL(url); throw new Error('Не удалось загрузить изображение'); }; originalImage.src = url; } function openAsBMP(file, arrayBuffer) { try { const bmpData = parseBMP(arrayBuffer); // Отображаем исходное BMP displayBMPImage(originalImage, bmpData); originalImage.style.display = 'block'; // Обрабатываем BMP processedImageData = removeStrictPinkBackground(bmpData); displayProcessedImage(processedImageData); // Показываем контейнер с превью previewContainer.style.display = 'flex'; controls.style.display = 'block'; // Показываем информацию о имени файла для скачивания const exportFilename = getExportFilename(currentFile.name); filenameInfo.textContent = `Будет сохранено как: ${exportFilename}`; // Добавляем отладочную информацию debugInfo.innerHTML = ` Формат: BMP<br> Размер: ${bmpData.width}×${bmpData.height}<br> Битность: ${bmpData.bitsPerPixel} бит<br> Пикселей: ${bmpData.pixels.length} `; debugInfo.style.display = 'block'; } catch (error) { throw new Error('Неверный формат BMP: ' + error.message); } } function parseBMP(arrayBuffer) { const dataView = new DataView(arrayBuffer); // Проверяем сигнатуру BMP if (dataView.getUint16(0, true) !== 0x4D42) { throw new Error('Неверная сигнатура BMP'); } const pixelDataOffset = dataView.getUint32(10, true); const headerSize = dataView.getUint32(14, true); let width, height, bitsPerPixel; if (headerSize === 12) { width = dataView.getUint16(18, true); height = dataView.getUint16(20, true); bitsPerPixel = dataView.getUint16(24, true); } else { width = dataView.getInt32(18, true); height = dataView.getInt32(22, true); bitsPerPixel = dataView.getUint16(28, true); } const absHeight = Math.abs(height); const isTopDown = height < 0; let pixels = []; if (bitsPerPixel === 32) { pixels = parse32BppBMP(dataView, width, absHeight, pixelDataOffset, isTopDown); } else if (bitsPerPixel === 24) { pixels = parse24BppBMP(dataView, width, absHeight, pixelDataOffset, isTopDown); } else if (bitsPerPixel === 8) { pixels = parse8BppBMP(dataView, width, absHeight, pixelDataOffset, headerSize, isTopDown); } else if (bitsPerPixel === 4) { pixels = parse4BppBMP(dataView, width, absHeight, pixelDataOffset, headerSize, isTopDown); } else { throw new Error(`Неподдерживаемая битность: ${bitsPerPixel}`); } return { width, height: absHeight, bitsPerPixel, pixels }; } function parse32BppBMP(dataView, width, height, dataOffset, isTopDown) { const pixels = []; const rowSize = width * 4; for (let y = 0; y < height; y++) { const rowY = isTopDown ? y : height - 1 - y; const rowOffset = dataOffset + rowY * rowSize; for (let x = 0; x < width; x++) { const pixelOffset = rowOffset + x * 4; const b = dataView.getUint8(pixelOffset); const g = dataView.getUint8(pixelOffset + 1); const r = dataView.getUint8(pixelOffset + 2); let a = dataView.getUint8(pixelOffset + 3); if (a === 0 && (r !== 0 || g !== 0 || b !== 0)) a = 255; pixels.push({ r, g, b, a }); } } return pixels; } function parse24BppBMP(dataView, width, height, dataOffset, isTopDown) { const pixels = []; const rowSize = Math.floor((width * 3 + 3) / 4) * 4; for (let y = 0; y < height; y++) { const rowY = isTopDown ? y : height - 1 - y; const rowOffset = dataOffset + rowY * rowSize; for (let x = 0; x < width; x++) { const pixelOffset = rowOffset + x * 3; const b = dataView.getUint8(pixelOffset); const g = dataView.getUint8(pixelOffset + 1); const r = dataView.getUint8(pixelOffset + 2); pixels.push({ r, g, b, a: 255 }); } } return pixels; } function parse8BppBMP(dataView, width, height, dataOffset, headerSize, isTopDown) { const pixels = []; const paletteOffset = 14 + headerSize; const palette = readPalette(dataView, 256, paletteOffset); const rowSize = Math.floor((width + 3) / 4) * 4; for (let y = 0; y < height; y++) { const rowY = isTopDown ? y : height - 1 - y; const rowOffset = dataOffset + rowY * rowSize; for (let x = 0; x < width; x++) { const index = dataView.getUint8(rowOffset + x); pixels.push({ ...palette[index], a: 255 }); } } return pixels; } function parse4BppBMP(dataView, width, height, dataOffset, headerSize, isTopDown) { const pixels = []; const paletteOffset = 14 + headerSize; const palette = readPalette(dataView, 16, paletteOffset); const rowSize = Math.floor((width + 7) / 8) * 4; for (let y = 0; y < height; y++) { const rowY = isTopDown ? y : height - 1 - y; const rowOffset = dataOffset + rowY * rowSize; for (let x = 0; x < width; x += 2) { const byteIndex = Math.floor(x / 2); const byte = dataView.getUint8(rowOffset + byteIndex); const index1 = (byte >> 4) & 0x0F; const index2 = byte & 0x0F; pixels.push({ ...palette[index1], a: 255 }); if (x + 1 < width) pixels.push({ ...palette[index2], a: 255 }); } } return pixels; } function readPalette(dataView, colorCount, paletteOffset) { const palette = []; for (let i = 0; i < colorCount; i++) { const offset = paletteOffset + i * 4; const b = dataView.getUint8(offset); const g = dataView.getUint8(offset + 1); const r = dataView.getUint8(offset + 2); palette.push({ r, g, b }); } return palette; } function displayBMPImage(imgElement, bmpData) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = bmpData.width; canvas.height = bmpData.height; const imgData = ctx.createImageData(bmpData.width, bmpData.height); for (let i = 0; i < bmpData.pixels.length; i++) { const pixel = bmpData.pixels[i]; const dataIndex = i * 4; imgData.data[dataIndex] = pixel.r; imgData.data[dataIndex + 1] = pixel.g; imgData.data[dataIndex + 2] = pixel.b; imgData.data[dataIndex + 3] = pixel.a; } ctx.putImageData(imgData, 0, 0); imgElement.src = canvas.toDataURL(); } function removePinkBackgroundFromImageData(imageData) { const data = imageData.data; for (let i = 0; i < data.length; i += 4) { // Удаляем только чистый розовый (255,0,255) if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { data[i + 3] = 0; // Устанавливаем прозрачность } } return imageData; } function removeStrictPinkBackground(bmpData) { const processedPixels = []; for (let i = 0; i < bmpData.pixels.length; i++) { const pixel = bmpData.pixels[i]; // УДАЛЯЕМ ТОЛЬКО ЧИСТЫЙ РОЗОВЫЙ (255,0,255) if (pixel.r === 255 && pixel.g === 0 && pixel.b === 255) { processedPixels.push({ r: 0, g: 0, b: 0, a: 0 }); } else { processedPixels.push({ ...pixel }); } } return { width: bmpData.width, height: bmpData.height, pixels: processedPixels }; } function displayProcessedImage(imageData) { const ctx = processedCanvas.getContext('2d'); const scale = 10; processedCanvas.width = imageData.width * scale; processedCanvas.height = imageData.height * scale; ctx.clearRect(0, 0, processedCanvas.width, processedCanvas.height); // Создаем временный canvas для масштабирования const tempCanvas = document.createElement('canvas'); tempCanvas.width = imageData.width; tempCanvas.height = imageData.height; const tempCtx = tempCanvas.getContext('2d'); if (imageData.data) { // Это ImageData из стандартного изображения tempCtx.putImageData(imageData, 0, 0); } else { // Это данные BMP const imgData = tempCtx.createImageData(imageData.width, imageData.height); for (let i = 0; i < imageData.pixels.length; i++) { const pixel = imageData.pixels[i]; const dataIndex = i * 4; imgData.data[dataIndex] = pixel.r; imgData.data[dataIndex + 1] = pixel.g; imgData.data[dataIndex + 2] = pixel.b; imgData.data[dataIndex + 3] = pixel.a; } tempCtx.putImageData(imgData, 0, 0); } ctx.imageSmoothingEnabled = false; ctx.drawImage(tempCanvas, 0, 0, imageData.width, imageData.height, 0, 0, processedCanvas.width, processedCanvas.height); } function getExportFilename(originalFilename) { // Убираем расширение файла const nameWithoutExt = originalFilename.replace(/\.[^/.]+$/, ""); // Добавляем суффикс и расширение PNG return `${nameWithoutExt}_no_bg.png`; } function downloadProcessedImage() { if (!processedImageData || !currentFile) return; try { const exportFilename = getExportFilename(currentFile.name); // Создаем временный canvas для экспорта const exportCanvas = document.createElement('canvas'); const exportCtx = exportCanvas.getContext('2d'); if (processedImageData.data) { // Это ImageData из стандартного изображения exportCanvas.width = processedImageData.width; exportCanvas.height = processedImageData.height; exportCtx.putImageData(processedImageData, 0, 0); } else { // Это данные BMP exportCanvas.width = processedImageData.width; exportCanvas.height = processedImageData.height; const imgData = exportCtx.createImageData(processedImageData.width, processedImageData.height); for (let i = 0; i < processedImageData.pixels.length; i++) { const pixel = processedImageData.pixels[i]; const dataIndex = i * 4; imgData.data[dataIndex] = pixel.r; imgData.data[dataIndex + 1] = pixel.g; imgData.data[dataIndex + 2] = pixel.b; imgData.data[dataIndex + 3] = pixel.a; } exportCtx.putImageData(imgData, 0, 0); } // Создаем ссылку для скачивания const link = document.createElement('a'); link.download = exportFilename; link.href = exportCanvas.toDataURL('image/png'); link.click(); // Показываем сообщение об успешном скачивании showWarning(`Изображение сохранено как: ${exportFilename}`); } catch (error) { showError('Ошибка при сохранении файла: ' + error.message); } } function formatFileSize(bytes) { if (bytes < 1024) return bytes + ' байт'; else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' КБ'; else return (bytes / 1048576).toFixed(1) + ' МБ'; } function showError(message) { errorMessage.textContent = message; errorMessage.style.display = 'block'; previewContainer.style.display = 'none'; fileInfo.style.display = 'none'; controls.style.display = 'none'; } function showWarning(message) { warningMessage.textContent = message; warningMessage.style.display = 'block'; } </script> </body> </html>
|
|
|