<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[kitecato's notes]]></title><description><![CDATA[Notes on pulling data out of Valheim's game files with a BepInEx plugin, and the problems that came up along the way.]]></description><link>https://kitecato.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ae8e86ccc2631e538602e40/4147f1d2-7dca-44a5-bd09-77162fd25bc5.png</url><title>kitecato&apos;s notes</title><link>https://kitecato.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:17:22 GMT</lastBuildDate><atom:link href="https://kitecato.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why my exported Valheim icons came out too dark]]></title><description><![CDATA[Valheim renders in linear colour space and a PNG expects sRGB. This is the C# conversion that fixed my exported icons.]]></description><link>https://kitecato.hashnode.dev/why-my-exported-valheim-icons-came-out-too-dark</link><guid isPermaLink="true">https://kitecato.hashnode.dev/why-my-exported-valheim-icons-came-out-too-dark</guid><category><![CDATA[unity]]></category><category><![CDATA[valheim]]></category><category><![CDATA[game]]></category><dc:creator><![CDATA[Hitorik]]></dc:creator><pubDate>Mon, 02 Nov 2026 19:59:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae8e86ccc2631e538602e40/f1316c09-da29-40cf-ac3f-56d7d8624614.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I wanted the item icons on my Valheim wiki to look the way they do in the game. I exported the sprites, and the PNGs came out noticeably darker than what the game shows. This post covers why that happens and how the export code ended up the way it is.</p>
<p>The code is part of a BepInEx plugin that dumps game data to JSON and PNG. It is open source, and the icon export lives in a single file.</p>
<h2>Reading pixels from a texture you can't read</h2>
<p>Textures in Valheim and in mods usually can't be read from the CPU, so calling GetPixels on them directly fails. The workaround is to draw the texture into a RenderTexture and read the result back into a Texture2D. This is the shape of it. It is condensed, and the plugin makes the same calls through reflection, so it doesn't depend on Unity's texture API at compile time:</p>
<pre><code class="language-csharp">static Texture2D ReadBack(Texture src, RenderTextureFormat rtFormat,
                          TextureFormat texFormat, bool linear)
{
    var rw = linear ? RenderTextureReadWrite.Linear : RenderTextureReadWrite.sRGB;
    var rt = RenderTexture.GetTemporary(src.width, src.height, 0, rtFormat, rw);
    var prev = RenderTexture.active;
    try
    {
        Graphics.Blit(src, rt);
        RenderTexture.active = rt;
        var tex = new Texture2D(src.width, src.height, texFormat, false, linear);
        tex.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0);
        tex.Apply();
        return tex;
    }
    finally
    {
        RenderTexture.active = prev;
        RenderTexture.ReleaseTemporary(rt);
    }
}
</code></pre>
<p>Two arguments decide the colours: the format of the render target, and whether it is Linear or sRGB. Both matter below.</p>
<h2>Why the icons come out dark</h2>
<p>Valheim renders in linear colour space. With a Linear render target, the values you read back are linear. A PNG is assumed to be sRGB, and nothing converts the numbers when the file is written. A viewer then treats linear numbers as if they were sRGB, and the icon looks too dark.</p>
<p>A colour that is (77, 201, 176) in sRGB is about (19, 149, 111) in linear values. Saved to a PNG as is, it shows up as that darker green.</p>
<h2>The conversion</h2>
<p>The fix is the sRGB transfer function, applied to red, green and blue. Alpha is not touched. This is the function from the plugin:</p>
<pre><code class="language-csharp">static float ToSrgb(float c)
{
    if (c &lt;= 0f) return 0f;
    if (c &gt;= 1f) return 1f;
    return c &lt;= 0.0031308f
        ? c * 12.92f
        : 1.055f * (float)Math.Pow(c, 1.0 / 2.4) - 0.055f;
}
</code></pre>
<h2>Cutting one sprite out of the texture</h2>
<p>A sprite is a rectangle inside a texture. For each sprite the plugin takes the texture and the sprite's textureRect (it falls back to rect), reads those pixels from the readable copy, converts the colour channels, and encodes the PNG:</p>
<pre><code class="language-csharp">Color[] px = readable.GetPixels(x, y, w, h, 0);
for (int i = 0; i &lt; px.Length; i++)
{
    px[i].r = ToSrgb(px[i].r);
    px[i].g = ToSrgb(px[i].g);
    px[i].b = ToSrgb(px[i].b);
}
var outTex = new Texture2D(w, h, TextureFormat.RGBA32, false, linear);
outTex.SetPixels(px);
outTex.Apply();
byte[] png = outTex.EncodeToPNG();
</code></pre>
<h2>Keeping the data in float</h2>
<p>In the default mode the render target is a float format (ARGBFloat). The values stay floats until the conversion is done, and only the final PNG is 8 bits per channel. If the float format isn't available on a machine, the plugin falls back to an 8-bit target and doesn't try the float one again.</p>
<h2>Three modes</h2>
<p>The config has three colour modes:</p>
<ul>
<li>LinearToSRGB is the default: a float linear target, then the per-pixel conversion above.</li>
<li>sRGB reads back through an sRGB render target and lets the GPU do the conversion.</li>
<li>Linear writes the raw linear values. These are the dark ones.</li>
</ul>
<p>I kept the last two so I can compare them with the default.</p>
<h2>Not reading the same texture twice</h2>
<p>Many sprites sit in the same texture, so reading a whole texture back for every sprite would repeat the same blit. The plugin keeps readable copies of the four textures it used most recently and destroys the older ones. If the same sprite comes up twice, it reuses the file it already wrote.</p>
<h2>Checking the result</h2>
<p>I compared the converted icons with the originals already on the wiki, which came from an earlier dump (WikiDump 1.1). The mean error is about 6 out of 255.</p>
<p>The plugin is open source: <a href="https://github.com/kitecato/WikiDump-">WikiDump on GitHub</a>, and the code above is in src/Icons.cs. The icons are used on Valheim Wiki, which also has a <a href="https://valheimwiki.net/en/seed-map">seed map for 1.0</a> that runs in the browser.</p>
]]></content:encoded></item></channel></rss>