ch.psi.imagej.cbf/src/main/java/imcacat/jcbf/CbfImageWriter.java
2014-03-28 15:12:02 +01:00

774 lines
31 KiB
Java

/*
* Copyright (c) 2011 J. Lewis Muir <jlmuir@imca.aps.anl.gov>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package imcacat.jcbf;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Locale;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.IIOByteBuffer;
import javax.imageio.stream.ImageOutputStream;
/**
* I write a minimal CBF (miniCBF) image like that produced by DECTRIS
* PILATUS detectors. I only write images using the MIME mini-header and the
* "none" or "byte_offset" compression algorithms. I do not support the
* "unsigned 1-bit integer" and "signed 32-bit complex IEEE" data element
* types.
*/
public class CbfImageWriter extends ImageWriter {
public CbfImageWriter(ImageWriterSpi originatingProvider) {
super(originatingProvider);
}
@Override
public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
return null;
}
@Override
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
CbfMetadata mdata = new CbfMetadata();
int bufferedImageType = imageType.getBufferedImageType();
int dataBufferType = imageType.getSampleModel().getDataType();
if (bufferedImageType == BufferedImage.TYPE_BYTE_BINARY) {
mdata.setDataElementType(CbfElementType.UNSIGNED_8_BIT_INTEGER);
} else if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {
mdata.setDataElementType(CbfElementType.UNSIGNED_8_BIT_INTEGER);
} else if (bufferedImageType == BufferedImage.TYPE_USHORT_GRAY) {
mdata.setDataElementType(CbfElementType.UNSIGNED_16_BIT_INTEGER);
} else if (dataBufferType == DataBuffer.TYPE_BYTE) {
mdata.setDataElementType(CbfElementType.UNSIGNED_8_BIT_INTEGER);
} else if (dataBufferType == DataBuffer.TYPE_SHORT) {
mdata.setDataElementType(CbfElementType.SIGNED_16_BIT_INTEGER);
} else if (dataBufferType == DataBuffer.TYPE_FLOAT) {
mdata.setDataElementType(CbfElementType.SIGNED_32_BIT_REAL_IEEE);
} else if (dataBufferType == DataBuffer.TYPE_INT) {
mdata.setDataElementType(CbfElementType.SIGNED_32_BIT_INTEGER);
} else if (dataBufferType == DataBuffer.TYPE_DOUBLE) {
mdata.setDataElementType(CbfElementType.SIGNED_64_BIT_REAL_IEEE);
}
int compressionMode = param.getCompressionMode();
if (compressionMode == ImageWriteParam.MODE_DISABLED) {
mdata.setDataCompression(CbfCompression.NONE);
} else if (compressionMode == ImageWriteParam.MODE_EXPLICIT) {
if (param.getCompressionType().equals(CbfImageWriteParam.COMPRESSION_BYTE_OFFSET)) {
mdata.setDataCompression(CbfCompression.BYTE_OFFSET);
} else if (param.getCompressionType().equals(CbfImageWriteParam.COMPRESSION_NONE)) {
mdata.setDataCompression(CbfCompression.NONE);
}
}
return mdata;
}
@Override
public ImageWriteParam getDefaultWriteParam() {
return new CbfImageWriteParam();
}
@Override
public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) {
return null;
}
@Override
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType,
ImageWriteParam param) {
return (inData instanceof CbfMetadata) ? inData : null;
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam iwparam)
throws IOException {
clearAbortRequest();
processImageStarted(0);
RenderedImage srcImage = image.getRenderedImage();
ImageWriteParam param = (iwparam == null) ? getDefaultWriteParam() : iwparam;
CbfMetadata mdata = null;
IIOMetadata srcMetadata = image.getMetadata();
if (srcMetadata != null) {
mdata =
(CbfMetadata)convertImageMetadata(srcMetadata,
ImageTypeSpecifier.createFromRenderedImage(srcImage), param);
}
if (mdata == null) mdata =
(CbfMetadata)getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(srcImage),
param);
IIOByteBuffer encodedImage = encode(image, mdata, param);
if (encodedImage == null) return;
if (abortRequested()) {
processWriteAborted();
return;
}
ImageOutputStream stream = getNonNullOutput();
stream.writeBytes(mdata.getIdentifier() + "\r\n");
stream.writeBytes("\r\n");
stream.writeBytes("data_" + mdata.getDataBlockName() + "\r\n");
stream.writeBytes("\r\n");
stream.writeBytes("_array_data.header_convention \"" + mdata.getHeaderConvention() + "\"\r\n");
stream.writeBytes("_array_data.header_contents\r\n");
stream.writeBytes(";\r\n");
if (mdata.getHeaderDetector() != null) {
stream.writeBytes("# Detector: ");
stream.writeBytes(mdata.getHeaderDetector());
stream.writeBytes("\r\n");
}
if (mdata.getHeaderDate() != null) {
stream.writeBytes("# ");
DateFormat format = new SimpleDateFormat("yyyy-MMM-dd'T'HH:mm:ss.SSS", Locale.US);
stream.writeBytes(format.format(mdata.getHeaderDate()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderPixelSize() != null) {
stream.writeBytes("# Pixel_size ");
float[] size = mdata.getHeaderPixelSize();
size[0] *= 1e6f;
size[1] *= 1e6f;
stream.writeBytes(String.format(Locale.US, "%.0fe-6 m x %.0fe-6 m", size[0], size[1]));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderSensorType() != null && mdata.getHeaderSensorThickness() != null) {
stream.writeBytes("# ");
stream.writeBytes(mdata.getHeaderSensorType());
stream.writeBytes(" sensor, thickness ");
stream.writeBytes(String.format(Locale.US, "%.6f m", mdata.getHeaderSensorThickness()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderExposureTime() != null) {
stream.writeBytes("# Exposure_time ");
stream.writeBytes(String.format(Locale.US, "%.6f s", mdata.getHeaderExposureTime()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderExposurePeriod() != null) {
stream.writeBytes("# Exposure_period ");
stream.writeBytes(String.format(Locale.US, "%.6f s", mdata.getHeaderExposurePeriod()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderTau() != null) {
stream.writeBytes("# Tau = ");
stream.writeBytes(String.format(Locale.US, "%.1fe-09 s", 1e9f * mdata.getHeaderTau()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderCountCutoff() != null) {
stream.writeBytes("# Count_cutoff ");
stream.writeBytes(String.format(Locale.US, "%d counts", mdata.getHeaderCountCutoff()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderThreshold() != null) {
stream.writeBytes("# Threshold_setting ");
stream.writeBytes(String.format(Locale.US, "%.0f eV", mdata.getHeaderThreshold()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderGainVrf() != null) {
stream.writeBytes("# Gain_setting ");
String type = mdata.getHeaderGainType();
stream.writeBytes((type == null) ? "not implemented" : type);
stream.writeBytes(String.format(Locale.US, " (vrf = %.3f)", mdata.getHeaderGainVrf()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderNumExcludedPixels() != null) {
stream.writeBytes("# N_excluded_pixels = ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getHeaderNumExcludedPixels()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderExcludedPixels() != null) {
stream.writeBytes("# Excluded_pixels: ");
stream.writeBytes(mdata.getHeaderExcludedPixels());
stream.writeBytes("\r\n");
}
if (mdata.getHeaderFlatField() != null) {
stream.writeBytes("# Flat_field: ");
stream.writeBytes(mdata.getHeaderFlatField());
stream.writeBytes("\r\n");
}
if (mdata.getHeaderTrim() != null) {
stream.writeBytes("# Trim_directory: ");
stream.writeBytes(mdata.getHeaderTrim());
stream.writeBytes("\r\n");
}
if (mdata.getHeaderWavelength() != null) {
stream.writeBytes("# Wavelength ");
stream.writeBytes(String.format(Locale.US, "%.4f A", mdata.getHeaderWavelength()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderEnergyRange() != null) {
stream.writeBytes("# Energy_range ");
float[] range = mdata.getHeaderEnergyRange();
stream.writeBytes(String.format(Locale.US, "(%.0f, %.0f) eV", range[0], range[1]));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderDetectorDistance() != null) {
stream.writeBytes("# Detector_distance ");
stream.writeBytes(String.format(Locale.US, "%.5f m", mdata.getHeaderDetectorDistance()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderDetectorVOffset() != null) {
stream.writeBytes("# Detector_Voffset ");
stream.writeBytes(String.format(Locale.US, "%.5f m", mdata.getHeaderDetectorVOffset()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderBeamXY() != null) {
stream.writeBytes("# Beam_xy ");
float[] beam = mdata.getHeaderBeamXY();
stream.writeBytes(String.format(Locale.US, "(%.2f, %.2f) pixels", beam[0], beam[1]));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderFlux() != null) {
stream.writeBytes("# Flux ");
stream.writeBytes(String.format(Locale.US, "%.4f ph/s", mdata.getHeaderFlux()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderFilterTransmission() != null) {
stream.writeBytes("# Filter_transmission ");
stream.writeBytes(String.format(Locale.US, "%.4f", mdata.getHeaderFilterTransmission()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderStartAngle() != null) {
stream.writeBytes("# Start_angle ");
stream.writeBytes(String.format(Locale.US, "%.4f deg.", mdata.getHeaderStartAngle()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderAngleIncrement() != null) {
stream.writeBytes("# Angle_increment ");
stream.writeBytes(String.format(Locale.US, "%.4f deg.", mdata.getHeaderAngleIncrement()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderDetector2Theta() != null) {
stream.writeBytes("# Detector_2theta ");
stream.writeBytes(String.format(Locale.US, "%.4f deg.", mdata.getHeaderDetector2Theta()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderPolarization() != null) {
stream.writeBytes("# Polarization ");
stream.writeBytes(String.format(Locale.US, "%.3f", mdata.getHeaderPolarization()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderAlpha() != null) {
stream.writeBytes("# Alpha ");
stream.writeBytes(String.format(Locale.US, "%.4f deg.", mdata.getHeaderAlpha()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderKappa() != null) {
stream.writeBytes("# Kappa ");
stream.writeBytes(String.format(Locale.US, "%.4f deg.", mdata.getHeaderKappa()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderPhi() != null) {
stream.writeBytes("# Phi ");
stream.writeBytes(String.format(Locale.US, "%.4f deg.", mdata.getHeaderPhi()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderChi() != null) {
stream.writeBytes("# Chi ");
stream.writeBytes(String.format(Locale.US, "%.4f deg.", mdata.getHeaderChi()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderOscillationAxis() != null) {
stream.writeBytes("# Oscillation_axis ");
stream.writeBytes(mdata.getHeaderOscillationAxis());
stream.writeBytes("\r\n");
}
if (mdata.getHeaderNumOscillations() != null) {
stream.writeBytes("# N_oscillations ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getHeaderNumOscillations()));
stream.writeBytes("\r\n");
}
if (mdata.getHeaderComment() != null) {
stream.writeBytes("# Comment: ");
stream.writeBytes(mdata.getHeaderComment());
stream.writeBytes("\r\n");
}
for (String each : mdata.getHeaderUnrecognizedLines()) {
stream.writeBytes(each);
stream.writeBytes("\r\n");
}
stream.writeBytes(";\r\n");
stream.writeBytes("\r\n");
stream.writeBytes("_array_data.data\r\n");
stream.writeBytes(";\r\n");
stream.writeBytes("--CIF-BINARY-FORMAT-SECTION--\r\n");
stream.writeBytes("Content-Type: application/octet-stream;\r\n");
stream.writeBytes(" conversions=\"");
stream.writeBytes(mdata.getDataCompression().toContentTypeConversions());
stream.writeBytes("\"\r\n");
stream.writeBytes("Content-Transfer-Encoding: BINARY\r\n");
stream.writeBytes("X-Binary-Size: ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getDataSize()));
stream.writeBytes("\r\n");
stream.writeBytes("X-Binary-ID: ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getDataId()));
stream.writeBytes("\r\n");
stream.writeBytes("X-Binary-Element-Type: \"");
stream.writeBytes(mdata.getDataElementType().toXBinaryElementType());
stream.writeBytes("\"\r\n");
stream.writeBytes("X-Binary-Element-Byte-Order: ");
stream.writeBytes(ByteOrder.LITTLE_ENDIAN.equals(mdata.getDataElementByteOrder())
? "LITTLE_ENDIAN" : "BIG_ENDIAN");
stream.writeBytes("\r\n");
stream.writeBytes("Content-MD5: ");
stream.writeBytes(Base64.encode(mdata.getDataMd5()));
stream.writeBytes("\r\n");
stream.writeBytes("X-Binary-Number-of-Elements: ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getDataNumElements()));
stream.writeBytes("\r\n");
stream.writeBytes("X-Binary-Size-Fastest-Dimension: ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getDataWidth()));
stream.writeBytes("\r\n");
stream.writeBytes("X-Binary-Size-Second-Dimension: ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getDataHeight()));
stream.writeBytes("\r\n");
stream.writeBytes("X-Binary-Size-Padding: ");
stream.writeBytes(String.format(Locale.US, "%d", mdata.getDataPadding()));
stream.writeBytes("\r\n");
stream.writeBytes("\r\n");
stream.write(new byte[] { 0x0C, 0x1A, 0x04, (byte)0xD5 });
stream.write(encodedImage.getData(), 0, encodedImage.getLength());
stream.write(new byte[mdata.getDataPadding()]);
stream.writeBytes("\r\n");
stream.writeBytes("--CIF-BINARY-FORMAT-SECTION----\r\n");
stream.writeBytes(";\r\n");
stream.writeBytes("\r\n");
stream.flush();
processImageComplete();
}
/*
* Ignores IIOMetadata of specified IIOImage; instead uses specified
* CbfMetadata. Specified ImageWriteParam takes precedence over specified
* CbfMetadata. Specified CbfMetadata will be modified as needed to match
* the encoding.
*/
private IIOByteBuffer encode(IIOImage iimage, CbfMetadata mdata, ImageWriteParam param)
throws IIOException {
RenderedImage rimage = iimage.getRenderedImage();
int dataBufferType = rimage.getSampleModel().getDataType();
CbfElementType elementType = mdata.getDataElementType();
if (elementType == null) {
elementType = CbfElementType.fromDataBufferType(dataBufferType);
mdata.setDataElementType(elementType);
}
CbfCompression compression = mdata.getDataCompression();
if (compression == null) compression = CbfCompression.BYTE_OFFSET;
if (param.getCompressionMode() == ImageWriteParam.MODE_DEFAULT) {
compression = CbfCompression.BYTE_OFFSET;
} else if (param.getCompressionMode() == ImageWriteParam.MODE_DISABLED) {
compression = CbfCompression.NONE;
} else if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
if (CbfImageWriteParam.COMPRESSION_BYTE_OFFSET.equals(param.getCompressionType())) {
compression = CbfCompression.BYTE_OFFSET;
} else {
compression = CbfCompression.NONE;
}
}
if (!compression.compatibleWith(elementType)) compression = CbfCompression.NONE;
if (CbfCompression.BYTE_OFFSET.equals(compression)) {
return encodeByteOffset(rimage, dataBufferType, mdata, param);
} else if (CbfCompression.NONE.equals(compression)) {
return encodeNone(rimage, dataBufferType, mdata, param);
} else {
throw new RuntimeException("Bug; compression: " + compression);
}
}
/*
* Specified CbfMetadata indicates the type of data that should be
* encoded. Only encodes to integer (non-floating point) data. Only
* encodes integer (non-floating point) source data with the exception of
* image data of type float which may be used as a signed or unsigned
* 32-bit integer representation in which case the values must fit in a
* signed or unsigned 32-bit integer, respectively, and the deltas must
* fit within a 64-bit signed integer.
*/
private IIOByteBuffer encodeByteOffset(RenderedImage image, int dataBufferType,
CbfMetadata mdata, ImageWriteParam param) {
Rectangle srcBounds = new Rectangle(image.getWidth(), image.getHeight());
Rectangle srcRegion = param.getSourceRegion();
srcRegion = (srcRegion == null) ? srcBounds : srcRegion.intersection(srcBounds);
int srcXSubsampOffset = param.getSubsamplingXOffset();
int srcYSubsampOffset = param.getSubsamplingYOffset();
srcRegion.translate(srcXSubsampOffset, srcYSubsampOffset);
srcRegion.setSize(srcRegion.width - srcXSubsampOffset, srcRegion.height - srcYSubsampOffset);
int srcRegionMaxY = srcRegion.y + srcRegion.height - 1;
Raster srcRaster = image.getData(srcRegion);
int srcXSubsamp = param.getSourceXSubsampling();
int srcYSubsamp = param.getSourceYSubsampling();
int srcWidth = srcRaster.getWidth();
int srcHeight = srcRaster.getHeight();
int srcMaxX = srcRaster.getMinX() + srcWidth - 1;
int srcMaxY = srcRaster.getMinY() + srcHeight - 1;
int[] srcBands = param.getSourceBands();
int srcBand = (srcBands == null) ? 0 : srcBands[0];
int dstWidth = srcWidth / srcXSubsamp;
if (srcWidth % srcXSubsamp != 0) dstWidth++;
int dstHeight = srcHeight / srcYSubsamp;
if (srcHeight % srcYSubsamp != 0) dstHeight++;
int progressRowPeriod = Math.max(1, (srcRegionMaxY + 1) / 100);
CbfElementType elementType = mdata.getDataElementType();
int estimatedSize = (int)((float)dstWidth * (float)dstHeight * 1.5f);
ByteBuffer buffer = ByteBuffer.wrap(new byte[estimatedSize]);
buffer.order(mdata.getDataElementByteOrder());
boolean clipped = false;
long value = 0;
long delta = 0;
for (int srcY = srcRaster.getMinY(); srcY <= srcMaxY; srcY += srcYSubsamp) {
if (abortRequested()) {
processWriteAborted();
return null;
}
if (srcY % progressRowPeriod == 0) {
processImageProgress(((float)srcY / (float)(srcRegionMaxY + 1)) * 100.0f);
}
for (int srcX = srcRaster.getMinX(); srcX <= srcMaxX; srcX += srcXSubsamp) {
long newValue;
switch (elementType) {
case UNSIGNED_8_BIT_INTEGER:
case UNSIGNED_16_BIT_INTEGER:
newValue = srcRaster.getSample(srcX, srcY, srcBand);
if (newValue < 0L) {
newValue = 0L;
clipped = true;
}
break;
case SIGNED_32_BIT_INTEGER:
if (dataBufferType == DataBuffer.TYPE_FLOAT) {
float newValueFloat = srcRaster.getSampleFloat(srcX, srcY, srcBand);
if (newValueFloat < Integer.MIN_VALUE) {
newValue = Integer.MIN_VALUE;
clipped = true;
} else if (newValueFloat > Integer.MAX_VALUE) {
newValue = Integer.MAX_VALUE;
clipped = true;
} else {
newValue = (long)newValueFloat;
if (newValue < Integer.MIN_VALUE) newValue = Integer.MIN_VALUE;
else if (newValue > Integer.MAX_VALUE) newValue = Integer.MAX_VALUE;
}
} else {
newValue = srcRaster.getSample(srcX, srcY, srcBand);
}
break;
case UNSIGNED_32_BIT_INTEGER:
if (dataBufferType == DataBuffer.TYPE_FLOAT) {
float newValueFloat = srcRaster.getSampleFloat(srcX, srcY, srcBand);
if (newValueFloat < 0.0f) {
newValue = 0L;
clipped = true;
} else if (newValueFloat > 4294967295.0f) {
newValue = 4294967295L;
clipped = true;
} else {
newValue = (long)newValueFloat;
if (newValue < 0L) newValue = 0L;
else if (newValue > 4294967295L) newValue = 4294967295L;
}
} else {
newValue = srcRaster.getSample(srcX, srcY, srcBand);
if (newValue < 0L) {
newValue = 0L;
clipped = true;
}
}
break;
default:
newValue = srcRaster.getSample(srcX, srcY, srcBand);
}
delta = newValue - value;
if (Byte.MIN_VALUE < delta && delta <= Byte.MAX_VALUE) {
buffer = ensureRemaining(buffer, 1);
buffer.put((byte)delta);
value = newValue;
continue;
}
buffer = ensureRemaining(buffer, 1);
buffer.put(Byte.MIN_VALUE);
if (Short.MIN_VALUE < delta && delta <= Short.MAX_VALUE) {
buffer = ensureRemaining(buffer, 2);
buffer.putShort((short)delta);
value = newValue;
continue;
}
buffer = ensureRemaining(buffer, 2);
buffer.putShort(Short.MIN_VALUE);
if (Integer.MIN_VALUE < delta && delta <= Integer.MAX_VALUE) {
buffer = ensureRemaining(buffer, 4);
buffer.putInt((int)delta);
value = newValue;
continue;
}
buffer = ensureRemaining(buffer, 4);
buffer.putInt(Integer.MIN_VALUE);
buffer = ensureRemaining(buffer, 8);
buffer.putLong(delta);
value = newValue;
}
}
if (clipped) {
processWarningOccurred(0, "Source value(s) clipped to fit in destination element type");
}
IIOByteBuffer result = new IIOByteBuffer(buffer.array(), 0, buffer.limit());
mdata.setDataCompression(CbfCompression.BYTE_OFFSET);
mdata.setDataWidth(dstWidth);
mdata.setDataHeight(dstHeight);
mdata.setDataNumElements(dstWidth * dstHeight);
mdata.setDataSize(result.getLength());
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
processWarningOccurred(0, "Failed to find MD5 algorithm for data MD5 digest calculation");
}
if (digest != null) {
digest.update(result.getData(), result.getOffset(), result.getLength());
mdata.setDataMd5(digest.digest());
}
return result;
}
private ByteBuffer ensureRemaining(ByteBuffer buffer, int remaining) {
if (buffer.remaining() >= remaining) return buffer;
int capacity = buffer.capacity();
int extraCapacity = capacity / 3;
int minExtraCapacity = remaining - buffer.remaining();
if (extraCapacity < minExtraCapacity) extraCapacity = minExtraCapacity;
int newCapacity = capacity + extraCapacity;
byte[] newArray = Arrays.copyOf(buffer.array(), newCapacity);
ByteBuffer newBuffer = ByteBuffer.wrap(newArray);
newBuffer.position(buffer.position());
newBuffer.order(buffer.order());
return newBuffer;
}
/*
* Specified CbfMetadata indicates the type of data that should be
* encoded.
*/
private IIOByteBuffer encodeNone(RenderedImage image, int dataBufferType, CbfMetadata mdata,
ImageWriteParam param) {
Rectangle srcBounds = new Rectangle(image.getWidth(), image.getHeight());
Rectangle srcRegion = param.getSourceRegion();
srcRegion = (srcRegion == null) ? srcBounds : srcRegion.intersection(srcBounds);
int srcXSubsampOffset = param.getSubsamplingXOffset();
int srcYSubsampOffset = param.getSubsamplingYOffset();
srcRegion.translate(srcXSubsampOffset, srcYSubsampOffset);
srcRegion.setSize(srcRegion.width - srcXSubsampOffset, srcRegion.height - srcYSubsampOffset);
int srcRegionMaxY = srcRegion.y + srcRegion.height - 1;
Raster srcRaster = image.getData(srcRegion);
int srcXSubsamp = param.getSourceXSubsampling();
int srcYSubsamp = param.getSourceYSubsampling();
int srcWidth = srcRaster.getWidth();
int srcHeight = srcRaster.getHeight();
int srcMaxX = srcRaster.getMinX() + srcWidth - 1;
int srcMaxY = srcRaster.getMinY() + srcHeight - 1;
int[] srcBands = param.getSourceBands();
int srcBand = (srcBands == null) ? 0 : srcBands[0];
int dstWidth = srcWidth / srcXSubsamp;
if (srcWidth % srcXSubsamp != 0) dstWidth++;
int dstHeight = srcHeight / srcYSubsamp;
if (srcHeight % srcYSubsamp != 0) dstHeight++;
int progressRowPeriod = Math.max(1, (srcRegionMaxY + 1) / 100);
CbfElementType elementType = mdata.getDataElementType();
int numBytesPerElement;
switch (elementType) {
case UNSIGNED_8_BIT_INTEGER:
case SIGNED_8_BIT_INTEGER:
numBytesPerElement = 1;
break;
case UNSIGNED_16_BIT_INTEGER:
case SIGNED_16_BIT_INTEGER:
numBytesPerElement = 2;
break;
case UNSIGNED_32_BIT_INTEGER:
case SIGNED_32_BIT_INTEGER:
case SIGNED_32_BIT_REAL_IEEE:
numBytesPerElement = 4;
break;
case SIGNED_64_BIT_REAL_IEEE:
numBytesPerElement = 8;
break;
default:
throw new RuntimeException("Bug; element type: " + elementType);
}
int dataSize = dstWidth * dstHeight * numBytesPerElement;
ByteBuffer buffer = ByteBuffer.wrap(new byte[dataSize]);
buffer.order(mdata.getDataElementByteOrder());
boolean clipped = false;
for (int srcY = srcRaster.getMinY(); srcY <= srcMaxY; srcY += srcYSubsamp) {
if (abortRequested()) {
processWriteAborted();
return null;
}
if (srcY % progressRowPeriod == 0) {
processImageProgress(((float)srcY / (float)(srcRegionMaxY + 1)) * 100.0f);
}
for (int srcX = srcRaster.getMinX(); srcX <= srcMaxX; srcX += srcXSubsamp) {
switch (elementType) {
case UNSIGNED_8_BIT_INTEGER:
int ubyteValue = srcRaster.getSample(srcX, srcY, srcBand);
if (ubyteValue < 0) {
ubyteValue = 0;
clipped = true;
}
buffer.put((byte)ubyteValue);
break;
case SIGNED_8_BIT_INTEGER:
buffer.put((byte)srcRaster.getSample(srcX, srcY, srcBand));
break;
case UNSIGNED_16_BIT_INTEGER:
int ushortValue = srcRaster.getSample(srcX, srcY, srcBand);
if (ushortValue < 0) {
ushortValue = 0;
clipped = true;
}
buffer.putShort((short)ushortValue);
break;
case SIGNED_16_BIT_INTEGER:
buffer.putShort((short)srcRaster.getSample(srcX, srcY, srcBand));
break;
case UNSIGNED_32_BIT_INTEGER:
if (dataBufferType == DataBuffer.TYPE_FLOAT) {
float floatValue = srcRaster.getSampleFloat(srcX, srcY, srcBand);
long longValue;
if (floatValue < 0.0f) {
longValue = 0L;
clipped = true;
} else if (floatValue > 4294967295.0f) {
longValue = 4294967295L;
clipped = true;
} else {
longValue = (long)floatValue;
if (longValue < 0L) longValue = 0L;
else if (longValue > 4294967295L) longValue = 4294967295L;
}
buffer.putInt((int)longValue);
} else {
int uintValue = srcRaster.getSample(srcX, srcY, srcBand);
if (uintValue < 0) {
uintValue = 0;
clipped = true;
}
buffer.putInt(uintValue);
}
break;
case SIGNED_32_BIT_INTEGER:
if (dataBufferType == DataBuffer.TYPE_FLOAT) {
float floatValue = srcRaster.getSampleFloat(srcX, srcY, srcBand);
long longValue;
if (floatValue < Integer.MIN_VALUE) {
longValue = Integer.MIN_VALUE;
clipped = true;
} else if (floatValue > Integer.MAX_VALUE) {
longValue = Integer.MAX_VALUE;
clipped = true;
} else {
longValue = (long)floatValue;
if (longValue < Integer.MIN_VALUE) longValue = Integer.MIN_VALUE;
else if (longValue > Integer.MAX_VALUE) longValue = Integer.MAX_VALUE;
}
buffer.putInt((int)longValue);
} else {
buffer.putInt(srcRaster.getSample(srcX, srcY, srcBand));
}
break;
case SIGNED_32_BIT_REAL_IEEE:
buffer.putFloat(srcRaster.getSampleFloat(srcX, srcY, srcBand));
break;
case SIGNED_64_BIT_REAL_IEEE:
buffer.putDouble(srcRaster.getSampleDouble(srcX, srcY, srcBand));
break;
default:
throw new RuntimeException("Bug; element type: " + elementType);
}
}
}
if (clipped) {
processWarningOccurred(0, "Source value(s) clipped to fit in destination element type");
}
IIOByteBuffer result = new IIOByteBuffer(buffer.array(), 0, buffer.limit());
mdata.setDataCompression(CbfCompression.NONE);
mdata.setDataWidth(dstWidth);
mdata.setDataHeight(dstHeight);
mdata.setDataNumElements(dstWidth * dstHeight);
mdata.setDataSize(result.getLength());
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
processWarningOccurred(0, "Failed to find MD5 algorithm for data MD5 digest calculation");
}
if (digest != null) {
digest.update(result.getData(), result.getOffset(), result.getLength());
mdata.setDataMd5(digest.digest());
}
return result;
}
private ImageOutputStream getNonNullOutput() {
Object output = getOutput();
if (output == null) throw new IllegalStateException("No output stream");
return (ImageOutputStream)output;
}
}