private static IEnumerator ComputeEdgeGradients() { float sqrt2 = Mathf.Sqrt(2f); yield return(QcAsync.CallAfter_Thread(() => { for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { var p = pixels[x, y]; if (p.originalValue > 0f && p.originalValue < 1f) { float g = -pixels[x - 1, y - 1].originalValue - pixels[x - 1, y + 1].originalValue + pixels[x + 1, y - 1].originalValue + pixels[x + 1, y + 1].originalValue; p.gradient.x = g + (pixels[x + 1, y].originalValue - pixels[x - 1, y].originalValue) * sqrt2; p.gradient.y = g + (pixels[x, y + 1].originalValue - pixels[x, y - 1].originalValue) * sqrt2; p.gradient.Normalize(); } } } }, "Edge grad A")); yield return(QcAsync.CallAfter_Thread(() => { for (int y = 0; y < height; y++) { int skip = ((y == 0) || (y == (height - 1))) ? 1 : (width - 1); for (int x = 0; x < width; x += skip) { var p = pixels[x, y]; if (p.originalValue > 0f && p.originalValue < 1f) { float g = -Pixels(x - 1, y - 1).originalValue - Pixels(x - 1, y + 1).originalValue + Pixels(x + 1, y - 1).originalValue + Pixels(x + 1, y + 1).originalValue; p.gradient.x = g + (Pixels(x + 1, y).originalValue - Pixels(x - 1, y).originalValue) * sqrt2; p.gradient.y = g + (Pixels(x, y + 1).originalValue - Pixels(x, y - 1).originalValue) * sqrt2; p.gradient.Normalize(); } } } }, "Edge grad B")); }
private static IEnumerator PostProcess(float maxDistance) { yield return(QcAsync.CallAfter_Thread(() => { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { var p = pixels[x, y]; if ((p.dX == 0 && p.dY == 0) || p.distance >= maxDistance) { continue; } float dX = p.dX, dY = p.dY; var closest = Pixels(x - p.dX, y - p.dY); var g = closest.gradient; if (g.x == 0f && g.y == 0f) { continue; } float df = ApproximateEdgeDelta(g.x, g.y, closest.originalValue); float t = dY * g.x - dX * g.y; float u = -df * g.x + t * g.y; float v = -df * g.y - t * g.x; if (Mathf.Abs(u) <= 0.5f && Mathf.Abs(v) <= 0.5f) { p.distance = Mathf.Sqrt((dX + u) * (dX + u) + (dY + v) * (dY + v)); } } } }, "Postprocess")); }
public static IEnumerator Generate( TextureMeta image, float maxInside, float maxOutside, float postProcessDistance) { width = image.width; height = image.height; destination = image; if (image.Pixels == null) { Debug.LogError("Pixels are null"); yield break; } pixels = new Pixel[width, height]; int x, y; float scale; yield return(QcAsync.CallAgain("Creating pixels")); yield return(QcAsync.CallAfter_Thread(() => InitializePixels(), "Creating pixels coroutine")); yield return(QcAsync.CallAgain("Filling max Inside")); //INSIDE if (maxInside > 0f) { for (var e = ComputeEdgeGradients(); e.MoveNext();) { yield return(e.Current); } for (var e = GenerateDistanceTransform(); e.MoveNext();) { yield return(e.Current); } if (postProcessDistance > 0f) { for (var e = PostProcess(postProcessDistance); e.MoveNext();) { yield return(e.Current); } } yield return(QcAsync.CallAgain("Setting Inside Pixels")); scale = 1f / maxInside; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { SetDestination(x, y, Mathf.Clamp01(pixels[x, y].distance * scale)); } yield return(QcAsync.CallAgain("Inside Pixels {0}".F(y))); } } yield return(QcAsync.CallAgain("Filling max Outside")); //OUTSIDE if (maxOutside > 0f) { yield return(QcAsync.CallAfter_Thread(() => { for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { pixels[x, y].FlipOriginal(); } } }, "Flipping pixels")); for (var e = ComputeEdgeGradients(); e.MoveNext();) { yield return(e.Current); } for (var e = GenerateDistanceTransform(); e.MoveNext();) { yield return(e.Current); } if (postProcessDistance > 0f) { for (var e = PostProcess(postProcessDistance); e.MoveNext();) { yield return(e.Current); } } scale = 1f / maxOutside; if (maxInside > 0f) { for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { float value = 0.5f + (destination.PixelUnSafe(x, y).r - Mathf.Clamp01(pixels[x, y].distance * scale)) * 0.5f; SetDestination(x, y, value); } } yield return(QcAsync.CallAgain("Setting Outside Pixels {0}/{1}".F(y, height))); } else { for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { var value = Mathf.Clamp01(1f - pixels[x, y].distance * scale); SetDestination(x, y, value); } yield return(QcAsync.CallAgain("Setting Outside Pixels {0}/{1}".F(y, height))); } } } pixels = null; }
private static IEnumerator GenerateDistanceTransform() { int x = 0, y = 0; Pixel p; yield return(QcAsync.CallAfter_Thread(() => { for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { pixels[x, y].ResetTransform(); } } }, "Reseting Dist Tf")); yield return(QcAsync.CallAfter_Thread(() => { for (y = 0; y < height; y++) { var dy = (y + height) % height; for (x = 0; x < width; x++) { UpdateDistanceSafe(x, dy); } for (x = width - 1; x >= 0; x--) { p = pixels[x, dy]; if (p.distance > 0f) { UpdateDistanceSafe(p, x, dy, 1, 0); } } } }, "Dist down")); yield return(QcAsync.CallAfter_Thread(() => { for (y = height - 1; y >= -8; y--) { var dy = (y + height) % height; for (x = width - 1; x >= 0; x--) { UpdateDistanceSafe(x, dy, 1); } for (x = 0; x < width; x++) { p = pixels[x, dy]; if (p.distance > 0f) { UpdateDistanceSafe(p, x, dy, -1, 0); } } } }, "Dist up")); yield return(QcAsync.CallAfter_Thread(() => { for (y = -2; y < 7; y++) { var dy = (y + height) % height; for (x = 0; x < width; x++) { UpdateDistanceSafe(x, dy); } } for (y = 6; y > -3; y--) { var dy = (y + height) % height; for (x = width - 1; x >= 0; x--) { UpdateDistanceSafe(x, dy, 1); } } }, "Dist edge")); }
public override bool Inspect() { if (ProcessEnumerator != null) { "Running Coroutine".nl(); _processEnumerator.Inspect_AsInList(); return(false); } var changed = false; if ("CPU blit options".conditional_enter(this.TargetIsTexture2D(), ref inspectedItems, 0).nl()) { "Disable Continious Lines".toggleIcon("If you see unwanted lines appearing on the texture as you paint, enable this.", ref disableContiniousLine).nl(ref changed); "CPU blit repaint delay".edit("Delay for video memory update when painting to Texture2D", 140, ref _repaintDelay, 0.01f, 0.5f).nl(ref changed); "Don't update mipMaps".toggleIcon("May increase performance, but your changes may not disaplay if you are far from texture.", ref dontRedoMipMaps).changes(ref changed); } if ("GPU blit options".enter(ref inspectedItems, 1).nl()) { "Update Texture2D after every stroke".toggleIcon(ref updateTex2DafterStroke).nl(); } #region Processors var newWidth = Cfg.SelectedWidthForNewTexture(); //PainterDataAndConfig.SizeIndexToSize(PainterCamera.Data.selectedWidthIndex); var newHeight = Cfg.SelectedHeightForNewTexture(); if ("Texture Processors".enter(ref inspectedItems, 6).nl()) { if (errorWhileReading) { "There was en error reading texture pixels, can't process it".writeWarning(); } else { if ("Resize ({0}*{1}) => ({2}*{3})".F(width, height, newWidth, newHeight).enter(ref _inspectedProcess, 0).nl_ifFoldedOut()) { "New Width ".select(60, ref PainterCamera.Data.selectedWidthIndex, PainterDataAndConfig.NewTextureSizeOptions).nl(ref changed); "New Height ".select(60, ref PainterCamera.Data.selectedHeightIndex, PainterDataAndConfig.NewTextureSizeOptions).nl(ref changed); if (newWidth != width || newHeight != height) { bool rescale; if (newWidth <= width && newHeight <= height) { rescale = "Downscale".Click(); } else if (newWidth >= width && newHeight >= height) { rescale = "Upscale".Click(); } else { rescale = "Rescale".Click(); } if (rescale) { Resize(newWidth, newHeight); } } pegi.nl(); } if (_inspectedProcess == -1) { if ((newWidth != width || newHeight != height) && icon.Size.Click("Resize").nl(ref changed)) { Resize(newWidth, newHeight); } pegi.nl(); } if ("Clear ".enter(ref _inspectedProcess, 1, false)) { "Clear Color".edit(80, ref clearColor).nl(); if ("Clear Texture".Click().nl()) { Colorize(clearColor); SetApplyUpdateRenderTexture(); } } if (_inspectedProcess == -1 && icon.Refresh.Click("Apply color {0}".F(clearColor)).nl()) { Colorize(clearColor); SetApplyUpdateRenderTexture(); } if ("Color to Alpha".enter(ref _inspectedProcess, 2).nl()) { "Background Color".edit(80, ref clearColor).nl(); if (Pixels != null) { if ("Color to Alpha".Click("Will Convert Background Color with transparency").nl()) { bool wasRt = WasRenderTexture(); for (int i = 0; i < _pixels.Length; i++) { _pixels[i] = BlitFunctions.ColorToAlpha(_pixels[i], clearColor); } SetAndApply(); if (wasRt) { ReturnToRenderTexture(); } } if ("Color from Alpha".Click("Will subtract background color from transparency").nl()) { bool wasRt = WasRenderTexture(); for (int i = 0; i < _pixels.Length; i++) { var col = _pixels[i]; col.a = BlitFunctions.ColorToAlpha(_pixels[i], clearColor).a; _pixels[i] = col; } SetAndApply(); if (wasRt) { ReturnToRenderTexture(); } } } } if ("Signed Distance Filelds generator".enter(ref _inspectedProcess, 4).nl()) { if (texture2D.IsColorTexture()) { "Texture is a color texture, best to switch to non-color for SDF. Save any changes first, as the texture will reimport.".writeWarning(); #if UNITY_EDITOR var ai = texture2D.GetTextureImporter(); if (ai != null && "Convert to non-Color".ClickConfirm("SDFnc", "This will undo any unsaved changes. Proceed?") && ai.WasWrongIsColor(false)) { ai.SaveAndReimport(); } #endif } "Will convert black and white color to black and white signed field".nl(); "SDF Max Inside".edit(ref sdfMaxInside).nl(); "SDF Max Outside".edit(ref sdfMaxOutside).nl(); "SDF Post Process".edit(ref sdfPostProcessDistance).nl(); if ("Generate Assync".Click("Will take a bit longer but you'll be able to use Unity")) { bool wasRt = WasRenderTexture(); var p = PlaytimePainter.inspected; if (p) { p.UpdateOrSetTexTarget(TexTarget.Texture2D); } _processEnumerator = QcAsync.StartManagedCoroutine( DistanceFieldProcessor.Generate(this, sdfMaxInside, sdfMaxOutside, sdfPostProcessDistance), () => { SetAndApply(); if (wasRt) { ReturnToRenderTexture(); } }); } pegi.nl(); } if ("Curves".enter(ref _inspectedProcess, 5).nl()) { var crv = TexMGMT.InspectAnimationCurve("Channel"); if (Pixels != null) { if ("Remap Alpha".Click()) { for (int i = 0; i < _pixels.Length; i++) { var col = _pixels[i]; col.a = crv.Evaluate(col.a); _pixels[i] = col; } SetApplyUpdateRenderTexture(); } if ("Remap Color".Click()) { for (int i = 0; i < _pixels.Length; i++) { var col = _pixels[i]; col.r = crv.Evaluate(col.r); col.g = crv.Evaluate(col.g); col.b = crv.Evaluate(col.b); _pixels[i] = col; } SetApplyUpdateRenderTexture(); } } } if ("Save Textures In Game ".enter(ref _inspectedProcess, 7).nl()) { "This is intended to test playtime saving. The functions to do so are quite simple. You can find them inside ImageData.cs class." .writeHint(); pegi.nl(); "Save Name".edit(70, ref saveName); if (icon.Folder.Click("Open Folder with textures").nl()) { QcFile.Explorer.OpenPersistentFolder(SavedImagesFolder); } if ("Save Playtime".Click("Will save to {0}/{1}".F(Application.persistentDataPath, saveName)).nl()) { SaveInPlayer(); } if (Cfg && Cfg.playtimeSavedTextures.Count > 0) { "Playtime Saved Textures".write_List(Cfg.playtimeSavedTextures, LoadTexturePegi); } } if ("Fade edges".enter(ref _inspectedProcess, 8).nl()) { ("This will cahange pixels on the edges of the texture. Useful when wrap mode " + "is set to clamp.").writeHint(); if (texture2D) { #if UNITY_EDITOR var ti = texture2D.GetTextureImporter(); if (ti) { if (ti.wrapMode != TextureWrapMode.Clamp && "Change wrap mode from {0} to Clamp" .F(ti.wrapMode).Click().nl(ref changed)) { ti.wrapMode = TextureWrapMode.Clamp; ti.SaveAndReimport(); } } #endif if ("Set edges to transparent".Click().nl(ref changed)) { SetEdges(Color.clear, ColorMask.A); SetAndApply(); } if ("Set edges to Clear Black".Click().nl(ref changed)) { SetEdges(Color.clear); SetAndApply(); } } } if ("Add Background".enter(ref _inspectedProcess, 9).nl()) { "Background Color".edit(80, ref clearColor).nl(); if ("Add Background".Click("Will Add Beckground color and make everything non transparent").nl()) { bool wasRt = WasRenderTexture(); for (int i = 0; i < _pixels.Length; i++) { _pixels[i] = BlitFunctions.AddBackground(_pixels[i], clearColor); } SetAndApply(); if (wasRt) { ReturnToRenderTexture(); } } } if ("Offset".enter(ref _inspectedProcess, 10).nl()) { "X:".edit(ref _offsetByX); if ((_offsetByX != width / 2) && "{0}/{1}".F(width / 2, width).Click()) { _offsetByX = width / 2; } pegi.nl(); "Y:".edit(ref _offsetByY); if ((_offsetByY != height / 2) && "{0}/{1}".F(height / 2, height).Click()) { _offsetByY = height / 2; } pegi.nl(); if (((_offsetByX % width != 0) || (_offsetByY % height != 0)) && "Apply Offset".Click()) { OffsetPixels(); SetAndApply(); } } } } #endregion if ("Enable Undo for '{0}'".F(NameForPEGI).toggle_enter(ref enableUndoRedo, ref inspectedItems, 2, ref changed).nl()) { "UNDOs: Tex2D".edit(80, ref _numberOfTexture2DBackups).changes(ref changed); "RendTex".edit(60, ref _numberOfRenderTextureBackups).nl(ref changed); "Backup manually".toggleIcon(ref backupManually).nl(); if (_numberOfTexture2DBackups > 50 || _numberOfRenderTextureBackups > 50) { "Too big of a number will eat up lot of memory".writeWarning(); } "Creating more backups will eat more memory".writeOneTimeHint("backupIsMem"); "This are not connected to Unity's Undo/Redo because when you run out of backups you will by accident start undoing other operations.".writeOneTimeHint("noNativeUndo"); "Use Z/X to undo/redo".writeOneTimeHint("ZxUndoRedo"); } if (inspectedItems == -1) { if (isAVolumeTexture) { "Is A volume texture".toggleIcon(ref isAVolumeTexture).nl(ref changed); } } return(changed); }
public void CombinedUpdate() { if (!this || !Data) { return; } lastManagedUpdate = QcUnity.TimeSinceStartup(); if (PlaytimePainter.IsCurrentTool && FocusedPainter) { FocusedPainter.ManagedUpdateOnFocused(); } QcAsync.UpdateManagedCoroutines(); MeshManager.CombinedUpdate(); if (!Application.isPlaying && depthProjectorCamera) { depthProjectorCamera.ManagedUpdate(); } #if UNITY_EDITOR if (refocusOnThis) { _scipFrames--; if (_scipFrames == 0) { QcUnity.FocusOn(refocusOnThis); refocusOnThis = null; _scipFrames = 3; } } #endif var p = PlaytimePainter.currentlyPaintedObjectPainter; if (p && !Application.isPlaying && ((QcUnity.TimeSinceStartup() - lastPainterCall) > 0.016f)) { if (p.TexMeta == null) { PlaytimePainter.currentlyPaintedObjectPainter = null; } else { p.ProcessStrokeState(); } } var needRefresh = false; if (CameraModuleBase.modules != null) { foreach (var pl in CameraModuleBase.modules) { if (pl != null) { pl.Update(); } else { needRefresh = true; } } } if (needRefresh) { Debug.Log("Refreshing modules"); CameraModuleBase.RefreshModules(); } lastPainterCall = QcUnity.TimeSinceStartup(); }
public void CombinedUpdate() { if (!this || !Data) { return; } lastManagedUpdate = Time.time; if (PlaytimePainter.IsCurrentTool && focusedPainter) { focusedPainter.ManagedUpdateOnFocused(); } if (GlobalBrush.previewDirty) { SHADER_BRUSH_UPDATE(); } QcAsync.UpdateManagedCoroutines(); MeshManager.CombinedUpdate(); if (!Application.isPlaying && depthProjectorCamera) { depthProjectorCamera.ManagedUpdate(); } #if UNITY_EDITOR if (refocusOnThis) { _scipFrames--; if (_scipFrames == 0) { QcUnity.FocusOn(refocusOnThis); refocusOnThis = null; _scipFrames = 3; } } #endif var p = PlaytimePainter.currentlyPaintedObjectPainter; if (p && !Application.isPlaying && ((Time.time - lastPainterCall) > 0.016f)) { if (p.TexMeta == null) { PlaytimePainter.currentlyPaintedObjectPainter = null; } else { p.ProcessStrokeState(); //TexMgmtData.brushConfig.Paint(p.stroke, p); //p.ManagedUpdateOnFocused(); } } var needRefresh = false; if (CameraModuleBase.modules != null) { foreach (var pl in CameraModuleBase.modules) { if (pl != null) { pl.Update(); } else { needRefresh = true; } } } if (needRefresh) { Debug.Log("Refreshing modules"); CameraModuleBase.RefreshPlugins(); } lastPainterCall = Time.time; }