From 6e5984ba1570f00be0338b6576f2b5dd0e18bfc5 Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 16 May 2026 16:36:29 +0100 Subject: [PATCH 1/5] getpixel --- .../DMProject/Tests/Icon/GetPixel.dm | 10 ++++ Content.Tests/DMProject/Tests/Icon/hanoi.dmi | Bin 0 -> 1006 bytes DMCompiler/DMStandard/Types/Icon.dm | 1 - .../Procs/Native/DreamProcNative.cs | 1 + .../Procs/Native/DreamProcNativeIcon.cs | 51 ++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 Content.Tests/DMProject/Tests/Icon/GetPixel.dm create mode 100644 Content.Tests/DMProject/Tests/Icon/hanoi.dmi diff --git a/Content.Tests/DMProject/Tests/Icon/GetPixel.dm b/Content.Tests/DMProject/Tests/Icon/GetPixel.dm new file mode 100644 index 0000000000..f9134d2f21 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Icon/GetPixel.dm @@ -0,0 +1,10 @@ +/proc/RunTest() + var/icon/I = icon('hanoi.dmi') + // ASSERT(I.GetPixel(5,5) == "#ff0000") + var/list/target_pixels = list("#e8083f", "#ff0000", "#6aee1d", "#f1b004", "#ffa308", "#ff34e2", "#6aeeff",\ + "#f4f14d", "#fbed01", "#757121", "#c07760", "#cc7d66", "#c98164", "#5fed26", "#43fc13", "#43fc13", "#43fc13",\ + "#43fc13", "#43fc13", "#48fa16", "#bf885e", "#625230", "#4d4b26", "#4c5234", "#09f5f3", "#00ffff", "#00ffff","#f44dfb",\ + "#2c14ff", "#001bf9", "#00ef58", "#f74d04") + for(var/x in 1 to 32) + world.log << I.GetPixel(x,16,"target") + ASSERT(I.GetPixel(x,16,"target") == target_pixels[x]) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Icon/hanoi.dmi b/Content.Tests/DMProject/Tests/Icon/hanoi.dmi new file mode 100644 index 0000000000000000000000000000000000000000..3215d27a7e7965b7ab12c091344b0614b1513f3e GIT binary patch literal 1006 zcmV1IpXn6{O(lz`x5&s6#xG<;%eyr0Qvv)OZ)#c;%n~yEdKH@{_!sU@dN+<^iBI} z?*H=fQ~dBB_Ua+^=^^szFf9G?^zlvm?E!XiB1}pqOHnil_48j*FJe+KVNx(pOeaiI zG|YWwb#WoRc3#MQWW{=7z;|HAietu%W5|7EL;MrTfn;CpCPMrYNBb7Ph+atg7Ri8Q zZQ?Fh@E}_5BEW}V%YS9dfMs@ZA+>T^OiLw8OC|j60f~YhYi1&9YcUV@^Xl?y^YBgb z?@jRVRR1>OhSvUD<^Egd{`Exr*rP$^lt<+><>3kZ;0yfY1pFry|7-3V5^v)aQ|}Z~ z@f1?>0aEe+RP2u48q*M6n>&Oj00001bW%=J06^y0W&i*HjCxd9bVOxyV{&P5bZKvH z004NLQ&w}aHJ3*o{;uC_5h~D z5P$##AOHafKma^IHthh7u@As7hBF&2R+-WP05-crDrbfj0FarLO)e)l&j|srB%1KpJLIJ9(YibGA)q?>V8k-0;!vR`c?$*TG+BvQR5YXAx-P0?suOArT z85kTA=N*Oz`20eF5!0v&03S~cU@RCC8V^tbgn52qGS-w|FTnJ7Qh=G+xoGnX^Z>Et zMFb##8enO8CEDtmZU90F=fL_#tPmBz=2loJ=u-tySNty6-ucNy0kFHbFYe$F7;xk{ zJ`s27h6T7t&v!t;+4;raF0Y^f*EhF!_k srcDreamIcon.Icon.Width || y < 1 || y > srcDreamIcon.Icon.Height) + return DreamValue.Null; + + string iconState = bundle.GetArgument(2, "icon_state").MustGetValueAsString(); + if(!srcDreamIcon.Icon.States.TryGetValue(iconState, out var iconStateObject)){ + if(iconState == string.Empty) + iconStateObject = srcDreamIcon.Icon.States.First().Value; + else //invalid icon state causes BYOND to create error.log but it's empty + throw new ArgumentException($"Invalid icon_state {iconState} passed to /icon.GetPixel()"); + } + + AtomDirection dir = (AtomDirection)bundle.GetArgument(3, "dir").MustGetValueAsInteger(); + if(dir == AtomDirection.None) + dir = iconStateObject.Directions.Keys.First(); + else if(!iconStateObject.Directions.ContainsKey(dir)) + return DreamValue.Null; + + int frame = Math.Max(0, bundle.GetArgument(4, "frame").MustGetValueAsInteger()); + if (iconStateObject.Frames < frame) + return DreamValue.Null; + + DreamValue moving = bundle.GetArgument(5, "moving"); //TODO what does this do? + + var stateDirFrame = iconStateObject.Directions[dir][frame]; + var pixel = stateDirFrame.Image![stateDirFrame.DMIFrame.X+x-1,stateDirFrame.DMIFrame.Y+y-1]; + if(pixel.A == 255) + return new DreamValue(new Color(pixel.ToVector4()).ToHexNoAlpha().ToLower()); + else + return new DreamValue(pixel.ToHex().ToLower()); + } + [DreamProc("Scale")] [DreamProcParameter("width", Type = DreamValueTypeFlag.Float)] [DreamProcParameter("height", Type = DreamValueTypeFlag.Float)] From 30ae0c96618bf99b5901c06a1d96d3fd3f1cc01f Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 16 May 2026 17:12:31 +0100 Subject: [PATCH 2/5] more comprehensive test --- .../DMProject/Tests/Icon/GetPixel.dm | 31 ++++++++++++++++--- .../Procs/Native/DreamProcNativeIcon.cs | 2 +- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Content.Tests/DMProject/Tests/Icon/GetPixel.dm b/Content.Tests/DMProject/Tests/Icon/GetPixel.dm index f9134d2f21..19146ee031 100644 --- a/Content.Tests/DMProject/Tests/Icon/GetPixel.dm +++ b/Content.Tests/DMProject/Tests/Icon/GetPixel.dm @@ -1,10 +1,33 @@ /proc/RunTest() var/icon/I = icon('hanoi.dmi') - // ASSERT(I.GetPixel(5,5) == "#ff0000") - var/list/target_pixels = list("#e8083f", "#ff0000", "#6aee1d", "#f1b004", "#ffa308", "#ff34e2", "#6aeeff",\ + ASSERT(I.GetPixel(5,5) == "#ff0000") + var/list/targetx_pixels = list("#e8083f", "#ff0000", "#6aee1d", "#f1b004", "#ffa308", "#ff34e2", "#6aeeff",\ "#f4f14d", "#fbed01", "#757121", "#c07760", "#cc7d66", "#c98164", "#5fed26", "#43fc13", "#43fc13", "#43fc13",\ "#43fc13", "#43fc13", "#48fa16", "#bf885e", "#625230", "#4d4b26", "#4c5234", "#09f5f3", "#00ffff", "#00ffff","#f44dfb",\ "#2c14ff", "#001bf9", "#00ef58", "#f74d04") + var/list/targety_pixels = list("#e8083f","#ff0000","#6bee1a","#1452f2","#e00bfc","#5be5fe","#00ffff","#f2ef4d",\ + "#6b6622","#4b4b25","#c07760","#cb8065","#57f020","#43fc13","#43fc13","#43fc13","#43fc13","#43fc13",\ + "#42fc12","#c68c63","#c87c64","#625230","#4c4a26","#f5e921","#2ffef1","#00f9ff","#ee54fc","#ffa04b",\ + "#f8a225","#3530e4","#00ef58","#f74d04") for(var/x in 1 to 32) - world.log << I.GetPixel(x,16,"target") - ASSERT(I.GetPixel(x,16,"target") == target_pixels[x]) \ No newline at end of file + ASSERT(I.GetPixel(x,16,"target") == targetx_pixels[x]) + for(var/y in 1 to 32) + ASSERT(I.GetPixel(16,y,"target") == targety_pixels[y]) + + //chose by fair dice roll, guaranteed to be random + var/list/random_pixels = list(\ + list(9, 2, "#ff0000"),\ + list(31, 8, "#00ef58"),\ + list(32, 32, "#fe1100"),\ + list(1, 1, "#d3105d"),\ + list(2, 13, "#ff0000"),\ + list(0, 0, null),\ + list(31, 31, "#00fe28"),\ + list(-1, 17, null),\ + ) + for(var/list/tuple in random_pixels) + ASSERT(I.GetPixel(tuple[1], tuple[2], "target") == tuple[3]) + +//byond bugs: +//- invalid iconstate causes error.log to be created but it's empty and no runtime triggers +//- invalid dir value (ie, 100) causes a hard crash \ No newline at end of file diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index f6aadc4e83..711f15d6bf 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -120,7 +120,7 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje DreamValue moving = bundle.GetArgument(5, "moving"); //TODO what does this do? var stateDirFrame = iconStateObject.Directions[dir][frame]; - var pixel = stateDirFrame.Image![stateDirFrame.DMIFrame.X+x-1,stateDirFrame.DMIFrame.Y+y-1]; + var pixel = stateDirFrame.Image![stateDirFrame.DMIFrame.X+x-1,stateDirFrame.DMIFrame.Y+(srcDreamIcon.Icon.Height-y)]; if(pixel.A == 255) return new DreamValue(new Color(pixel.ToVector4()).ToHexNoAlpha().ToLower()); else From 950e7a6d5fc9045190489660df12d351926783db Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 16 May 2026 17:33:12 +0100 Subject: [PATCH 3/5] test that crashes byond lmao --- Content.Tests/DMProject/Tests/Icon/GetPixelBadArgs.dm | 7 +++++++ OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 Content.Tests/DMProject/Tests/Icon/GetPixelBadArgs.dm diff --git a/Content.Tests/DMProject/Tests/Icon/GetPixelBadArgs.dm b/Content.Tests/DMProject/Tests/Icon/GetPixelBadArgs.dm new file mode 100644 index 0000000000..396dca8d1f --- /dev/null +++ b/Content.Tests/DMProject/Tests/Icon/GetPixelBadArgs.dm @@ -0,0 +1,7 @@ +/proc/RunTest() + var/icon/I = icon('hanoi.dmi') + ASSERT(I.GetPixel(1,1,"invalid iconstate") == null) //iconstate that doesn't exist + ASSERT(I.GetPixel(1,1,"0",dir=EAST) == null) //not a dir in this file + ASSERT(I.GetPixel(1,1,"0",dir=100) == null) //nonsense dir + ASSERT(I.GetPixel(1,1,"0",frame=-100) == "#ff0000") //frame is clipped at 1 + ASSERT(I.GetPixel(1,1,"0",frame=100) == null) //there's only 1 frame in the file diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index 711f15d6bf..97a8a8aa08 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -113,11 +113,11 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje else if(!iconStateObject.Directions.ContainsKey(dir)) return DreamValue.Null; - int frame = Math.Max(0, bundle.GetArgument(4, "frame").MustGetValueAsInteger()); + int frame = Math.Max(1, bundle.GetArgument(4, "frame").MustGetValueAsInteger()); if (iconStateObject.Frames < frame) return DreamValue.Null; - DreamValue moving = bundle.GetArgument(5, "moving"); //TODO what does this do? + DreamValue moving = bundle.GetArgument(5, "moving"); // TODO: implement movement states var stateDirFrame = iconStateObject.Directions[dir][frame]; var pixel = stateDirFrame.Image![stateDirFrame.DMIFrame.X+x-1,stateDirFrame.DMIFrame.Y+(srcDreamIcon.Icon.Height-y)]; From 8731acbd53c3baf6e68f5e95a93084300fd37eb5 Mon Sep 17 00:00:00 2001 From: amy Date: Sun, 17 May 2026 15:19:06 +0100 Subject: [PATCH 4/5] fixes --- .../Procs/Native/DreamProcNativeIcon.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index 97a8a8aa08..849a402a30 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -1,11 +1,8 @@ -using System.ComponentModel; using System.Linq; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Processing; using BlendType = OpenDreamRuntime.Objects.DreamIconOperationBlend.BlendType; using DreamValueTypeFlag = OpenDreamRuntime.DreamValue.DreamValueTypeFlag; @@ -100,11 +97,11 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje return DreamValue.Null; string iconState = bundle.GetArgument(2, "icon_state").MustGetValueAsString(); - if(!srcDreamIcon.Icon.States.TryGetValue(iconState, out var iconStateObject)){ + if(!srcDreamIcon.Icon.GenerateDMI().DMI.States.TryGetValue(iconState, out var iconStateObject)){ if(iconState == string.Empty) - iconStateObject = srcDreamIcon.Icon.States.First().Value; + iconStateObject = srcDreamIcon.Icon.GenerateDMI().DMI.States.First().Value; else //invalid icon state causes BYOND to create error.log but it's empty - throw new ArgumentException($"Invalid icon_state {iconState} passed to /icon.GetPixel()"); + return DreamValue.Null; //throw new ArgumentException($"Invalid icon_state {iconState} passed to /icon.GetPixel()"); } AtomDirection dir = (AtomDirection)bundle.GetArgument(3, "dir").MustGetValueAsInteger(); @@ -113,14 +110,16 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje else if(!iconStateObject.Directions.ContainsKey(dir)) return DreamValue.Null; - int frame = Math.Max(1, bundle.GetArgument(4, "frame").MustGetValueAsInteger()); - if (iconStateObject.Frames < frame) + int frame = Math.Max(1, bundle.GetArgument(4, "frame").MustGetValueAsInteger())-1; + + if (iconStateObject.FrameCount < frame) return DreamValue.Null; DreamValue moving = bundle.GetArgument(5, "moving"); // TODO: implement movement states var stateDirFrame = iconStateObject.Directions[dir][frame]; - var pixel = stateDirFrame.Image![stateDirFrame.DMIFrame.X+x-1,stateDirFrame.DMIFrame.Y+(srcDreamIcon.Icon.Height-y)]; + + var pixel = srcDreamIcon.Icon.GenerateDMI().Texture[stateDirFrame.X+x-1,stateDirFrame.Y+(srcDreamIcon.Icon.Height-y)]; if(pixel.A == 255) return new DreamValue(new Color(pixel.ToVector4()).ToHexNoAlpha().ToLower()); else From 2f4e4071f8f3e2ffbc92b1997add5e461c896b4f Mon Sep 17 00:00:00 2001 From: amy Date: Sun, 17 May 2026 15:22:00 +0100 Subject: [PATCH 5/5] steal the return from #2524 --- OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index 849a402a30..aa01508780 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -120,10 +120,11 @@ public static DreamValue NativeProc_GetPixel(NativeProc.Bundle bundle, DreamObje var stateDirFrame = iconStateObject.Directions[dir][frame]; var pixel = srcDreamIcon.Icon.GenerateDMI().Texture[stateDirFrame.X+x-1,stateDirFrame.Y+(srcDreamIcon.Icon.Height-y)]; - if(pixel.A == 255) - return new DreamValue(new Color(pixel.ToVector4()).ToHexNoAlpha().ToLower()); - else - return new DreamValue(pixel.ToHex().ToLower()); + return pixel.A switch { + 0 => DreamValue.Null, + 255 => new DreamValue($"#{pixel.R:x2}{pixel.G:x2}{pixel.B:x2}"), + _ => new DreamValue($"#{pixel.R:x2}{pixel.G:x2}{pixel.B:x2}{pixel.A:x2}") + }; } [DreamProc("Scale")]