Example #1
0
        public static void compressDXT5A(ColorBlock src, ref AlphaBlockDXT5 dst, int iterationCount = 8)
        {
            AlphaBlock4x4 tmp = new AlphaBlock4x4();

            tmp.init(src, 3);
            compressDXT5A(tmp, ref dst, iterationCount);
        }
        /*static uint computeAlphaError8( ColorBlock & rgba,  AlphaBlockDXT5 * block, int bestError = INT_MAX)
         * {
         *      int totalError = 0;
         *
         *      for (uint i = 0; i < 16; i++)
         *      {
         *              byte alpha = rgba.color[i].a;
         *
         *              totalError += alphaDistance(alpha, nearestAlpha8(alpha, block.alpha0, block.alpha1));
         *
         *              if (totalError > bestError)
         *              {
         *                      // early out
         *                      return totalError;
         *              }
         *      }
         *
         *      return totalError;
         * }*/

        static float computeAlphaError(AlphaBlock4x4 src, AlphaBlockDXT5 dst, float bestError = float.MaxValue)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, false); // @@ Use target decoder.

            float totalError = 0;

            for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];

                uint minDist = int.MaxValue;
                for (uint p = 0; p < 8; p++)
                {
                    uint dist = alphaDistance(alpha, alphas[p]);
                    minDist = Math.Min(dist, minDist);
                }

                totalError += minDist * src.weights[i];

                if (totalError > bestError)
                {
                    // early out
                    return(totalError);
                }
            }

            return(totalError);
        }
        void compressDXT5A(ColorBlock src, AlphaBlockDXT5 dst)
        {
            AlphaBlock4x4 tmp = new AlphaBlock4x4();

            tmp.init(src, 3);
            compressDXT5A(tmp, dst);
        }
Example #4
0
        static uint computeAlphaIndices(AlphaBlock4x4 src, AlphaBlockDXT5 block)
        {
            byte[] alphas = new byte[8];
            block.evaluatePalette(alphas, false);     // @@ Use target decoder.

            uint totalError = 0;

            for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];

                uint besterror = 256 * 256;
                uint best      = 8;
                for (uint p = 0; p < 8; p++)
                {
                    int  d     = alphas[p] - alpha;
                    uint error = (uint)(d * d);

                    if (error < besterror)
                    {
                        besterror = error;
                        best      = p;
                    }
                }
                //nvDebugCheck(best < 8);

                totalError += besterror;
                block.setIndex(i, best);
            }

            return(totalError);
        }
