画像フィルター完全ガイド|プロ級エフェクトとアート効果の実装技術
画像フィルターの仕組み、Instagram風フィルター、アート効果、カラーグレーディング、リアルタイム処理、GPUアクセラレーション、カスタムフィルター作成まで、4500字で徹底解説
画像フィルター完全ガイド
はじめに:画像フィルターの芸術と科学
画像フィルターは、写真を芸術作品に変換する強力なツールです。SNSの普及により、誰もがプロフェッショナルな画像編集を行える時代になりました。本記事では、画像フィルターの技術的な仕組みから、Instagram風エフェクト、アート効果、GPUを活用した高速処理まで、実装技術を包括的に解説します。
💡 統計データ: Instagram 2024レポートによると、投稿画像の89%にフィルターが適用され、フィルター使用画像は平均38%多いエンゲージメントを獲得しています。
第1章:画像フィルターの基礎理論
1.1 カラー変換の数学
RGB色空間での変換行列
class ColorTransform {
// カラーマトリックス変換
applyColorMatrix(pixels, matrix) {
const data = pixels.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
data[i] = r * matrix[0] + g * matrix[1] + b * matrix[2] + matrix[3] * 255;
data[i + 1] = r * matrix[4] + g * matrix[5] + b * matrix[6] + matrix[7] * 255;
data[i + 2] = r * matrix[8] + g * matrix[9] + b * matrix[10] + matrix[11] * 255;
// クランプ処理
data[i] = Math.max(0, Math.min(255, data[i]));
data[i + 1] = Math.max(0, Math.min(255, data[i + 1]));
data[i + 2] = Math.max(0, Math.min(255, data[i + 2]));
}
return pixels;
}
// 基本フィルター行列
getFilterMatrices() {
return {
grayscale: [
0.299, 0.587, 0.114, 0,
0.299, 0.587, 0.114, 0,
0.299, 0.587, 0.114, 0,
0, 0, 0, 1
],
sepia: [
0.393, 0.769, 0.189, 0,
0.349, 0.686, 0.168, 0,
0.272, 0.534, 0.131, 0,
0, 0, 0, 1
],
vintage: [
0.6, 0.3, 0.1, 0.03,
0.2, 0.7, 0.1, 0.02,
0.2, 0.3, 0.5, 0.03,
0, 0, 0, 1
],
polaroid: [
1.438, -0.062, -0.062, 0,
-0.122, 1.378, -0.122, 0,
-0.016, -0.016, 1.483, 0,
0, 0, 0, 1
]
};
}
}
1.2 畳み込みフィルター
エッジ検出とぼかし効果
class ConvolutionFilter {
applyKernel(imageData, kernel, kernelSize) {
const pixels = imageData.data;
const width = imageData.width;
const height = imageData.height;
const output = new Uint8ClampedArray(pixels);
const half = Math.floor(kernelSize / 2);
for (let y = half; y < height - half; y++) {
for (let x = half; x < width - half; x++) {
let r = 0, g = 0, b = 0;
for (let ky = 0; ky < kernelSize; ky++) {
for (let kx = 0; kx < kernelSize; kx++) {
const px = x + kx - half;
const py = y + ky - half;
const idx = (py * width + px) * 4;
const weight = kernel[ky * kernelSize + kx];
r += pixels[idx] * weight;
g += pixels[idx + 1] * weight;
b += pixels[idx + 2] * weight;
}
}
const outputIdx = (y * width + x) * 4;
output[outputIdx] = r;
output[outputIdx + 1] = g;
output[outputIdx + 2] = b;
}
}
imageData.data.set(output);
return imageData;
}
getKernels() {
return {
blur: [
1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9
],
gaussianBlur: [
1/16, 2/16, 1/16,
2/16, 4/16, 2/16,
1/16, 2/16, 1/16
],
sharpen: [
0, -1, 0,
-1, 5, -1,
0, -1, 0
],
edgeDetect: [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
],
emboss: [
-2, -1, 0,
-1, 1, 1,
0, 1, 2
]
};
}
}
第2章:Instagram風フィルターの実装
2.1 人気フィルターの再現
Instagram風エフェクト
class InstagramFilters {
// Clarendon フィルター
clarendon(canvas) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// コントラスト増加
this.adjustContrast(imageData, 1.2);
// 彩度増加
this.adjustSaturation(imageData, 1.35);
// シアンのオーバーレイ
this.applyOverlay(imageData, { r: 127, g: 187, b: 227 }, 0.2);
ctx.putImageData(imageData, 0, 0);
}
// Nashville フィルター
nashville(canvas) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 暖色系シフト
this.applyColorShift(imageData, {
red: 20,
green: 10,
blue: -30
});
// コントラスト調整
this.adjustContrast(imageData, 1.2);
// ピンクのオーバーレイ
this.applyOverlay(imageData, { r: 247, g: 218, b: 174 }, 0.3);
// ヴィンテージ効果
this.addVignette(canvas);
ctx.putImageData(imageData, 0, 0);
}
// Valencia フィルター
valencia(canvas) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 暖色トーン
const warmMatrix = [
1.1, 0.05, 0, 0.08,
0, 1.05, 0.05, 0.08,
0, 0, 0.95, 0.08,
0, 0, 0, 1
];
this.applyColorMatrix(imageData, warmMatrix);
// 彩度とコントラスト
this.adjustSaturation(imageData, 1.2);
this.adjustContrast(imageData, 1.08);
ctx.putImageData(imageData, 0, 0);
}
// X-Pro II フィルター
xpro2(canvas) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// カラーカーブ調整
this.applyCurves(imageData, {
red: this.createCurve([0, 0], [100, 150], [255, 255]),
green: this.createCurve([0, 0], [100, 100], [255, 255]),
blue: this.createCurve([0, 30], [100, 80], [255, 255])
});
// 強いヴィネット
this.addVignette(canvas, 0.6);
ctx.putImageData(imageData, 0, 0);
}
// ヘルパー関数
adjustContrast(imageData, factor) {
const data = imageData.data;
factor = (factor * 255 - 255) / 255;
for (let i = 0; i < data.length; i += 4) {
data[i] = data[i] + (data[i] - 128) * factor;
data[i + 1] = data[i + 1] + (data[i + 1] - 128) * factor;
data[i + 2] = data[i + 2] + (data[i + 2] - 128) * factor;
}
}
adjustSaturation(imageData, saturation) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
data[i] = gray + (data[i] - gray) * saturation;
data[i + 1] = gray + (data[i + 1] - gray) * saturation;
data[i + 2] = gray + (data[i + 2] - gray) * saturation;
}
}
addVignette(canvas, strength = 0.4) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const gradient = ctx.createRadialGradient(
width / 2, height / 2, 0,
width / 2, height / 2, Math.sqrt(width * width + height * height) / 2
);
gradient.addColorStop(0, `rgba(0,0,0,0)`);
gradient.addColorStop(0.5, `rgba(0,0,0,${strength * 0.2})`);
gradient.addColorStop(1, `rgba(0,0,0,${strength})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
}
2.2 VSCOスタイルフィルター
フィルムエミュレーション
class VSCOFilters {
// フィルムエミュレーション
filmEmulation(canvas, filmType) {
const films = {
'Fuji Velvia': {
curves: {
red: [[0, 0], [64, 56], [128, 128], [192, 196], [255, 255]],
green: [[0, 0], [64, 61], [128, 128], [192, 194], [255, 255]],
blue: [[0, 0], [64, 68], [128, 128], [192, 188], [255, 255]]
},
saturation: 1.4,
contrast: 1.2
},
'Kodak Portra': {
curves: {
red: [[0, 0], [64, 70], [128, 135], [192, 195], [255, 255]],
green: [[0, 0], [64, 65], [128, 128], [192, 190], [255, 255]],
blue: [[0, 0], [64, 62], [128, 124], [192, 188], [255, 245]]
},
saturation: 0.9,
contrast: 1.05
},
'Ilford HP5': {
grayscale: true,
curves: {
all: [[0, 10], [64, 58], [128, 134], [192, 200], [255, 250]]
},
grain: true,
contrast: 1.3
}
};
const film = films[filmType];
if (!film) return;
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
if (film.grayscale) {
this.toGrayscale(imageData);
}
this.applyCurvesAdjustment(imageData, film.curves);
if (film.saturation) {
this.adjustSaturation(imageData, film.saturation);
}
if (film.contrast) {
this.adjustContrast(imageData, film.contrast);
}
if (film.grain) {
this.addFilmGrain(imageData);
}
ctx.putImageData(imageData, 0, 0);
}
// フィルムグレイン効果
addFilmGrain(imageData, intensity = 0.1) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const noise = (Math.random() - 0.5) * intensity * 255;
data[i] = Math.max(0, Math.min(255, data[i] + noise));
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + noise));
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + noise));
}
}
// カーブ調整
applyCurvesAdjustment(imageData, curves) {
const data = imageData.data;
const lookupTables = {};
// ルックアップテーブルの作成
for (const channel in curves) {
lookupTables[channel] = this.createLookupTable(curves[channel]);
}
for (let i = 0; i < data.length; i += 4) {
if (lookupTables.red) {
data[i] = lookupTables.red[data[i]];
}
if (lookupTables.green) {
data[i + 1] = lookupTables.green[data[i + 1]];
}
if (lookupTables.blue) {
data[i + 2] = lookupTables.blue[data[i + 2]];
}
if (lookupTables.all) {
data[i] = lookupTables.all[data[i]];
data[i + 1] = lookupTables.all[data[i + 1]];
data[i + 2] = lookupTables.all[data[i + 2]];
}
}
}
createLookupTable(points) {
const lut = new Uint8Array(256);
// スプライン補間
for (let i = 0; i < 256; i++) {
lut[i] = this.cubicSplineInterpolate(points, i);
}
return lut;
}
}
第3章:アート効果フィルター
3.1 油絵効果
油絵風レンダリング
class ArtisticFilters {
oilPainting(canvas, brushSize = 4, intensity = 20) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = imageData.width;
const height = imageData.height;
const output = new Uint8ClampedArray(data);
for (let y = brushSize; y < height - brushSize; y++) {
for (let x = brushSize; x < width - brushSize; x++) {
const intensityBins = new Array(intensity + 1).fill(null).map(() => ({
r: 0, g: 0, b: 0, count: 0
}));
// 周辺ピクセルを強度ビンに分類
for (let dy = -brushSize; dy <= brushSize; dy++) {
for (let dx = -brushSize; dx <= brushSize; dx++) {
const idx = ((y + dy) * width + (x + dx)) * 4;
// 強度を計算
const intensity = Math.floor(
((data[idx] + data[idx + 1] + data[idx + 2]) / 3) *
intensity / 255
);
intensityBins[intensity].r += data[idx];
intensityBins[intensity].g += data[idx + 1];
intensityBins[intensity].b += data[idx + 2];
intensityBins[intensity].count++;
}
}
// 最も頻度の高い強度ビンを選択
let maxBin = intensityBins[0];
for (const bin of intensityBins) {
if (bin.count > maxBin.count) {
maxBin = bin;
}
}
const outputIdx = (y * width + x) * 4;
if (maxBin.count > 0) {
output[outputIdx] = maxBin.r / maxBin.count;
output[outputIdx + 1] = maxBin.g / maxBin.count;
output[outputIdx + 2] = maxBin.b / maxBin.count;
output[outputIdx + 3] = 255;
}
}
}
imageData.data.set(output);
ctx.putImageData(imageData, 0, 0);
}
// 水彩画効果
watercolor(canvas) {
const ctx = canvas.getContext('2d');
// ステップ1: エッジ保持平滑化
this.bilateralFilter(canvas);
// ステップ2: 色の単純化
this.quantizeColors(canvas, 8);
// ステップ3: 紙テクスチャの追加
this.addPaperTexture(canvas);
// ステップ4: エッジの強調
const edges = this.detectEdges(canvas);
ctx.globalCompositeOperation = 'multiply';
ctx.drawImage(edges, 0, 0);
}
// ペン画効果
penSketch(canvas) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// グレースケール変換
const imageData = ctx.getImageData(0, 0, width, height);
this.toGrayscale(imageData);
ctx.putImageData(imageData, 0, 0);
// エッジ検出
const edges = this.sobelEdgeDetection(canvas);
// ハッチング効果
const hatching = this.createHatching(edges);
// 合成
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.drawImage(hatching, 0, 0);
}
createHatching(edgeCanvas) {
const ctx = edgeCanvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, edgeCanvas.width, edgeCanvas.height);
const data = imageData.data;
const hatchCanvas = document.createElement('canvas');
hatchCanvas.width = edgeCanvas.width;
hatchCanvas.height = edgeCanvas.height;
const hatchCtx = hatchCanvas.getContext('2d');
hatchCtx.strokeStyle = 'black';
hatchCtx.lineWidth = 0.5;
for (let y = 0; y < edgeCanvas.height; y += 2) {
for (let x = 0; x < edgeCanvas.width; x += 2) {
const idx = (y * edgeCanvas.width + x) * 4;
const brightness = data[idx] / 255;
if (brightness < 0.8) {
const lineCount = Math.floor((1 - brightness) * 4);
for (let i = 0; i < lineCount; i++) {
const angle = (brightness * Math.PI) + (i * Math.PI / 4);
const dx = Math.cos(angle) * 2;
const dy = Math.sin(angle) * 2;
hatchCtx.beginPath();
hatchCtx.moveTo(x - dx, y - dy);
hatchCtx.lineTo(x + dx, y + dy);
hatchCtx.stroke();
}
}
}
}
return hatchCanvas;
}
}
3.2 ポップアート効果
Andy Warhol風エフェクト
class PopArtFilter {
warholEffect(canvas, colors = 4) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// ポスタリゼーション
const posterized = this.posterize(canvas, colors);
// カラーパレット
const palettes = [
['#FF00FF', '#00FFFF', '#FFFF00', '#000000'],
['#FF0000', '#00FF00', '#0000FF', '#FFFFFF'],
['#FFA500', '#FF1493', '#00CED1', '#FFD700'],
['#8A2BE2', '#32CD32', '#FF69B4', '#1E90FF']
];
// 4分割グリッド作成
const gridCanvas = document.createElement('canvas');
gridCanvas.width = width * 2;
gridCanvas.height = height * 2;
const gridCtx = gridCanvas.getContext('2d');
for (let row = 0; row < 2; row++) {
for (let col = 0; col < 2; col++) {
const palette = palettes[row * 2 + col];
const colored = this.applyColorPalette(posterized, palette);
gridCtx.drawImage(
colored,
col * width,
row * height,
width,
height
);
}
}
// 元のキャンバスにリサイズして描画
ctx.drawImage(gridCanvas, 0, 0, width, height);
}
posterize(canvas, levels) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const factor = 255 / (levels - 1);
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.round(data[i] / factor) * factor;
data[i + 1] = Math.round(data[i + 1] / factor) * factor;
data[i + 2] = Math.round(data[i + 2] / factor) * factor;
}
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
tempCanvas.getContext('2d').putImageData(imageData, 0, 0);
return tempCanvas;
}
applyColorPalette(canvas, palette) {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const ctx = tempCanvas.getContext('2d');
ctx.drawImage(canvas, 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 gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
const index = Math.floor(gray * (palette.length - 1) / 255);
const color = this.hexToRgb(palette[index]);
data[i] = color.r;
data[i + 1] = color.g;
data[i + 2] = color.b;
}
ctx.putImageData(imageData, 0, 0);
return tempCanvas;
}
hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
}
第4章:WebGLによる高速フィルター処理
4.1 GPUアクセラレーション
WebGLシェーダーでの実装
class WebGLFilters {
constructor(canvas) {
this.gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
this.setupShaders();
}
setupShaders() {
// 頂点シェーダー
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0, 1);
v_texCoord = a_texCoord;
}
`;
// フラグメントシェーダー(ブラー効果)
const blurFragmentShader = `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_blurRadius;
varying vec2 v_texCoord;
void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
vec4 colorSum = vec4(0.0);
float weightSum = 0.0;
for (float x = -u_blurRadius; x <= u_blurRadius; x += 1.0) {
for (float y = -u_blurRadius; y <= u_blurRadius; y += 1.0) {
float weight = exp(-(x*x + y*y) / (2.0 * u_blurRadius * u_blurRadius));
colorSum += texture2D(u_image, v_texCoord + onePixel * vec2(x, y)) * weight;
weightSum += weight;
}
}
gl_FragColor = colorSum / weightSum;
}
`;
this.blurProgram = this.createProgram(vertexShaderSource, blurFragmentShader);
}
createProgram(vertexSource, fragmentSource) {
const gl = this.gl;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
return program;
}
applyFilter(image, filterType, params) {
const gl = this.gl;
// テクスチャ作成
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// プログラム使用
gl.useProgram(this.blurProgram);
// ユニフォーム設定
const textureSizeLocation = gl.getUniformLocation(this.blurProgram, 'u_textureSize');
gl.uniform2f(textureSizeLocation, image.width, image.height);
const blurRadiusLocation = gl.getUniformLocation(this.blurProgram, 'u_blurRadius');
gl.uniform1f(blurRadiusLocation, params.radius || 5.0);
// 描画
this.render();
}
render() {
const gl = this.gl;
// ビューポート設定
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// クリア
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 描画
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
}
第5章:カスタムフィルターの作成
5.1 ユーザー定義フィルター
カスタムフィルタービルダー
class CustomFilterBuilder {
constructor() {
this.pipeline = [];
}
addStep(operation, params) {
this.pipeline.push({ operation, params });
return this;
}
brightness(value) {
return this.addStep('brightness', { value });
}
contrast(value) {
return this.addStep('contrast', { value });
}
saturation(value) {
return this.addStep('saturation', { value });
}
hue(degrees) {
return this.addStep('hue', { degrees });
}
colorMatrix(matrix) {
return this.addStep('colorMatrix', { matrix });
}
curves(curveData) {
return this.addStep('curves', { curveData });
}
blend(blendImage, mode, opacity) {
return this.addStep('blend', { blendImage, mode, opacity });
}
apply(canvas) {
const ctx = canvas.getContext('2d');
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (const step of this.pipeline) {
imageData = this.executeStep(imageData, step);
}
ctx.putImageData(imageData, 0, 0);
}
executeStep(imageData, step) {
const operations = {
brightness: (data, params) => {
const factor = params.value;
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.min(255, data[i] * factor);
data[i + 1] = Math.min(255, data[i + 1] * factor);
data[i + 2] = Math.min(255, data[i + 2] * factor);
}
},
contrast: (data, params) => {
const factor = (params.value + 100) / 100;
const intercept = 128 * (1 - factor);
for (let i = 0; i < data.length; i += 4) {
data[i] = data[i] * factor + intercept;
data[i + 1] = data[i + 1] * factor + intercept;
data[i + 2] = data[i + 2] * factor + intercept;
}
},
hue: (data, params) => {
const angle = params.degrees * Math.PI / 180;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
for (let i = 0; i < data.length; i += 4) {
const [h, s, l] = this.rgbToHsl(data[i], data[i + 1], data[i + 2]);
const newHue = (h + params.degrees / 360) % 1;
const [r, g, b] = this.hslToRgb(newHue, s, l);
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
}
}
};
const operation = operations[step.operation];
if (operation) {
operation(imageData.data, step.params);
}
return imageData;
}
// 色空間変換ヘルパー
rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const l = (max + min) / 2;
if (max === min) {
return [0, 0, l];
}
const d = max - min;
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
let h;
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
return [h, s, l];
}
hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
}
// 使用例
const customFilter = new CustomFilterBuilder()
.brightness(1.2)
.contrast(1.1)
.saturation(1.3)
.hue(10)
.colorMatrix([
1.1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0.9, 0,
0, 0, 0, 1
])
.apply(canvas);
보안 및 개인정보 보호
모든 처리는 브라우저 내에서 완료되며 데이터는 외부로 전송되지 않습니다. 개인정보나 기밀 데이터도 안심하고 이용할 수 있습니다.
문제 해결
일반적인 문제
- 작동하지 않음: 브라우저 캐시를 지우고 새로고침
- 처리 속도 느림: 파일 크기 확인 (권장 20MB 이하)
- 예상과 다른 결과: 입력 형식 및 설정 확인
문제가 해결되지 않으면 브라우저를 최신 버전으로 업데이트하거나 다른 브라우저를 시도하세요.
まとめ:プロフェッショナルな画像フィルター活用
画像フィルターは、写真を芸術作品に変換する強力なツールです。以下のポイントを押さえることで、効果的な実装を実現できます:
- 基礎理論の理解:カラーマトリックスと畳み込みフィルター
- 人気エフェクトの実装:Instagram風、VSCO風フィルター
- アート効果の活用:油絵、水彩、ペン画効果
- パフォーマンス最適化:WebGLによるGPUアクセラレーション
- カスタマイズ性:ユーザー定義フィルターの作成
i4uの画像フィルターツールを活用することで、簡単にプロ級のエフェクトを適用できます。
カテゴリ別ツール
他のツールもご覧ください:
関連ツール
관련 기사
OCR 도구 완벽 가이드 2025|이미지에서 고정밀 텍스트 추출
이미지와 PDF에서 즉시 텍스트 추출. 일본어, 영어, 중국어, 한국어를 지원하는 고정밀 OCR 도구. 명함 데이터화, 문서 디지털화, 스캔 문서 편집에 최적. 브라우저 완결형으로 개인정보 보호.
2025年最新!AIブログアイデアジェネレーターの選び方と活用法완벽 가이드
ブログのネタ切れに悩むあなたへ。AIブログアイデアジェネレーターを使って無限のコンテンツアイデアを生み出す方法を、実例とともに徹底解説します。
2025年最新!AI画像アップスケーラー완벽 가이드|低解像度画像を高画質化する方法
古い写真や低解像度画像を最新のAI技術で高画質化。無料で使えるi4u AI画像アップスケーラーの使い方から、プロレベルの活用テクニックまで徹底解説します。