Search code examples
c#unity-game-enginejpegtransparency

unity texture2d EncodeToJPG transparent black


I have a texture user drawn(on a mobile device), and I want to encode that to jpg so that user could share with that but I found the tranparent part will be black in the encoded jpg:

enter image description here

while I want the jpg to be like this: enter image description here

But I don't find any overridden method of texture2d.EncodeToJPG() to do this.

any ideas?

NOTE

the wing of the bird was drawn specifically to Color.white so it could be white in the encoded jpg.

Approach now

I finally manage to make this work by:

 Color32[] pixels = text.GetPixels32();
 Color blackTransparent = Color.black;
 blackTransparent.a = 0;
 for(int i = 0;i < pixels.Length; i++)
 {
    if(pixels[i] == blackTransparent)
    {
            pixels[i] = Color.white;
     }
  }
  text.SetPixels32(pixels);
  text.Apply();

But this will loop through all the pixels of the texture, if someone has a better method, plz let us know.

Problem with this approach:

we found that there will be some edge(when meeting with the black line) on the jpg using above code, maybe need some graphic processing knowledge to solve this? enter image description here

Here is How we load image to texture2d(as required by comment)

  Texture2D text = new Texture2D(1, 1, TextureFormat.PVRTC_RGBA4, false);
  byte[] imagebytes = null;
  string path = "image/path/sample.png";
  if (System.IO.File.Exists(path))
  {
      Debug.Log(" really load file from " + path);
      imagebytes = System.IO.File.ReadAllBytes(path);
  }
  text.LoadImage(imagebytes, false);

Solution

  • For your first question edit that asked how improve that code that is looping through all the pixels, you can. Just replace for(int i = 0;i < pixels.Length; i++) with for (int i = 0; i < pixels.Length; i += 4) then access each index from i+0 to i+3 inside the loop. The loop is about 4x faster.

    Color32[] pixels = text.GetPixels32();
    Color blackTransparent = Color.black;
    Color overwriteColor = Color.white;
    
    blackTransparent.a = 0;
    for (int i = 0; i < pixels.Length; i += 4)
    {
        if (pixels[i] == blackTransparent)
            pixels[i] = overwriteColor;
    
    
        if (pixels[i + 1] == blackTransparent)
            pixels[i + 1] = overwriteColor;
    
    
        if (pixels[i + 2] == blackTransparent)
            pixels[i + 2] = overwriteColor;
    
    
        if (pixels[i + 3] == blackTransparent)
            pixels[i + 3] = overwriteColor;
    }
    text.SetPixels32(pixels);
    text.Apply(true);
    

    Although, both code will run into the-same jagged line problem like mentioned in your second edit.

    enter image description here



    To fix it, don't compare the pixels directly with the == sign. Use a function that let's you use a threshold to compare color not just the numbers. This is the function I use to compare RGB colors in Unity and I suggest you start using it.

    You then have to manually check if the alpha is below or equals to certain value. The value of 90 seems to working great for this.

    This is what the script looks like:

    Color32[] pixels = text.GetPixels32();
    Color blackTransparent = Color.black;
    
    for (int i = 0; i < pixels.Length; i += 4)
    {
        checkPixel(ref pixels[i], blackTransparent, Color.white, 600);
        checkPixel(ref pixels[i + 1], blackTransparent, Color.white, 600);
        checkPixel(ref pixels[i + 2], blackTransparent, Color.white, 600);
        checkPixel(ref pixels[i + 3], blackTransparent, Color.white, 600);
    }
    text.SetPixels32(pixels);
    text.Apply();
    

    The functions checkPixel and ColourDistance functions:

    void checkPixel(ref Color32 pixel1, Color32 pixel2, Color32 newColor, int threshold)
    {
        if (ColourDistance(pixel1, pixel2) <= threshold)
        {
            if (pixel1.a <= 90)
            {
                pixel1 = Color.white;
            }
        }
    }
    
    private static double ColourDistance(Color32 c1, Color32 c2)
    {
        double rmean = (c1.r + c2.r) / 2;
    
        int r = c1.r - c2.r;
        int g = c1.g - c2.g;
        int b = c1.b - c2.b;
        double weightR = 2 + rmean / 256;
        double weightG = 4.0;
        double weightB = 2 + (255 - rmean) / 256;
        return Math.Sqrt(weightR * r * r + weightG * g * g + weightB * b * b);
    }
    

    And this is the result:

    enter image description here

    It can still be improved.