Example #5
0
        static void compressDXT5A(AlphaBlock4x4 src, ref AlphaBlockDXT5 dst, int iterationCount = 8)
        {
            byte alpha0 = 0;
            byte alpha1 = 255;

            // Get min/max alpha.
            for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];
                alpha0 = Math.Max(alpha0, alpha);
                alpha1 = Math.Min(alpha1, alpha);
            }

            AlphaBlockDXT5 block = new AlphaBlockDXT5();

            block.alpha0 = (byte)(alpha0 - (alpha0 - alpha1) / 34);
            block.alpha1 = (byte)(alpha1 + (alpha0 - alpha1) / 34);
            uint besterror = computeAlphaIndices(src, block);

            AlphaBlockDXT5 bestblock = block;

            for (int i = 0; i < iterationCount; i++)
            {
                optimizeAlpha8(src, block);
                uint error = computeAlphaIndices(src, block);

                if (error >= besterror)
                {
                    // No improvement, stop.
                    break;
                }
                if (sameIndices(block, bestblock))
                {
                    bestblock = block;
                    break;
                }

                besterror = error;
                bestblock = block;
            }
            ;

            // Copy best block to result;
            dst = bestblock;
        }
        static float computeAlphaError_RGBM(ColorSet src, ColorBlock RGB, AlphaBlockDXT5 dst, float bestError = float.MaxValue)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, /*d3d9=*/ false); // @@ Use target decoder.

            float totalError = 0;

            for (uint i = 0; i < 16; i++)
            {
                float R = src.color[i].x;
                float G = src.color[i].y;
                float B = src.color[i].z;

                float r = (float)(RGB.color[i].r) / 255.0f;
                float g = (float)(RGB.color[i].g) / 255.0f;
                float b = (float)(RGB.color[i].b) / 255.0f;

                float minDist = float.MaxValue;
                for (uint p = 0; p < 8; p++)
                {
                    // Compute M.
                    float M = (float)(alphas[p]) / 255.0f * (1 - threshold) + threshold;

                    // Decode color.
                    float fr = r * M;
                    float fg = g * M;
                    float fb = b * M;

                    // Measure error.
                    float error = Mathf.Pow(R - fr, 2) + Mathf.Pow(G - fg, 2) + Mathf.Pow(B - fb, 2);

                    minDist = Mathf.Min(error, minDist);
                }

                totalError += minDist * src.weights[i];

                if (totalError > bestError)
                {
                    // early out
                    return(totalError);
                }
            }

            return(totalError);
        }
        static void computeAlphaIndices_RGBM(ColorSet src, ColorBlock RGB, AlphaBlockDXT5 dst)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, /*d3d9=*/ false); // @@ Use target decoder.

            for (uint i = 0; i < 16; i++)
            {
                float R = src.color[i].x;
                float G = src.color[i].y;
                float B = src.color[i].z;

                float r = (float)(RGB.color[i].r) / 255.0f;
                float g = (float)(RGB.color[i].g) / 255.0f;
                float b = (float)(RGB.color[i].b) / 255.0f;

                float minDist   = float.MaxValue;
                uint  bestIndex = 8;
                for (uint p = 0; p < 8; p++)
                {
                    // Compute M.
                    float M = (float)(alphas[p]) / 255.0f * (1 - threshold) + threshold;

                    // Decode color.
                    float fr = r * M;
                    float fg = g * M;
                    float fb = b * M;

                    // Measure error.
                    float error = Mathf.Pow(R - fr, 2) + Mathf.Pow(G - fg, 2) + Mathf.Pow(B - fb, 2);

                    if (error < minDist)
                    {
                        minDist   = error;
                        bestIndex = p;
                    }
                }

                dst.setIndex(i, bestIndex);
            }
        }
        static void computeAlphaIndices(AlphaBlock4x4 src, AlphaBlockDXT5 dst)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, /*d3d9=*/ false); // @@ Use target decoder.

            for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];

                uint minDist   = int.MaxValue;
                uint bestIndex = 8;
                for (uint p = 0; p < 8; p++)
                {
                    uint dist = alphaDistance(alpha, alphas[p]);

                    if (dist < minDist)
                    {
                        minDist   = dist;
                        bestIndex = p;
                    }
                }
                dst.setIndex(i, bestIndex);
            }
        }
        void compressDXT5A_RGBM(ColorSet src, ColorBlock RGB, AlphaBlockDXT5 dst)
        {
            byte mina = 255;
            byte maxa = 0;

            byte mina_no01 = 255;
            byte maxa_no01 = 0;

            // Get min/max alpha.
            /*for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];
                mina = min(mina, alpha);
                maxa = max(maxa, alpha);

                if (alpha != 0 && alpha != 255) {
                    mina_no01 = min(mina_no01, alpha);
                    maxa_no01 = max(maxa_no01, alpha);
                }
            }*/
            mina = 0;
            maxa = 255;
            mina_no01 = 0;
            maxa_no01 = 255;

            /*if (maxa - mina < 8) {
                dst.alpha0 = maxa;
                dst.alpha1 = mina;

                nvDebugCheck(computeAlphaError(src, dst) == 0);
            }
            else if (maxa_no01 - mina_no01 < 6) {
                dst.alpha0 = mina_no01;
                dst.alpha1 = maxa_no01;

                nvDebugCheck(computeAlphaError(src, dst) == 0);
            }
            else*/
            {
                float besterror = computeAlphaError_RGBM(src, RGB, dst);
                byte besta0 = maxa;
                byte besta1 = mina;

                // Expand search space a bit.
                int alphaExpand = 8;
                mina = (byte)((mina <= alphaExpand) ? 0 : mina - alphaExpand);
                maxa = (byte)((maxa >= 255 - alphaExpand) ? 255 : maxa + alphaExpand);

                for (byte a0 = (byte)(mina + 9); a0 < maxa; a0++)
                {
                    for (byte a1 = mina; a1 < a0 - 8; a1++)
                    {

                        dst.alpha0 = a0;
                        dst.alpha1 = a1;
                        float error = computeAlphaError_RGBM(src, RGB, dst, besterror);

                        if (error < besterror)
                        {
                            besterror = error;
                            besta0 = a0;
                            besta1 = a1;
                        }
                    }
                }

                // Try using the 6 step encoding.
                /*if (mina == 0 || maxa == 255)*/
                {

                    // Expand search space a bit.
                    alphaExpand = 6;
                    mina_no01 = (byte)((mina_no01 <= alphaExpand) ? 0 : mina_no01 - alphaExpand);
                    maxa_no01 = (byte)((maxa_no01 >= 255 - alphaExpand) ? 255 : maxa_no01 + alphaExpand);

                    for (byte a0 = (byte)(mina_no01 + 9); a0 < maxa_no01; a0++)
                    {
                        for (byte a1 = mina_no01; a1 < a0 - 8; a1++)
                        {
                            dst.alpha0 = a1;
                            dst.alpha1 = a0;
                            float error = computeAlphaError_RGBM(src, RGB, dst, besterror);

                            if (error < besterror)
                            {
                                besterror = error;
                                besta0 = a1;
                                besta1 = a0;
                            }
                        }
                    }
                }

                dst.alpha0 = besta0;
                dst.alpha1 = besta1;
            }

            computeAlphaIndices_RGBM(src, RGB, dst);
        }
 void compressDXT5A(ColorBlock src, AlphaBlockDXT5 dst)
 {
     AlphaBlock4x4 tmp = new AlphaBlock4x4();
     tmp.init(src, 3);
     compressDXT5A(tmp, dst);
 }
        static void computeAlphaIndices_RGBM(ColorSet src, ColorBlock RGB, AlphaBlockDXT5 dst)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, /*d3d9=*/false); // @@ Use target decoder.

            for (uint i = 0; i < 16; i++)
            {
                float R = src.color[i].x;
                float G = src.color[i].y;
                float B = src.color[i].z;

                float r = (float)(RGB.color[i].r) / 255.0f;
                float g = (float)(RGB.color[i].g) / 255.0f;
                float b = (float)(RGB.color[i].b) / 255.0f;

                float minDist = float.MaxValue;
                uint bestIndex = 8;
                for (uint p = 0; p < 8; p++)
                {
                    // Compute M.
                    float M = (float)(alphas[p]) / 255.0f * (1 - threshold) + threshold;

                    // Decode color.
                    float fr = r * M;
                    float fg = g * M;
                    float fb = b * M;

                    // Measure error.
                    float error = Mathf.Pow(R - fr, 2) + Mathf.Pow(G - fg, 2) + Mathf.Pow(B - fb, 2);

                    if (error < minDist)
                    {
                        minDist = error;
                        bestIndex = p;
                    }
                }

                dst.setIndex(i, bestIndex);
            }
        }
        static void computeAlphaIndices(AlphaBlock4x4 src, AlphaBlockDXT5 dst)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, /*d3d9=*/false); // @@ Use target decoder.

            for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];

                uint minDist = int.MaxValue;
                uint bestIndex = 8;
                for (uint p = 0; p < 8; p++)
                {
                    uint dist = alphaDistance(alpha, alphas[p]);

                    if (dist < minDist)
                    {
                        minDist = dist;
                        bestIndex = p;
                    }
                }
                dst.setIndex(i, bestIndex);
            }
        }
        static float computeAlphaError_RGBM(ColorSet src, ColorBlock RGB, AlphaBlockDXT5 dst, float bestError = float.MaxValue)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, /*d3d9=*/false); // @@ Use target decoder.

            float totalError = 0;

            for (uint i = 0; i < 16; i++)
            {
                float R = src.color[i].x;
                float G = src.color[i].y;
                float B = src.color[i].z;

                float r = (float)(RGB.color[i].r) / 255.0f;
                float g = (float)(RGB.color[i].g) / 255.0f;
                float b = (float)(RGB.color[i].b) / 255.0f;

                float minDist = float.MaxValue;
                for (uint p = 0; p < 8; p++)
                {
                    // Compute M.
                    float M = (float)(alphas[p]) / 255.0f * (1 - threshold) + threshold;

                    // Decode color.
                    float fr = r * M;
                    float fg = g * M;
                    float fb = b * M;

                    // Measure error.
                    float error = Mathf.Pow(R - fr, 2) + Mathf.Pow(G - fg, 2) + Mathf.Pow(B - fb, 2);

                    minDist = Mathf.Min(error, minDist);
                }

                totalError += minDist * src.weights[i];

                if (totalError > bestError)
                {
                    // early out
                    return totalError;
                }
            }

            return totalError;
        }
        /*static uint computeAlphaError8( ColorBlock & rgba,  AlphaBlockDXT5 * block, int bestError = INT_MAX)
        {
                int totalError = 0;

                for (uint i = 0; i < 16; i++)
                {
                        byte alpha = rgba.color[i].a;

                        totalError += alphaDistance(alpha, nearestAlpha8(alpha, block.alpha0, block.alpha1));

                        if (totalError > bestError)
                        {
                                // early out
                                return totalError;
                        }
                }

                return totalError;
        }*/
        static float computeAlphaError(AlphaBlock4x4 src, AlphaBlockDXT5 dst, float bestError = float.MaxValue)
        {
            byte[] alphas = new byte[8];
            dst.evaluatePalette(alphas, false); // @@ Use target decoder.

            float totalError = 0;

            for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];

                uint minDist = int.MaxValue;
                for (uint p = 0; p < 8; p++)
                {
                    uint dist = alphaDistance(alpha, alphas[p]);
                    minDist = Math.Min(dist, minDist);
                }

                totalError += minDist * src.weights[i];

                if (totalError > bestError)
                {
                    // early out
                    return totalError;
                }
            }

            return totalError;
        }
        void compressDXT5A_RGBM(ColorSet src, ColorBlock RGB, AlphaBlockDXT5 dst)
        {
            byte mina = 255;
            byte maxa = 0;

            byte mina_no01 = 255;
            byte maxa_no01 = 0;

            // Get min/max alpha.

            /*for (uint i = 0; i < 16; i++)
             * {
             *  byte alpha = src.alpha[i];
             *  mina = min(mina, alpha);
             *  maxa = max(maxa, alpha);
             *
             *  if (alpha != 0 && alpha != 255) {
             *      mina_no01 = min(mina_no01, alpha);
             *      maxa_no01 = max(maxa_no01, alpha);
             *  }
             * }*/
            mina      = 0;
            maxa      = 255;
            mina_no01 = 0;
            maxa_no01 = 255;

            /*if (maxa - mina < 8) {
             *  dst.alpha0 = maxa;
             *  dst.alpha1 = mina;
             *
             *  nvDebugCheck(computeAlphaError(src, dst) == 0);
             * }
             * else if (maxa_no01 - mina_no01 < 6) {
             *  dst.alpha0 = mina_no01;
             *  dst.alpha1 = maxa_no01;
             *
             *  nvDebugCheck(computeAlphaError(src, dst) == 0);
             * }
             * else*/
            {
                float besterror = computeAlphaError_RGBM(src, RGB, dst);
                byte  besta0    = maxa;
                byte  besta1    = mina;

                // Expand search space a bit.
                int alphaExpand = 8;
                mina = (byte)((mina <= alphaExpand) ? 0 : mina - alphaExpand);
                maxa = (byte)((maxa >= 255 - alphaExpand) ? 255 : maxa + alphaExpand);

                for (byte a0 = (byte)(mina + 9); a0 < maxa; a0++)
                {
                    for (byte a1 = mina; a1 < a0 - 8; a1++)
                    {
                        dst.alpha0 = a0;
                        dst.alpha1 = a1;
                        float error = computeAlphaError_RGBM(src, RGB, dst, besterror);

                        if (error < besterror)
                        {
                            besterror = error;
                            besta0    = a0;
                            besta1    = a1;
                        }
                    }
                }

                // Try using the 6 step encoding.
                /*if (mina == 0 || maxa == 255)*/
                {
                    // Expand search space a bit.
                    alphaExpand = 6;
                    mina_no01   = (byte)((mina_no01 <= alphaExpand) ? 0 : mina_no01 - alphaExpand);
                    maxa_no01   = (byte)((maxa_no01 >= 255 - alphaExpand) ? 255 : maxa_no01 + alphaExpand);

                    for (byte a0 = (byte)(mina_no01 + 9); a0 < maxa_no01; a0++)
                    {
                        for (byte a1 = mina_no01; a1 < a0 - 8; a1++)
                        {
                            dst.alpha0 = a1;
                            dst.alpha1 = a0;
                            float error = computeAlphaError_RGBM(src, RGB, dst, besterror);

                            if (error < besterror)
                            {
                                besterror = error;
                                besta0    = a1;
                                besta1    = a0;
                            }
                        }
                    }
                }

                dst.alpha0 = besta0;
                dst.alpha1 = besta1;
            }

            computeAlphaIndices_RGBM(src, RGB, dst);
        }
