private int instreamObjectCount = 0;
/** a mapping of resourceInfo --> include name */
- private final Map/*<AFPResourceInfo,String>*/ includeNameMap
- = new java.util.HashMap()/*<AFPResourceInfo,String>*/;
+ private final Map<AFPResourceInfo, String> includeNameMap
+ = new java.util.HashMap<AFPResourceInfo, String>();
- private Map pageSegmentMap = new java.util.HashMap();
+ /** a mapping of resourceInfo --> page segment name */
+ private Map<AFPResourceInfo, String> pageSegmentMap
+ = new java.util.HashMap<AFPResourceInfo, String>();
private AFPResourceLevelDefaults resourceLevelDefaults = new AFPResourceLevelDefaults();
}
/**
- * Creates a new data object in the AFP datastream
- *
+ * Tries to create an include of a data object that has been previously added to the
+ * AFP data stream. If no such object was available, the method returns false which serves
+ * as a signal that the object has to be created.
* @param dataObjectInfo the data object info
- *
+ * @return true if the inclusion succeeded, false if the object was not available
* @throws IOException thrown if an I/O exception of some sort has occurred.
*/
- public void createObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
- AbstractNamedAFPObject namedObj = null;
-
+ public boolean tryIncludeObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
updateResourceInfoUri(resourceInfo);
- String objectName = (String)includeNameMap.get(resourceInfo);
+ String objectName = includeNameMap.get(resourceInfo);
if (objectName != null) {
// an existing data resource so reference it by adding an include to the current page
includeObject(dataObjectInfo, objectName);
- return;
+ return true;
}
- objectName = (String)pageSegmentMap.get(resourceInfo);
+ objectName = pageSegmentMap.get(resourceInfo);
if (objectName != null) {
// an existing data resource so reference it by adding an include to the current page
includePageSegment(dataObjectInfo, objectName);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Creates a new data object in the AFP datastream
+ *
+ * @param dataObjectInfo the data object info
+ *
+ * @throws IOException thrown if an I/O exception of some sort has occurred.
+ */
+ public void createObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
+ if (tryIncludeObject(dataObjectInfo)) {
+ //Object has already been produced and is available by inclusion, so return early.
return;
}
+ AbstractNamedAFPObject namedObj = null;
+ AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
+
boolean useInclude = true;
Registry.ObjectType objectType = null;
resourceGroup.addObject(namedObj);
// create the include object
- objectName = namedObj.getName();
+ String objectName = namedObj.getName();
if (usePageSegment) {
includePageSegment(dataObjectInfo, objectName);
pageSegmentMap.put(resourceInfo, objectName);
resourceInfo.setName(resourceName);
resourceInfo.setUri(uri.toASCIIString());
- String objectName = (String)includeNameMap.get(resourceInfo);
+ String objectName = includeNameMap.get(resourceInfo);
if (objectName == null) {
if (log.isDebugEnabled()) {
log.debug("Adding included resource: " + resourceName);
resourceInfo.setName(resourceName);
resourceInfo.setUri(uri.toASCIIString());
- String resource = (String)includeNameMap.get(resourceInfo);
+ String resource = includeNameMap.get(resourceInfo);
if (resource == null) {
ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
//The included resource may already be wrapped in a resource object
AbstractNamedAFPObject resourceObject = new AbstractNamedAFPObject(null) {
+ @Override
protected void writeContent(OutputStream os) throws IOException {
InputStream inputStream = null;
try {
}
//bypass super.writeStart
+ @Override
protected void writeStart(OutputStream os) throws IOException { }
//bypass super.writeEnd
+ @Override
protected void writeEnd(OutputStream os) throws IOException { }
};
ImageFlavor.RENDERED_IMAGE
};
- private AFPDataObjectInfo updateDataObjectInfo // CSOK: MethodLength
- (AFPImageObjectInfo imageObjectInfo,
- AFPPaintingState paintingState, ImageRendered imageRendered, Dimension targetSize)
- throws IOException {
-
- long start = System.currentTimeMillis();
-
- int resolution = paintingState.getResolution();
- int maxPixelSize = paintingState.getBitsPerPixel();
- if (paintingState.isColorImages()) {
- if (paintingState.isCMYKImagesSupported()) {
- maxPixelSize *= 4; //CMYK is maximum
- } else {
- maxPixelSize *= 3; //RGB is maximum
- }
- }
- float ditheringQuality = paintingState.getDitheringQuality();
- RenderedImage renderedImage = imageRendered.getRenderedImage();
-
- ImageInfo imageInfo = imageRendered.getInfo();
- ImageSize intrinsicSize = imageInfo.getSize();
-
- boolean useFS10 = (maxPixelSize == 1) || BitmapImageUtil.isMonochromeImage(renderedImage);
- int functionSet = useFS10 ? 10 : 11;
- boolean usePageSegments = useFS10
- && !imageObjectInfo.getResourceInfo().getLevel().isInline();
-
- ImageSize effIntrinsicSize = intrinsicSize;
- if (usePageSegments) {
- //Resize, optionally resample and convert image
- Dimension resampledDim = new Dimension(
- (int)Math.ceil(UnitConv.mpt2px(targetSize.getWidth(), resolution)),
- (int)Math.ceil(UnitConv.mpt2px(targetSize.getHeight(), resolution)));
-
- imageObjectInfo.setCreatePageSegment(true);
- imageObjectInfo.getResourceInfo().setImageDimension(resampledDim);
-
- //Only resample/downsample if image is smaller than its intrinsic size
- //to make print file smaller
- boolean resample = resampledDim.width < renderedImage.getWidth()
- && resampledDim.height < renderedImage.getHeight();
- if (resample) {
- if (log.isDebugEnabled()) {
- log.debug("Resample from " + intrinsicSize.getDimensionPx()
- + " to " + resampledDim);
- }
- renderedImage = BitmapImageUtil.convertToMonochrome(renderedImage,
- resampledDim, ditheringQuality);
- effIntrinsicSize = new ImageSize(
- resampledDim.width, resampledDim.height, resolution);
- } else if (ditheringQuality >= 0.5f) {
- renderedImage = BitmapImageUtil.convertToMonochrome(renderedImage,
- intrinsicSize.getDimensionPx(), ditheringQuality);
- }
- }
-
- imageObjectInfo.setDataHeightRes((int)Math.round(
- effIntrinsicSize.getDpiHorizontal() * 10));
- imageObjectInfo.setDataWidthRes((int)Math.round(
- effIntrinsicSize.getDpiVertical() * 10));
-
- int dataHeight = renderedImage.getHeight();
- imageObjectInfo.setDataHeight(dataHeight);
-
- int dataWidth = renderedImage.getWidth();
- imageObjectInfo.setDataWidth(dataWidth);
-
- //TODO To reduce AFP file size, investigate using a compression scheme.
- //Currently, all image data is uncompressed.
- ColorModel cm = renderedImage.getColorModel();
- if (log.isTraceEnabled()) {
- log.trace("ColorModel: " + cm);
- }
- int pixelSize = cm.getPixelSize();
- if (cm.hasAlpha()) {
- pixelSize -= 8;
- }
-
- byte[] imageData = null;
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- boolean allowDirectEncoding = true;
- if (allowDirectEncoding && (pixelSize <= maxPixelSize)) {
- //Attempt to encode without resampling the image
- ImageEncodingHelper helper = new ImageEncodingHelper(renderedImage, pixelSize == 32);
- ColorModel encodedColorModel = helper.getEncodedColorModel();
- boolean directEncode = true;
- if (helper.getEncodedColorModel().getPixelSize() > maxPixelSize) {
- directEncode = false; //pixel size needs to be reduced
- }
- if (BitmapImageUtil.getColorIndexSize(renderedImage) > 2) {
- directEncode = false; //Lookup tables are not implemented, yet
- }
- if (useFS10
- && BitmapImageUtil.isMonochromeImage(renderedImage)
- && BitmapImageUtil.isZeroBlack(renderedImage)) {
- directEncode = false;
- //need a special method to invert the bit-stream since setting the subtractive mode
- //in AFP alone doesn't seem to do the trick.
- if (encodeInvertedBilevel(helper, imageObjectInfo, baos)) {
- imageData = baos.toByteArray();
- }
- }
- if (directEncode) {
- log.debug("Encoding image directly...");
- imageObjectInfo.setBitsPerPixel(encodedColorModel.getPixelSize());
- if (pixelSize == 32) {
- functionSet = 45; //IOCA FS45 required for CMYK
- }
-
- helper.encode(baos);
- imageData = baos.toByteArray();
- }
- }
- if (imageData == null) {
- log.debug("Encoding image via RGB...");
-
- //Convert image to 24bit RGB
- ImageEncodingHelper.encodeRenderedImageAsRGB(renderedImage, baos);
- imageData = baos.toByteArray();
- imageObjectInfo.setBitsPerPixel(24);
-
- boolean colorImages = paintingState.isColorImages();
- imageObjectInfo.setColor(colorImages);
-
- // convert to grayscale
- if (!colorImages) {
- log.debug("Converting RGB image to grayscale...");
- baos.reset();
- int bitsPerPixel = paintingState.getBitsPerPixel();
- imageObjectInfo.setBitsPerPixel(bitsPerPixel);
- //TODO this should be done off the RenderedImage to avoid buffering the
- //intermediate 24bit image
- ImageEncodingHelper.encodeRGBAsGrayScale(
- imageData, dataWidth, dataHeight, bitsPerPixel, baos);
- imageData = baos.toByteArray();
- if (bitsPerPixel == 1) {
- imageObjectInfo.setSubtractive(true);
- }
- }
- }
-
- switch (functionSet) {
- case 10:
- imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS10);
- break;
- case 11:
- imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS11);
- break;
- case 45:
- imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS45);
- break;
- default:
- throw new IllegalStateException("Invalid IOCA function set: " + functionSet);
- }
-
- imageObjectInfo.setData(imageData);
-
- // set object area info
- AFPObjectAreaInfo objectAreaInfo = imageObjectInfo.getObjectAreaInfo();
- objectAreaInfo.setWidthRes(resolution);
- objectAreaInfo.setHeightRes(resolution);
-
- if (log.isDebugEnabled()) {
- long duration = System.currentTimeMillis() - start;
- log.debug("Image encoding took " + duration + "ms.");
- }
-
- return imageObjectInfo;
- }
-
- /**
- * Efficiently encodes a bi-level image in inverted form as a plain bit-stream.
- * @param helper the image encoding helper used to analyze the image
- * @param imageObjectInfo the AFP image object
- * @param out the output stream
- * @return true if the image was encoded, false if there was something prohibiting that
- * @throws IOException if an I/O error occurs
- */
- private boolean encodeInvertedBilevel(ImageEncodingHelper helper,
- AFPImageObjectInfo imageObjectInfo, OutputStream out) throws IOException {
- RenderedImage renderedImage = helper.getImage();
- if (!BitmapImageUtil.isMonochromeImage(renderedImage)) {
- throw new IllegalStateException("This method only supports binary images!");
- }
- int tiles = renderedImage.getNumXTiles() * renderedImage.getNumYTiles();
- if (tiles > 1) {
- return false;
- }
- SampleModel sampleModel = renderedImage.getSampleModel();
- SampleModel expectedSampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
- renderedImage.getWidth(), renderedImage.getHeight(), 1);
- if (!expectedSampleModel.equals(sampleModel)) {
- return false; //Pixels are not packed
- }
-
- imageObjectInfo.setBitsPerPixel(1);
-
- Raster raster = renderedImage.getTile(0, 0);
- DataBuffer buffer = raster.getDataBuffer();
- if (buffer instanceof DataBufferByte) {
- DataBufferByte byteBuffer = (DataBufferByte)buffer;
- log.debug("Encoding image as inverted bi-level...");
- byte[] rawData = byteBuffer.getData();
- int remaining = rawData.length;
- int pos = 0;
- byte[] data = new byte[4096];
- while (remaining > 0) {
- int size = Math.min(remaining, data.length);
- for (int i = 0; i < size; i++) {
- data[i] = (byte)~rawData[pos]; //invert bits
- pos++;
- }
- out.write(data, 0, size);
- remaining -= size;
- }
- return true;
- }
- return false;
- }
-
private void setDefaultResourceLevel(AFPImageObjectInfo imageObjectInfo,
AFPResourceManager resourceManager) {
AFPResourceInfo resourceInfo = imageObjectInfo.getResourceInfo();
AFPRenderingContext afpContext = (AFPRenderingContext)context;
AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)createDataObjectInfo();
+ AFPPaintingState paintingState = afpContext.getPaintingState();
// set resource information
setResourceInformation(imageObjectInfo,
setDefaultResourceLevel(imageObjectInfo, afpContext.getResourceManager());
// Positioning
- imageObjectInfo.setObjectAreaInfo(createObjectAreaInfo(afpContext.getPaintingState(), pos));
+ imageObjectInfo.setObjectAreaInfo(createObjectAreaInfo(paintingState, pos));
Dimension targetSize = pos.getSize();
+
// Image content
ImageRendered imageRend = (ImageRendered)image;
- updateDataObjectInfo(imageObjectInfo, afpContext.getPaintingState(), imageRend, targetSize);
+ RenderedImageEncoder encoder = new RenderedImageEncoder(imageRend, targetSize);
+ encoder.prepareEncoding(imageObjectInfo, paintingState);
+
+ boolean included = afpContext.getResourceManager().tryIncludeObject(imageObjectInfo);
+ if (!included) {
+ long start = System.currentTimeMillis();
+ //encode only if the same image has not been encoded, yet
+ encoder.encodeImage(imageObjectInfo, paintingState);
+ if (log.isDebugEnabled()) {
+ long duration = System.currentTimeMillis() - start;
+ log.debug("Image encoding took " + duration + "ms.");
+ }
- // Create image
- afpContext.getResourceManager().createObject(imageObjectInfo);
+ // Create image
+ afpContext.getResourceManager().createObject(imageObjectInfo);
+ }
}
/** {@inheritDoc} */
&& targetContext instanceof AFPRenderingContext;
}
+ private static final class RenderedImageEncoder {
+
+ private ImageRendered imageRendered;
+ private Dimension targetSize;
+
+ private boolean useFS10;
+ private int maxPixelSize;
+ private boolean usePageSegments;
+ private Dimension resampledDim;
+ private ImageSize intrinsicSize;
+ private ImageSize effIntrinsicSize;
+
+ private RenderedImageEncoder(ImageRendered imageRendered, Dimension targetSize) {
+ this.imageRendered = imageRendered;
+ this.targetSize = targetSize;
+
+ }
+
+ private void prepareEncoding(AFPImageObjectInfo imageObjectInfo,
+ AFPPaintingState paintingState) {
+ maxPixelSize = paintingState.getBitsPerPixel();
+ if (paintingState.isColorImages()) {
+ if (paintingState.isCMYKImagesSupported()) {
+ maxPixelSize *= 4; //CMYK is maximum
+ } else {
+ maxPixelSize *= 3; //RGB is maximum
+ }
+ }
+ RenderedImage renderedImage = imageRendered.getRenderedImage();
+ useFS10 = (maxPixelSize == 1) || BitmapImageUtil.isMonochromeImage(renderedImage);
+
+ ImageInfo imageInfo = imageRendered.getInfo();
+ this.intrinsicSize = imageInfo.getSize();
+ this.effIntrinsicSize = intrinsicSize;
+
+ AFPResourceInfo resourceInfo = imageObjectInfo.getResourceInfo();
+ this.usePageSegments = useFS10 && !resourceInfo.getLevel().isInline();
+ if (usePageSegments) {
+ //The image may need to be resized/resampled for use as a page segment
+ int resolution = paintingState.getResolution();
+ this.resampledDim = new Dimension(
+ (int)Math.ceil(UnitConv.mpt2px(targetSize.getWidth(), resolution)),
+ (int)Math.ceil(UnitConv.mpt2px(targetSize.getHeight(), resolution)));
+ resourceInfo.setImageDimension(resampledDim);
+ effIntrinsicSize = new ImageSize(
+ resampledDim.width, resampledDim.height, resolution);
+ }
+
+ //Update image object info
+ imageObjectInfo.setDataHeightRes((int)Math.round(
+ effIntrinsicSize.getDpiHorizontal() * 10));
+ imageObjectInfo.setDataWidthRes((int)Math.round(
+ effIntrinsicSize.getDpiVertical() * 10));
+ imageObjectInfo.setDataHeight(effIntrinsicSize.getHeightPx());
+ imageObjectInfo.setDataWidth(effIntrinsicSize.getWidthPx());
+
+ // set object area info
+ int resolution = paintingState.getResolution();
+ AFPObjectAreaInfo objectAreaInfo = imageObjectInfo.getObjectAreaInfo();
+ objectAreaInfo.setWidthRes(resolution);
+ objectAreaInfo.setHeightRes(resolution);
+ }
+
+ private AFPDataObjectInfo encodeImage
+ (AFPImageObjectInfo imageObjectInfo,
+ AFPPaintingState paintingState)
+ throws IOException {
+
+ RenderedImage renderedImage = imageRendered.getRenderedImage();
+ int functionSet = useFS10 ? 10 : 11;
+
+ if (usePageSegments) {
+ assert resampledDim != null;
+ //Resize, optionally resample and convert image
+
+ imageObjectInfo.setCreatePageSegment(true);
+
+ float ditheringQuality = paintingState.getDitheringQuality();
+ //Only resample/downsample if image is smaller than its intrinsic size
+ //to make print file smaller
+ boolean resample = resampledDim.width < renderedImage.getWidth()
+ && resampledDim.height < renderedImage.getHeight();
+ if (resample) {
+ if (log.isDebugEnabled()) {
+ log.debug("Resample from " + intrinsicSize.getDimensionPx()
+ + " to " + resampledDim);
+ }
+ renderedImage = BitmapImageUtil.convertToMonochrome(renderedImage,
+ resampledDim, ditheringQuality);
+ } else if (ditheringQuality >= 0.5f) {
+ renderedImage = BitmapImageUtil.convertToMonochrome(renderedImage,
+ intrinsicSize.getDimensionPx(), ditheringQuality);
+ }
+ }
+
+ //TODO To reduce AFP file size, investigate using a compression scheme.
+ //Currently, all image data is uncompressed.
+ ColorModel cm = renderedImage.getColorModel();
+ if (log.isTraceEnabled()) {
+ log.trace("ColorModel: " + cm);
+ }
+ int pixelSize = cm.getPixelSize();
+ if (cm.hasAlpha()) {
+ pixelSize -= 8;
+ }
+
+ byte[] imageData = null;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ boolean allowDirectEncoding = true;
+ if (allowDirectEncoding && (pixelSize <= maxPixelSize)) {
+ //Attempt to encode without resampling the image
+ ImageEncodingHelper helper = new ImageEncodingHelper(renderedImage,
+ pixelSize == 32);
+ ColorModel encodedColorModel = helper.getEncodedColorModel();
+ boolean directEncode = true;
+ if (helper.getEncodedColorModel().getPixelSize() > maxPixelSize) {
+ directEncode = false; //pixel size needs to be reduced
+ }
+ if (BitmapImageUtil.getColorIndexSize(renderedImage) > 2) {
+ directEncode = false; //Lookup tables are not implemented, yet
+ }
+ if (useFS10
+ && BitmapImageUtil.isMonochromeImage(renderedImage)
+ && BitmapImageUtil.isZeroBlack(renderedImage)) {
+ directEncode = false;
+ //need a special method to invert the bit-stream since setting the
+ //subtractive mode in AFP alone doesn't seem to do the trick.
+ if (encodeInvertedBilevel(helper, imageObjectInfo, baos)) {
+ imageData = baos.toByteArray();
+ }
+ }
+ if (directEncode) {
+ log.debug("Encoding image directly...");
+ imageObjectInfo.setBitsPerPixel(encodedColorModel.getPixelSize());
+ if (pixelSize == 32) {
+ functionSet = 45; //IOCA FS45 required for CMYK
+ }
+
+ helper.encode(baos);
+ imageData = baos.toByteArray();
+ }
+ }
+ if (imageData == null) {
+ log.debug("Encoding image via RGB...");
+ imageData = encodeViaRGB(renderedImage, imageObjectInfo, paintingState, baos);
+ }
+
+ switch (functionSet) {
+ case 10:
+ imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS10);
+ break;
+ case 11:
+ imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS11);
+ break;
+ case 45:
+ imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS45);
+ break;
+ default:
+ throw new IllegalStateException("Invalid IOCA function set: " + functionSet);
+ }
+
+ imageObjectInfo.setData(imageData);
+
+ return imageObjectInfo;
+ }
+
+ private byte[] encodeViaRGB(RenderedImage renderedImage,
+ AFPImageObjectInfo imageObjectInfo, AFPPaintingState paintingState,
+ ByteArrayOutputStream baos) throws IOException {
+ byte[] imageData;
+ //Convert image to 24bit RGB
+ ImageEncodingHelper.encodeRenderedImageAsRGB(renderedImage, baos);
+ imageData = baos.toByteArray();
+ imageObjectInfo.setBitsPerPixel(24);
+
+ boolean colorImages = paintingState.isColorImages();
+ imageObjectInfo.setColor(colorImages);
+
+ // convert to grayscale
+ if (!colorImages) {
+ log.debug("Converting RGB image to grayscale...");
+ baos.reset();
+ int bitsPerPixel = paintingState.getBitsPerPixel();
+ imageObjectInfo.setBitsPerPixel(bitsPerPixel);
+ //TODO this should be done off the RenderedImage to avoid buffering the
+ //intermediate 24bit image
+ ImageEncodingHelper.encodeRGBAsGrayScale(
+ imageData, renderedImage.getWidth(), renderedImage.getHeight(),
+ bitsPerPixel, baos);
+ imageData = baos.toByteArray();
+ if (bitsPerPixel == 1) {
+ imageObjectInfo.setSubtractive(true);
+ }
+ }
+ return imageData;
+ }
+
+ /**
+ * Efficiently encodes a bi-level image in inverted form as a plain bit-stream.
+ * @param helper the image encoding helper used to analyze the image
+ * @param imageObjectInfo the AFP image object
+ * @param out the output stream
+ * @return true if the image was encoded, false if there was something prohibiting that
+ * @throws IOException if an I/O error occurs
+ */
+ private boolean encodeInvertedBilevel(ImageEncodingHelper helper,
+ AFPImageObjectInfo imageObjectInfo, OutputStream out) throws IOException {
+ RenderedImage renderedImage = helper.getImage();
+ if (!BitmapImageUtil.isMonochromeImage(renderedImage)) {
+ throw new IllegalStateException("This method only supports binary images!");
+ }
+ int tiles = renderedImage.getNumXTiles() * renderedImage.getNumYTiles();
+ if (tiles > 1) {
+ return false;
+ }
+ SampleModel sampleModel = renderedImage.getSampleModel();
+ SampleModel expectedSampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
+ renderedImage.getWidth(), renderedImage.getHeight(), 1);
+ if (!expectedSampleModel.equals(sampleModel)) {
+ return false; //Pixels are not packed
+ }
+
+ imageObjectInfo.setBitsPerPixel(1);
+
+ Raster raster = renderedImage.getTile(0, 0);
+ DataBuffer buffer = raster.getDataBuffer();
+ if (buffer instanceof DataBufferByte) {
+ DataBufferByte byteBuffer = (DataBufferByte)buffer;
+ log.debug("Encoding image as inverted bi-level...");
+ byte[] rawData = byteBuffer.getData();
+ int remaining = rawData.length;
+ int pos = 0;
+ byte[] data = new byte[4096];
+ while (remaining > 0) {
+ int size = Math.min(remaining, data.length);
+ for (int i = 0; i < size; i++) {
+ data[i] = (byte)~rawData[pos]; //invert bits
+ pos++;
+ }
+ out.write(data, 0, size);
+ remaining -= size;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ }
+
}