- Home /
 
How to get Sprite color palette
Hey there,
I have a psb image which contains of multiple layers, each layer represents a gameObject and I would love to get the color of each of those elements... usually each gameObject contains only 1-3 colors and i would love to know which ones these are.
Is there any other / better approach to get that information ?
My scripts is as follows:
 using UnityEngine;
 using UnityEngine.Rendering;
 
 [System.Serializable]
 public class PageData : MonoBehaviour
 {
     private void Start()
     {
         foreach (Transform child in this.transform)
         {
             Sprite sprite = child.GetComponent<SpriteRenderer>().sprite;
             Color[] pix = sprite.texture.GetPixels();
             Debug.Log( pix );
             // ?
         }
     }
 }
 
              If you look into Sprite documentation page, you will see that there is a texture property already.
 Sprite sprite = child.GetComponent<SpriteRenderer>().sprite;
 Texture2D sourceTex = sprite.texture;
 
                 Good to know, thx.
However even with this simplified script i still get the out of bounds error...
   private void Start()
     {
         foreach (Transform child in this.transform)
         {
 
             Sprite sprite = child.GetComponent<SpriteRenderer>().sprite;
 
             Texture2D sourceTex = sprite.texture;
             
            Rect sourceRect = child.gameObject.GetComponent<RectTransform>().rect;
             int x = $$anonymous$$athf.FloorToInt(sourceRect.x);
             int y = $$anonymous$$athf.FloorToInt(sourceRect.y);
             int width = $$anonymous$$athf.FloorToInt(sourceRect.width);
             int height = $$anonymous$$athf.FloorToInt(sourceRect.height);
             Color[] pix = sourceTex.GetPixels(x, y, width, height);
             
         }
     }
                 You're seeing this error because RectTransform values are not texture coordinates but ui hierarchy coordinates. It's close equivalent to using transform.positionas texture x and y - not an ideal fit. 
That being said - you can probably construct a RectTransform hierarchy to mirror texture coordinates OR figure out a way of calculating one from the other
 Color[] pix = sourceTex.GetPixels();
 
                  That's it.
Alternatively:
 Color[] pix = sourceTex.GetPixels( 0 , 0 , sourceTex.width , sourceTex.height );
 
                  But the result is the same.
