774 lines
31 KiB
Java
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;
|
|
}
|
|
}
|