Esempio n. 1
0
#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);
                        });