use of it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageWriteParam.ProgressionOrder in project imageio-ext by geosolutions-it.
the class JP2KKakaduImageWriter method write.
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
// ////////////////////////////////////////////////////////////////////
//
// Variables initialization
//
// ////////////////////////////////////////////////////////////////////
final String fileName = outputFile.getAbsolutePath();
JP2KKakaduImageWriteParam jp2Kparam;
final boolean writeCodeStreamOnly;
final double quality;
int cLayers = 1;
int cLevels;
final boolean cycc;
byte[] geoJp2 = null;
final boolean orgGen_plt;
int orgGen_tlm = JP2KKakaduImageWriteParam.UNSPECIFIED_ORG_GEN_TLM;
int qGuard = -1;
String orgT_parts = null;
String cPrecincts = null;
boolean setTiling = false;
int tileW = Integer.MIN_VALUE;
int tileH = Integer.MIN_VALUE;
ProgressionOrder cOrder = null;
double[] bitRates = null;
boolean addCommentMarker = ADD_COMMENT_MARKER;
int sProfile = JP2KKakaduImageWriteParam.DEFAULT_SPROFILE;
Compression compression = Compression.UNDEFINED;
// //
if (param == null) {
param = getDefaultWriteParam();
}
if (param instanceof JP2KKakaduImageWriteParam) {
jp2Kparam = (JP2KKakaduImageWriteParam) param;
writeCodeStreamOnly = jp2Kparam.isWriteCodeStreamOnly();
bitRates = jp2Kparam.getQualityLayersBitRates();
double q = jp2Kparam.getQuality();
if (q < 0.01) {
q = 0.01;
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Quality level should be in the range 0.01 - 1. /n Reassigning it to 0.01");
}
quality = q;
geoJp2 = jp2Kparam.getGeoJp2();
setTiling = jp2Kparam.getTilingMode() == ImageWriteParam.MODE_EXPLICIT;
if (setTiling) {
tileH = jp2Kparam.getTileHeight();
tileW = jp2Kparam.getTileWidth();
}
// COD PARAMS
cOrder = jp2Kparam.getcOrder();
cPrecincts = jp2Kparam.getcPrecincts();
cLevels = jp2Kparam.getCLevels();
cLayers = jp2Kparam.getQualityLayers();
// ORG PARAMS
orgGen_plt = jp2Kparam.isOrgGen_plt();
orgGen_tlm = jp2Kparam.getOrgGen_tlm();
orgT_parts = jp2Kparam.getOrgT_parts();
qGuard = jp2Kparam.getqGuard();
addCommentMarker &= jp2Kparam.isAddCommentMarker();
sProfile = jp2Kparam.getsProfile();
compression = jp2Kparam.getCompression();
if (bitRates != null && bitRates.length != cLayers) {
throw new IllegalArgumentException(" Specified bitRates parameter's length " + bitRates.length + " should match the quality layers parameter " + "(cLayers): " + cLayers);
}
if (compression != null) {
switch(compression) {
case LOSSY:
if (bitRates != null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Applying lossy compression leveraging on provided quality bit rates");
}
} else {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Applying lossy compression leveraging on quality factor");
}
}
break;
case NUMERICALLY_LOSSLESS:
if (bitRates != null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Applying numerically lossless compression leveraging on " + "provided quality bit rates");
}
if (KakaduUtilities.notEqual(bitRates[bitRates.length - 1], 0)) {
throw new IllegalArgumentException("Asking for a Numerically Lossless " + "but the last quality layer's bit rate should be 0 " + " instead of " + bitRates[bitRates.length - 1]);
}
} else {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Applying numerically lossless compression");
}
}
break;
}
} else {
compression = Compression.UNDEFINED;
}
} else {
orgGen_plt = false;
writeCodeStreamOnly = true;
quality = JP2KKakaduImageWriteParam.DEFAULT_QUALITY;
cLevels = JP2KKakaduImageWriteParam.DEFAULT_C_LEVELS;
}
// ////////////////////////////////////////////////////////////////////
//
// Image properties initialization
//
// ////////////////////////////////////////////////////////////////////
final RenderedImage inputRenderedImage = image.getRenderedImage();
final int sourceWidth = inputRenderedImage.getWidth();
final int sourceHeight = inputRenderedImage.getHeight();
final int sourceMinX = inputRenderedImage.getMinX();
final int sourceMinY = inputRenderedImage.getMinY();
final SampleModel sm = inputRenderedImage.getSampleModel();
final int dataType = sm.getDataType();
final boolean isDataSigned = (dataType != DataBuffer.TYPE_USHORT && dataType != DataBuffer.TYPE_BYTE);
final ColorModel colorModel = inputRenderedImage.getColorModel();
final boolean hasPalette = colorModel instanceof IndexColorModel ? true : false;
final int[] numberOfBits = colorModel.getComponentSize();
// The number of bytes used by header, markers, boxes
int bytesOverHead = 0;
// We suppose all bands share the same bitDepth
final int bits = numberOfBits[0];
int nComponents = sm.getNumBands();
// Array to store optional look up table entries
byte[] reds = null;
byte[] greens = null;
byte[] blues = null;
// //
if (hasPalette) {
cycc = false;
cLevels = 1;
IndexColorModel icm = (IndexColorModel) colorModel;
final int lutSize = icm.getMapSize();
final int numColorComponents = colorModel.getNumColorComponents();
// Updating the number of components to write as RGB (3 bands)
if (writeCodeStreamOnly) {
nComponents = numColorComponents;
// //
//
// Caching look up table for future accesses.
//
// //
reds = new byte[lutSize];
blues = new byte[lutSize];
greens = new byte[lutSize];
icm.getReds(reds);
icm.getGreens(greens);
icm.getBlues(blues);
} else {
// adding pclr and cmap boxes overhead bytes
// NE + NC + Bi
bytesOverHead += (4 + 2 + numColorComponents + 1);
// pclr LUT
bytesOverHead += lutSize * numColorComponents + 4;
// cmap
bytesOverHead += 20;
}
} else if (quality == 1) {
cycc = false;
} else {
cycc = true;
}
// //
//
// Setting regions and sizes and retrieving parameters
//
// //
final int xSubsamplingFactor = param.getSourceXSubsampling();
final int ySubsamplingFactor = param.getSourceYSubsampling();
final Rectangle originalBounds = new Rectangle(sourceMinX, sourceMinY, sourceWidth, sourceHeight);
final Rectangle imageBounds = (Rectangle) originalBounds.clone();
final Dimension destSize = new Dimension();
KakaduUtilities.computeRegions(imageBounds, destSize, param);
boolean resampleInputImage = false;
if (xSubsamplingFactor != 1 || ySubsamplingFactor != 1 || !imageBounds.equals(originalBounds)) {
resampleInputImage = true;
}
// Destination sizes
final int destinationWidth = destSize.width;
final int destinationHeight = destSize.height;
final int rowSize = (destinationWidth * nComponents);
final int bandSize = destinationHeight * destinationWidth;
final int imageSize = bandSize * nComponents;
// ////////////////////////////////////////////////////////////////////
//
// Kakadu objects initialization
//
// ////////////////////////////////////////////////////////////////////
Kdu_compressed_target outputTarget = null;
Jp2_target target = null;
Jp2_family_tgt familyTarget = null;
// Setting decomposition levels
cLevels = setDecompositionLevels(cLevels, destinationWidth);
try {
if (writeCodeStreamOnly) {
// Open a simple file target
outputTarget = new Kdu_simple_file_target();
((Kdu_simple_file_target) outputTarget).Open(fileName);
final int extensionIndex = fileName.lastIndexOf(".");
final String suffix = fileName.substring(extensionIndex, fileName.length());
if (suffix.equalsIgnoreCase(".jp2") && LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("When writing codestreams, the \".j2c\" file suffix is suggested instead of \".jp2\"");
}
} else {
familyTarget = new Jp2_family_tgt();
familyTarget.Open(fileName);
target = new Jp2_target();
target.Open(familyTarget);
// Adding signature, fileType, image header, Jp2Header box bytes
// + jp2c marker.
bytesOverHead += 84;
if (geoJp2 != null) {
bytesOverHead += geoJp2.length;
}
}
bytesOverHead += addMarkerBytes(nComponents, destinationWidth, destinationHeight, tileW, tileH, orgGen_tlm, addCommentMarker);
if (bytesOverHead >= imageSize) {
bytesOverHead = 0;
}
final long qualityLayersSize = bitRates != null ? imageSize : (long) ((imageSize - bytesOverHead) * quality * bits * KakaduUtilities.BIT_TO_BYTE_FACTOR);
// //
//
// Parameters initialization
//
// //
final Kdu_codestream codeStream = new Kdu_codestream();
Siz_params params = new Siz_params();
initializeParams(params, destinationWidth, destinationHeight, bits, nComponents, isDataSigned, tileW, tileH, sProfile);
// Create a codestream on the proper output object.
if (writeCodeStreamOnly) {
codeStream.Create(params, outputTarget, null);
} else {
codeStream.Create(params, target, null);
}
if (!initializeCodestream(codeStream, cycc, cLevels, quality, cLayers, colorModel, writeCodeStreamOnly, dataType, target, geoJp2, orgGen_plt, orgGen_tlm, orgT_parts, qGuard, cOrder, cPrecincts, compression)) {
throw new IOException("Unable to initialize the codestream due to a missing " + "Jp2_target object");
}
// //
//
// Preparing parameters for stripe compression
//
// //
final Kdu_stripe_compressor compressor = new Kdu_stripe_compressor();
// Array with one entry for each image component, identifying the
// number of lines supplied for that component in the present call.
// All entries must be non-negative.
final int[] stripeHeights = new int[nComponents];
// Array with one entry for each image component, identifying
// the separation between horizontally adjacent samples within the
// corresponding stripe buffer found in the stripe_bufs array.
final int[] sampleGaps = new int[nComponents];
// Array with one entry for each image component, identifying
// the separation between vertically adjacent samples within the
// corresponding stripe buffer found in the stripe_bufs array.
final int[] rowGaps = new int[nComponents];
// Array with one entry for each image component, identifying the
// position of the first sample of that component within the buffer array.
final int[] sampleOffsets = new int[nComponents];
// Array with one entry for each image component, identifying the
// number of significant bits used to represent each sample.
final int[] precisions = new int[nComponents];
initializeStripeCompressor(compressor, codeStream, quality, cLayers, qualityLayersSize, bitRates, compression, rowSize, destinationHeight, destinationWidth, nComponents, stripeHeights, sampleGaps, rowGaps, sampleOffsets, precisions, numberOfBits, addCommentMarker);
// ////////////////////////////////////////////////////////////////
//
// Pushing Stripes
//
// ////////////////////////////////////////////////////////////////
pushStripes(compressor, inputRenderedImage, imageBounds, originalBounds, isDataSigned, resampleInputImage, writeCodeStreamOnly, hasPalette, nComponents, bits, destinationWidth, destinationHeight, xSubsamplingFactor, ySubsamplingFactor, stripeHeights, sampleGaps, rowGaps, sampleOffsets, precisions, reds, greens, blues);
// ////////////////////////////////////////////////////////////////
//
// Kakadu Objects Finalization
//
// ////////////////////////////////////////////////////////////////
compressor.Finish();
compressor.Native_destroy();
codeStream.Destroy();
} catch (KduException e) {
throw (IOException) new IOException("Error caused by a Kakadu exception during write operation").initCause(e);
} finally {
// //
if (!writeCodeStreamOnly && target != null) {
try {
if (target.Exists())
target.Close();
} catch (Throwable e) {
// Does Nothing
}
try {
target.Native_destroy();
} catch (Throwable e) {
// Does Nothing
}
if (familyTarget != null) {
try {
if (familyTarget.Exists())
familyTarget.Close();
} catch (Throwable e) {
// Does Nothing
}
try {
familyTarget.Native_destroy();
} catch (Throwable e) {
// Does Nothing
}
}
} else if (writeCodeStreamOnly && outputTarget != null) {
try {
outputTarget.Close();
} catch (Throwable e) {
// Does Nothing
}
try {
outputTarget.Native_destroy();
} catch (Throwable e) {
// Does Nothing
}
}
// //
if (outputStream != null) {
writeOnStream();
}
}
}
Aggregations