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); } }
/// <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); }
public SurfaceViewModel() { SurfaceModel = new SurfaceModel(); }
/// <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)); }
/// <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); }
/// <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); }
public override bool RayIntersects(Ray ray, out float distance) { return(SurfaceModel.RayIntersects(ray, this, out distance)); }
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)); } }