ID:2386179
 
(See the best response by IceFire2050.)
Code:
obj
icon = 'object.dmi'

Click()
var/icon/i = icon(src.icon)

var/width = i.Width()
var/height = i.Height()

for(var/x = 1 to width)
for(var/y = 1 to height)
i.SwapColor(rgb(255, x, y), rgb(255 * rand(0, 1), 255 * rand(0, 1), 255 * rand(0, 1)))

src.icon = i


Problem description:
I use the above code to generate a coloured noise effect covering an entire icon. It works but it's slow. Outputting world.timeofday before and after gives about 4/10ths of a second to execute the code (which is noticable).

Is there a faster way to change the colour of a known pixel value or pixel location? I also tried DrawBox but it yields the same results.

In my case above, the width and height of the icon are 128 pixels.

I have other effects that I would like to do, but they requrie changing pixels are different positions in the icon, this method is too slow.
Any ideas?

Also, is there any way to access a pixel at a known location like an array without using GetPixel?

Something like:

var/icon/i = icon(src.icon)
var/pixelColour = i[1][1]
i[5][5] = rgb(255, 0, 0)


Which would give the colour at 1,1 in the icon and would set the colour of 5,5 to red.
var/icon/i = icon(src.icon)
var/pixelColour = i.GetPixel(1, 1)
i.DrawBox(rgb(255, 0, 0), 5, 5)

There's really no better alternative. Maybe your icon has a lot of frames and states? SwapColor and DrawBox apply to every frame, so it would probably slow down with the number of frames in all states. If you want to only be modifying a specific frame and state, you should be specifying those in icon(), then you can Insert it to where it needs to go in the full icon.
If you are really determined to obtain a faster method, it should be possible to send the raw data for icons to JavaScript for processing, then sending back the edited versions as a base64 string, which can then be saved and reloaded through a savefile, allowing the game to use it.

This sounds far-fetched, but should actually be possible with enough work. First, you would need to use the ExportText proc (savefile) to obtain the raw binary data for the icon file in the form of a base64 string. It might be possible to instead use the browse_rsc proc, to send the file to BYOND's browser cache, but I'm not sure if JavaScript can reference raw file data from that location, due to security restrictions and limitations of the browser.

Next, once you have the base64 string or possibly the name of the file to reference in the browser cache, you can then send the data to JavaScript using the output proc. Depending on the JavaScript library you use, you may or may not need to convert the base64 into a binary blob.

For libraries, I would recommend trying Jimp, which supports alpha channels, and doesn't need HTML canvas to function, making it more likely to run in BYOND's browser control. You can also take a look at some of the features of other libraries.

While those libraries might be great for processing normal image files, they can't tell the difference between a DMI and a PNG. They can't tell where one frame ends and another begins. This is fine if the icon you are working with is just a single state with a single static frame, or if you just want to apply a basic filter to every state and frame of the icon. Otherwise, you will need to write some DMI wrapper functions for any of the libraries mentioned above. To do this you will need another library to read the metadata where the DMI info is stored. DMI Read looks like a good option.

Important: Since many of these libraries were written for Node.js, you will almost certainly need to process them with something like Browserify or Webpack before you can test them in BYOND's browser. I have not tested any of this, so I have no idea if it will actually work.

Once you have finished processing the image in JavaScript, you will need to convert it back to a base64 string, add that string to the topic text of a url string, then navigate window.location to that url. This should then be received by the Topic proc (client) on the BYOND server side. Override this proc to obtain the new base64 string, and using the ExportText proc (savefile) output as a template, replace the old base64 data with the new one, and send it back through the ImportText proc (savefile). Then just reload the icon from the savefile, use it anywhere you like, and you will see that it has been modified.

Basically, this is already possible. You just need lots of duct tape code to put it all together.


If we had an arbitrary precision arithmetic library for DM, we could modify binary file data directly in BYOND, without the need for JavaScript, but that would be a lot of work, and the results would be quite slow. Still, it's an interesting idea that could provide features far beyond anything that icon operations would ever support.
@Kaiochao
Thank you, I did have a lot of states and didn't realise DrawBox goes over every frame / state. Selecting a specific state and frame sped the process a little bit but it is still slower than I would like.

@Multiverse7
I am going to try this and post back to see if this works, this is interesting and seems possible.
So, I have a problem.. I don't know how to reference the javascript file.

