private void writeValue(final ImageOutputStream stream, final Object value) throws IOException { if (value instanceof String) { byte[] data = ((String) value).getBytes(StandardCharsets.UTF_8); stream.writeShort(data.length); stream.write(data); } else if (value instanceof byte[]) { byte[] data = (byte[]) value; stream.writeShort(data.length); stream.write(data); } else if (value instanceof Integer) { // TODO: Need to know types from tag stream.writeShort(2); stream.writeShort((Integer) value); } } }
@Override public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { assertOpen(); output.write(pBytes, pOffset, pLength); }
/** * Creates the binary header for the raster file. * * <p>The space for the header of the rasterfile is created, filling the spaces with zeros. * After the compression the values will be rewritten * * @param rows number of rows that will be written. * @throws IOException if an error occurs while trying to write the header. */ private void createEmptyHeader(int rows) throws IOException { addressesOfRows = new long[rows + 1]; // the size of a long in C? imageOS.write(4); // write the addresses of the row begins. Since we don't know how // much // they will be compressed, they will be filled after the // compression for (int i = 0; i < rows + 1; i++) { imageOS.writeInt(0); } pointerInFilePosition = imageOS.getStreamPosition(); addressesOfRows[0] = pointerInFilePosition; }
imageOutput.write(buf);
private void writeImageData(final IIOImage image) throws IOException { // - dump data as is (or convert, if TYPE_INT_xxx) // Enforce RGB/CMYK order for such data! // TODO: Loop over x/y tiles, using 0,0 is only valid for BufferedImage // TODO: PNM/PAM does not support tiling, we must iterate all tiles along the x-axis for each row we write Raster tile = image.hasRaster() ? image.getRaster() : image.getRenderedImage().getTile(0, 0); SampleModel sampleModel = tile.getSampleModel(); DataBuffer dataBuffer = tile.getDataBuffer(); int tileWidth = tile.getWidth(); int tileHeight = tile.getHeight(); final int transferType = sampleModel.getTransferType(); Object data = null; for (int y = 0; y < tileHeight; y++) { data = sampleModel.getDataElements(0, y, tileWidth, 1, data, dataBuffer); // TODO: Support other (short, float) data types if (transferType == DataBuffer.TYPE_BYTE) { imageOutput.write((byte[]) data); } else if (transferType == DataBuffer.TYPE_USHORT) { short[] shortData = (short[]) data; imageOutput.writeShorts(shortData, 0, shortData.length); } processImageProgress(y * 100f / tileHeight); // TODO: Take tile y into account if (abortRequested()) { processWriteAborted(); break; } } }
@Override public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException { assertOutput(); if (sequenceIndex >= 0) { throw new IllegalStateException("writeSequence already started"); } writeICOHeader(); // Count: Needs to be updated for each new image imageOutput.writeShort(0); sequenceIndex = 0; // TODO: Allow passing the initial size of the directory in the stream metadata? // - as this is much more efficient than growing... // How do we update the "image directory" containing "image entries", // and which must be written *before* the image data? // - Allocate a block of N * 16 bytes // - If image count % N > N, we need to move the first image backwards in the file and allocate another N items... imageOutput.write(new byte[INITIAL_ENTRY_COUNT * ENTRY_SIZE]); // Allocate room for 8 entries for now }
@Override public void write(final int pByte) throws IOException { assertOpen(); output.write(pByte); }
case DataBuffer.TYPE_BYTE: rowRaster.setDataElements(0, 0, raster.createChild(0, y, raster.getWidth(), 1, 0, 0, null)); imageOutput.write(((DataBufferByte) buffer).getData()); break; case DataBuffer.TYPE_USHORT:
@Override public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException { // Write PAM magic imageOutput.writeShort(PNM.PAM); imageOutput.write('\n'); // Comments writeComments(image.getMetadata(), provider); // Write width/height and number of channels imageOutput.write(String.format("WIDTH %s\nHEIGHT %s\n", getWidth(image), getHeight(image)).getBytes(UTF8)); imageOutput.write(String.format("DEPTH %s\n", getNumBands(image)).getBytes(UTF8)); // TODO: maxSample (8 or16 bit) imageOutput.write(String.format("MAXVAL %s\n", getMaxVal(image)).getBytes(UTF8)); // TODO: Determine tuple type based on input color model and image data TupleType tupleType = getNumBands(image) > 3 ? TupleType.RGB_ALPHA : TupleType.RGB; imageOutput.write(String.format("TUPLTYPE %s\nENDHDR\n", tupleType).getBytes(UTF8)); } }
@Override public void write(final byte[] pBytes) throws IOException { assertOpen(); output.write(pBytes); }
@Override public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException { // Write P4/P5/P6 magic (Support only RAW formats for now; if we are to support PLAIN formats, pass parameter) // TODO: Determine PBM, PBM or PPM based on input color model and image data? short type = PNM.PPM; imageOutput.writeShort(type); imageOutput.write('\n'); // Comments writeComments(image.getMetadata(), provider); // Dimensions (width/height) imageOutput.write(String.format("%s %s\n", getWidth(image), getHeight(image)).getBytes(HeaderWriter.UTF8)); // MaxSample if (type != PNM.PBM) { imageOutput.write(String.format("%s\n", getMaxVal(image)).getBytes(HeaderWriter.UTF8)); } } }
@Override public void write(int b) throws IOException { flushBits(); stream.write(b); streamPos++; }
@Override public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException { notNull(directory, "directory"); notNull(stream, "stream"); // TODO: Make sure we always write application record version (2.00) // TODO: Write encoding UTF8? for (Entry entry : directory) { int tag = (Integer) entry.getIdentifier(); Object value = entry.getValue(); if (IPTC.Tags.isArray((short) tag)) { Object[] values = (Object[]) value; for (Object v : values) { stream.write(0x1c); stream.writeShort(tag); writeValue(stream, v); } } else { stream.write(0x1c); stream.writeShort(tag); writeValue(stream, value); } } return false; }
@Override public void write(byte[] b, int off, int len) throws IOException { flushBits(); stream.write(b, off, len); streamPos += len; }
writeUncompressed(false, (BufferedImage) img, img.getWidth(), img.getHeight()); imageOutput.write(new byte[((width * height + 31) / 32) * 4]);
protected final void writeComments(final IIOMetadata metadata, final ImageWriterSpi provider) throws IOException { // TODO: Only write creator if not already present imageOutput.write(String.format("# CREATOR: %s %s\n", provider.getVendorName(), provider.getDescription(Locale.getDefault())).getBytes(UTF8)); // Comments from metadata if (metadata != null && metadata.isStandardMetadataFormatSupported()) { IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); NodeList textEntries = root.getElementsByTagName("TextEntry"); for (int i = 0; i < textEntries.getLength(); i++) { // TODO: Write on the format "# KEYWORD: value" (if keyword != comment)? IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i); imageOutput.write(String.format("# %s", textEntry.getAttribute("value")).getBytes(UTF8)); } } }
private void writeData(ImageInputStream input, ImageOutputStream output, long offset, long length) throws IOException { input.seek(offset); byte[] buffer = new byte[(int) length]; stream.readFully(buffer); output.write(buffer); }
private int[] writeData(long[] offsets, long[] byteCounts, ImageOutputStream outputStream) throws IOException { int[] newOffsets = new int[offsets.length]; for (int i = 0; i < offsets.length; i++) { newOffsets[i] = (int) outputStream.getStreamPosition(); stream.seek(offsets[i]); byte[] buffer = new byte[(int) byteCounts[i]]; try { stream.readFully(buffer); } catch (EOFException e) { // invalid strip length } outputStream.write(buffer); } return newOffsets; }
void writeUncompressed(boolean isTopDown, BufferedImage img, int height, int width) throws IOException { // TODO: Fix if (img.getType() != BufferedImage.TYPE_4BYTE_ABGR) { throw new IIOException("Blows!"); } // Support // - TODO: IndexColorModel (ucompressed, RLE4, RLE8 or BI_PNG) // - TODO: ComponentColorModel (1 channel gray, 3 channel BGR and 4 channel BGRA, uncompressed and RLE8? BI_BITFIELDS? BI_PNG? BI_JPEG?) // - TODO: Packed/DirectColorModel (16 and 32 bit, BI_BITFIELDS, BI_PNG? BI_JPEG?) Raster raster = img.getRaster(); WritableRaster rowRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, 1, width * 4, 4, new int[]{2, 1, 0, 3}, null); byte[] row = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); for (int i = 0; i < height; i++) { int line = isTopDown ? i : height - 1 - i; rowRaster.setDataElements(0, 0, raster.createChild(0, line, width, 1, 0, 0, new int[]{2, 1, 0, 3})); imageOutput.write(row); if (abortRequested()) { processWriteAborted(); break; } processImageProgress(100f * i / (float) height); } } }
private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException { short type = getType(entry); long valueLength = getValueLength(type, getCount(entry)); if (valueLength <= LONGWORD_LENGTH) { writeValueInline(entry.getValue(), type, stream); // Pad for (long i = valueLength; i < LONGWORD_LENGTH; i++) { stream.write(0); } return 0; } else { writeValueAt(dataOffset, entry.getValue(), type, stream); return valueLength; } }