/* * Copyright (c) 2011 J. Lewis Muir * * 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; } }