If I try to do something like thisL
<script src="./jimp.min.js"></script>


But DM doesn't read it at all.

I tried with a simple javascript file that contained a change to a div's innerHTML and it also didn't work, but when I put the javascript in the code it worked.

The problem with doing that is I can't put Jimp in DM because it has non-english characters which aren't being read.

Is there a way to refer to external js files?
In response to U_CANT_C_ME1
U_CANT_C_ME1 wrote:
Is there a way to refer to external js files?

Yes, you can use browse_rsc() to save the js file to the browser cache, which can then be referenced in the html.

Then it should be as simple as:
<script src="jimp.min.js"></script>


The only limitation is that for some reason, subfolders are not supported by that proc. Ironically, if you manually copy a folder with files into your cache folder while the world is still running, you can actually reference files in said subfolder within the browser's html.
Best response
Can you make the effect a separate icon and just use Blend()?
@Multiverse7
It turns out it was too much of a challenge for me, I'm not even that good at DM let alone Javascript. But it's thank you for the sound advice, I really appreciate it.

@IceFire2050
Thank you, that's what I ended up doing. I created multiple icons and used blending as well as atom.color to get the effects to work.
In response to U_CANT_C_ME1
I can understand that. I'm just glad you at least gave it a try. Most wouldn't go that far. I saw that you have been around at BYOND for quite a few years, so I took the chance that maybe you could figure that out. This interests me too or I wouldn't have bothered to write that guide.

I will be giving this a try myself, but I'm skeptical that I can make it work either. I'm expecting to run into a barrier of obscure JavaScript errors which require polyfills that don't even exist, or workarounds that require you know exactly how the library you are using works.
Out of curiosity, when you do have the base64 of the modified icon, how do you convert it to an icon? I noticed there is a CRC32 value in the save file and I couldn't get it to work with dummy base64 files..
I seem to have forgotten about one important detail. The ImportText proc (savefile), or more specifically, the internal filedata() function, won't accept the base64 data unless the checksum matches it.

While the parameter might be called "crc32", it's not actually using the official version of the CRC32 algorithm. What it's using is a variant of the XFER algorithm which seems to be associated with Acorn Computers. The only difference between XFER and the BYOND savefile variant is that XFER has an init value of 0x0, while BYOND uses 0xffffffff. This results in XFER having a check value of 0xbd0be338, while BYOND has a check value of 0xa5fd3138.

Comparison:
XFER
width=32 poly=0x000000af init=0x00000000 refin=false refout=false xorout=0x00000000 check=0xbd0be338 name="XFER"

BYOND Savefile Variant
width=32 poly=0x000000af init=0xffffffff refin=false refout=false xorout=0x00000000 check=0xa5fd3138 name=(none)

It's easier to understand by trying a CRC Calculator yourself. How to get BYOND's version of the algorithm:
1. At the first section, CRC width, set this to CRC-32.
2. In the second section, click the Predefined drop-down list, and select the last option, which should say "CRC32_XFER".
3. Leaving everything how it is, click on "Custom", to the right of that same drop-down list.
4. In the third section, look for the input box labeled "Initial Value", and replace the 0x0 with 0xffffffff in that box ONLY.
5. Finally, you can just click the "Calculate CRC!" button, and if you did everything right, you should get 0xA5FD3138 as the result. This is the check value.

I have verified that this algorithm is the correct one with many different test cases. The easiest way to try this is to save a plaintext file with something written in it, to a savefile. Then output the value returned by savefile.ExportText(). Copy everything inside of {" "}. On the CRC Calculator page, after having done the above steps, paste this into the CRC Input Data section, making sure the value is set to "String", not "Bytes". After that, you can click on "Calculate CRC!" and then you can see that the value matches the "crc32" value returned by ExportText(). For testing any other type of file, you would first need to convert the base64 string into bytes.

Basically, this checksum is the only thing standing in the way of having native binary file I/O in BYOND. While it would be nice to implement this algorithm directly in DM, that would most likely require an arbitrary precision arithmetic library, which is a huge project. It should be easiest to calculate this in JavaScript, for now anyway. I had planned to do this a long time ago but never got around to it.

I can't help but wonder why such an obscure algorithm was used as a checksum for savefiles. Is this supposed to be a secret? Obfuscation is not a form of security.
Thanks heaps for the thorough explanations. I am still wrapping my head around it all but it honestly gives a good insight with how BYOND's save files and IO works!