fix(frontend): ウォーターマーク配置のエフェクトが壊れている問題を修正
This commit is contained in:
parent
8cfd147555
commit
5594341218
|
|
@ -9,80 +9,135 @@ const shader = `#version 300 es
|
|||
precision mediump float;
|
||||
|
||||
const float PI = 3.141592653589793;
|
||||
const float TWO_PI = 6.283185307179586;
|
||||
const float HALF_PI = 1.5707963267948966;
|
||||
|
||||
in vec2 in_uv;
|
||||
uniform sampler2D in_texture;
|
||||
uniform vec2 in_resolution;
|
||||
uniform sampler2D u_texture_watermark;
|
||||
uniform vec2 u_resolution_watermark;
|
||||
uniform float u_scale;
|
||||
uniform float u_angle;
|
||||
uniform float u_opacity;
|
||||
uniform bool u_repeat;
|
||||
uniform int u_alignX; // 0: left, 1: center, 2: right
|
||||
uniform int u_alignY; // 0: top, 1: center, 2: bottom
|
||||
uniform float u_alignMargin;
|
||||
uniform int u_fitMode; // 0: contain, 1: cover
|
||||
in vec2 in_uv; // 0..1
|
||||
uniform sampler2D in_texture; // 背景
|
||||
uniform vec2 in_resolution; // 出力解像度(px)
|
||||
|
||||
uniform sampler2D u_watermark; // ウォーターマーク
|
||||
uniform vec2 u_wmResolution; // ウォーターマーク元解像度(px)
|
||||
|
||||
uniform float u_opacity; // 0..1
|
||||
uniform float u_scale; // watermarkのスケール
|
||||
uniform float u_angle; // -1..1 (PI倍)
|
||||
uniform bool u_cover; // cover基準 or fit基準
|
||||
uniform bool u_repeat; // タイル敷き詰め
|
||||
uniform int u_alignX; // 0:left 1:center 2:right
|
||||
uniform int u_alignY; // 0:top 1:center 2:bottom
|
||||
uniform float u_margin; // 余白(比率)
|
||||
uniform bool u_noBBoxExpansion; // 回転時のBounding Box拡張を抑止
|
||||
uniform bool u_wmEnabled; // watermark有効
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
mat2 rot(float a) {
|
||||
float c = cos(a), s = sin(a);
|
||||
return mat2(c, -s, s, c);
|
||||
}
|
||||
|
||||
// cover/fitとscaleから、最終的なサイズ(px)を計算
|
||||
vec2 computeWmSize(vec2 outSize, vec2 wmSize, bool cover, float scale) {
|
||||
float wmAspect = wmSize.x / wmSize.y;
|
||||
float outAspect = outSize.x / outSize.y;
|
||||
vec2 size;
|
||||
if (cover) {
|
||||
if (wmAspect >= outAspect) {
|
||||
size.y = outSize.y * scale;
|
||||
size.x = size.y * wmAspect;
|
||||
} else {
|
||||
size.x = outSize.x * scale;
|
||||
size.y = size.x / wmAspect;
|
||||
}
|
||||
} else {
|
||||
if (wmAspect >= outAspect) {
|
||||
size.x = outSize.x * scale;
|
||||
size.y = size.x / wmAspect;
|
||||
} else {
|
||||
size.y = outSize.y * scale;
|
||||
size.x = size.y * wmAspect;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 in_color = texture(in_texture, in_uv);
|
||||
float in_x_ratio = max(in_resolution.x / in_resolution.y, 1.0);
|
||||
float in_y_ratio = max(in_resolution.y / in_resolution.x, 1.0);
|
||||
vec2 outSize = in_resolution;
|
||||
vec2 p = in_uv * outSize; // 出力のピクセル座標
|
||||
vec4 base = texture(in_texture, in_uv);
|
||||
|
||||
bool contain = u_fitMode == 0;
|
||||
if (!u_wmEnabled) {
|
||||
out_color = base;
|
||||
return;
|
||||
}
|
||||
|
||||
float x_ratio = u_resolution_watermark.x / in_resolution.x;
|
||||
float y_ratio = u_resolution_watermark.y / in_resolution.y;
|
||||
float theta = u_angle * PI; // ラジアン
|
||||
vec2 wmSize = computeWmSize(outSize, u_wmResolution, u_cover, u_scale);
|
||||
vec2 margin = wmSize * u_margin;
|
||||
|
||||
float aspect_ratio = contain ?
|
||||
(min(x_ratio, y_ratio) / max(x_ratio, y_ratio)) :
|
||||
(max(x_ratio, y_ratio) / min(x_ratio, y_ratio));
|
||||
vec4 wmCol = vec4(0.0);
|
||||
bool painted = false;
|
||||
|
||||
float x_scale = contain ?
|
||||
(x_ratio > y_ratio ? 1.0 * u_scale : aspect_ratio * u_scale) :
|
||||
(x_ratio > y_ratio ? aspect_ratio * u_scale : 1.0 * u_scale);
|
||||
if (u_repeat) {
|
||||
// パターンはキャンバス回転と同じ見え方にするため、スクリーン->パターン座標への逆回転を使う
|
||||
vec2 center = outSize * 0.5;
|
||||
vec2 q = center + rot(-theta) * (p - center);
|
||||
vec2 tile = wmSize + margin * 2.0;
|
||||
vec2 local = vec2(mod(q.x, tile.x), mod(q.y, tile.y));
|
||||
|
||||
float y_scale = contain ?
|
||||
(y_ratio > x_ratio ? 1.0 * u_scale : aspect_ratio * u_scale) :
|
||||
(y_ratio > x_ratio ? aspect_ratio * u_scale : 1.0 * u_scale);
|
||||
// modは負値で負になる可能性があるため補正
|
||||
local = mix(local, local + tile, step(local, vec2(0.0)));
|
||||
bool inside = all(greaterThanEqual(local, margin)) && all(lessThan(local, margin + wmSize));
|
||||
if (inside) {
|
||||
vec2 uvWm = (local - margin) / wmSize; // 0..1
|
||||
wmCol = texture(u_watermark, uvWm);
|
||||
painted = true;
|
||||
}
|
||||
} else {
|
||||
// 非リピート: アライメントと回転に従い一枚だけ描画
|
||||
float rotateX = 0.0;
|
||||
float rotateY = 0.0;
|
||||
if (abs(theta) > 1e-6 && !u_noBBoxExpansion) {
|
||||
rotateX = abs(abs(wmSize.x * cos(theta)) + abs(wmSize.y * sin(theta)) - wmSize.x) * 0.5;
|
||||
rotateY = abs(abs(wmSize.x * sin(theta)) + abs(wmSize.y * cos(theta)) - wmSize.y) * 0.5;
|
||||
}
|
||||
|
||||
float x_offset = u_alignX == 0 ? x_scale / 2.0 : u_alignX == 2 ? 1.0 - (x_scale / 2.0) : 0.5;
|
||||
float y_offset = u_alignY == 0 ? y_scale / 2.0 : u_alignY == 2 ? 1.0 - (y_scale / 2.0) : 0.5;
|
||||
float x;
|
||||
if (u_alignX == 1) {
|
||||
x = (outSize.x - wmSize.x) * 0.5;
|
||||
} else if (u_alignX == 0) {
|
||||
x = rotateX + margin.x;
|
||||
} else {
|
||||
x = outSize.x - wmSize.x - margin.x - rotateX;
|
||||
}
|
||||
|
||||
x_offset += (u_alignX == 0 ? 1.0 : u_alignX == 2 ? -1.0 : 0.0) * u_alignMargin;
|
||||
y_offset += (u_alignY == 0 ? 1.0 : u_alignY == 2 ? -1.0 : 0.0) * u_alignMargin;
|
||||
float y;
|
||||
if (u_alignY == 1) {
|
||||
y = (outSize.y - wmSize.y) * 0.5;
|
||||
} else if (u_alignY == 0) {
|
||||
y = rotateY + margin.y;
|
||||
} else {
|
||||
y = outSize.y - wmSize.y - margin.y - rotateY;
|
||||
}
|
||||
|
||||
float angle = -(u_angle * PI);
|
||||
vec2 center = vec2(x_offset, y_offset);
|
||||
//vec2 centeredUv = (in_uv - center) * vec2(in_x_ratio, in_y_ratio);
|
||||
vec2 centeredUv = (in_uv - center);
|
||||
vec2 rotatedUV = vec2(
|
||||
centeredUv.x * cos(angle) - centeredUv.y * sin(angle),
|
||||
centeredUv.x * sin(angle) + centeredUv.y * cos(angle)
|
||||
) + center;
|
||||
|
||||
// trim
|
||||
if (!u_repeat) {
|
||||
bool isInside = rotatedUV.x > x_offset - (x_scale / 2.0) && rotatedUV.x < x_offset + (x_scale / 2.0) &&
|
||||
rotatedUV.y > y_offset - (y_scale / 2.0) && rotatedUV.y < y_offset + (y_scale / 2.0);
|
||||
if (!isInside) {
|
||||
out_color = in_color;
|
||||
return;
|
||||
vec2 rectMin = vec2(x, y);
|
||||
vec2 rectMax = rectMin + wmSize;
|
||||
vec2 rectCenter = (rectMin + rectMax) * 0.5;
|
||||
// 画素をウォーターマークのローカル座標へ(逆回転)
|
||||
vec2 q = rectCenter + rot(-theta) * (p - rectCenter);
|
||||
bool inside = all(greaterThanEqual(q, rectMin)) && all(lessThan(q, rectMax));
|
||||
if (inside) {
|
||||
vec2 uvWm = (q - rectMin) / wmSize;
|
||||
wmCol = texture(u_watermark, uvWm);
|
||||
painted = true;
|
||||
}
|
||||
}
|
||||
|
||||
vec4 watermark_color = texture(u_texture_watermark, vec2(
|
||||
(rotatedUV.x - (x_offset - (x_scale / 2.0))) / x_scale,
|
||||
(rotatedUV.y - (y_offset - (y_scale / 2.0))) / y_scale
|
||||
));
|
||||
|
||||
out_color.r = mix(in_color.r, watermark_color.r, u_opacity * watermark_color.a);
|
||||
out_color.g = mix(in_color.g, watermark_color.g, u_opacity * watermark_color.a);
|
||||
out_color.b = mix(in_color.b, watermark_color.b, u_opacity * watermark_color.a);
|
||||
out_color.a = in_color.a * (1.0 - u_opacity * watermark_color.a) + watermark_color.a * u_opacity;
|
||||
if (painted) {
|
||||
float a = clamp(wmCol.a * u_opacity, 0.0, 1.0);
|
||||
out_color = mix(base, vec4(wmCol.rgb, 1.0), a);
|
||||
} else {
|
||||
out_color = base;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -90,7 +145,7 @@ export const FX_watermarkPlacement = defineImageEffectorFx({
|
|||
id: 'watermarkPlacement',
|
||||
name: '(internal)',
|
||||
shader,
|
||||
uniforms: ['texture_watermark', 'resolution_watermark', 'scale', 'angle', 'opacity', 'repeat', 'alignX', 'alignY', 'alignMargin', 'fitMode'] as const,
|
||||
uniforms: ['opacity', 'scale', 'angle', 'cover', 'repeat', 'alignX', 'alignY', 'margin', 'noBBoxExpansion', 'wmResolution', 'wmEnabled', 'watermark'] as const,
|
||||
params: {
|
||||
cover: {
|
||||
type: 'boolean',
|
||||
|
|
@ -125,29 +180,39 @@ export const FX_watermarkPlacement = defineImageEffectorFx({
|
|||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
noBoundingBoxExpansion: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
watermark: {
|
||||
type: 'texture',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
main: ({ gl, u, params, textures }) => {
|
||||
if (textures.watermark == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, textures.watermark.texture);
|
||||
gl.uniform1i(u.texture_watermark, 1);
|
||||
|
||||
gl.uniform2fv(u.resolution_watermark, [textures.watermark.width, textures.watermark.height]);
|
||||
gl.uniform1f(u.scale, params.scale);
|
||||
|
||||
gl.uniform1f(u.opacity, params.opacity);
|
||||
gl.uniform1f(u.angle, params.angle);
|
||||
// 基本パラメータ
|
||||
gl.uniform1f(u.opacity, params.opacity ?? 1.0);
|
||||
gl.uniform1f(u.scale, params.scale ?? 0.3);
|
||||
gl.uniform1f(u.angle, params.angle ?? 0.0);
|
||||
gl.uniform1i(u.cover, params.cover ? 1 : 0);
|
||||
gl.uniform1i(u.repeat, params.repeat ? 1 : 0);
|
||||
gl.uniform1i(u.alignX, params.align.x === 'left' ? 0 : params.align.x === 'right' ? 2 : 1);
|
||||
gl.uniform1i(u.alignY, params.align.y === 'top' ? 0 : params.align.y === 'bottom' ? 2 : 1);
|
||||
gl.uniform1f(u.alignMargin, params.align.margin ?? 0);
|
||||
gl.uniform1i(u.fitMode, params.cover ? 1 : 0);
|
||||
const ax = params.align?.x === 'left' ? 0 : params.align?.x === 'center' ? 1 : 2;
|
||||
const ay = params.align?.y === 'top' ? 0 : params.align?.y === 'center' ? 1 : 2;
|
||||
gl.uniform1i(u.alignX, ax);
|
||||
gl.uniform1i(u.alignY, ay);
|
||||
gl.uniform1f(u.margin, (params.align?.margin ?? 0));
|
||||
gl.uniform1i(u.noBBoxExpansion, params.noBoundingBoxExpansion ? 1 : 0);
|
||||
|
||||
// ウォーターマークテクスチャ
|
||||
const wm = textures.watermark;
|
||||
if (wm) {
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, wm.texture);
|
||||
gl.uniform1i(u.watermark, 1);
|
||||
gl.uniform2f(u.wmResolution, wm.width, wm.height);
|
||||
gl.uniform1i(u.wmEnabled, 1);
|
||||
} else {
|
||||
gl.uniform1i(u.wmEnabled, 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue