All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.formats.wvt.WaveletCodec Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.formats.wvt;

import gov.nasa.worldwind.util.*;

import java.awt.*;
import java.awt.image.*;
import java.io.*;

/**
 * @author brownrigg
 * @version $Id: WaveletCodec.java 1171 2013-02-11 21:45:02Z dcollins $
 */

public class WaveletCodec
{
    private final int type;
    private final int resolutionX;
    private final int resolutionY;
    private byte[][] xform;
    public static final int TYPE_BYTE_GRAY  = 0x67726179; // ascii "gray"
    public static final int TYPE_3BYTE_BGR  = 0x72676220; // ascii "rgb "
    public static final int TYPE_4BYTE_ARGB = 0x61726762; // ascii "argb"
    /**
     * A suggested filename extension for wavelet-encodings.
     */
    public static final String WVT_EXT = ".wvt";

    private WaveletCodec(int type, int resolutionX, int resolutionY)
    {
        if (!isTypeValid(type))
        {
            String message = "Invalid type: " + type;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.type = type;
        this.resolutionX = resolutionX;
        this.resolutionY = resolutionY;
    }

    public final int getType()
    {
        return type;
    }

    /**
     * Returns the resolution of this wavelet encoding.
     *
     * @return resolution
     */
    public final int getResolutionX()
    {
        return this.resolutionX;
    }

    /**
     * Returns the resolution of this wavelet encoding.
     *
     * @return resolution
     */
    public final int getResolutionY()
    {
        return this.resolutionY;
    }

    /**
     * Reconstructs an image from this wavelet encoding at the given resolution. The specified resolution
     * must be a power of two, and must be less than or equal to the resolution of the encoding.
     *
     * This reconstruction algorithm was hinted at in:
     *
     *    "Principles of Digital Image Synthesis"
     *    Andrew Glassner
     *    1995, pp. 296
     *
     * @param resolution
     * @return reconstructed image.
     * @throws IllegalArgumentException
     */
    public BufferedImage reconstruct(int resolution) throws IllegalArgumentException {

        // Allocate memory for the BufferedImage
        int numBands = this.xform.length;
        int[][] imageData = new int[numBands][this.resolutionX * this.resolutionY];
        byte[][] imageBytes = new byte[numBands][this.resolutionX * this.resolutionY];

        // we need working buffers as large as 1/2 the output resolution...
        // Note how these are named after Glassner's convention...

        int res2 = (resolution/2) * (resolution/2);
        int[][] A = new int[numBands][res2];
        int[][] D = new int[numBands][res2];
        int[][] V = new int[numBands][res2];
        int[][] H = new int[numBands][res2];

        // Prime the process. Recall that the first byte of each channel is a color value, not
        // signed coefficients. So treat it as an unsigned value.
        for (int k=0; k < numBands; k++)
            imageData[k][0] = 0x000000ff & this.xform[k][0];

        int scale = 1;
        int offset = 1;
        do {
            // load up our A,D,V,H component arrays...
            int numVals = scale*scale;
            if (numVals >= resolution*resolution) break;

            int next = 0;
            for (int j=0; j resolutionX || resolution > resolutionY)
            throw new IllegalArgumentException("WaveletCodec.loadPartially(): input resolution greater than encoded image");

        int type = buffer.getInt();
        if (!isTypeValid(type))
            throw new IllegalArgumentException("WaveletCodec.loadPartially(): invalid encoding type");

        int numBands = buffer.getInt();
        byte[][] xform = new byte[numBands][resolution*resolution];
        for (int k = 0; k < numBands; k++) {
            buffer.position(4*(Integer.SIZE/Byte.SIZE) + k * (resolutionX * resolutionY));
            buffer.get(xform[k], 0, xform[k].length);
        }

        WaveletCodec codec = new WaveletCodec(type, resolutionX, resolutionY);
        codec.xform = xform;
        return codec;
    }

    /**
     * Creates a wavelet encoding from the given BufferedImage. The image must have dimensions that are
     * a power of 2. If the incoming image has at least 3 bands, the first three are assumed to be RGB channels.
     * If only one-band, it is assumed to be grayscale. The SampleModel component-type must be BYTE.
     *
     * @param image
     * @return
     * @throws IllegalArgumentException
     */
    public static WaveletCodec encode(BufferedImage image) throws IllegalArgumentException {

        if (image == null)
            throw new IllegalArgumentException("WaveletCodec.encode: null image");

        // Does image have the required resolution constraints?
        int xRes = image.getWidth();
        int yRes = image.getHeight();
        if (!WWMath.isPowerOfTwo(xRes) || !WWMath.isPowerOfTwo(yRes))
            throw new IllegalArgumentException("Image dimensions are not a power of 2");

        // Try to determine image type...
        SampleModel sampleModel = image.getSampleModel();
        int numBands = sampleModel.getNumBands();
        if ( !(numBands == 1 || numBands == 3 || numBands == 4) || sampleModel.getDataType() != DataBuffer.TYPE_BYTE)
            throw new IllegalArgumentException("Image is not of BYTE type, or not recognized as grayscale, RGB, or ARGB");

        int type = getWaveletType(image);
        if (!isTypeValid(type))
            throw new IllegalArgumentException("Image is not recognized as grayscale, RGB, or ARGB");                

        // Looks good to go;  grab the image data.  We'll need to make a copy, as we need some
        // temp working space and we don't want to corrupt the BufferedImage's data...

        int bandSize = xRes * yRes;
        //int next = 0;
        Raster rast = image.getRaster();
        //float[] dataElems = new float[numBands];
        float[][] imageData = new float[numBands][bandSize];

        for (int k = 0; k < numBands; k++) {
            rast.getSamples(0, 0, xRes, yRes, k, imageData[k]);
        }
        //for (int j = 0; j < yRes; j++) {
        //    for (int i = 0; i < xRes; i++) {
        //        rast.getPixel(i, j, dataElems);
        //        for (int k = 0; k < numBands; k++) {
        //            imageData[k][next] = dataElems[k];
        //        }
        //        ++next;
        //    }
        //}

        // We need some temporary work space the size of the image...
        float[][] workspace = new float[numBands][bandSize];

        // Perform the transformation...
        int level = 0;
        int xformXres = xRes;
        int xformYres = yRes;

        while (true) {
            ++level;

            if ( !(xformXres > 0 || xformYres > 0)) break;
            int halfXformXres = xformXres / 2;
            int halfXformYres = xformYres / 2;

            // transform along the rows...
            for (int j = 0; j < xformYres; j++) {

                int offset = j * yRes;      // IMPORTANT THAT THIS REFLECT SOURCE IMAGE, NOT THE CURRENT LEVEL!

                for (int i = 0; i < halfXformXres; i++) {
                    int indx1 = offset + i*2;
                    int indx2 = offset + i*2 + 1;

                    // horizontally...
                    for (int k = 0; k < numBands; k++) {
                        float average = (imageData[k][indx1] + imageData[k][indx2]) / 2f;
                        float detail = imageData[k][indx1] - average;
                        workspace[k][offset + i] = average;
                        workspace[k][offset + i + halfXformXres] = detail;
                    }
                }

            }

            // copy transformed data from this iteration back into our source arrays...
            for (int k=0; k < numBands; k++)
                System.arraycopy(workspace[k], 0, imageData[k], 0, workspace[k].length);

            // now transform along columns...
            for (int j = 0; j < xformXres; j++) {
                for (int i = 0; i < halfXformYres; i++) {
                    int indx1 = j + (i*2)*yRes;
                    int indx2 = j + (i*2+1)*yRes;

                    // horizontally...
                    for (int k = 0; k < numBands; k++) {
                        float average = (imageData[k][indx1] + imageData[k][indx2]) / 2f;
                        float detail = imageData[k][indx1] - average;
                        workspace[k][j + i*yRes] = average;
                        workspace[k][j + (i+halfXformYres)*yRes] = detail;
                    }
                }

            }

            xformXres /= 2;
            xformYres /= 2;

            // copy transformed data from this iteration back into our source arrays...
            for (int k=0; k < numBands; k++)
                System.arraycopy(workspace[k], 0, imageData[k], 0, workspace[k].length);
        }

        // Our return WaveletCodec...
        WaveletCodec codec = new WaveletCodec(type, xRes, yRes);
        codec.xform = new byte[numBands][bandSize];

        //
        // Rearrange in memory for optimal, hierarchical layout on disk, quantizing down to
        // byte values as we go.
        //

        // NOTE: the first byte of each channel is different; it represents the average color of the
        // overall image, and as such should be an unsigned quantity in the range 0..255.
        // All other values are signed coefficents, so the clamping boundaries are different.
        for (int k=0; k 0) ? scale * xRes : 0;
                for (int j = 0; j < scale; j++) {
                    for (int i = 0; i < scale; i++, next++) {
                        int indx = rowOffset + colOffset + j*xRes + i;
                        for (int k = 0; k < numBands; k++) {
                           codec.xform[k][next] = (byte) Math.max(Byte.MIN_VALUE, Math.min(Byte.MAX_VALUE, Math.round(imageData[k][indx])));
                        }
                    }
                }
            }
            scale *= 2;
        }

        // Done!
        return codec;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy