Exemple #1
0
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _workflow.Calculate();

                var triangulation = SurfaceModel.GetVisualModel(_workflow.CutTriangulatedTopHorizon);

                modelVisual.Content = triangulation;

                viewPort.ZoomExtents(0);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Unable to process request:\n{ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
Exemple #2
0
        /// <summary>
        /// 立ち絵から顔画像を生成し、MagickImageオブジェクトを返す
        /// </summary>
        /// <param name="faceWidth">顔画像の幅</param>
        /// <param name="faceHeight">顔画像の高さ</param>
        /// <param name="faceTrimRange">顔画像の範囲指定 (left, top, width, height) nullを指定した場合は自動処理</param>
        public virtual MagickImage DrawFaceImage(SurfaceModel surfaceModel, int faceWidth, int faceHeight, Tuple <int, int, int, int> faceTrimRange)
        {
            // まずは立ち絵画像を生成
            var surface = DrawSurface(surfaceModel, trim: false); // 余白はこの段階では切らない

            // 顔画像範囲指定があれば、立ち絵をその範囲で切り抜く
            if (faceTrimRange != null)
            {
                // 切り抜き前のチェック
                CheckFaceTrimRangeBeforeDrawing(surface, faceTrimRange);

                surface.Crop(new ImageMagick.MagickGeometry(faceTrimRange.Item1, faceTrimRange.Item2, faceTrimRange.Item3, faceTrimRange.Item4));
                surface.RePage(); // ページ範囲を更新
            }
            else
            {
                // 顔画像範囲指定がなければ、余白の削除のみ行う
                surface.Trim();
                surface.RePage(); // ページ範囲を更新
            }

            // 縮小率を決定 (幅が収まるように縮小する)
            var scaleRate = faceWidth / (double)surface.Width;

            if (scaleRate > 1.0)
            {
                scaleRate = 1.0; // 拡大はしない
            }

            // リサイズ処理
            surface.Resize((int)Math.Round(surface.Width * scaleRate), (int)Math.Round(surface.Height * scaleRate));

            // 切り抜く
            surface.Crop(faceWidth, faceHeight);

            // 顔画像のサイズに合うように余白追加
            var backgroundColor = ImageMagick.MagickColor.FromRgba(255, 255, 255, 0);

            surface.Extent(faceWidth, faceHeight,
                           gravity: ImageMagick.Gravity.South,
                           backgroundColor: backgroundColor); // (中央下寄せ)

            return(surface);
        }
Exemple #3
0
 public SurfaceViewModel()
 {
     SurfaceModel = new SurfaceModel();
 }
Exemple #4
0
 /// <summary>
 /// 立ち絵から顔画像を生成し、MagickImageオブジェクトを返す
 /// </summary>
 /// <param name="faceWidth">顔画像の幅</param>
 /// <param name="faceHeight">顔画像の高さ</param>
 public virtual MagickImage DrawFaceImage(SurfaceModel surfaceModel, int faceWidth, int faceHeight)
 {
     return(DrawFaceImage(surfaceModel, faceWidth, faceHeight, null));
 }
Exemple #5
0
        /// <summary>
        /// サーフェスを立ち絵として描画し、Imageオブジェクトを返す
        /// </summary>
        /// <param name="trim">画像周辺の余白を削除するかどうか</param>
        public virtual MagickImage DrawSurface(SurfaceModel model, bool trim = true)
        {
            var layers = model.Layers.ToList();

            // まずは1枚目のレイヤを読み込む
            var sw1st   = Stopwatch.StartNew();
            var surface = LoadAndProcessSurfaceFile(layers[0].Path);

            string interimLogPath = null;

            if (InterimOutputDirPathForDebug != null)
            {
                Directory.CreateDirectory(InterimOutputDirPathForDebug);
                interimLogPath = Path.Combine(InterimOutputDirPathForDebug, string.Format(@"s{0:0000}.log", model.Id));
                if (File.Exists(interimLogPath))
                {
                    File.Delete(interimLogPath);
                }

                surface.Write(Path.Combine(InterimOutputDirPathForDebug, string.Format(@"s{0:0000}_p{1:0000}.png", model.Id, 0)));
                var msg = string.Format("p{0:000} : {1} method={2} x={3} y={4} (rendering time: {5} ms)", 0, Path.GetFileName(layers[0].Path), layers[0].ComposingMethod.ToString(), layers[0].X, layers[0].Y, sw1st.ElapsedMilliseconds);
                File.AppendAllLines(interimLogPath, new[] { msg });
            }
            ;

            // 2枚目以降のレイヤが存在するなら、上に重ねていく
            if (layers.Count >= 2)
            {
                for (var i = 1; i < layers.Count; i++)
                {
                    var sw       = Stopwatch.StartNew();
                    var layer    = layers[i];
                    var layerBmp = LoadAndProcessSurfaceFile(layer.Path);

                    if (InterimOutputDirPathForDebug != null)
                    {
                        layerBmp.Write(Path.Combine(InterimOutputDirPathForDebug, $"s{model.Id:0000}_p{i:0000}_loaded.png"));
                    }
                    ;

                    // このとき、描画時に元画像のサイズをはみ出すなら、元画像の描画領域を広げる (SSP仕様)
                    if (layer.X + layerBmp.Width > surface.Width ||
                        layer.Y + layerBmp.Height > surface.Height)
                    {
                        // 余白追加
                        var backgroundColor = ImageMagick.MagickColor.FromRgba(255, 255, 255, 0);
                        surface.Extent(Math.Max(layer.X + layerBmp.Width, surface.Width), Math.Max(layer.Y + layerBmp.Height, surface.Height),
                                       backgroundColor: backgroundColor);

                        if (InterimOutputDirPathForDebug != null)
                        {
                            surface.Write(Path.Combine(InterimOutputDirPathForDebug, string.Format(@"s{0:0000}_p{1:0000}_extented.png", model.Id, i)));
                        }
                        ;
                    }

                    // レイヤ描画
                    // メソッドによって処理を分ける
                    if (layer.ComposingMethod == Seriko.ComposingMethodType.Reduce)
                    {
                        // 新規画像のサイズがベース画像より小さい場合の補正
                        if (layerBmp.Width != surface.Width || layerBmp.Height != surface.Height)
                        {
                            // 画像サイズ補正処理
                            SizeAdjustForComposingBitmap(surface, ref layerBmp, layer.X, layer.Y);

                            // 上記処理の後でもまだサイズが異なる場合(縦が大きいが横は小さいような場合)はUNSUPPORTED
                            if (layerBmp.Width != surface.Width || layerBmp.Height != surface.Height)
                            {
                                throw new IllegalImageFormatException("複数の画像を重ねる際に、2つの画像の間でサイズが異なり、かつ縦横のサイズが矛盾しているような画像が存在します。")
                                      {
                                          Unsupported = true
                                      };
                            }
                        }

                        // 合成
                        // すでに画像サイズ調整を行っているため、0原点とする
                        surface.Composite(layerBmp, 0, 0, CompositeOperator.DstIn);
                    }
                    else if (layer.ComposingMethod == Seriko.ComposingMethodType.Interpolate)
                    {
                        // 新規画像のサイズがベース画像より小さい場合の補正
                        if (layerBmp.Width != surface.Width || layerBmp.Height != surface.Height)
                        {
                            // 画像サイズ補正処理
                            SizeAdjustForComposingBitmap(surface, ref layerBmp, layer.X, layer.Y);

                            // 上記処理の後でもまだサイズが異なる場合(縦が大きいが横は小さいような場合)はUNSUPPORTED
                            if (layerBmp.Width != surface.Width || layerBmp.Height != surface.Height)
                            {
                                throw new IllegalImageFormatException("複数の画像を重ねる際に、2つの画像の間でサイズが異なり、かつ縦横のサイズが矛盾しているような画像が存在します。")
                                      {
                                          Unsupported = true
                                      };
                            }
                        }

                        // 合成
                        // すでに画像サイズ調整を行っているため、0原点とする
                        surface.Composite(layerBmp, 0, 0, CompositeOperator.DstOver);
                    }
                    else if (layer.ComposingMethod == Seriko.ComposingMethodType.Move)
                    {
                        // moveは無視
                    }
                    else
                    {
                        // 上記以外はoverlay扱いで、普通に重ねていく
                        // 重ねる際のメソッドにはoverを使用
                        // <https://www.imagemagick.org/Usage/compose/#over>
                        surface.Composite(layerBmp, layer.X, layer.Y, CompositeOperator.Over);
                    }

                    if (InterimOutputDirPathForDebug != null)
                    {
                        surface.Write(Path.Combine(InterimOutputDirPathForDebug, string.Format(@"s{0:0000}_p{1:0000}.png", model.Id, i)));
                        var msg = string.Format("p{0:000} : {1} method={2} x={3} y={4} (rendering time: {5} ms)", i, Path.GetFileName(layer.Path), layer.ComposingMethod.ToString(), layer.X, layer.Y, sw.ElapsedMilliseconds);
                        File.AppendAllLines(interimLogPath, new[] { msg });
                    }
                    ;
                }
            }

            // 空白があればトリム
            if (trim)
            {
                surface.Trim();
                surface.RePage(); // 切り抜き後の画像サイズ調整
            }

            // 合成後のサーフェスを返す
            return(surface);
        }
Exemple #6
0
        /// <summary>
        /// 指定IDのサーフェス情報を読み込む (ファイルの検索は行うが、画像ファイルの中身は読み込まない)
        /// </summary>
        /// <param name="enabledBindGroupIds">有効になっている着せ替えグループIDのコレクション</param>
        /// <param name="targetCharacter">対象のキャラクタ (sakura, kero, char2, char3, ...) 。alias定義を探すときに使う</param>
        /// <param name="alreadyPassedSurfaceIds">読み込み済みのサーフェスIDコレクション (循環参照による無限ループを防ぐために使用)</param>
        /// <param name="parentPatternComposingMethod">animation定義で指定した合成メソッド (animation定義の中で指定されたサーフェスIDの情報を読み込む場合に使用)</param>
        /// <returns>サーフェス情報。非表示ID (ID=-1) が指定された場合や、読み込みに失敗した場合はnull</returns>
        public virtual SurfaceModel LoadSurfaceModel(
            int surfaceId
            , string targetCharacter
            , ISet <int> enabledBindGroupIds
            , ISet <int> alreadyPassedSurfaceIds = null
            , Seriko.ComposingMethodType?parentPatternComposingMethod = null
            , List <string> interimLogs = null
            , int depth = 0
            )
        {
            var isTopLevel       = depth == 0;
            var interimLogPrefix = string.Format("[{0}]", surfaceId);

            for (var i = 0; i < depth; i++)
            {
                interimLogPrefix = "  " + interimLogPrefix;
            }

            // 非表示の場合はnullを返す
            if (surfaceId == -1)
            {
                return(null);
            }

            // currentLoadedSurfaceIds が未指定の場合は生成
            alreadyPassedSurfaceIds = (alreadyPassedSurfaceIds ?? new HashSet <int>());

            // interimLogsが未指定の場合は生成
            if (interimLogs == null && InterimOutputDirPathForDebug != null)
            {
                interimLogs = new List <string>();
            }

            // animation*.pattern* 内で指定されたサーフェスIDと対応するサーフェス情報
            var childSurfaceModels = new Dictionary <int, SurfaceModel>();

            // surface*.txt から、指定IDと対応するalias定義を探す
            foreach (var surfacesText in SurfacesTextList)
            {
                var newId = surfacesText.FindActualSurfaceId(targetCharacter, surfaceId);
                if (newId != surfaceId)
                {
                    surfaceId = newId;
                    break; // 1件見つかったら終了
                }
            }

            // surface*.txt から、指定IDと対応するelement, MAYUNA定義をすべて取得
            var elements   = new List <Seriko.Element>();
            var animations = new Dictionary <int, Seriko.Animation>();

            foreach (var surfacesText in SurfacesTextList)
            {
                var defInfo = surfacesText.GetSurfaceDefinitionInfo(surfaceId);

                // 対象の surface*.txt 内に存在するelementを結合
                elements = elements.Concat(defInfo.Elements).ToList();

                // 対象の surface*.txt 内に存在するanimationを結合
                foreach (var pair in defInfo.Animations)
                {
                    var animId = pair.Key;
                    var anim   = pair.Value;

                    if (animations.ContainsKey(animId))
                    {
                        // 以前の surface*.txt にすでに同じIDのanimationが含まれているならば、interval情報とpattern指定をマージ
                        if (anim.PatternDisplayForStaticImage != Seriko.Animation.PatternDisplayType.No)
                        {
                            animations[animId].PatternDisplayForStaticImage = anim.PatternDisplayForStaticImage;
                        }
                        anim.Patterns.AddRange(anim.Patterns);
                    }
                    else
                    {
                        // 以前の surface*.txt に同じIDのanimationが含まれていなければ、取得したAnimation定義をそのまま格納
                        animations[animId] = anim;
                    }
                }
            }

            // elementとMAYUNA定義を元に、サーフェスモデルを構築
            var surfaceModel = new SurfaceModel(surfaceId);

            {
                // まずはベースサーフィス分のレイヤを登録
                // element指定が1件以上あれば、elementを合成してベースサーフィスとする
                // 1件もなければ、IDと対応する画像ファイルを取得してベースサーフィスとする
                if (elements.Count >= 1)
                {
                    foreach (var elem in elements) // elementはsurfaces.txtで書いた順に処理 (ID順ではない)
                    {
                        var filePath = Path.Combine(DirPath, elem.FileName);
                        if (File.Exists(filePath))
                        {
                            var layer = new SurfaceModel.Layer(filePath, elem.Method)
                            {
                                X = elem.OffsetX,
                                Y = elem.OffsetY
                            };
                            surfaceModel.BaseSurfaceLayers.Add(layer);
                            if (interimLogs != null)
                            {
                                interimLogs.Add(interimLogPrefix + string.Format("element{0} - use {1}", elem.Id, Path.GetFileName(filePath)));
                            }
                        }
                        else
                        {
                            if (interimLogs != null)
                            {
                                interimLogs.Add(interimLogPrefix + string.Format("ERROR: element{0} image not found ({1})", elem.Id, Path.GetFileName(filePath)));
                            }
                        }
                    }
                }
                else
                {
                    // 指定IDのサーフェスファイルを検索
                    var surfacePath = FindSurfaceFile(surfaceId);

                    // 画像がある場合はレイヤとして追加
                    if (surfacePath != null)
                    {
                        if (interimLogs != null)
                        {
                            interimLogs.Add(interimLogPrefix + string.Format("use base image ({0})", Path.GetFileName(surfacePath)));
                        }

                        var method = (parentPatternComposingMethod.HasValue ? parentPatternComposingMethod.Value : Seriko.ComposingMethodType.Base);
                        surfaceModel.BaseSurfaceLayers.Add(new SurfaceModel.Layer(surfacePath, method));
                    }
                    else
                    {
                        if (interimLogs != null)
                        {
                            interimLogs.Add(interimLogPrefix + string.Format("base image not found"));
                        }
                    }
                }

                // それ以降に、初期状態で有効なbindgroupの着せ替えレイヤを重ねる
                foreach (var pair in animations.OrderBy(k => k.Key))
                {
                    var animId = pair.Key;
                    var anim   = pair.Value;

                    if (anim.PatternDisplayForStaticImage == Seriko.Animation.PatternDisplayType.No)
                    {
                        continue; // 表示対象外の場合はスキップ
                    }

                    if (anim.UsingBindGroup && !enabledBindGroupIds.Contains(animId))
                    {
                        continue; // 着せ替え定義の場合、初期状態で有効でないbindGroupはスキップ
                    }

                    if (interimLogs != null)
                    {
                        interimLogs.Add(interimLogPrefix + string.Format("use animation{0} (display: {1})", animId, anim.PatternDisplayForStaticImage));
                    }

                    // interval指定によっては、全patternを重ね合わせるのではなく、最終patternのみ処理する (例: bind+runonce)
                    var usingPatterns = anim.Patterns.OrderBy(e => e.Id).ToList();
                    if (usingPatterns.Any() &&
                        anim.PatternDisplayForStaticImage == Seriko.Animation.PatternDisplayType.LastOnly)
                    {
                        var lastPatterns = new[] { usingPatterns.Last() };
                        usingPatterns = lastPatterns.ToList();
                    }

                    // patternの処理
                    var cx       = 0;
                    var cy       = 0;
                    var relative = anim.OffsetInterpriting == Seriko.Animation.OffsetInterpritingType.RelativeFromPreviousFrame;
                    foreach (var pattern in usingPatterns) // IDが小さい順に処理
                    {
                        if (interimLogs != null)
                        {
                            interimLogs.Add(interimLogPrefix + string.Format("  pattern{0} (surfaceId={1})", pattern.Id, pattern.SurfaceId));
                        }

                        // サーフェスIDが負数なら非表示指定のため無視 (-1, -2など)
                        if (pattern.SurfaceId < 0)
                        {
                            continue;
                        }

                        // 座標決定
                        if (relative)
                        {
                            // 前コマからのずらし
                            cx = (cx + pattern.OffsetX);
                            cy = (cy + pattern.OffsetY);
                        }
                        else
                        {
                            // 絶対指定
                            cx = pattern.OffsetX;
                            cy = pattern.OffsetY;
                        }

                        // 自分自身のサーフェスIDが指定されたかどうかによって処理を変える
                        // (例: surface0 のブレス内で、SurfaceID = 0を指定した場合)
                        if (pattern.SurfaceId == surfaceId)
                        {
                            // 対象IDのpngファイルを探す
                            var filePath = FindSurfaceFile(pattern.SurfaceId);

                            // 画像が見つかった場合のみレイヤ追加
                            if (filePath != null)
                            {
                                var layer = new SurfaceModel.Layer(filePath, pattern.Method)
                                {
                                    X = cx,
                                    Y = cy
                                };
                                if (anim.BackgroundOption)
                                {
                                    surfaceModel.BackgroundAnimationLayers.Add(layer);
                                }
                                else
                                {
                                    surfaceModel.NormalAnimationLayers.Add(layer);
                                }

                                if (interimLogs != null)
                                {
                                    interimLogs.Add(interimLogPrefix + string.Format("    use {0}", Path.GetFileName(filePath)));
                                }
                            }
                            else
                            {
                                if (interimLogs != null)
                                {
                                    interimLogs.Add(interimLogPrefix + string.Format("    ERROR: image not found"));
                                }
                            }
                        }
                        else
                        {
                            // 循環定義の場合は無視
                            if (alreadyPassedSurfaceIds.Contains(pattern.SurfaceId))
                            {
                                continue;
                            }

                            // まだ定義を読み込んでいないサーフェスIDであれば
                            // 指定されたサーフェスIDと対応するサーフェスモデルを構築
                            if (!childSurfaceModels.ContainsKey(pattern.SurfaceId))
                            {
                                alreadyPassedSurfaceIds.Add(surfaceId);
                                if (interimLogs != null)
                                {
                                    interimLogs.Add(interimLogPrefix + string.Format("    load child surface model - s{0}", pattern.SurfaceId));
                                }

                                childSurfaceModels[pattern.SurfaceId] = LoadSurfaceModel(
                                    pattern.SurfaceId
                                    , targetCharacter
                                    , enabledBindGroupIds
                                    , alreadyPassedSurfaceIds
                                    , pattern.Method
                                    , interimLogs: interimLogs
                                    , depth: depth + 1
                                    );
                            }
                            var childSurfaceModel = childSurfaceModels[pattern.SurfaceId];

                            // 指定IDのサーフェスモデルが見つかった (画像が存在し、正しく読み込めた) 場合のみ、
                            // そのサーフェスモデルのベースサーフェス分レイヤを追加
                            if (childSurfaceModel != null)
                            {
                                foreach (var childLayer in childSurfaceModel.Layers)
                                {
                                    var layer = new SurfaceModel.Layer(childLayer.Path, childLayer.ComposingMethod)
                                    {
                                        X = cx + childLayer.X, // patternの処理によって決まった原点座標 + element側でのoffset
                                        Y = cy + childLayer.Y  // 同上
                                    };

                                    if (anim.BackgroundOption)
                                    {
                                        surfaceModel.BackgroundAnimationLayers.Add(layer);
                                    }
                                    else
                                    {
                                        surfaceModel.NormalAnimationLayers.Add(layer);
                                    }

                                    if (interimLogs != null)
                                    {
                                        interimLogs.Add(interimLogPrefix + string.Format("    use {0}", Path.GetFileName(layer.Path)));
                                    }
                                }
                            }
                        }
                    }
                }

                // デフォルトサーフェスであるにもかかわらず、レイヤが1枚もない(画像ファイルが見つからなかったなど)場合は描画失敗
                if (isTopLevel && !surfaceModel.Layers.Any())
                {
                    // element定義かanimation定義がある場合は、「定義されているが対応画像が見つからない」状態であるため表示メッセージを変える
                    if (elements.Count >= 1 || animations.Count >= 1)
                    {
                        throw new DefaultSurfaceNotFoundException(string.Format("デフォルトサーフェス (ID={0}) の定義で指定された画像ファイルが見つかりませんでした。", surfaceId));
                    }
                    else
                    {
                        throw new DefaultSurfaceNotFoundException(string.Format("デフォルトサーフェス (ID={0}) が見つかりませんでした。", surfaceId))
                              {
                                  Unsupported = true
                              };
                    }
                }
            }

            // デフォルトサーフェスの構築が終わった場合、中間結果を出力
            if (isTopLevel && interimLogs != null)
            {
                Directory.CreateDirectory(InterimOutputDirPathForDebug);
                var path = Path.Combine(InterimOutputDirPathForDebug, string.Format("surfaceModel_{0:0000}.log", surfaceId));
                File.WriteAllLines(path, interimLogs);
            }

            // 構築したサーフェスモデルを返す
            return(surfaceModel);
        }
Exemple #7
0
 public override bool RayIntersects(Ray ray, out float distance)
 {
     return(SurfaceModel.RayIntersects(ray, this, out distance));
 }
Exemple #8
0
 public void SetSurface(SurfaceModel surfaceModel)
 {
     Size = new Surface(surfaceModel);
 }
        /// <inheritdoc />
        public override MagickImage DrawFaceImage(SurfaceModel surfaceModel, int faceWidth, int faceHeight)
        {
            var desc = Explorer2Descript;

            // descript.txt 内で顔画像範囲指定があれば、立ち絵をその範囲で切り抜く
            if (desc != null &&
                (
                    desc.Values.ContainsKey("face.left") ||
                    desc.Values.ContainsKey("face.top") ||
                    desc.Values.ContainsKey("face.width") ||
                    desc.Values.ContainsKey("face.height")
                ))
            {
                // 一部だけが指定された場合はエラー
                if (
                    !desc.Values.ContainsKey("face.left") ||
                    !desc.Values.ContainsKey("face.top") ||
                    !desc.Values.ContainsKey("face.width") ||
                    !desc.Values.ContainsKey("face.height")
                    )
                {
                    throw new InvalidDescriptException(@"face.left ~ face.height は4つとも指定する必要があります。");
                }

                int left;
                if (!int.TryParse(desc.Values["face.left"], out left))
                {
                    throw new InvalidDescriptException(@"face.left の指定が不正です。");
                }

                if (left < 0)
                {
                    throw new InvalidDescriptException(@"face.left が負数です。");
                }

                int top;
                if (!int.TryParse(desc.Values["face.top"], out top))
                {
                    throw new InvalidDescriptException(@"face.top の指定が不正です。");
                }

                if (top < 0)
                {
                    throw new InvalidDescriptException(@"face.top が負数です。");
                }

                int dWidth;
                if (!int.TryParse(desc.Values["face.width"], out dWidth))
                {
                    throw new InvalidDescriptException(@"face.width の指定が不正です。");
                }

                if (dWidth < 0)
                {
                    throw new InvalidDescriptException(@"face.width が負数です。");
                }

                int dHeight;
                if (!int.TryParse(desc.Values["face.height"], out dHeight))
                {
                    throw new InvalidDescriptException(@"face.height の指定が不正です。");
                }

                if (dHeight < 0)
                {
                    throw new InvalidDescriptException(@"face.height が負数です。");
                }

                // 親処理を呼び出す
                return(base.DrawFaceImage(surfaceModel, faceWidth, faceHeight, faceTrimRange: Tuple.Create(left, top, dWidth, dHeight)));
            }
            else
            {
                // 親処理を呼び出す
                return(base.DrawFaceImage(surfaceModel, faceWidth, faceHeight));
            }
        }