#pragma warning restore SA1516 // Elements should be separated by blank line /// <inheritdoc/> internal override MagickImage GenerateToonLitImage() { using (var disposables = new CompositeDisposable()) { var main = CompositeMainImage(); if (DecalEnable) { var decal = CompositeDecalImage(); disposables.Add(decal); MagickImageUtility.ResizeForLarger(main, decal); var decalScaleToAspectFill = main.Width / (float)Math.Min(decal.Width, decal.Height); var decalPercentageX = new Percentage(decalScaleToAspectFill * DecalScaleX * 100.0); var decalPercentageY = new Percentage(decalScaleToAspectFill * DecalScaleY * 100.0); decal.Scale(decalPercentageX, decalPercentageY); decal.BackgroundColor = MagickColors.Transparent; decal.Rotate(DecalRotation); var decalX = Mathf.RoundToInt(main.Width * DecalPositionX - decal.Width / 2); var decalY = main.Height - Mathf.RoundToInt(main.Height * DecalPositionY + decal.Height / 2); // Draw basic decal as decalCanvas with mirror mode var decalCanvas = new MagickImage(MagickColors.Transparent, main.Width, main.Height); disposables.Add(decalCanvas); switch (DecalMirrorMode) { case SunaoDecalMirrorMode.Normal: // 仕様上、ミラー側をコントロールすることができないので通常と同じにする case SunaoDecalMirrorMode.Fixed: case SunaoDecalMirrorMode.Mirror1: decalCanvas.Composite(decal, decalX, decalY, CompositeOperator.Over); break; // ミラー側のデカールを反転させることが設定の意図なので、反転させる case SunaoDecalMirrorMode.Mirror2: decal.Flop(); decalCanvas.Composite(decal, decalX, decalY, CompositeOperator.Over); break; case SunaoDecalMirrorMode.CopyMirror: using (var halfDecal = new MagickImage(MagickColors.Transparent, main.Width, main.Height)) { var leftIsNormal = DecalPositionX < 0.5; halfDecal.Composite(decal, decalX, decalY, CompositeOperator.Over); var crop = new MagickGeometry { X = leftIsNormal ? 0 : decalCanvas.Width / 2, Y = 0, Width = decalCanvas.Width / 2, Height = decalCanvas.Height, }; halfDecal.Crop(crop); var normalX = leftIsNormal ? 0 : decalCanvas.Width / 2; var copyX = leftIsNormal ? decalCanvas.Width / 2 : 0; decalCanvas.Composite(halfDecal, normalX, 0, CompositeOperator.Over); halfDecal.Flop(); decalCanvas.Composite(halfDecal, copyX, 0, CompositeOperator.Over); } break; case SunaoDecalMirrorMode.CopyFixed: using (var leftDecal = new MagickImage(MagickColors.Transparent, main.Width / 2, main.Height)) { if (DecalPositionX < 0.5) { leftDecal.Composite(decal, decalX, decalY, CompositeOperator.Over); } else { using (var mirror = decal.Clone()) { mirror.Flop(); leftDecal.Composite(mirror, Mathf.RoundToInt(main.Width * (1 - DecalPositionX) - decal.Width / 2), decalY, CompositeOperator.Over); } } decalCanvas.Composite(leftDecal, 0, 0, CompositeOperator.Over); } using (var rightDecal = new MagickImage(MagickColors.Transparent, main.Width, main.Height)) { if (DecalPositionX < 0.5) { rightDecal.Composite(decal, Mathf.RoundToInt(main.Width * (1 - DecalPositionX) - decal.Width / 2), 0, CompositeOperator.Over); } else { using (var mirror = decal.Clone()) { mirror.Flop(); rightDecal.Composite(mirror, decalX, decalY, CompositeOperator.Over); } } rightDecal.Crop(new MagickGeometry { X = main.Width / 2, Y = 0, Width = decalCanvas.Width / 2, Height = decalCanvas.Height, }); decalCanvas.Composite(rightDecal, main.Width / 2, 0, CompositeOperator.Over); } break; default: throw new Exception($"Unhandled DecalMirrorMode: {DecalMirrorMode}"); } // Apply decal switch (DecalMode) { case SunaoDecalMode.Override: main.Composite(decalCanvas, CompositeOperator.Over); break; case SunaoDecalMode.Add: main.Composite(decalCanvas, CompositeOperator.Plus); break; case SunaoDecalMode.Multiply: main.Composite(decalCanvas, CompositeOperator.Multiply); break; case SunaoDecalMode.MultiplyMono: using (var mono = CompositeMainForMultiplyMonoDecalMode()) { decalCanvas.Composite(mono, CompositeOperator.Multiply); } main.Composite(decalCanvas, CompositeOperator.Over); break; case SunaoDecalMode.EmissiveAdd: using (var pc = decalCanvas.GetPixels()) { var values = pc.GetValues(); var channels = pc.Channels; Parallel.For(0, values.Length / channels, (index) => { var baseIndex = index * channels; var r = (float)values[baseIndex + 0]; var g = (float)values[baseIndex + 1]; var b = (float)values[baseIndex + 2]; var a = (float)values[baseIndex + 3]; var mono = (0.2126f * r) + (0.7152f * g) + (0.0722f * b); values[baseIndex + 0] = Saturate(r * a / Quantum.Max); values[baseIndex + 1] = Saturate(g * a / Quantum.Max); values[baseIndex + 2] = Saturate(b * a / Quantum.Max); values[baseIndex + 3] = Saturate(a); }); pc.SetPixels(values); } main.Composite(decalCanvas, CompositeOperator.Plus); break; case SunaoDecalMode.EmissiveOverride: main.Composite(decalCanvas, CompositeOperator.Over); using (var pc = decalCanvas.GetPixels()) { var values = pc.GetValues(); var channels = pc.Channels; var intensity = DecalEmissionIntensity; Parallel.For(0, values.Length / channels, (index) => { var baseIndex = index * channels; var r = (float)values[baseIndex + 0]; var g = (float)values[baseIndex + 1]; var b = (float)values[baseIndex + 2]; var a = (float)values[baseIndex + 3]; values[baseIndex + 0] = Saturate(r * a * intensity / Quantum.Max); values[baseIndex + 1] = Saturate(g * a * intensity / Quantum.Max); values[baseIndex + 2] = Saturate(b * a * intensity / Quantum.Max); }); pc.SetPixels(values); } main.Composite(decalCanvas, CompositeOperator.Plus); break; default: throw new Exception($"Unhandled DecalMode: {DecalMode}"); } } // Apply emission if (EmissionEnable) { var emission = CompositeEmissionImage(); disposables.Add(emission); MagickImageUtility.ResizeForLarger(main, emission); switch (EmissionMode) { case SunaoEmissionMode.Add: main.Composite(emission, CompositeOperator.Plus); break; case SunaoEmissionMode.Multiply: using (var multiply = new MagickImage(MagickColors.White, emission.Width, emission.Height)) using (var emi = main.Clone()) using (var reflectionOffset = new MagickImage(MagickColor.FromRgb(13, 13, 13), multiply.Width, multiply.Height)) { multiply.Composite(emission, CompositeOperator.MinusSrc); emi.Composite(reflectionOffset, CompositeOperator.Plus); emi.Composite(emission, CompositeOperator.Multiply); main.Composite(multiply, CompositeOperator.Multiply); main.Composite(emi, CompositeOperator.Plus); } break; case SunaoEmissionMode.Minus: main.Composite(emission, CompositeOperator.MinusSrc); break; default: throw new Exception($"Unhandled EmissionMode: {EmissionMode}"); } } // Apply gamma if (GammaFixEnable) { var powR = 1.0f / (1.0f / Mathf.Max(GammaR, 0.00001f)); var powG = 1.0f / (1.0f / Mathf.Max(GammaG, 0.00001f)); var powB = 1.0f / (1.0f / Mathf.Max(GammaB, 0.00001f)); using (var pc = main.GetPixels()) { var values = pc.GetValues(); var channels = pc.Channels; Parallel.For(0, values.Length / channels, (index) => { var baseIndex = index * channels; var r = Mathf.Pow(values[baseIndex + 0] / (float)Quantum.Max, powR) * Quantum.Max; var g = Mathf.Pow(values[baseIndex + 1] / (float)Quantum.Max, powG) * Quantum.Max; var b = Mathf.Pow(values[baseIndex + 2] / (float)Quantum.Max, powB) * Quantum.Max; values[baseIndex + 0] = Saturate(r); values[baseIndex + 1] = Saturate(g); values[baseIndex + 2] = Saturate(b); }); pc.SetPixels(values); } } // Apply brightness if (BrightnessFixEnable) { using (var pc = main.GetPixels()) { var offset = BrightnessOffset * Quantum.Max; var values = pc.GetValues(); var channels = pc.Channels; var brightness = OutputBrightness; Parallel.For(0, values.Length / channels, (index) => { var baseIndex = index * channels; var r = values[baseIndex + 0] * brightness + offset; var g = values[baseIndex + 1] * brightness + offset; var b = values[baseIndex + 2] * brightness + offset; values[baseIndex + 0] = Saturate(r); values[baseIndex + 1] = Saturate(g); values[baseIndex + 2] = Saturate(b); }); pc.SetPixels(values); } } // Apply limitter if (OutputLimitterEnable) { using (var pc = main.GetPixels()) { var limit = LimitterMax; var values = pc.GetValues(); var channels = pc.Channels; Parallel.For(0, values.Length / channels, (index) => { // OUT.rgb = min(OUT.rgb , _LimitterMax) の詳細が分からない // hsv化してv^2を制限するとそれっぽくなる var baseIndex = index * channels; var r = values[baseIndex + 0]; var g = values[baseIndex + 1]; var b = values[baseIndex + 2]; var c = new Color( r / (float)Quantum.Max, g / (float)Quantum.Max, b / (float)Quantum.Max); Color.RGBToHSV(c, out float h, out float s, out float v); var limitedV2 = Mathf.Min(limit, v * v); var limited = Color.HSVToRGB(h, s, (float)Math.Sqrt(limitedV2)); values[baseIndex + 0] = (ushort)(limited.r * Quantum.Max); values[baseIndex + 1] = (ushort)(limited.g * Quantum.Max); values[baseIndex + 2] = (ushort)(limited.b * Quantum.Max); });