From 08d3b6aa3aab8e3168026f85a9a9b7a8007579cf Mon Sep 17 00:00:00 2001 From: Sorrowfulwinds <22553216+Sorrowfulwinds@users.noreply.github.com> Date: Mon, 2 Mar 2026 02:30:30 -0600 Subject: [PATCH 1/5] GetPixel work prototype frame out --- .../Procs/Native/DreamProcNativeIcon.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index 2817d08bb9..febdd1b60a 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Resources; @@ -107,5 +108,60 @@ public static DreamValue NativeProc_Turn(NativeProc.Bundle bundle, DreamObject? public static void _NativeProc_TurnInternal(DreamObjectIcon src, float angle) { src.Turn(angle); } + + [DreamProc("GetPixel")] + [DreamProcParameter("x", Type = DreamValueTypeFlag.Float)] + [DreamProcParameter("y", Type = DreamValueTypeFlag.Float)] + [DreamProcParameter("icon_state", Type = DreamValueTypeFlag.String)] + [DreamProcParameter("dir", Type = DreamValueTypeFlag.Float, DefaultValue = 2)] + [DreamProcParameter("frame", Type = DreamValueTypeFlag.Float, DefaultValue = 1)] + [DreamProcParameter("moving", Type = DreamValueTypeFlag.Float, DefaultValue = -1)] + public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + bundle.GetArgument(0, "x").TryGetValueAsInteger(out var xPos); + bundle.GetArgument(1, "y").TryGetValueAsInteger(out var yPos); + bundle.GetArgument(2, "icon_state").TryGetValueAsString(out var iconState); + bundle.GetArgument(3, "dir").TryGetValueAsInteger(out var dir); + bundle.GetArgument(4, "frame").TryGetValueAsInteger(out var frame); + //TODO: Implement moving var + bundle.GetArgument(5, "moving").TryGetValueAsInteger(out var moving); + + DreamIcon iconObj = ((DreamObjectIcon)src!).Icon; + if(!iconObj.States.TryGetValue(iconState ?? string.Empty, out var state)) + //Check does this situation actually return null? + return DreamValue.Null; + if(frame > state.Frames) + //Check what requesting a nonexistent frame does + return DreamValue.Null; + //Check what requesting a bad dir does, and if diagonals fallthrough + //Also this switch should be an extended function on AtomDirection anyway + AtomDirection atomDir = dir switch { + 0 or 2 => AtomDirection.South, + 1 => AtomDirection.North, + 4 => AtomDirection.East, + 5 => AtomDirection.Northeast, + 6 => AtomDirection.Southeast, + 8 => AtomDirection.West, + 9 => AtomDirection.Northwest, + 10 => AtomDirection.Southwest, + _ => AtomDirection.None + }; + + if (!state.Directions.TryGetValue(atomDir, out var frameList)) + //Check what a dir fail actually does + return DreamValue.Null; + var finalFrame = frameList[frame].Image; + if (finalFrame is not null) { + var bob = finalFrame[xPos, yPos]; + var r = bob.R.ToString("X2"); + var g = bob.G.ToString("X2"); + var b = bob.B.ToString("X2"); + var a = bob.A.ToString("X2"); + //If A is fully transparent return null + //if A is partially transparent return "#RRGGBBAA" + //if A is not transparent return "#RRGGBB" + } + + return DreamValue.Null; + } } } From 5e5e485b867a673f7f9cbdb81a35155fa7780830 Mon Sep 17 00:00:00 2001 From: Sorrowfulwinds Date: Mon, 2 Mar 2026 19:59:59 -0600 Subject: [PATCH 2/5] finish prototype --- .../Procs/Native/DreamProcNativeIcon.cs | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index febdd1b60a..6161caae95 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -113,8 +113,8 @@ public static void _NativeProc_TurnInternal(DreamObjectIcon src, float angle) { [DreamProcParameter("x", Type = DreamValueTypeFlag.Float)] [DreamProcParameter("y", Type = DreamValueTypeFlag.Float)] [DreamProcParameter("icon_state", Type = DreamValueTypeFlag.String)] - [DreamProcParameter("dir", Type = DreamValueTypeFlag.Float, DefaultValue = 2)] - [DreamProcParameter("frame", Type = DreamValueTypeFlag.Float, DefaultValue = 1)] + [DreamProcParameter("dir", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] + [DreamProcParameter("frame", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] [DreamProcParameter("moving", Type = DreamValueTypeFlag.Float, DefaultValue = -1)] public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { bundle.GetArgument(0, "x").TryGetValueAsInteger(out var xPos); @@ -129,9 +129,14 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje if(!iconObj.States.TryGetValue(iconState ?? string.Empty, out var state)) //Check does this situation actually return null? return DreamValue.Null; - if(frame > state.Frames) + + if(frame > state.Frames || frame < 0) //Check what requesting a nonexistent frame does + //Check what subzero frame actually does. return DreamValue.Null; + + if(frame != 0) frame -= 1; //1 indexed to 0 indexed conversion. + //Check what requesting a bad dir does, and if diagonals fallthrough //Also this switch should be an extended function on AtomDirection anyway AtomDirection atomDir = dir switch { @@ -149,19 +154,19 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje if (!state.Directions.TryGetValue(atomDir, out var frameList)) //Check what a dir fail actually does return DreamValue.Null; - var finalFrame = frameList[frame].Image; - if (finalFrame is not null) { - var bob = finalFrame[xPos, yPos]; - var r = bob.R.ToString("X2"); - var g = bob.G.ToString("X2"); - var b = bob.B.ToString("X2"); - var a = bob.A.ToString("X2"); - //If A is fully transparent return null - //if A is partially transparent return "#RRGGBBAA" - //if A is not transparent return "#RRGGBB" - } - return DreamValue.Null; + var finalFrame = frameList[frame].Image; + if (finalFrame is null) return DreamValue.Null; + + var pix = finalFrame[xPos, yPos]; + //If A is fully transparent return null + //if A is partially transparent return "#RRGGBBAA" + //if A is not transparent return "#RRGGBB" + return pix.A switch { + 0 => DreamValue.Null, + 255 => new DreamValue($"#{pix.R:X2}{pix.G:X2}{pix.B:X2}"), + _ => new DreamValue($"#{pix.R:X2}{pix.G:X2}{pix.B:X2}{pix.A:X2}") + }; } } } From 78889f6b75ed63991420f5f8711de8cfbb428de5 Mon Sep 17 00:00:00 2001 From: Sorrowfulwinds <22553216+Sorrowfulwinds@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:37:04 -0600 Subject: [PATCH 3/5] Confirm outliers and byondisms Near finish need to verify with an A-B test byond-opendream --- .../Procs/Native/DreamProcNativeIcon.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index 6161caae95..5167541cef 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -127,18 +127,20 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje DreamIcon iconObj = ((DreamObjectIcon)src!).Icon; if(!iconObj.States.TryGetValue(iconState ?? string.Empty, out var state)) - //Check does this situation actually return null? + //Bad icon_state returns null return DreamValue.Null; - if(frame > state.Frames || frame < 0) - //Check what requesting a nonexistent frame does - //Check what subzero frame actually does. - return DreamValue.Null; + //Values less than 1 are out of bounds. + if (xPos < 1 || yPos < 1) return DreamValue.Null; - if(frame != 0) frame -= 1; //1 indexed to 0 indexed conversion. + if (frame < 1) { + frame = 0; //BYONDISM: Frames less than 1 count as 0, + } else if (frame > state.Frames) { + return DreamValue.Null; + } else { + frame -= 1; // Convert from 1-index to 0-index + } - //Check what requesting a bad dir does, and if diagonals fallthrough - //Also this switch should be an extended function on AtomDirection anyway AtomDirection atomDir = dir switch { 0 or 2 => AtomDirection.South, 1 => AtomDirection.North, @@ -150,14 +152,22 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje 10 => AtomDirection.Southwest, _ => AtomDirection.None }; + //BYONDISM: Bad dir values just crash instantly :) + if (atomDir == AtomDirection.None) return DreamValue.Null; if (!state.Directions.TryGetValue(atomDir, out var frameList)) - //Check what a dir fail actually does + //Empty dir's return null return DreamValue.Null; var finalFrame = frameList[frame].Image; if (finalFrame is null) return DreamValue.Null; + //Out-of-bounds xy values return null. + if (xPos > finalFrame.Width || yPos > finalFrame.Height) return DreamValue.Null; + //SixLabors.Image is 0-indexed from the top-left, BYOND is 1-indexed from the bottom left. + xPos -= 1; + yPos = finalFrame.Height - yPos; + var pix = finalFrame[xPos, yPos]; //If A is fully transparent return null //if A is partially transparent return "#RRGGBBAA" From 371c921493d6a6c3d530fe361e14f920c02e8c60 Mon Sep 17 00:00:00 2001 From: Sorrowfulwinds <22553216+Sorrowfulwinds@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:20:25 -0600 Subject: [PATCH 4/5] lowercase not upper --- DMCompiler/DMStandard/Types/Icon.dm | 1 - OpenDreamRuntime/Procs/Native/DreamProcNative.cs | 1 + OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs | 8 +++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DMCompiler/DMStandard/Types/Icon.dm b/DMCompiler/DMStandard/Types/Icon.dm index bd211b7de7..7b0f87bc4a 100644 --- a/DMCompiler/DMStandard/Types/Icon.dm +++ b/DMCompiler/DMStandard/Types/Icon.dm @@ -16,7 +16,6 @@ set opendream_unimplemented = TRUE proc/GetPixel(x, y, icon_state, dir = 0, frame = 0, moving = -1) - set opendream_unimplemented = TRUE proc/Height() diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index 34a227b15e..ad4fbf40cf 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -176,6 +176,7 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetNativeProc(objectTree.Icon, DreamProcNativeIcon.NativeProc_Blend); objectTree.SetNativeProc(objectTree.Icon, DreamProcNativeIcon.NativeProc_Scale); objectTree.SetNativeProc(objectTree.Icon, DreamProcNativeIcon.NativeProc_Turn); + objectTree.SetNativeProc(objectTree.Icon, DreamProcNativeIcon.NativeProc_GetPixel); objectTree.SetNativeProc(objectTree.Savefile, DreamProcNativeSavefile.NativeProc_ExportText); objectTree.SetNativeProc(objectTree.Savefile, DreamProcNativeSavefile.NativeProc_Flush); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index 5167541cef..58833c6e3b 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -126,11 +126,13 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje bundle.GetArgument(5, "moving").TryGetValueAsInteger(out var moving); DreamIcon iconObj = ((DreamObjectIcon)src!).Icon; + //TODO BYONDISM: A non-existent icon_state will default to the empty string icon_state, + //or if one doesn't exist it will default to the first icon_state in the DMI. if(!iconObj.States.TryGetValue(iconState ?? string.Empty, out var state)) //Bad icon_state returns null return DreamValue.Null; - //Values less than 1 are out of bounds. + //Position values less than 1 are out of bounds. Early escape. if (xPos < 1 || yPos < 1) return DreamValue.Null; if (frame < 1) { @@ -174,8 +176,8 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje //if A is not transparent return "#RRGGBB" return pix.A switch { 0 => DreamValue.Null, - 255 => new DreamValue($"#{pix.R:X2}{pix.G:X2}{pix.B:X2}"), - _ => new DreamValue($"#{pix.R:X2}{pix.G:X2}{pix.B:X2}{pix.A:X2}") + 255 => new DreamValue($"#{pix.R:x2}{pix.G:x2}{pix.B:x2}"), + _ => new DreamValue($"#{pix.R:x2}{pix.G:x2}{pix.B:x2}{pix.A:x2}") }; } } From 3d196ad656a396347a8ecd5ed29b2fc3bf2a08e2 Mon Sep 17 00:00:00 2001 From: Sorrowfulwinds <22553216+Sorrowfulwinds@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:42:35 -0600 Subject: [PATCH 5/5] cleanup remove unused directive remove unused var, todo will suffice --- OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index 58833c6e3b..181a4e2d33 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Resources; @@ -123,7 +122,6 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje bundle.GetArgument(3, "dir").TryGetValueAsInteger(out var dir); bundle.GetArgument(4, "frame").TryGetValueAsInteger(out var frame); //TODO: Implement moving var - bundle.GetArgument(5, "moving").TryGetValueAsInteger(out var moving); DreamIcon iconObj = ((DreamObjectIcon)src!).Icon; //TODO BYONDISM: A non-existent icon_state will default to the empty string icon_state,