Example #16
0
        static void optimizeAlpha8(AlphaBlock4x4 src, AlphaBlockDXT5 block)
        {
            float alpha2_sum    = 0;
            float beta2_sum     = 0;
            float alphabeta_sum = 0;
            float alphax_sum    = 0;
            float betax_sum     = 0;

            for (uint i = 0; i < 16; i++)
            {
                uint  idx = block.index(i);
                float alpha;
                if (idx < 2)
                {
                    alpha = 1.0f - idx;
                }
                else
                {
                    alpha = (8.0f - idx) / 7.0f;
                }

                float beta = 1 - alpha;

                alpha2_sum    += alpha * alpha;
                beta2_sum     += beta * beta;
                alphabeta_sum += alpha * beta;
                alphax_sum    += alpha * src.alpha[i];
                betax_sum     += beta * src.alpha[i];
            }

            float factor = 1.0f / (alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum);

            float a = (alphax_sum * beta2_sum - betax_sum * alphabeta_sum) * factor;
            float b = (betax_sum * alpha2_sum - alphax_sum * alphabeta_sum) * factor;

            byte alpha0 = (byte)(Math.Min(Math.Max(a, 0.0f), 255.0f));
            byte alpha1 = (byte)(Math.Min(Math.Max(b, 0.0f), 255.0f));

            if (alpha0 < alpha1)
            {
                swap(ref alpha0, ref alpha1);

                // Flip indices:
                for (uint i = 0; i < 16; i++)
                {
                    uint idx = block.index(i);
                    if (idx < 2)
                    {
                        block.setIndex(i, 1 - idx);
                    }
                    else
                    {
                        block.setIndex(i, 9 - idx);
                    }
                }
            }
            else if (alpha0 == alpha1)
            {
                for (uint i = 0; i < 16; i++)
                {
                    block.setIndex(i, 0);
                }
            }

            block.alpha0 = alpha0;
            block.alpha1 = alpha1;
        }
Example #17
0
        /*
         * static void optimizeAlpha6( ColorBlock & rgba, AlphaBlockDXT5 * block)
         * {
         *      float alpha2_sum = 0;
         *      float beta2_sum = 0;
         *      float alphabeta_sum = 0;
         *      float alphax_sum = 0;
         *      float betax_sum = 0;
         *
         *      for (int i = 0; i < 16; i++)
         *      {
         *              byte x = rgba.color[i].a;
         *              if (x == 0 || x == 255) continue;
         *
         *              uint bits = block.index(i);
         *              if (bits == 6 || bits == 7) continue;
         *
         *              float alpha;
         *              if (bits == 0) alpha = 1.0f;
         *              else if (bits == 1) alpha = 0.0f;
         *              else alpha = (6.0f - block.index(i)) / 5.0f;
         *
         *              float beta = 1 - alpha;
         *
         *              alpha2_sum += alpha * alpha;
         *              beta2_sum += beta * beta;
         *              alphabeta_sum += alpha * beta;
         *              alphax_sum += alpha * x;
         *              betax_sum += beta * x;
         *      }
         *
         *       float factor = 1.0f / (alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum);
         *
         *      float a = (alphax_sum * beta2_sum - betax_sum * alphabeta_sum) * factor;
         *      float b = (betax_sum * alpha2_sum - alphax_sum * alphabeta_sum) * factor;
         *
         *      uint alpha0 = uint(min(max(a, 0.0f), 255.0f));
         *      uint alpha1 = uint(min(max(b, 0.0f), 255.0f));
         *
         *      if (alpha0 > alpha1)
         *      {
         *              swap(alpha0, alpha1);
         *      }
         *
         *      block.alpha0 = alpha0;
         *      block.alpha1 = alpha1;
         * }
         */

        static bool sameIndices(AlphaBlockDXT5 block0, AlphaBlockDXT5 block1)
        {
            ulong mask = ~(ulong)(0xFFFF);

            return((block0.u | mask) == (block1.u | mask));
        }
        void compressDXT5A(AlphaBlock4x4 src, AlphaBlockDXT5 dst)
        {
            byte mina = 255;
            byte maxa = 0;

            byte mina_no01 = 255;
            byte maxa_no01 = 0;

            // Get min/max alpha.
            for (uint i = 0; i < 16; i++)
            {
                byte alpha = src.alpha[i];
                mina = Math.Min(mina, alpha);
                maxa = Math.Max(maxa, alpha);

                if (alpha != 0 && alpha != 255)
                {
                    mina_no01 = Math.Min(mina_no01, alpha);
                    maxa_no01 = Math.Max(maxa_no01, alpha);
                }
            }

            if (maxa - mina < 8)
            {
                dst.alpha0 = maxa;
                dst.alpha1 = mina;
            }
            else if (maxa_no01 - mina_no01 < 6)
            {
                dst.alpha0 = mina_no01;
                dst.alpha1 = maxa_no01;
            }
            else
            {
                float besterror = computeAlphaError(src, dst);
                byte  besta0    = maxa;
                byte  besta1    = mina;

                // Expand search space a bit.
                int alphaExpand = 8;
                mina = (byte)((mina <= alphaExpand) ? 0 : mina - alphaExpand);
                maxa = (byte)((maxa >= 255 - alphaExpand) ? 255 : maxa + alphaExpand);

                for (byte a0 = (byte)(mina + 9); a0 < maxa; a0++)
                {
                    for (byte a1 = mina; a1 < a0 - 8; a1++)
                    {
                        dst.alpha0 = a0;
                        dst.alpha1 = a1;
                        float error = computeAlphaError(src, dst, besterror);

                        if (error < besterror)
                        {
                            besterror = error;
                            besta0    = a0;
                            besta1    = a1;
                        }
                    }
                }

                // Try using the 6 step encoding.
                /*if (mina == 0 || maxa == 255)*/
                {
                    // Expand search space a bit.
                    alphaExpand = 6;
                    mina_no01   = (byte)((mina_no01 <= alphaExpand) ? 0 : mina_no01 - alphaExpand);
                    maxa_no01   = (byte)((maxa_no01 >= 255 - alphaExpand) ? 255 : maxa_no01 + alphaExpand);

                    for (byte a0 = (byte)(mina_no01 + 9); a0 < maxa_no01; a0++)
                    {
                        for (byte a1 = mina_no01; a1 < a0 - 8; a1++)
                        {
                            dst.alpha0 = a1;
                            dst.alpha1 = a0;
                            float error = computeAlphaError(src, dst, besterror);

                            if (error < besterror)
                            {
                                besterror = error;
                                besta0    = a1;
                                besta1    = a0;
                            }
                        }
                    }
                }

                dst.alpha0 = besta0;
                dst.alpha1 = besta1;
            }

            computeAlphaIndices(src, dst);
        }