/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind;

/**
 * @author Tom Gaskins
 * @version $Id: DDSConverter.java 1614 2007-04-23 22:38:20Z garakl $
 */
public class DDSConverter
{
    static final int DDSD_CAPS = 0x0001;
    static final int DDSD_HEIGHT = 0x0002;
    static final int DDSD_WIDTH = 0x0004;
    static final int DDSD_PIXELFORMAT = 0x1000;
    static final int DDSD_MIPMAPCOUNT = 0x20000;
    static final int DDSD_LINEARSIZE = 0x80000;
    static final int DDPF_FOURCC = 0x0004;
    static final int DDSCAPS_TEXTURE = 0x1000;

    protected static class Color
    {
        private int r, g, b;

        public Color()
        {
            this.r = this.g = this.b = 0;
        }

        public Color(int r, int g, int b)
        {
            this.r = r;
            this.g = g;
            this.b = b;
        }

        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            final gov.nasa.worldwind.DDSConverter.Color color = (gov.nasa.worldwind.DDSConverter.Color) o;

            if (b != color.b)
                return false;
            if (g != color.g)
                return false;
            //noinspection RedundantIfStatement
            if (r != color.r)
                return false;

            return true;
        }

        public int hashCode()
        {
            int result;
            result = r;
            result = 29 * result + g;
            result = 29 * result + b;
            return result;
        }
    }

    /**
     * @param image
     * @param mimeType
     * @return
     * @throws IllegalArgumentException if either <code>image</code> or <code>mimeType</code> is null or if
     *                                  <code>mimeType</code> does not yield a valid suffix
     * @throws java.io.IOException
     */
    public static java.nio.ByteBuffer convertToDxt1NoTransparency(java.nio.ByteBuffer image, String mimeType)
        throws java.io.IOException
    {
        if (image == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (mimeType == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull");
            gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        String suffix = WWIO.getSuffixForMimeType(mimeType);
        if (suffix == null)
        {
            String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType;
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        java.io.File tempFile = WWIO.saveBufferToTempFile(image, suffix);

        return convertToDxt1NoTransparency(tempFile);
    }

    /**
     * @param file
     * @return
     * @throws IllegalArgumentException if either <code>file</code> is null, does not exist, or cannot be read
     * @throws java.io.IOException
     */
    public static java.nio.ByteBuffer convertToDxt1NoTransparency(java.io.File file) throws java.io.IOException
    {
        if (file == null)
        {     //
            String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (!file.exists() || !file.canRead())
        {
            String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);

        if (image == null)
        {
            return null;
        }

//        // Change its orientation to that of OpenGL.
//        java.awt.geom.AffineTransform xform = java.awt.geom.AffineTransform.getScaleInstance(1, -1);
//        xform.translate(0, -image.getHeight(null));
//        java.awt.image.AffineTransformOp op = new java.awt.image.AffineTransformOp(
//                xform, java.awt.image.AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
//        image = op.filter(image, null);

        int[] pixels = new int[16];
        int bufferSize = 128 + image.getWidth() * image.getHeight() / 2;
        java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(bufferSize);
        buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
        buildHeaderDxt1(buffer, image.getWidth(), image.getHeight());

        int numTilesWide = image.getWidth() / 4;
        int numTilesHigh = image.getHeight() / 4;
        for (int i = 0; i < numTilesHigh; i++)
        {
            for (int j = 0; j < numTilesWide; j++)
            {
                java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4);
                originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4);
                Color[] colors = getColors888(pixels);

                for (int k = 0; k < pixels.length; k++)
                {
                    pixels[k] = getPixel565(colors[k]);
                    colors[k] = getColor565(pixels[k]);
                }

                int[] extremaIndices = determineExtremeColors(colors);
                if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]])
                {
                    int t = extremaIndices[0];
                    extremaIndices[0] = extremaIndices[1];
                    extremaIndices[1] = t;
                }

                buffer.putShort((short) pixels[extremaIndices[0]]);
                buffer.putShort((short) pixels[extremaIndices[1]]);

                long bitmask = computeBitMask(colors, extremaIndices);
                buffer.putInt((int) bitmask);
            }
        }

        return buffer;
    }

    /**
     * @param image
     * @param mimeType
     * @return
     * @throws IllegalArgumentException if either <code>image</code> or <code>mimeType</code> is null, or if
     *                                  <code>mimeType</code> does not yield a valid suffix
     * @throws java.io.IOException
     */
    public static java.nio.ByteBuffer convertToDxt1WithTransparency(java.nio.ByteBuffer image, String mimeType)
        throws java.io.IOException
    {
        if (image == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (mimeType == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull");
            gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        String suffix = WWIO.getSuffixForMimeType(mimeType);
        if (suffix == null)
        {
            String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType;
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        java.io.File tempFile = WWIO.saveBufferToTempFile(image, suffix);

        return convertToDxt1WithTransparency(tempFile);
    }

    /**
     * @param image
     * @param mimeType
     * @return
     * @throws IllegalArgumentException if either <code>image</code> or <code>mimeType</code> is null, or if
     *                                  <code>mimeType</code> does not yield a valid suffix
     * @throws java.io.IOException
     */
    public static java.nio.ByteBuffer convertToDxt3(java.nio.ByteBuffer image, String mimeType)
        throws java.io.IOException
    {
        if (image == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (mimeType == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull");
            gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        String suffix = WWIO.getSuffixForMimeType(mimeType);
        if (suffix == null)
        {
            String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType;
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        java.io.File tempFile = WWIO.saveBufferToTempFile(image, suffix);

        return convertToDxt3(tempFile);
    }

    /**
     * @param file
     * @return
     * @throws IllegalArgumentException if either <code>file</code> is null, does not exist, or cannot be read
     * @throws java.io.IOException
     */
    public static java.nio.ByteBuffer convertToDxt3(java.io.File file) throws java.io.IOException
    {
        if (file == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (!file.exists() || !file.canRead())
        {
            String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);
        if (image == null)
        {
            return null;
        }

//        // Change its orientation to that of OpenGL.
//        java.awt.geom.AffineTransform xform = java.awt.geom.AffineTransform.getScaleInstance(1, -1);
//        xform.translate(0, -image.getHeight(null));
//        java.awt.image.AffineTransformOp op = new java.awt.image.AffineTransformOp(
//                xform, java.awt.image.AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
//        image = op.filter(image, null);

        int[] pixels = new int[16];
        int bufferSize = 128 + image.getWidth() * image.getHeight() / 2;
        bufferSize *= 2;
        java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(bufferSize);
        buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
        buildHeaderDxt3(buffer, image.getWidth(), image.getHeight());

        int numTilesWide = image.getWidth() / 4;
        int numTilesHigh = image.getHeight() / 4;
        for (int i = 0; i < numTilesHigh; i++)
        {
            for (int j = 0; j < numTilesWide; j++)
            {
                long alphas = makeAlphas(pixels);
                buffer.putLong(alphas);

                java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4);
                originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4);
                Color[] colors = getColors888(pixels);

                for (int k = 0; k < pixels.length; k++)
                {
                    pixels[k] = getPixel565(colors[k]);
                    colors[k] = getColor565(pixels[k]);
                }

                int[] extremaIndices = determineExtremeColors(colors);
                if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]])
                {
                    int t = extremaIndices[0];
                    extremaIndices[0] = extremaIndices[1];
                    extremaIndices[1] = t;
                }

                buffer.putShort((short) pixels[extremaIndices[0]]);
                buffer.putShort((short) pixels[extremaIndices[1]]);

                long bitmask = computeBitMask(colors, extremaIndices);
                buffer.putInt((int) bitmask);
            }
        }

        return buffer;
    }

    private static long makeAlphas(int[] pixels)
    {
        long sol = 0;
        for (int i = 0; i < pixels.length; i++)
        {
            sol |= (pixels[i] & 0xC0000000) >> (i * 2);
        }

        return sol;
    }

    protected static void buildHeaderDxt1(java.nio.ByteBuffer buffer, int width, int height)
    {
        buffer.rewind();
        buffer.put((byte) 'D');
        buffer.put((byte) 'D');
        buffer.put((byte) 'S');
        buffer.put((byte) ' ');
        buffer.putInt(124);
        int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE;
        buffer.putInt(flag);
        buffer.putInt(height);
        buffer.putInt(width);
        buffer.putInt(width * height / 2);
        buffer.putInt(0); // depth
        buffer.putInt(0); // mipmap count
        buffer.position(buffer.position() + 44); // 11 unused double-words
        buffer.putInt(32); // pixel format size
        buffer.putInt(DDPF_FOURCC);
        buffer.put((byte) 'D');
        buffer.put((byte) 'X');
        buffer.put((byte) 'T');
        buffer.put((byte) '1');
        buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats
        buffer.putInt(0); // rgb bit masks for RGB formats
        buffer.putInt(0); // rgb bit masks for RGB formats
        buffer.putInt(0); // rgb bit masks for RGB formats
        buffer.putInt(0); // alpha mask for RGB formats
        buffer.putInt(DDSCAPS_TEXTURE);
        buffer.putInt(0); // ddsCaps2
        buffer.position(buffer.position() + 12); // 3 unused double-words
    }

    protected static void buildHeaderDxt3(java.nio.ByteBuffer buffer, int width, int height)
    {
        buffer.rewind();
        buffer.put((byte) 'D');
        buffer.put((byte) 'D');
        buffer.put((byte) 'S');
        buffer.put((byte) ' ');
        buffer.putInt(124);
        int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE;
        buffer.putInt(flag);
        buffer.putInt(height);
        buffer.putInt(width);
        buffer.putInt(width * height / 2);
        buffer.putInt(0); // depth
        buffer.putInt(0); // mipmap count
        buffer.position(buffer.position() + 44); // 11 unused double-words
        buffer.putInt(32); // pixel format size
        buffer.putInt(DDPF_FOURCC);
        buffer.put((byte) 'D');
        buffer.put((byte) 'X');
        buffer.put((byte) 'T');
        buffer.put((byte) '3');
        buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats
        buffer.putInt(0); // rgb bit masks for RGB formats
        buffer.putInt(0); // rgb bit masks for RGB formats
        buffer.putInt(0); // rgb bit masks for RGB formats
        buffer.putInt(0); // alpha mask for RGB formats
        buffer.putInt(DDSCAPS_TEXTURE);
        buffer.putInt(0); // ddsCaps2
        buffer.position(buffer.position() + 12); // 3 unused double-words
    }

    protected static int[] determineExtremeColors(Color[] colors)
    {
        int farthest = Integer.MIN_VALUE;
        int[] ex = new int[2];

        for (int i = 0; i < colors.length - 1; i++)
        {
            for (int j = i + 1; j < colors.length; j++)
            {
                int d = distance(colors[i], colors[j]);
                if (d > farthest)
                {
                    farthest = d;
                    ex[0] = i;
                    ex[1] = j;
                }
            }
        }

        return ex;
    }

    protected static long computeBitMask(Color[] colors, int[] extremaIndices)
    {
        Color[] colorPoints = new Color[] {null, null, new Color(), new Color()};
        colorPoints[0] = colors[extremaIndices[0]];
        colorPoints[1] = colors[extremaIndices[1]];
        if (colorPoints[0].equals(colorPoints[1]))
            return 0;

//        colorPoints[0].r = (colorPoints[0].r & 0xF8) | (colorPoints[0].r >> 5 );
//        colorPoints[0].g = (colorPoints[0].g & 0xFC) | (colorPoints[0].g >> 6 );
//        colorPoints[0].b = (colorPoints[0].b & 0xF8) | (colorPoints[0].b >> 5 );
//
//        colorPoints[1].r = (colorPoints[1].r & 0xF8) | (colorPoints[1].r >> 5 );
//        colorPoints[1].g = (colorPoints[1].g & 0xFC) | (colorPoints[1].g >> 6 );
//        colorPoints[1].b = (colorPoints[1].b & 0xF8) | (colorPoints[1].b >> 5 );

        colorPoints[2].r = (2 * colorPoints[0].r + colorPoints[1].r + 1) / 3;
        colorPoints[2].g = (2 * colorPoints[0].g + colorPoints[1].g + 1) / 3;
        colorPoints[2].b = (2 * colorPoints[0].b + colorPoints[1].b + 1) / 3;
        colorPoints[3].r = (colorPoints[0].r + 2 * colorPoints[1].r + 1) / 3;
        colorPoints[3].g = (colorPoints[0].g + 2 * colorPoints[1].g + 1) / 3;
        colorPoints[3].b = (colorPoints[0].b + 2 * colorPoints[1].b + 1) / 3;

        long bitmask = 0;
        for (int i = 0; i < colors.length; i++)
        {
            int closest = Integer.MAX_VALUE;
            int mask = 0;
            for (int j = 0; j < colorPoints.length; j++)
            {
                int d = distance(colors[i], colorPoints[j]);
                if (d < closest)
                {
                    closest = d;
                    mask = j;
                }
            }
            bitmask |= mask << i * 2;
        }

        return bitmask;
    }




    protected static int getPixel565(Color color)
    {
        int r = color.r >> 3;
        int g = color.g >> 2;
        int b = color.b >> 3;
        return r << 11 | g << 5 | b;
    }

    protected static Color getColor565(int pixel)
    {
        Color color = new Color();

        color.r = (int) (((long) pixel) & 0xf800) >> 11;
        color.g = (int) (((long) pixel) & 0x07e0) >> 5;
        color.b = (int) (((long) pixel) & 0x001f);

        return color;
    }

    protected static Color getColor888(int r8g8b8)
    {
        return new Color(
            (int) (((long) r8g8b8) & 0xff0000) >> 16,
            (int) (((long) r8g8b8) & 0x00ff00) >>  8,
            (int) (((long) r8g8b8) & 0x0000ff)
        );
    }


    protected static Color[] getColors888(int[] pixels)
    {
        Color[] colors = new Color[pixels.length];

        for (int i = 0; i < pixels.length; i++)
        {
            colors[i] = new Color();
            colors[i].r = (int) (((long) pixels[i]) & 0xff0000) >> 16;
            colors[i].g = (int) (((long) pixels[i]) & 0x00ff00) >> 8;
            colors[i].b = (int) (((long) pixels[i]) & 0x0000ff);
        }

        return colors;
    }

    protected static int distance(Color ca, Color cb)
    {
        return (cb.r - ca.r) * (cb.r - ca.r) + (cb.g - ca.g) * (cb.g - ca.g) + (cb.b - ca.b) * (cb.b - ca.b);
    }

    /**
     * @param file
     * @return
     * @throws IllegalArgumentException if either <code>file</code> is null, does not exist, or cannot be read
     * @throws java.io.IOException
     */
    public static java.nio.ByteBuffer convertToDxt1WithTransparency(java.io.File file) throws java.io.IOException
    {
        /*java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);

        int bufferSize = 128 + image.getWidth() * image.getHeight() / 2;
        java.nio.ByteBuffer testBuffer = java.nio.ByteBuffer.allocate(bufferSize);
        testBuffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
        buildHeaderDxt1(testBuffer, image.getWidth(), image.getHeight());

        int numTilesWide = image.getWidth() / 4;
        int numTilesHigh = image.getHeight() / 4;

        for (int i = 0; i < numTilesHigh; i++)
        {
            for (int j = 0; j < numTilesWide; j++)
            {
                short min = 255;
                short max = 127;

                testBuffer.putShort(min);
                testBuffer.putShort(max);

                int bitmask = 0x012345678;//0xffff43cf;
                testBuffer.putInt(bitmask);
            }
        }

        return testBuffer;
*/

        if (file == null)
        {     //
            String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (!file.exists() || !file.canRead())
        {
            String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);

        if (image == null)
            return null;

        int[] pixels = new int[16];
        int bufferSize = 128 + image.getWidth() * image.getHeight() / 2;
        java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(bufferSize);
        buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
        buildHeaderDxt1(buffer, image.getWidth(), image.getHeight());

        int numTilesWide = image.getWidth() / 4;
        int numTilesHigh = image.getHeight() / 4;
        for (int i = 0; i < numTilesHigh; i++)
        {
            for (int j = 0; j < numTilesWide; j++)
            {
                java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4);
                originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4); //  these are in format "TYPE_INT_ARGB" 16,5,6,5

                TransparentColor[] colors = getColors5551(pixels);

                int[] extremaIndices = determineExtremeColors(colors);

                // we want extremaIndices[0] to be greater than extremaIndices[1]
                short min = getShort5551(colors[extremaIndices[0]]);
                short max = getShort5551(colors[extremaIndices[1]]);
                if (min > max)
                {
                    // we want min to be less than max.
                    int t = extremaIndices[0];
                    extremaIndices[0] = extremaIndices[1];
                    extremaIndices[1] = t;

                    short temp = min;
                    min = max;
                    max = temp;
                }
                else if (min == max)
                {
                    // because of this case, we don't get transparency.
                    equalTransparentCase(colors, extremaIndices, min);

                    min = getShort5551(colors[extremaIndices[0]]);
                    max = getShort5551(colors[extremaIndices[1]]);
                }

                buffer.putShort(min);
                buffer.putShort(max);

                long bitmask = computeBitMask(colors, extremaIndices);
                buffer.putInt((int) bitmask);
            }
        }

        return buffer;
    }

    protected static void equalTransparentCase(TransparentColor[] colors, int[] extremaIndices, short value)
    {
        // we want extremaIndices[0] to be greater than extremaIndices[1]

        if (value == 0)
        {
            // transparent
            colors[extremaIndices[0]] = TransparentColor.OFF_TRANSPARENT;
        }
        /*else
        {
            // not transparent anywhere - it's all one colour, so we don't need to bother making changes
        }*/

    }

    protected static int distance(TransparentColor ca, TransparentColor cb)
    {
        return (cb.r - ca.r) * (cb.r - ca.r) + (cb.g - ca.g) * (cb.g - ca.g) + (cb.b - ca.b) * (cb.b - ca.b)
            + (cb.a - ca.a) * (cb.a - ca.a);
    }

//    public static void main(String[] args)
//    {
//        try
//        {
//            String fileName = "testdata/0000_0001";
//            java.nio.ByteBuffer buffer = convertToDxt1NoTransparency(new java.io.File(fileName + ".jpg"));
//            buffer.rewind();
//            java.io.FileOutputStream fos = new java.io.FileOutputStream(fileName + ".dds");
//            java.nio.channels.FileChannel channel = fos.getChannel();
//            channel.write(buffer);
//        }
//        catch (java.io.IOException e)
//        {
//            e.printStackTrace();
//        }
//
//        return;
//    }

    protected static long computeBitMask(TransparentColor[] colors, int[] extremaIndices)
    {
        TransparentColor[] colorPoints = {null, null, new TransparentColor(), new TransparentColor()};

        colorPoints[0] = colors[extremaIndices[0]];
        colorPoints[1] = colors[extremaIndices[1]];

        colorPoints[2].r = (byte) ((colorPoints[0].r + colorPoints[1].r) / 2);
        colorPoints[2].g = (byte) ((colorPoints[0].g + colorPoints[1].g) / 2);
        colorPoints[2].b = (byte) ((colorPoints[0].b + colorPoints[1].b) / 2);
        colorPoints[2].a = 1;

        colorPoints[3].r = 0;
        colorPoints[3].g = 0;
        colorPoints[3].b = 0;
        colorPoints[3].a = 0;

        long bitmask = 0;
        for (int i = 0; i < colors.length; i++)
        {
            int closest = Integer.MAX_VALUE;
            int mask = 0;
            if (colors[i].a == 0)
            {
                mask = 3;
            }
            else
            {
                for (int j = 0; j < colorPoints.length; j++)
                {
                    int d = distance(colors[i], colorPoints[j]);
                    if (d < closest)
                    {
                        closest = d;
                        mask = j;
                    }
                }
            }
            bitmask |= mask << i * 2;
        }

        return bitmask;
    }

    protected static short getShort5551(TransparentColor color)
    {
        short s = 0;
        s |= ((color.r & 0x0f8) << 8) | ((color.g & 0x0f8) << 4) | ((color.b & 0x0f8) >> 3) | ((color.a & 0x0f8) >> 7);
//        System.out.println(Integer.toBinaryString(s));
        return s;
    }

    protected static int[] determineExtremeColors(TransparentColor[] colors)
    {
        int farthest = Integer.MIN_VALUE;
        int[] ex = {0, 0};

        for (int i = 0; i < colors.length - 1; i++)
        {
            for (int j = i + 1; j < colors.length; j++)
            {
                int d = distance(colors[i], colors[j]);
                if (d > farthest)
                {
                    farthest = d;
                    ex[0] = i;
                    ex[1] = j;
                }
            }
        }

        return ex;
    }

    protected static TransparentColor[] getColors5551(int[] pixels)
    {
        TransparentColor colors[] = new TransparentColor[pixels.length];

        for (int i = 0; i < pixels.length; i++)
        {
            colors[i] = generateColor5551(pixels[i]);
        }
        return colors;
    }

    protected static TransparentColor generateColor5551(int pixel)
    {
        short alpha = (short) (pixel >> 24);
        if ((alpha & 0xf0) == 0)
        {
            return TransparentColor.TRANSPARENT;
        }

        // ok, it's not transparent - that's already been ruled out.

        TransparentColor tc = new TransparentColor();
        tc.a = (byte) 0x000000ff;
        tc.r = (byte) ((pixel & 0x00ff0000) >> 16);
        tc.g = (byte) ((pixel & 0x0000ff00) >> 8);
        tc.b = (byte) (pixel & 0x000000ff);

        return tc;
    }

    protected static class TransparentColor
    {
        private static final TransparentColor TRANSPARENT = new TransparentColor((byte) 0, (byte) 0, (byte) 0,
            (byte) 0);
        private static final TransparentColor OFF_TRANSPARENT = new TransparentColor((byte) 0, (byte) 0, (byte) 1,
            (byte) 0);
        private byte r, g, b, a;

        private TransparentColor()
        {
        }

        private TransparentColor(byte r, byte g, byte b, byte a)
        {
            this.r = r;
            this.g = g;
            this.b = b;
            this.a = a;
        }

        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            final gov.nasa.worldwind.DDSConverter.TransparentColor that =
                (gov.nasa.worldwind.DDSConverter.TransparentColor) o;

            if (a != that.a)
                return false;
            if (b != that.b)
                return false;
            if (g != that.g)
                return false;
            //noinspection RedundantIfStatement
            if (r != that.r)
                return false;

            return true;
        }

        public int hashCode()
        {
            int result;
            result = r;
            result = 29 * result + g;
            result = 29 * result + b;
            result = 29 * result + a;
            return result;
        }

        public String toString()
        {
            return "TransColor argb: " + this.a + ", " + this.r + ", " + this.g + ", " + this.b;
        }
    }
}