ok this seems to work without any errors, thx a lot...
However, it seems that unity is struggling quite a bit now... showing the color arry in the editor shows that each image as 2-10 million pixels and unity is really struggling.
$$anonymous$$aybe my approach is also the wrong one here... What I would love to achieve is the following:
I have a psb image which contains of multiple layers, each layer represents a gameObject and I would love to get the color of each of those elements... usually each gameObject contains only 1-3 colors and i would love to know which ones these are.
Is there any other / better approach to get that information ?
Answer by andrew-lukasik · Sep 29, 2020 at 10:31 AM
That's right. Going through Color[] to compile color palette data in an inefficient process ideally should be done as pre-processing step (in editor as asset/scene preparation) and saved for easy and fast access while application is running
Click RMB over component in Inspector window and choose "Update Color Data" to update.

 using System.Collections.Generic;
 using System.Linq;
 using UnityEngine;
 
 public class PageData : MonoBehaviour
 {
 
     [SerializeField] ColorTrio _sharedColors;
     [SerializeField] SpriteRenderer[] _spriteRenderers;
     [SerializeField] ColorTrio[] _colors;
 
     void Start ()
     {
         for( int i=0 ; i<_spriteRenderers.Length ; i++ )
         {
             var spriteRenderer = _spriteRenderers[i];
             var color = _colors[i];
             
             Debug.Log( $"{spriteRenderer.name}'s colors: {color}" , spriteRenderer.gameObject );
         }
     }
 
     [ContextMenu("Update Color Data")]
     void UpdateColorData ()
     {
         _spriteRenderers = GetComponentsInChildren<SpriteRenderer>( includeInactive:true );
         _colors = new ColorTrio[ _spriteRenderers.Length ];
         Dictionary<Color,int> shared_palette_unsorted = new Dictionary<Color,int>();
         
         for( int i=0 ; i<_spriteRenderers.Length ; i++ )
         {
             var spriteRenderer = _spriteRenderers[i];
 
             // compile sorted array of colors (from most to least common one):
             Color[] palette;
             {
                 Dictionary<Color,int> palette_unsorted = new Dictionary<Color,int>();//to represent Color and number of pixels of that Color
                 Sprite sprite = spriteRenderer.sprite;
                 Rect spriteRect = sprite.rect;
                 Color[] spritePixels = sprite.texture.GetPixels( (int)spriteRect.x , (int)spriteRect.y , (int)spriteRect.width , (int)spriteRect.height );
                 foreach( var color in spritePixels )
                 {
                     if( color.a!=1 ) continue;// lets skip all transparent and semi-transparent pixels
                     
                     if( palette_unsorted.ContainsKey(color) )
                         palette_unsorted[color]++;// another pixel of this color
                     else
                         palette_unsorted.Add( color , 1 );// first
                 }
                 palette = palette_unsorted
                     .OrderByDescending( (kv)=>kv.Value )
                     .Select( (kv)=> kv.Key )
                     .ToArray();
                 
                 foreach( var kv in palette_unsorted )
                 {
                     Color color = kv.Key;
                     int count = kv.Value;
                     if( shared_palette_unsorted.ContainsKey(color) )
                         shared_palette_unsorted[color] += count;
                     else
                         shared_palette_unsorted.Add( color , count );
                 }
             }
             
             // pick colors:
             {
                 ColorTrio trio = new ColorTrio();
                 trio.primary = palette[0];
                 trio.secondary = palette.FirstOrDefault( (col)=> col!=trio.primary );// will result in Color(0,0,0,0) if none found
                 trio.tertiary = palette.FirstOrDefault( (col)=> col!=trio.primary && col!=trio.secondary );// will result in Color(0,0,0,0) if none found
                 _colors[i] = trio;
             }
             
             #if DEBUG
             // print debug info
             {
                 var sb = new System.Text.StringBuilder();
                 foreach( var color in palette ) sb.AppendLine( $"\tcolor: {color}" );
                 Debug.Log( $"'{spriteRenderer.name}' colors: {_colors[i].primary}, {_colors[i].secondary}, {_colors[i].tertiary}\nall colors:\n{sb}" , spriteRenderer.gameObject );
             }
             #endif
         }
 
         // pick shared colors:
         {
             Color[] shared_sorted = shared_palette_unsorted
                 .OrderByDescending( (kv)=>kv.Value )
                 .Select( (kv)=> kv.Key )
                 .ToArray();
             
             ColorTrio trio = new ColorTrio();
             trio.primary = shared_sorted[0];
             trio.secondary = shared_sorted.FirstOrDefault( (col)=> col!=trio.primary );// will result in Color(0,0,0,0) if none found
             trio.tertiary = shared_sorted.FirstOrDefault( (col)=> col!=trio.primary && col!=trio.secondary );// will result in Color(0,0,0,0) if none found
             _sharedColors = trio;
 
             #if DEBUG
             Debug.Log( $"'{this.name}' SHARED COLORS: {_sharedColors.primary}, {_sharedColors.secondary}, {_sharedColors.tertiary}" , gameObject );
             #endif
         }
 
         #if UNITY_EDITOR
         UnityEditor.EditorUtility.SetDirty( this );
         #endif
     }
 
     public ColorTrio GetColorsFor ( SpriteRenderer spriteRenderer )
     {
         for( int i=0 ; i<_spriteRenderers.Length ; i++ )
         {
             if( _spriteRenderers[i]==spriteRenderer )
                 return _colors[i];
         }
         // else not found:
         Debug.LogWarning( $"'{spriteRenderer.name}' not found among '{this.name}' SpriteRenderers" , spriteRenderer );
         return default(ColorTrio);
     }
     public ColorTrio GetColorsFor ( GameObject go )
     {
         for( int i=0 ; i<_spriteRenderers.Length ; i++ )
         {
             if( _spriteRenderers[i].gameObject==go )
                 return _colors[i];
         }
         // else not found:
         Debug.LogWarning( $"'{go.name}' not found among '{this.name}' SpriteRenderers" , go );
         return default(ColorTrio);
     }
 
     [System.Serializable]
     public struct ColorTrio
     {
         public Color primary, secondary, tertiary;
         public override string ToString () => $"( primary:{primary} , secondary:{secondary} , tertiary:{tertiary} )";
     }
     
 }
 
              Understood, the issue is that i want to make a color by number game, meaning each picture consits of up to 300 parts (GameObjects) setting the proper colour for each of them is literally a pain in the ***, therefore i made a shader to recolour the gameobjects white upon game start. In order to deter$$anonymous$$e now which colors are being needed (show the buttons etc.) and setting up ids for each gameobject to deter$$anonymous$$e wether the right color got selected upon taping these i would need to know the acctual color of each sprite...
$$anonymous$$aybe there is also a different option like using a polygon collider and getting the centerpoint of that or something?!
Consider creating a lookup table of colors for each SpriteRenderer (sample code added above) 
Sorry for the confusion - I just updated the answer with code that actually works now :)
[Context$$anonymous$$enu("Update Color Data")] adds an Inspector context menu with that name - click over this component with R$$anonymous$$B and this new option should be right there now (it will update both arrays automagically) 
Your answer