src.gov.nasa.worldwind.formats.wvt.WaveletCodec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* 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;
}
}