💫 酷咔图片转ICO工具

主要新增功能:
尺寸选择网格:提供了9个常用ICO尺寸选项(16×16 到 512×512)
自定义尺寸输入:可以输入任意宽度和高度(1-1024像素)
预设尺寸快捷按钮:快速设置常见尺寸
实时显示已选尺寸:在顶部显示当前选择的尺寸列表
尺寸管理:可以随时添加或删除尺寸
尺寸选择反馈:选中的尺寸有视觉高亮效果
代码分享
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>酷咔图片转ICO工具 - 支持自定义尺寸</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 40px;
color: white;
}
h1 {
font-size: 2.8rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
}
.app-card {
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.upload-area {
border: 3px dashed #667eea;
border-radius: 15px;
padding: 60px 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: #f8f9ff;
margin-bottom: 30px;
}
.upload-area:hover {
border-color: #764ba2;
background: #f0f3ff;
}
.upload-area.dragover {
border-color: #764ba2;
background: #e8ebff;
}
.upload-icon {
font-size: 4rem;
color: #667eea;
margin-bottom: 20px;
}
.upload-text {
font-size: 1.3rem;
margin-bottom: 10px;
color: #555;
}
.upload-hint {
color: #888;
font-size: 0.9rem;
}
.settings-section {
background: #f8f9ff;
border-radius: 15px;
padding: 25px;
margin-bottom: 30px;
display: none;
}
.settings-title {
font-size: 1.2rem;
margin-bottom: 20px;
color: #555;
font-weight: 600;
}
.size-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.size-option {
display: flex;
align-items: center;
gap: 10px;
padding: 12px;
background: white;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid #e0e0e0;
}
.size-option:hover {
border-color: #667eea;
transform: translateY(-2px);
}
.size-option.selected {
border-color: #764ba2;
background: #f0e6ff;
}
.size-checkbox {
width: 20px;
height: 20px;
border-radius: 4px;
border: 2px solid #667eea;
display: flex;
align-items: center;
justify-content: center;
}
.size-checkbox.checked::after {
content: '✓';
color: #764ba2;
font-weight: bold;
}
.size-label {
font-weight: 500;
color: #555;
}
.custom-size {
margin-top: 20px;
}
.custom-size-inputs {
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
.size-input-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.size-input-group label {
font-size: 0.9rem;
color: #666;
}
.size-input {
padding: 8px 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
width: 100px;
}
.size-input:focus {
outline: none;
border-color: #667eea;
}
.add-custom-btn {
background: #667eea;
color: white;
border: none;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: background 0.3s ease;
}
.add-custom-btn:hover {
background: #764ba2;
}
.preset-sizes {
display: flex;
gap: 10px;
margin-top: 10px;
flex-wrap: wrap;
}
.preset-btn {
padding: 6px 12px;
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
}
.preset-btn:hover {
background: #e0e0e0;
}
.controls {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 20px;
}
.btn {
padding: 15px 40px;
border: none;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #f0f0f0;
color: #666;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.progress-container {
margin-top: 20px;
text-align: center;
display: none;
}
.progress-bar {
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
color: #666;
font-size: 0.9rem;
}
.status-message {
padding: 15px;
border-radius: 10px;
margin: 20px 0;
text-align: center;
font-weight: 500;
}
.status-success {
background: #e8f5e9;
color: #2e7d32;
border: 1px solid #c8e6c9;
}
.status-error {
background: #ffebee;
color: #c62828;
border: 1px solid #ffcdd2;
}
.download-link {
display: inline-block;
padding: 10px 20px;
background: #4CAF50;
color: white;
text-decoration: none;
border-radius: 5px;
margin: 10px 5px;
transition: background 0.3s ease;
}
.download-link:hover {
background: #388e3c;
}
.download-links {
margin-top: 20px;
text-align: center;
}
.error-details {
background: #f5f5f5;
border-radius: 5px;
padding: 10px;
margin-top: 10px;
font-family: monospace;
font-size: 0.9rem;
color: #666;
text-align: left;
}
.current-sizes {
margin-top: 20px;
padding: 15px;
background: #f8f9ff;
border-radius: 10px;
}
.current-sizes-title {
font-weight: 600;
margin-bottom: 10px;
color: #555;
}
.size-badge {
display: inline-block;
padding: 4px 12px;
background: #667eea;
color: white;
border-radius: 20px;
margin: 5px;
font-size: 0.9rem;
}
.remove-size {
background: #ff6b6b;
color: white;
border: none;
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5px;
font-size: 12px;
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
.app-card {
padding: 20px;
}
.controls {
flex-direction: column;
}
.btn {
width: 100%;
}
.size-grid {
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.custom-size-inputs {
flex-direction: column;
align-items: stretch;
}
.size-input {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>酷咔图片转ICO工具</h1>
<p class="subtitle">支持自定义尺寸设置</p>
</header>
<div class="app-card">
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📁</div>
<div class="upload-text">点击或拖放图片文件到这里</div>
<div class="upload-hint">支持PNG、JPG、JPEG格式,单文件最大5MB</div>
<input type="file" id="fileInput" accept=".png,.jpg,.jpeg" style="display: none;">
</div>
<div class="settings-section" id="settingsSection">
<div class="settings-title">选择ICO尺寸</div>
<div class="current-sizes" id="currentSizes" style="display: none;">
<div class="current-sizes-title">已选择尺寸:</div>
<div id="selectedSizesList"></div>
</div>
<div class="size-grid" id="sizeGrid">
<!-- 尺寸选项会由JS动态生成 -->
</div>
<div class="custom-size">
<div class="settings-title">自定义尺寸</div>
<div class="custom-size-inputs">
<div class="size-input-group">
<label for="customWidth">宽度 (px)</label>
<input type="number" id="customWidth" class="size-input" min="1" max="512" value="64" placeholder="宽度">
</div>
<div class="size-input-group">
<label for="customHeight">高度 (px)</label>
<input type="number" id="customHeight" class="size-input" min="1" max="512" value="64" placeholder="高度">
</div>
<button class="add-custom-btn" id="addCustomBtn">添加自定义尺寸</button>
</div>
<div class="preset-sizes">
<button class="preset-btn" onclick="setPresetSize(16, 16)">16×16</button>
<button class="preset-btn" onclick="setPresetSize(32, 32)">32×32</button>
<button class="preset-btn" onclick="setPresetSize(48, 48)">48×48</button>
<button class="preset-btn" onclick="setPresetSize(64, 64)">64×64</button>
<button class="preset-btn" onclick="setPresetSize(128, 128)">128×128</button>
<button class="preset-btn" onclick="setPresetSize(256, 256)">256×256</button>
<button class="preset-btn" onclick="setPresetSize(512, 512)">512×512</button>
</div>
</div>
</div>
<div class="controls">
<button class="btn btn-secondary" id="resetBtn" style="display: none;">
重置
</button>
<button class="btn btn-primary" id="convertBtn" disabled>
转换为ICO
</button>
</div>
<div class="progress-container" id="progressContainer">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text" id="progressText">准备转换...</div>
</div>
<div id="statusArea"></div>
<div id="downloadArea" style="display: none;"></div>
</div>
</div>
<script>
class ICOSizeConverter {
constructor() {
this.currentFile = null;
this.isProcessing = false;
this.selectedSizes = new Set(['16x16', '32x32', '48x48', '256x256']);
this.availableSizes = [
{width: 16, height: 16, label: '16×16'},
{width: 24, height: 24, label: '24×24'},
{width: 32, height: 32, label: '32×32'},
{width: 48, height: 48, label: '48×48'},
{width: 64, height: 64, label: '64×64'},
{width: 96, height: 96, label: '96×96'},
{width: 128, height: 128, label: '128×128'},
{width: 256, height: 256, label: '256×256'},
{width: 512, height: 512, label: '512×512'}
];
this.init();
}
init() {
this.setupEventListeners();
this.generateSizeOptions();
this.updateSelectedSizesDisplay();
}
setupEventListeners() {
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const convertBtn = document.getElementById('convertBtn');
const resetBtn = document.getElementById('resetBtn');
const addCustomBtn = document.getElementById('addCustomBtn');
const sizeGrid = document.getElementById('sizeGrid');
// 点击上传区域
uploadArea.addEventListener('click', () => fileInput.click());
// 文件选择变化
fileInput.addEventListener('change', (e) => this.handleFileSelect(e.target.files));
// 拖放功能
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
this.handleFileSelect(e.dataTransfer.files);
});
// 转换按钮
convertBtn.addEventListener('click', () => this.convertToICO());
// 重置按钮
resetBtn.addEventListener('click', () => this.reset());
// 添加自定义尺寸按钮
addCustomBtn.addEventListener('click', () => this.addCustomSize());
// 尺寸选择
sizeGrid.addEventListener('click', (e) => {
const sizeOption = e.target.closest('.size-option');
if (sizeOption) {
const width = parseInt(sizeOption.dataset.width);
const height = parseInt(sizeOption.dataset.height);
this.toggleSize(width, height);
}
});
}
generateSizeOptions() {
const sizeGrid = document.getElementById('sizeGrid');
sizeGrid.innerHTML = '';
this.availableSizes.forEach(size => {
const sizeKey = `${size.width}x${size.height}`;
const isSelected = this.selectedSizes.has(sizeKey);
const option = document.createElement('div');
option.className = `size-option ${isSelected ? 'selected' : ''}`;
option.dataset.width = size.width;
option.dataset.height = size.height;
option.innerHTML = `
<div class="size-checkbox ${isSelected ? 'checked' : ''}"></div>
<div class="size-label">${size.label}</div>
`;
sizeGrid.appendChild(option);
});
}
toggleSize(width, height) {
const sizeKey = `${width}x${height}`;
if (this.selectedSizes.has(sizeKey)) {
this.selectedSizes.delete(sizeKey);
} else {
this.selectedSizes.add(sizeKey);
}
this.generateSizeOptions();
this.updateSelectedSizesDisplay();
this.updateConvertButton();
}
addCustomSize() {
const widthInput = document.getElementById('customWidth');
const heightInput = document.getElementById('customHeight');
const width = parseInt(widthInput.value);
const height = parseInt(heightInput.value);
if (!width || !height || width < 1 || height < 1 || width > 1024 || height > 1024) {
this.showError('请输入有效的尺寸(1-1024像素)');
return;
}
const sizeKey = `${width}x${height}`;
// 检查是否已存在
if (!this.selectedSizes.has(sizeKey)) {
this.selectedSizes.add(sizeKey);
// 添加到可选尺寸列表
const exists = this.availableSizes.some(s => s.width === width && s.height === height);
if (!exists) {
this.availableSizes.push({
width,
height,
label: `${width}×${height}`
});
this.availableSizes.sort((a, b) => a.width - b.width);
}
this.generateSizeOptions();
this.updateSelectedSizesDisplay();
this.updateConvertButton();
// 清空输入框
widthInput.value = '';
heightInput.value = '';
}
}
removeSize(sizeKey) {
this.selectedSizes.delete(sizeKey);
this.generateSizeOptions();
this.updateSelectedSizesDisplay();
this.updateConvertButton();
}
updateSelectedSizesDisplay() {
const currentSizes = document.getElementById('currentSizes');
const selectedSizesList = document.getElementById('selectedSizesList');
if (this.selectedSizes.size === 0) {
currentSizes.style.display = 'none';
return;
}
currentSizes.style.display = 'block';
selectedSizesList.innerHTML = '';
Array.from(this.selectedSizes).sort().forEach(sizeKey => {
const sizeBadge = document.createElement('span');
sizeBadge.className = 'size-badge';
sizeBadge.innerHTML = `
${sizeKey}
<button class="remove-size" onclick="icoConverter.removeSize('${sizeKey}')">×</button>
`;
selectedSizesList.appendChild(sizeBadge);
});
}
handleFileSelect(files) {
if (files.length === 0) return;
const file = files[0];
// 文件大小检查
if (file.size > 5 * 1024 * 1024) {
this.showError('文件大小不能超过5MB');
return;
}
// 文件类型检查
if (!file.type.match('image.*')) {
this.showError('请选择图片文件(PNG、JPG、JPEG格式)');
return;
}
this.currentFile = file;
document.getElementById('convertBtn').disabled = false;
document.getElementById('resetBtn').style.display = 'inline-block';
document.getElementById('settingsSection').style.display = 'block';
this.showStatus(`已选择文件: ${file.name} (${this.formatFileSize(file.size)})`, 'success');
}
async convertToICO() {
if (!this.currentFile || this.isProcessing) return;
if (this.selectedSizes.size === 0) {
this.showError('请至少选择一个尺寸');
return;
}
this.isProcessing = true;
const convertBtn = document.getElementById('convertBtn');
const progressContainer = document.getElementById('progressContainer');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
// 显示进度条
progressContainer.style.display = 'block';
progressFill.style.width = '0%';
convertBtn.disabled = true;
try {
// 更新进度
progressText.textContent = '正在读取文件...';
progressFill.style.width = '10%';
// 读取图片文件
const img = await this.loadImage(this.currentFile);
// 更新进度
progressText.textContent = '正在处理图片...';
progressFill.style.width = '20%';
// 解析选择的尺寸
const sizes = Array.from(this.selectedSizes).map(sizeKey => {
const [width, height] = sizeKey.split('x').map(Number);
return { width, height };
});
// 创建不同尺寸的图片
const canvases = [];
const totalSizes = sizes.length;
for (let i = 0; i < totalSizes; i++) {
const size = sizes[i];
const progressPercent = 20 + (i / totalSizes) * 60;
progressText.textContent = `生成 ${size.width}x${size.height} 图标...`;
progressFill.style.width = `${progressPercent}%`;
const canvas = await this.resizeImageToCanvas(img, size.width, size.height);
canvases.push(canvas);
// 避免UI卡顿
await new Promise(resolve => setTimeout(resolve, 50));
}
// 更新进度
progressText.textContent = '生成ICO文件...';
progressFill.style.width = '90%';
// 生成ICO数据
const icoData = await this.createICOData(canvases, sizes);
// 更新进度
progressText.textContent = '转换完成!';
progressFill.style.width = '100%';
// 提供下载
const filename = this.currentFile.name.replace(/\.[^/.]+$/, "") + '.ico';
await this.downloadICOFile(icoData, filename);
this.showStatus(`转换成功!生成了${sizes.length}个尺寸的ICO文件。`, 'success');
} catch (error) {
console.error('转换失败:', error);
this.showError(`转换失败: ${error.message}`, error);
} finally {
this.isProcessing = false;
// 2秒后隐藏进度条
setTimeout(() => {
progressContainer.style.display = 'none';
convertBtn.disabled = false;
}, 2000);
}
}
loadImage(file) {
return new Promise((resolve, reject) => {
const img = new Image();
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('图片加载失败'));
};
reader.onerror = () => reject(new Error('文件读取失败'));
reader.readAsDataURL(file);
});
}
resizeImageToCanvas(img, width, height) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 设置高质量缩放
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
// 清空画布
ctx.clearRect(0, 0, width, height);
// 计算缩放比例
const scale = Math.min(width / img.width, height / img.height);
const scaledWidth = img.width * scale;
const scaledHeight = img.height * scale;
const x = (width - scaledWidth) / 2;
const y = (height - scaledHeight) / 2;
// 绘制图片
ctx.drawImage(img, x, y, scaledWidth, scaledHeight);
resolve(canvas);
});
}
async createICOData(canvases, sizes) {
const icoParts = [];
// ICO文件头
icoParts.push(new Uint8Array([0, 0, 1, 0])); // 保留字段和类型
icoParts.push(new Uint8Array([canvases.length, 0])); // 图片数量
let offset = 6 + (canvases.length * 16);
const pngDatas = [];
for (let i = 0; i < canvases.length; i++) {
const canvas = canvases[i];
const pngData = await this.canvasToPNG(canvas);
pngDatas.push(pngData);
}
// 生成目录项
for (let i = 0; i < canvases.length; i++) {
const size = sizes[i];
const pngData = pngDatas[i];
const pngSize = pngData.length;
const directoryEntry = new Uint8Array(16);
// 宽度和高度
directoryEntry[0] = size.width >= 256 ? 0 : size.width;
directoryEntry[1] = size.height >= 256 ? 0 : size.height;
// 颜色数
directoryEntry[2] = 0;
// 保留字段
directoryEntry[3] = 0;
// 颜色平面
directoryEntry[4] = 0;
directoryEntry[5] = 0;
// 每像素位数
directoryEntry[6] = 32;
directoryEntry[7] = 0;
// PNG数据大小
directoryEntry[8] = pngSize & 0xFF;
directoryEntry[9] = (pngSize >> 8) & 0xFF;
directoryEntry[10] = (pngSize >> 16) & 0xFF;
directoryEntry[11] = (pngSize >> 24) & 0xFF;
// 数据偏移
directoryEntry[12] = offset & 0xFF;
directoryEntry[13] = (offset >> 8) & 0xFF;
directoryEntry[14] = (offset >> 16) & 0xFF;
directoryEntry[15] = (offset >> 24) & 0xFF;
icoParts.push(directoryEntry);
offset += pngSize;
}
// 添加PNG数据
for (const pngData of pngDatas) {
icoParts.push(pngData);
}
// 合并所有部分
const totalSize = icoParts.reduce((sum, part) => sum + part.length, 0);
const result = new Uint8Array(totalSize);
let position = 0;
for (const part of icoParts) {
result.set(part, position);
position += part.length;
}
return result.buffer;
}
canvasToPNG(canvas) {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
const reader = new FileReader();
reader.onload = () => {
resolve(new Uint8Array(reader.result));
};
reader.readAsArrayBuffer(blob);
}, 'image/png');
});
}
downloadICOFile(data, filename) {
return new Promise((resolve, reject) => {
try {
const blob = new Blob([data], { type: 'image/vnd.microsoft.icon' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 100);
resolve();
} catch (error) {
try {
const downloadArea = document.getElementById('downloadArea');
downloadArea.innerHTML = '';
const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([data], { type: 'image/vnd.microsoft.icon' }));
link.download = filename;
link.textContent = `点击下载 ${filename}`;
link.className = 'download-link';
downloadArea.appendChild(link);
downloadArea.style.display = 'block';
this.showStatus('请点击上方的链接下载文件', 'success');
resolve();
} catch (fallbackError) {
reject(fallbackError);
}
}
});
}
reset() {
this.currentFile = null;
this.isProcessing = false;
this.selectedSizes = new Set(['16x16', '32x32', '48x48', '256x256']);
document.getElementById('fileInput').value = '';
document.getElementById('convertBtn').disabled = true;
document.getElementById('resetBtn').style.display = 'none';
document.getElementById('progressContainer').style.display = 'none';
document.getElementById('settingsSection').style.display = 'none';
document.getElementById('statusArea').innerHTML = '';
document.getElementById('downloadArea').innerHTML = '';
document.getElementById('downloadArea').style.display = 'none';
this.generateSizeOptions();
this.updateSelectedSizesDisplay();
}
showStatus(message, type = 'success') {
const statusArea = document.getElementById('statusArea');
statusArea.innerHTML = `
<div class="status-message status-${type}">
${message}
</div>
`;
}
showError(message, error = null) {
this.showStatus(message, 'error');
if (error) {
const statusArea = document.getElementById('statusArea');
const errorDetails = document.createElement('div');
errorDetails.className = 'error-details';
errorDetails.textContent = `错误详情: ${error.message}`;
statusArea.appendChild(errorDetails);
}
}
updateConvertButton() {
const convertBtn = document.getElementById('convertBtn');
convertBtn.disabled = !this.currentFile || this.selectedSizes.size === 0 || this.isProcessing;
}
formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
}
// 初始化转换器
const icoConverter = new ICOSizeConverter();
// 全局辅助函数
function setPresetSize(width, height) {
document.getElementById('customWidth').value = width;
document.getElementById('customHeight').value = height;
}
</script>
</body>
</html>
演示效果
阅读:46
发布时间: