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