/* Copyright 2014 The jeo project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jeo.raster;
import io.jeo.util.Dimension;
import java.nio.ByteBuffer;
/**
* Wrapper around a {@link java.nio.ByteBuffer} that abstracts away data type of values
* put into the buffer.
* <p>
* The buffer wrapper maintains a native {@link #datatype()}. Values returned from {@link #get()}
* are of this native type. Values of any type may be put into the buffer via {@link #put(Object)}
* </p>
*/
public abstract class DataBuffer<T> {
/**
* Creates a new wrapper from a byte buffer and specified datatype.
*/
public static DataBuffer create(ByteBuffer buffer, DataType datatype) {
switch(datatype) {
case BYTE:
return new DataBuffer<Byte>(buffer, datatype) {
@Override
public Byte get() {
return buffer.get();
}
};
case SHORT:
return new DataBuffer<Short>(buffer, datatype) {
@Override
public Short get() {
return buffer.getShort();
}
};
case INT:
return new DataBuffer<Integer>(buffer, datatype) {
@Override
public Integer get() {
return buffer.getInt();
}
};
case LONG:
return new DataBuffer<Long>(buffer, datatype) {
@Override
public Long get() {
return buffer.getLong();
}
};
case FLOAT:
return new DataBuffer<Float>(buffer, datatype) {
@Override
public Float get() {
return buffer.getFloat();
}
};
case DOUBLE:
return new DataBuffer<Double>(buffer, datatype) {
@Override
public Double get() {
return buffer.getDouble();
}
};
case CHAR:
return new DataBuffer<Character>(buffer, datatype) {
@Override
public Character get() {
return buffer.getChar();
}
};
default:
throw new IllegalArgumentException("unsupported data type: " + datatype);
}
}
/**
* Creates a new wrapper by allocating a new buffer of the specified size and type.
*/
public static DataBuffer create(int size, DataType datatype) {
switch(datatype) {
case BYTE:
case SHORT:
case INT:
case LONG:
case FLOAT:
case DOUBLE:
case CHAR:
return create(ByteBuffer.allocate(size*datatype.size()), datatype);
default:
throw new IllegalArgumentException("unsupported data type: " + datatype);
}
}
/**
* Resamples the buffer using nearest neighbour interpolation.
* <p>
* Resampling works in two dimensions and requires the image dimensions of this buffer and the
* buffer being resampled to.
* </p>
* @param buffer The buffer to resample.
* @param from The image dimensions of this buffer.
* @param to The image dimensions of the resampled buffer.
* @param <T> Buffer type.
*
* @return The resampled buffer.
*/
public static <T> DataBuffer<T> resample(DataBuffer<T> buffer, Dimension from, Dimension to) {
DataBuffer resampled = create(to.width()*to.height(), buffer.datatype());
resampled.buffer().order(buffer.buffer().order());
double xratio = from.width() / (double)to.width();
double yratio = from.height() / (double)to.height();
int px, py ;
for (int j = 0; j < to.height(); j++) {
int offset = j*to.width();
for (int i = 0; i < to.width(); i++ ) {
px = (int) Math.floor(i*xratio);
py = (int) Math.floor(j*yratio);
resampled.put(offset+i, buffer.get(py*from.width()+px));
}
}
return resampled.rewind();
}
ByteBuffer buffer;
DataType datatype;
int word = 0;
DataBuffer(ByteBuffer buffer, DataType datatype) {
this.buffer = buffer;
this.datatype = datatype;
}
/**
* The underlying byte buffer.
*/
public ByteBuffer buffer() {
return buffer;
}
/**
* Retrieves the value of type {@link #datatype()} from the current position.
* <p>
* This method does no bounds checking to ensure the current position is valid.
* </p>
*/
public abstract T get();
/**
* Retrieves the value of type {@link #datatype()} at the specified position.
* <p>
* The position <tt>i</tt> is relative to the datatype size. For example in a buffer
* of type {@link DataType#INT} a position of <tt>10</tt> refers to the <tt>10th</tt>
* integer, not the <tt>10th</tt> byte. In this case it would map to an underlying
* position of <tt>10*4 = 40</tt>.
* </p>
* <p>
* This method does no bounds checking to ensure the index is valid.
* </p>
*/
public T get(int i) {
buffer.position(i*datatype.size());
return get();
}
/**
* Puts data into the buffer at the current position.
* <p>
* The value <tt>val</tt> must be one of the well known primitive wrapper types
* (other than Boolean).
* </p>
* <p>
* This method does no bounds checking to ensure the current position is valid.
* </p>
*/
public DataBuffer<T> put(Object val) {
if (val instanceof Byte) {
buffer.put((Byte)val);
}
else if (val instanceof Short) {
buffer.putShort((Short)val);
}
else if (val instanceof Character) {
buffer.putChar((Character)val);
}
else if (val instanceof Integer) {
buffer.putInt((Integer)val);
}
else if (val instanceof Long) {
buffer.putLong((Long)val);
}
else if (val instanceof Float) {
buffer.putFloat((Float)val);
}
else if (val instanceof Double) {
buffer.putDouble((Double)val);
}
else {
throw new IllegalArgumentException("unknown data value: " + val);
}
return this;
}
/**
* Puts data into the buffer at the specified position.
* <p>
* The position <tt>i</tt> is relative to the datatype size. For example in a buffer
* of type {@link DataType#INT} a position of <tt>10</tt> refers to the <tt>10th</tt>
* integer, not the <tt>10th</tt> byte. In this case it would map to an underlying
* position of <tt>10*4 = 40</tt>.
* </p>
* <p>
* The value <tt>val</tt> must be one of the well known primitive wrapper types
* (other than Boolean).
* </p>
* <p>
* This method does no bounds checking to ensure the index is valid.
* </p>
*/
public DataBuffer<T> put(int i, T val) {
buffer.position(i*datatype.size());
put(val);
return this;
}
/**
* Flips the buffer.
*
* @see {@link java.nio.Buffer#flip()}
*/
public DataBuffer<T> flip() {
buffer.flip();
return this;
}
/**
* Rewinds the buffer.
* <p>
* This method resets the word index to 0 in addition to resetting the buffer position.
* </p>
*
* @see {@link java.nio.Buffer#rewind()}
*/
public DataBuffer<T> rewind() {
word = 0;
buffer.rewind();
return this;
}
/**
* Advances the word index to the next value and sets the buffer position
* to the same value.
* <p>
* This method is used to keep the buffer aligned at "word boundaries" in cases
* where data types of different sizes are being put into the buffer.
* </p>
*/
public DataBuffer<T> word() {
word += datatype.size();
buffer.position(word);
return this;
}
/**
* The size of the buffer, relative to the {@link #datatype()}.
* <p>
* For data types other than {@link DataType#BYTE} this is not the same
* as <tt>buffer().capacity()</tt>. Rather it is calculated as:
* <pre>
* buffer().capacity()/datatype().size()
* </pre>
*
* </p>
*/
public int size() {
return buffer.capacity() / datatype.size();
}
/**
* The data type of the buffer.
*/
public DataType datatype() {
return datatype;
}
}