Dot絵エディター3 : Grid, Palette, Layer
やること
rock, opacity, groupのためのgui
visibilityとopacity反映されてない
Gridクラス
addLayer
addLayer() {
const targetObj = getSelectedTarget();
if (targetObj && targetObj.type === 'grid') {
const newLayerIndex = this.layerManager.createLayer(this.rowCount, this.colCount);
this.layerManager.updateLayerList();
this.draw();
return newLayerIndex;
} else {
alert('レイヤーの追加はグリッド全体を選択している場合のみ可能です。');
}
}
draw
ver240706a
・キャンパスのクリア
・一時的なDOMキャンバスの作成
・cellsは色オブジェクトの二重配列
・透明度aは0-255であって、fillStyleに格納する時/255して0-1に収められる
各レイヤーは一時キャンバスに描き込み、
一時キャンバスはレイヤー毎にメインキャンバスに描きこまれる。
この時ブレンドモードが処理される。
this.ctx.globalCompositeOperation = 'source-over';
以降はグリッドの線と
表面レイヤー(一時表示用、たとえば選択範囲とか)の処理
draw() {
this.ctx.clearRect(0, 0, this.width, this.height);
const tempCanvas = document.createElement('canvas');
tempCanvas.width = this.width;
tempCanvas.height = this.height;
const tempCtx = tempCanvas.getContext('2d');
this.layerManager.layers.forEach((layer, index) => {
if (layer.visible) {
tempCtx.clearRect(0, 0, this.width, this.height);
for (let row = 0; row < this.rowCount; row++) {
for (let col = 0; col < this.colCount; col++) {
const color = layer.cells[row][col];
if (color.a > 0) {
tempCtx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a / 255})`;
tempCtx.fillRect(col * this.cellWidth, row * this.cellHeight, this.cellWidth, this.cellHeight);
}
}
}
this.ctx.globalCompositeOperation = layer.blendMode;
this.ctx.drawImage(tempCanvas, 0, 0);
}
});
this.ctx.globalCompositeOperation = 'source-over';
if (this.onGridLine) {
this.drawGridLines();
}
for (let row = 0; row < this.rowCount; row++) {
for (let col = 0; col < this.colCount; col++) {
const color = this.surfaceLayer.cells[row][col];
if (color.a > 0) {
this.ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a / 255})`;
this.ctx.fillRect(col * this.cellWidth, row * this.cellHeight, this.cellWidth, this.cellHeight);
}
}
}
}
ver240706b:LayerGroup
layerを処理する関数をdrawLayer変数につっこんでいる。
レイヤーグループの場合、再帰する。
セル毎にアルファ値を設定する選択肢と
globalAlphaでレイヤー全体の透明度を操作する選択肢がある。
draw() {
this.ctx.clearRect(0, 0, this.width, this.height);
const tempCanvas = document.createElement('canvas');
tempCanvas.width = this.width;
tempCanvas.height = this.height;
const tempCtx = tempCanvas.getContext('2d');
const drawLayer = (layer) => {
if (!layer.visible) return;
if (layer instanceof Layer) {
tempCtx.clearRect(0, 0, this.width, this.height);
for (let row = 0; row < this.rowCount; row++) {
for (let col = 0; col < this.colCount; col++) {
const color = layer.cells[row][col];
if (color.a > 0) {
tempCtx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a / 255})`;
tempCtx.fillRect(col * this.cellWidth, row * this.cellHeight, this.cellWidth, this.cellHeight);
}
}
}
this.ctx.globalAlpha = layer.opacity;
this.ctx.globalCompositeOperation = layer.blendMode;
this.ctx.drawImage(tempCanvas, 0, 0);
} else if (layer instanceof LayerGroup) {
layer.layers.forEach(drawLayer);
}
};
this.layerManager.layers.forEach(drawLayer);
this.ctx.globalCompositeOperation = 'source-over';
this.ctx.globalAlpha = 1;
if (this.onGridLine) {
this.drawGridLines();
}
// Surface layer drawing
for (let row = 0; row < this.rowCount; row++) {
for (let col = 0; col < this.colCount; col++) {
const color = this.surfaceLayer.cells[row][col];
if (color.a > 0) {
this.ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a / 255})`;
this.ctx.fillRect(col * this.cellWidth, row * this.cellHeight, this.cellWidth, this.cellHeight);
}
}
}
}
resize
エラー込バージョン。
このバージョンはサーフェスレイヤーを考慮していない。
resize(newRowCount, newColCount, newWidth, newHeight) {
const oldLayers = this.layerManager.layers;
const oldRowCount = this.rowCount;
const oldColCount = this.colCount;
this.rowCount = newRowCount;
this.colCount = newColCount;
this.canvas.width = newWidth;
this.canvas.height = newHeight;
this._cellWidth = newWidth / newColCount;
this._cellHeight = newHeight / newRowCount;
this.layerManager.reset();
const minRowCount = Math.min(oldRowCount, newRowCount);
const minColCount = Math.min(oldColCount, newColCount);
oldLayers.forEach(oldLayer => {
const {newLayer, newLayerIndex} = this.layerManager.createLayer(newRowCount, newColCount, oldLayer.name);
newLayer.blendMode = oldLayer.blendMode;
newLayer.visible = oldLayer.visible;
for (let r = 0; r < minRowCount; r++) {
for (let c = 0; c < minColCount; c++) {
newLayer.cells[r][c] = oldLayer.cells[r][c];
}
}
});
this.dirtyRegion = new Set();
for (let r = 0; r < this.rowCount; r++) {
for (let c = 0; c < this.colCount; c++) {
this.dirtyRegion.add(`${r},${c}`);
}
}
this.draw();
this.layerManager.updateLayerList();
this.updateGridOutput();
}
サーフェス変更込み。
resize(newRowCount, newColCount, newWidth, newHeight) {
const oldLayers = this.layerManager.layers;
const oldRowCount = this.rowCount;
const oldColCount = this.colCount;
this.rowCount = newRowCount;
this.colCount = newColCount;
this.canvas.width = newWidth;
this.canvas.height = newHeight;
this._cellWidth = newWidth / newColCount;
this._cellHeight = newHeight / newRowCount;
// surfaceLayerのリサイズ
const newSurfaceLayer = new Layer(newRowCount, newColCount, "Surface Layer");
for (let r = 0; r < Math.min(oldRowCount, newRowCount); r++) {
for (let c = 0; c < Math.min(oldColCount, newColCount); c++) {
newSurfaceLayer.cells[r][c] = this.surfaceLayer.cells[r][c];
}
}
this.surfaceLayer = newSurfaceLayer;
this.layerManager.reset();
const minRowCount = Math.min(oldRowCount, newRowCount);
const minColCount = Math.min(oldColCount, newColCount);
oldLayers.forEach(oldLayer => {
const {newLayer, newLayerIndex} = this.layerManager.createLayer(newRowCount, newColCount, oldLayer.name);
newLayer.blendMode = oldLayer.blendMode;
newLayer.visible = oldLayer.visible;
for (let r = 0; r < minRowCount; r++) {
for (let c = 0; c < minColCount; c++) {
newLayer.cells[r][c] = oldLayer.cells[r][c];
}
}
});
this.dirtyRegion = new Set();
for (let r = 0; r < this.rowCount; r++) {
for (let c = 0; c < this.colCount; c++) {
this.dirtyRegion.add(`${r},${c}`);
}
}
this.draw();
this.layerManager.updateLayerList();
this.updateGridOutput();
}
Paletteクラス
パレットは
色オブジェクトの配列をもつ(レイヤーは二重配列)
レイヤー構造を持たず、
ブレンドモードを持たない。
しかしグリッドのようなグリッド線の描画能力や
セル毎のイベントを持つ。
class Palette {
constructor(rowCount, colCount, width, height) {
this.toolManager = ToolManager.getInstance();
this.canvas = document.createElement('canvas');
this.canvas.width = width;
this.canvas.height = height;
this.ctx = this.canvas.getContext("2d");
this.rowCount = rowCount;
this.colCount = colCount;
this.cellWidth = width / this.colCount;
this.cellHeight = height / this.rowCount;
this.colors = [];
this.canvas.addEventListener('click', this.handleClick.bind(this));
}
setColors(colors) {
this.colors = colors.slice(0, this.rowCount * this.colCount);
this.draw();
}
draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
//canvas
for (let i = 0; i < this.colors.length; i++) {
const row = Math.floor(i / this.colCount);
const col = i % this.colCount;
const color = this.colors[i];
this.ctx.fillStyle = `rgb(${color.r},${color.g},${color.b})`;
this.ctx.fillRect(col * this.cellWidth, row * this.cellHeight, this.cellWidth, this.cellHeight);
}
//line
this.ctx.strokeStyle = '#000000';
this.ctx.lineWidth = 1;
for (let i = 0; i <= this.colCount; i++) {
this.ctx.beginPath();
this.ctx.moveTo(i * this.cellWidth, 0);
this.ctx.lineTo(i * this.cellWidth, this.canvas.height);
this.ctx.stroke();
}
for (let i = 0; i <= this.rowCount; i++) {
this.ctx.beginPath();
this.ctx.moveTo(0, i * this.cellHeight);
this.ctx.lineTo(this.canvas.width, i * this.cellHeight);
this.ctx.stroke();
}
}
handleClick(x, y) {
const col = Math.floor(x / this.cellWidth);
const row = Math.floor(y / this.cellHeight);
const index = row * this.colCount + col;
if (index < this.colors.length) {
const selectedColor = this.colors[index];
this.toolManager.setCurrentColor(selectedColor);
document.getElementById('colorPicker').value = colorToHex(selectedColor);
// 色が選択されたことを通知するカスタムイベントを発火
const event = new CustomEvent('colorSelected', { detail: selectedColor });
document.dispatchEvent(event);
}
}
appendTo(container) {
container.appendChild(this.canvas);
}
}
PaletteManagerクラス
//DOMとしてのPalette
createDOMPalette(id, name, width = 200, height = 200) {
const domPalette = document.createElement('div');
domPalette.className = 'palette-item';
domPalette.style.display = 'flex';
domPalette.style.flexDirection = 'column';
domPalette.style.alignItems = 'center'; // 中央揃え
const h4 = document.createElement('h4');
h4.textContent = name;
domPalette.appendChild(h4);
const canvas = document.createElement('canvas');
canvas.className = 'palette-canvas';
canvas.width = width;
canvas.height = height;
canvas.dataset.id = id;
domPalette.appendChild(canvas);
const button = document.createElement('button');
button.className = 'remove-palette-btn';
button.dataset.id = id;
button.textContent = 'Remove';
domPalette.appendChild(button);
return domPalette;
}
//managerからパレットを取り出し、新規にCanvasを含むDOMを作成して
//そこにパレットからCanvasをコピー、DOMをコンテナに突っ込んでいく
updatePaletteDisplay() {
const container = document.getElementById('paletteContainer');
container.innerHTML = '';
this.palettes.forEach(({ id, name, palette }) => {
//表示用DOMCanvas
const newDOMPalette = this.createDOMPalette(id, name, palette.canvas.width, palette.canvas.height);
container.appendChild(newDOMPalette);
const domCanvas = newDOMPalette.querySelector('canvas');
domCanvas.getContext('2d').drawImage(palette.canvas, 0, 0);
// チェックボックスを追加
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = id === this.currentPaletteId;
checkbox.addEventListener('change', () => {
if (checkbox.checked) {
this.setCurrentPalette(id);
this.updatePaletteDisplay(); // 他のチェックボックスの状態を更新
}
});
newDOMPalette.insertBefore(checkbox, newDOMPalette.firstChild);
// 表示用のCanvasにイベントリスナーを追加
domCanvas.addEventListener('click', (event) => {
const rect = domCanvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// クリックイベントをPaletteオブジェクトに渡す
palette.handleClick(x, y);
});
});
}
Layerクラス
cellsは色オブジェクトの二重配列
まずrowCount個分の要素をundefinedでfillし、
各undefinedをカラー配列に入れ替える(map)
class Layer {
constructor(rowCount, colCount, name = "") {
this.cells = Array(rowCount).fill().map(() => Array(colCount).fill(new Color(255, 255, 255, 0)));
this.name = name;
this.blendMode = 'normal';
this.visible = true;
this.opacity = 1; // 新しいプロパティ
this.locked = false; // 新しいプロパティ
}
}
LayerManagerクラス
createLayer
createLayer(rowCount, colCount, name = "") {
this.layerCount++;
const layer = new Layer(rowCount, colCount, name || `レイヤー ${this.layerCount}`);
this.layers.push(layer);
this.currentLayerIndex = this.layers.length - 1;
return layer;
}
updateLayerList
240706a
updateLayerList() {
const layerList = document.getElementById('layerList');
layerList.innerHTML = '';
this.layers.forEach((layer, index) => {
const li = document.createElement('li');
const visibilityCheckbox = document.createElement('input');
visibilityCheckbox.type = 'checkbox';
visibilityCheckbox.className = 'layer-visibility';
visibilityCheckbox.checked = layer.visible;
visibilityCheckbox.addEventListener('change', () => handleToggleLayerVisibility(index));
li.appendChild(visibilityCheckbox);
const radio = document.createElement('input');
radio.type = 'radio';
radio.name = 'layer';
radio.value = index;
radio.checked = index === this.currentLayerIndex;
radio.addEventListener('change', () => {
this.setCurrentLayerIndex(index);
});
li.appendChild(radio);
li.appendChild(document.createTextNode(layer.name));
const blendModeSelect = document.createElement('select');
blendModeSelect.innerHTML = `
<option value="normal" ${layer.blendMode === 'normal' ? 'selected' : ''}>Normal</option>
<option value="multiply" ${layer.blendMode === 'multiply' ? 'selected' : ''}>Multiply</option>
<option value="screen" ${layer.blendMode === 'screen' ? 'selected' : ''}>Screen</option>
<option value="overlay" ${layer.blendMode === 'overlay' ? 'selected' : ''}>Overlay</option>
<option value="darken" ${layer.blendMode === 'darken' ? 'selected' : ''}>Darken</option>
<option value="lighten" ${layer.blendMode === 'lighten' ? 'selected' : ''}>Lighten</option>
`;
blendModeSelect.addEventListener('change', (e) => {
layer.blendMode = e.target.value;
this.draw();
});
li.appendChild(blendModeSelect);
const buttonContainer = document.createElement('div');
buttonContainer.className = 'layer-buttons';
const upButton = document.createElement('button');
upButton.textContent = '↑';
upButton.className = 'layer-button';
upButton.addEventListener('click', () => handleMoveLayerUp(index));
buttonContainer.appendChild(upButton);
const downButton = document.createElement('button');
downButton.textContent = '↓';
downButton.className = 'layer-button';
downButton.addEventListener('click', () => handleMoveLayerDown(index));
buttonContainer.appendChild(downButton);
li.appendChild(buttonContainer);
layerList.appendChild(li);
});
}
240706b
ハンドラでthis.toggleLayerVisibilityなどをしていしているが、
opacity含め、これらの操作はGUI変動直後にグリッド再描画が必要である。
つまりgrid呼んでdrawなりする必要があるがここではそれが考慮されてない。
特にマネージャーレベルを渡る関数は今の所外にだすルールを勝手に設けている。ゆえにここで指定するハンドラにthisがつくことはない。
updateLayerList() {
const layerList = document.getElementById('layerList');
layerList.innerHTML = '';
const createLayerItem = (layer, index, indent = 0) => {
const li = document.createElement('li');
li.style.paddingLeft = `${indent * 20}px`;
// 可視性チェックボックス
const visibilityCheckbox = document.createElement('input');
visibilityCheckbox.type = 'checkbox';
visibilityCheckbox.className = 'layer-visibility';
visibilityCheckbox.checked = layer.visible;
visibilityCheckbox.addEventListener('change', () => this.toggleLayerVisibility(index));
li.appendChild(visibilityCheckbox);
// レイヤー選択ラジオボタン
const radio = document.createElement('input');
radio.type = 'radio';
radio.name = 'layer';
radio.value = index;
radio.checked = index === this.currentLayerIndex;
radio.addEventListener('change', () => this.setCurrentLayerIndex(index));
li.appendChild(radio);
// レイヤー名
const nameSpan = document.createElement('span');
nameSpan.textContent = layer.name;
nameSpan.addEventListener('dblclick', () => this.renameLayer(index));
li.appendChild(nameSpan);
if (!layer.layers) { // グループでない通常のレイヤーの場合
// ブレンドモード選択
const blendModeSelect = document.createElement('select');
blendModeSelect.innerHTML = `
<option value="normal" ${layer.blendMode === 'normal' ? 'selected' : ''}>Normal</option>
<option value="multiply" ${layer.blendMode === 'multiply' ? 'selected' : ''}>Multiply</option>
<option value="screen" ${layer.blendMode === 'screen' ? 'selected' : ''}>Screen</option>
<option value="overlay" ${layer.blendMode === 'overlay' ? 'selected' : ''}>Overlay</option>
<option value="darken" ${layer.blendMode === 'darken' ? 'selected' : ''}>Darken</option>
<option value="lighten" ${layer.blendMode === 'lighten' ? 'selected' : ''}>Lighten</option>
<option value="color-dodge" ${layer.blendMode === 'color-dodge' ? 'selected' : ''}>Color Dodge</option>
<option value="color-burn" ${layer.blendMode === 'color-burn' ? 'selected' : ''}>Color Burn</option>
<option value="hard-light" ${layer.blendMode === 'hard-light' ? 'selected' : ''}>Hard Light</option>
<option value="soft-light" ${layer.blendMode === 'soft-light' ? 'selected' : ''}>Soft Light</option>
<option value="difference" ${layer.blendMode === 'difference' ? 'selected' : ''}>Difference</option>
<option value="exclusion" ${layer.blendMode === 'exclusion' ? 'selected' : ''}>Exclusion</option>
`;
blendModeSelect.addEventListener('change', (e) => {
layer.blendMode = e.target.value;
this.draw();
});
li.appendChild(blendModeSelect);
// 不透明度スライダー
const opacitySlider = document.createElement('input');
opacitySlider.type = 'range';
opacitySlider.min = '0';
opacitySlider.max = '1';
opacitySlider.step = '0.1';
opacitySlider.value = layer.opacity || 1;
opacitySlider.addEventListener('input', (e) => {
this.setLayerOpacity(index, parseFloat(e.target.value));
this.draw();
});
li.appendChild(opacitySlider);
// ロックボタン
const lockButton = document.createElement('button');
lockButton.textContent = layer.locked ? '🔒' : '🔓';
lockButton.addEventListener('click', () => {
this.toggleLayerLock(index);
lockButton.textContent = layer.locked ? '🔒' : '🔓';
});
li.appendChild(lockButton);
} else { // グループの場合
// グループ展開/折りたたみボタン
const toggleButton = document.createElement('button');
toggleButton.textContent = layer.expanded ? '▼' : '▶';
toggleButton.addEventListener('click', () => {
layer.expanded = !layer.expanded;
this.updateLayerList();
});
li.insertBefore(toggleButton, nameSpan);
}
// レイヤー操作ボタン
const buttonContainer = document.createElement('div');
buttonContainer.className = 'layer-buttons';
const upButton = document.createElement('button');
upButton.textContent = '↑';
upButton.className = 'layer-button';
upButton.addEventListener('click', () => this.moveLayerUp(index));
buttonContainer.appendChild(upButton);
const downButton = document.createElement('button');
downButton.textContent = '↓';
downButton.className = 'layer-button';
downButton.addEventListener('click', () => this.moveLayerDown(index));
buttonContainer.appendChild(downButton);
li.appendChild(buttonContainer);
return li;
};
const renderLayers = (layers, indent = 0) => {
layers.forEach((layer, index) => {
const li = createLayerItem(layer, index, indent);
layerList.appendChild(li);
if (layer.layers && layer.expanded) {
renderLayers(layer.layers, indent + 1);
}
});
};
renderLayers(this.layers);
}
ハンドラ
マネージャーやグリッドを跨ぐケースは外に出したい。
しかしこんな単純なことでも結構な問題を引き起こす。
function handleToggleLayerVisibility() {
const targetObj = getSelectedTarget();
if (targetObj && targetObj.type === 'layer') {
targetObj.target.visible = !targetObj.target.visible;
layerManager.updateLayerList();
grid.draw();
}
}
function handleShowOnlySelectedLayer() {
layerManager.layers.forEach((layer, index) => {
layer.visible = (index === layerManager.currentLayerIndex);
});
layerManager.updateLayerList();
grid.draw();
}
トグルスイッチは処理対象がなんであろうとオンオフ動作はするべきである。よって以下のような場合分けは、レイヤー以外を選択している時にトグルスイッチをオンオフしてもハンドラが動かないという問題を起こす。
if (targetObj && targetObj.type === 'layer')
押されたトグルスイッチとCurrentLayerIndexは必ずしも一致しない。
const currentIndex = layerManager.currentLayerIndex;
layerManager.toggleLayerVisibility(currentIndex);
layerManager.updateLayerList();
grid.draw();
セレクトターゲットはレイヤーとは限らず、かといってレイヤーで場合分けしてしまうと前述の理由で正しく機能しない。
const targetObj = getSelectedTarget();
const index = grid.layers.indexOf(targetObj.target);
結論として
トグルスイッチが対応するレイヤーインデックスは
updateLayerListしか知らない
updateLayerList() {
const layerList = document.getElementById('layerList');
layerList.innerHTML = '';
const createLayerItem = (layer, index, indent = 0) => {
const li = document.createElement('li');
li.style.paddingLeft = `${indent * 20}px`;
// 可視性チェックボックス
const visibilityCheckbox = document.createElement('input');
visibilityCheckbox.type = 'checkbox';
visibilityCheckbox.className = 'layer-visibility';
visibilityCheckbox.checked = layer.visible;
visibilityCheckbox.addEventListener('change', () => handleToggleLayerVisibility(index));
li.appendChild(visibilityCheckbox);
function handleToggleLayerVisibility(layerIndex) {
//layerIndexはトグルボタンが対応するレイヤーのインデックス。
//この値はupdateLayerListしか知らない。
layerManager.toggleLayerVisibility(layerIndex);
layerManager.updateLayerList();
grid.draw();
}
リネームもターゲット場合分けは不要
ただしCurrentLayerIndexは使用できる。
function handleRenameCurrentLayer() {
const currentIndex = layerManager.currentLayerIndex;
const newName = prompt('新しいレイヤー名を入力してください:');
if (newName) {
layerManager.renameLayer(currentIndex, newName);
layerManager.updateLayerList();
}
// const targetObj = getSelectedTarget();
// if (targetObj && targetObj.type === 'layer') {
// const index = layerManager.layers.indexOf(targetObj.target);
// const newName = prompt('新しいレイヤー名を入力してください:', targetObj.target.name);
// if (newName) {
// layerManager.renameLayer(index, newName);
// layerManager.updateLayerList();
// }
// } else {
// alert('レイヤーの名前変更は特定のレイヤーを選択している場合のみ可能です。');
// }
}
これらも同じである。
function handleMoveLayerUp(layerIndex) {
layerManager.moveLayerUp(layerIndex);
layerManager.updateLayerList();
grid.draw();
// const targetObj = getSelectedTarget();
// if (targetObj && targetObj.type === 'layer') {
// const index = grid.layers.indexOf(targetObj.target);
// layerManager.moveLayerUp(index);
// layerManager.updateLayerList();
// grid.draw();
// }
}
function handleMoveLayerDown(layerIndex) {
layerManager.moveLayerDown(layerIndex);
layerManager.updateLayerList();
grid.draw();
// const targetObj = getSelectedTarget();
// if (targetObj && targetObj.type === 'layer') {
// const index = grid.layers.indexOf(targetObj.target);
// layerManager.moveLayerDown(index);
// layerManager.updateLayerList(layerIndex);
// grid.draw();
// }
}
processImageForGridAndPalette
ぜんぜん上手くいってないやつ。
function processImageForGridAndPalette(img) {
const method = document.getElementById('extractMethod').value;
const toCurrent = document.getElementById('toCurrentCheckbox').checked;
const toNew = document.getElementById('toNewCheckbox').checked;
const toLayer = document.getElementById('toLayerCheckbox').checked;
const toPalette = document.getElementById('toPaletteCheckbox').checked;
let imageData, colors;
if (toLayer) {
imageData = resizeImage(img, grid.colCount, grid.rowCount);
colors = extractColors(imageData, method);
if (toCurrent) {
applyColorsToGrid(grid, colors, imageData.width, imageData.height);
}
if (toNew) {
const newLayerIndex = grid.addLayer();
applyColorsToGrid(grid, colors, imageData.width, imageData.height, newLayerIndex);
}
grid.draw();
}
if (toPalette) {
const paletteSize = 16;
imageData = resizeImage(img, paletteSize, paletteSize);
colors = extractColors(imageData, method);
if (toCurrent) {
const selectedPaletteId = document.getElementById('palettePicker').value;
if (selectedPaletteId) {
const palette = paletteManager.getPalette(selectedPaletteId);
palette.setColors(colors);
}
}
if (toNew) {
paletteManager.addPalette(paletteSize, 1, 200, 200, "Image Palette", colors);
}
updatePaletteDisplay();
}
updateTargetSelect();
}
なおしたりなおしてもらったり。
function processImageForGridAndPalette(img) {
const method = document.getElementById('extractMethod').value;
const toCurrent = document.getElementById('toCurrentCheckbox').checked;
const toNew = document.getElementById('toNewCheckbox').checked;
const toLayer = document.getElementById('toLayerCheckbox').checked;
const toPalette = document.getElementById('toPaletteCheckbox').checked;
let imageData, colors;
if (toLayer) {
//imgで返る
imageData = resizeImage(img, grid.colCount, grid.rowCount);
colors = extractColors(imageData, method);
if (toCurrent) {
applyColorsToGrid(grid, colors, imageData.width, imageData.height);
}
if (toNew) {
const newLayerIndex = grid.addLayer();
applyColorsToGrid(grid, colors, imageData.width, imageData.height, newLayerIndex);
}
grid.draw();
}
if (toPalette) {
const paletteRowsInput = document.getElementById("paletteRows");
const paletteRowsValue = paletteRowsInput.value;
const paletteColsInput = document.getElementById("paletteCols");
const paletteColsValue = paletteColsInput.value;
imageData = resizeImage(img, paletteColsValue, paletteRowsValue);
colors = extractColors(imageData, method);
// const paletteSize = 16;
// imageData = resizeImage(img, paletteSize, paletteSize);
// colors = extractColors(imageData, method);
if (toCurrent) {
//const selectedPaletteId = document.getElementById('palettePicker').value;
const selectedPaletteId = paletteManager.currentPaletteId;
if (selectedPaletteId) {
const palette = paletteManager.getPalette(selectedPaletteId);
// palette.setColors(colors);
applyColorsToPalette(palette, colors, imageData.width, imageData.height);
}
}
if (toNew) {
// paletteManager.addPalette(paletteRowsValue, paletteColsValue, 200, 200, "Image Palette", colors);
const newPalette = new Palette(paletteRowsValue, paletteColsValue, 200, 200);
applyColorsToPalette(newPalette, colors, imageData.width, imageData.height);
paletteManager.addPalette(paletteRowsValue, paletteColsValue, 200, 200, "Image Palette", newPalette.colors);
}
updatePaletteDisplay();
}
updateTargetSelect();
}
applyColorsToGrid
function applyColorsToGrid(grid, colors, sourceWidth, sourceHeight, layerIndex = null) {
// layerIndex が指定されていない場合は現在のレイヤーを使用
const targetLayer = layerIndex !== null ? grid.layers[layerIndex] : grid.currentLayer;
for (let y = 0; y < sourceHeight; y++) {
for (let x = 0; x < sourceWidth; x++) {
const index = y * sourceWidth + x;
const color = colors[index];
// グリッドの範囲内であることを確認
if (y < grid.rowCount && x < grid.colCount) {
targetLayer.cells[y][x] = color;
}
}
}
}
applyColorsToPalette
function applyColorsToPalette(palette, colors, sourceWidth, sourceHeight) {
const paletteWidth = palette.colCount;
const paletteHeight = palette.rowCount;
const newColors = [];
for (let y = 0; y < paletteHeight; y++) {
for (let x = 0; x < paletteWidth; x++) {
// パレットの座標を元の画像の座標にマッピング
const sourceX = Math.floor(x * sourceWidth / paletteWidth);
const sourceY = Math.floor(y * sourceHeight / paletteHeight);
const index = sourceY * sourceWidth + sourceX;
// 範囲内であることを確認
if (index < colors.length) {
newColors.push(colors[index]);
} else {
// 範囲外の場合は透明色を追加
newColors.push(new Color(255, 255, 255, 0));
}
}
}
// パレットの色を更新
palette.setColors(newColors);
}
この記事が気に入ったらサポートをしてみませんか?