001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.tiff.write;
018
019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.DEFAULT_TIFF_BYTE_ORDER;
020import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D;
021import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3;
022import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4;
023import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW;
024import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS;
025import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED;
026import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_ADOBE;
027import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE;
028import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_HEADER_SIZE;
029
030import java.awt.image.BufferedImage;
031import java.awt.image.ColorModel;
032import java.io.IOException;
033import java.io.OutputStream;
034import java.nio.ByteOrder;
035import java.nio.charset.StandardCharsets;
036import java.util.ArrayList;
037import java.util.Collections;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.List;
041import java.util.Map;
042
043import org.apache.commons.imaging.ImageWriteException;
044import org.apache.commons.imaging.PixelDensity;
045import org.apache.commons.imaging.common.BinaryOutputStream;
046import org.apache.commons.imaging.common.PackBits;
047import org.apache.commons.imaging.common.RationalNumber;
048import org.apache.commons.imaging.common.itu_t4.T4AndT6Compression;
049import org.apache.commons.imaging.common.mylzw.MyLzwCompressor;
050import org.apache.commons.imaging.common.ZlibDeflate;
051import org.apache.commons.imaging.formats.tiff.TiffElement;
052import org.apache.commons.imaging.formats.tiff.TiffImageData;
053import org.apache.commons.imaging.formats.tiff.TiffImagingParameters;
054import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
055import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants;
056import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
057
058public abstract class TiffImageWriterBase {
059
060    protected final ByteOrder byteOrder;
061
062    public TiffImageWriterBase() {
063        this.byteOrder = DEFAULT_TIFF_BYTE_ORDER;
064    }
065
066    public TiffImageWriterBase(final ByteOrder byteOrder) {
067        this.byteOrder = byteOrder;
068    }
069
070    protected static int imageDataPaddingLength(final int dataLength) {
071        return (4 - (dataLength % 4)) % 4;
072    }
073
074    public abstract void write(OutputStream os, TiffOutputSet outputSet)
075            throws IOException, ImageWriteException;
076
077    protected TiffOutputSummary validateDirectories(final TiffOutputSet outputSet)
078            throws ImageWriteException {
079        final List<TiffOutputDirectory> directories = outputSet.getDirectories();
080
081        if (directories.isEmpty()) {
082            throw new ImageWriteException("No directories.");
083        }
084
085        TiffOutputDirectory exifDirectory = null;
086        TiffOutputDirectory gpsDirectory = null;
087        TiffOutputDirectory interoperabilityDirectory = null;
088        TiffOutputField exifDirectoryOffsetField = null;
089        TiffOutputField gpsDirectoryOffsetField = null;
090        TiffOutputField interoperabilityDirectoryOffsetField = null;
091
092        final List<Integer> directoryIndices = new ArrayList<>();
093        final Map<Integer, TiffOutputDirectory> directoryTypeMap = new HashMap<>();
094        for (final TiffOutputDirectory directory : directories) {
095            final int dirType = directory.type;
096            directoryTypeMap.put(dirType, directory);
097            // Debug.debug("validating dirType", dirType + " ("
098            // + directory.getFields().size() + " fields)");
099
100            if (dirType < 0) {
101                switch (dirType) {
102                    case TiffDirectoryConstants.DIRECTORY_TYPE_EXIF:
103                        if (exifDirectory != null) {
104                            throw new ImageWriteException(
105                                    "More than one EXIF directory.");
106                        }
107                        exifDirectory = directory;
108                        break;
109
110                    case TiffDirectoryConstants.DIRECTORY_TYPE_GPS:
111                        if (gpsDirectory != null) {
112                            throw new ImageWriteException(
113                                    "More than one GPS directory.");
114                        }
115                        gpsDirectory = directory;
116                        break;
117
118                    case TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY:
119                        if (interoperabilityDirectory != null) {
120                            throw new ImageWriteException(
121                                    "More than one Interoperability directory.");
122                        }
123                        interoperabilityDirectory = directory;
124                        break;
125                    default:
126                        throw new ImageWriteException("Unknown directory: "
127                                + dirType);
128                }
129            } else {
130                if (directoryIndices.contains(dirType)) {
131                    throw new ImageWriteException(
132                            "More than one directory with index: " + dirType
133                                    + ".");
134                }
135                directoryIndices.add(dirType);
136                // dirMap.put(arg0, arg1)
137            }
138
139            final HashSet<Integer> fieldTags = new HashSet<>();
140            final List<TiffOutputField> fields = directory.getFields();
141            for (final TiffOutputField field : fields) {
142                if (fieldTags.contains(field.tag)) {
143                    throw new ImageWriteException("Tag ("
144                            + field.tagInfo.getDescription()
145                            + ") appears twice in directory.");
146                }
147                fieldTags.add(field.tag);
148
149                if (field.tag == ExifTagConstants.EXIF_TAG_EXIF_OFFSET.tag) {
150                    if (exifDirectoryOffsetField != null) {
151                        throw new ImageWriteException(
152                                "More than one Exif directory offset field.");
153                    }
154                    exifDirectoryOffsetField = field;
155                } else if (field.tag == ExifTagConstants.EXIF_TAG_INTEROP_OFFSET.tag) {
156                    if (interoperabilityDirectoryOffsetField != null) {
157                        throw new ImageWriteException(
158                                "More than one Interoperability directory offset field.");
159                    }
160                    interoperabilityDirectoryOffsetField = field;
161                } else if (field.tag == ExifTagConstants.EXIF_TAG_GPSINFO.tag) {
162                    if (gpsDirectoryOffsetField != null) {
163                        throw new ImageWriteException(
164                                "More than one GPS directory offset field.");
165                    }
166                    gpsDirectoryOffsetField = field;
167                }
168            }
169            // directory.
170        }
171
172        if (directoryIndices.isEmpty()) {
173            throw new ImageWriteException("Missing root directory.");
174        }
175
176        // "normal" TIFF directories should have continous indices starting with
177        // 0, ie. 0, 1, 2...
178        directoryIndices.sort(null);
179
180        TiffOutputDirectory previousDirectory = null;
181        for (int i = 0; i < directoryIndices.size(); i++) {
182            final Integer index = directoryIndices.get(i);
183            if (index != i) {
184                throw new ImageWriteException("Missing directory: " + i + ".");
185            }
186
187            // set up chain of directory references for "normal" directories.
188            final TiffOutputDirectory directory = directoryTypeMap.get(index);
189            if (null != previousDirectory) {
190                previousDirectory.setNextDirectory(directory);
191            }
192            previousDirectory = directory;
193        }
194
195        final TiffOutputDirectory rootDirectory = directoryTypeMap.get(
196                TiffDirectoryConstants.DIRECTORY_TYPE_ROOT);
197
198        // prepare results
199        final TiffOutputSummary result = new TiffOutputSummary(byteOrder,
200                rootDirectory, directoryTypeMap);
201
202        if (interoperabilityDirectory == null
203                && interoperabilityDirectoryOffsetField != null) {
204            // perhaps we should just discard field?
205            throw new ImageWriteException(
206                    "Output set has Interoperability Directory Offset field, but no Interoperability Directory");
207        }
208        if (interoperabilityDirectory != null) {
209            if (exifDirectory == null) {
210                exifDirectory = outputSet.addExifDirectory();
211            }
212
213            if (interoperabilityDirectoryOffsetField == null) {
214                interoperabilityDirectoryOffsetField =
215                        TiffOutputField.createOffsetField(
216                                ExifTagConstants.EXIF_TAG_INTEROP_OFFSET,
217                                byteOrder);
218                exifDirectory.add(interoperabilityDirectoryOffsetField);
219            }
220
221            result.add(interoperabilityDirectory,
222                    interoperabilityDirectoryOffsetField);
223        }
224
225        // make sure offset fields and offset'd directories correspond.
226        if (exifDirectory == null && exifDirectoryOffsetField != null) {
227            // perhaps we should just discard field?
228            throw new ImageWriteException(
229                    "Output set has Exif Directory Offset field, but no Exif Directory");
230        }
231        if (exifDirectory != null) {
232            if (exifDirectoryOffsetField == null) {
233                exifDirectoryOffsetField = TiffOutputField.createOffsetField(
234                        ExifTagConstants.EXIF_TAG_EXIF_OFFSET, byteOrder);
235                rootDirectory.add(exifDirectoryOffsetField);
236            }
237
238            result.add(exifDirectory, exifDirectoryOffsetField);
239        }
240
241        if (gpsDirectory == null && gpsDirectoryOffsetField != null) {
242            // perhaps we should just discard field?
243            throw new ImageWriteException(
244                    "Output set has GPS Directory Offset field, but no GPS Directory");
245        }
246        if (gpsDirectory != null) {
247            if (gpsDirectoryOffsetField == null) {
248                gpsDirectoryOffsetField = TiffOutputField.createOffsetField(
249                        ExifTagConstants.EXIF_TAG_GPSINFO, byteOrder);
250                rootDirectory.add(gpsDirectoryOffsetField);
251            }
252
253            result.add(gpsDirectory, gpsDirectoryOffsetField);
254        }
255
256        return result;
257
258        // Debug.debug();
259    }
260
261    private static final int MAX_PIXELS_FOR_RGB = 1024*1024;
262    /**
263     * Check an image to see if any of its pixels are non-opaque.
264     * @param src a valid image
265     * @return true if at least one non-opaque pixel is found.
266     */
267    private boolean checkForActualAlpha(final BufferedImage src){
268        // to conserve memory, very large images may be read
269        // in pieces.
270        final int width = src.getWidth();
271        final int height = src.getHeight();
272        int nRowsPerRead = MAX_PIXELS_FOR_RGB/width;
273        if(nRowsPerRead<1){
274            nRowsPerRead = 1;
275        }
276        final int nReads = (height+nRowsPerRead-1)/nRowsPerRead;
277        final int []argb = new int[nRowsPerRead*width];
278        for(int iRead=0; iRead<nReads; iRead++){
279            final int i0 = iRead*nRowsPerRead;
280            final int i1 = i0+nRowsPerRead>height? height: i0+nRowsPerRead;
281            src.getRGB(0, i0, width, i1-i0, argb, 0, width);
282            final int n = (i1-i0)*width;
283            for(int i=0; i<n; i++){
284                if((argb[i]&0xff000000)!=0xff000000){
285                    return true;
286                }
287            }
288        }
289        return false;
290    }
291
292    private void applyPredictor(final int width, final int bytesPerSample, final byte[] b) {
293        final int nBytesPerRow = bytesPerSample * width;
294        final int nRows = b.length / nBytesPerRow;
295        for (int iRow = 0; iRow < nRows; iRow++) {
296            final int offset = iRow * nBytesPerRow;
297            for (int i = nBytesPerRow-1; i >= bytesPerSample; i--) {
298                b[offset + i] -= b[offset + i - bytesPerSample];
299            }
300        }
301    }
302
303    public void writeImage(final BufferedImage src, final OutputStream os, TiffImagingParameters params)
304            throws ImageWriteException, IOException {
305        TiffOutputSet userExif = params.getOutputSet();
306
307        String xmpXml = params.getXmpXml();
308
309        PixelDensity pixelDensity = params.getPixelDensity();
310        if (pixelDensity == null) {
311            pixelDensity = PixelDensity.createFromPixelsPerInch(72, 72);
312        }
313
314        final int width = src.getWidth();
315        final int height = src.getHeight();
316
317        // If the source image has a color model that supports alpha,
318        // this module performs a call to checkForActualAlpha() to see whether
319        // the image that was supplied to the API actually contains
320        // non-opaque data in its alpha channel. It is common for applications
321        // to create a BufferedImage using TYPE_INT_ARGB, and fill the entire
322        // image with opaque pixels. In such a case, the file size of the output
323        // can be reduced by 25 percent by storing the image in an 3-byte RGB
324        // format. This approach will also make a small reduction in the runtime
325        // to read the resulting file when it is accessed by an application.
326        final ColorModel cModel = src.getColorModel();
327        final boolean hasAlpha = cModel.hasAlpha() && checkForActualAlpha(src);
328
329
330        // 10/2020: In the case of an image with pre-multiplied alpha
331        // (what the TIFF specification calls "associated alpha"), the
332        // Java getRGB method adjusts the value to a non-premultiplied
333        // alpha state.  However, this class could access the pre-multiplied
334        // alpha data by obtaining the underlying raster.  At this time,
335        // the value of such a little-used feature does not seem
336        // commensurate with the complexity of the extra code it would require.
337
338        int compression = TIFF_COMPRESSION_LZW;
339        short predictor = TiffTagConstants.PREDICTOR_VALUE_NONE;
340
341        int stripSizeInBits = 64000; // the default from legacy implementation
342        Integer compressionParameter = params.getCompression();
343        if (compressionParameter != null) {
344            compression = compressionParameter;
345            final Integer stripSizeInBytes = params.getLzwCompressionBlockSize();
346            if (stripSizeInBytes != null) {
347                if (stripSizeInBytes < 8000) {
348                    throw new ImageWriteException(
349                            "Block size parameter " + stripSizeInBytes
350                            + " is less than 8000 minimum");
351                }
352                stripSizeInBits = stripSizeInBytes * 8;
353            }
354        }
355
356        int samplesPerPixel;
357        int bitsPerSample;
358        int photometricInterpretation;
359        if (compression == TIFF_COMPRESSION_CCITT_1D
360                || compression == TIFF_COMPRESSION_CCITT_GROUP_3
361                || compression == TIFF_COMPRESSION_CCITT_GROUP_4) {
362            samplesPerPixel = 1;
363            bitsPerSample = 1;
364            photometricInterpretation = 0;
365        } else {
366            samplesPerPixel = hasAlpha? 4: 3;
367            bitsPerSample = 8;
368            photometricInterpretation = 2;
369        }
370
371        int rowsPerStrip = stripSizeInBits / (width * bitsPerSample * samplesPerPixel);
372        rowsPerStrip = Math.max(1, rowsPerStrip); // must have at least one.
373
374        final byte[][] strips = getStrips(src, samplesPerPixel, bitsPerSample, rowsPerStrip);
375
376        // System.out.println("width: " + width);
377        // System.out.println("height: " + height);
378        // System.out.println("fRowsPerStrip: " + fRowsPerStrip);
379        // System.out.println("fSamplesPerPixel: " + fSamplesPerPixel);
380        // System.out.println("stripCount: " + stripCount);
381
382        int t4Options = 0;
383        int t6Options = 0;
384        if (compression == TIFF_COMPRESSION_CCITT_1D) {
385            for (int i = 0; i < strips.length; i++) {
386                strips[i] = T4AndT6Compression.compressModifiedHuffman(
387                        strips[i], width, strips[i].length / ((width + 7) / 8));
388            }
389        } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_3) {
390            final Integer t4Parameter = params.getT4Options();
391            if (t4Parameter != null) {
392                t4Options = t4Parameter.intValue();
393            }
394            t4Options &= 0x7;
395            final boolean is2D = (t4Options & 1) != 0;
396            final boolean usesUncompressedMode = (t4Options & 2) != 0;
397            if (usesUncompressedMode) {
398                throw new ImageWriteException(
399                        "T.4 compression with the uncompressed mode extension is not yet supported");
400            }
401            final boolean hasFillBitsBeforeEOL = (t4Options & 4) != 0;
402            for (int i = 0; i < strips.length; i++) {
403                if (is2D) {
404                    strips[i] = T4AndT6Compression.compressT4_2D(strips[i],
405                            width, strips[i].length / ((width + 7) / 8),
406                            hasFillBitsBeforeEOL, rowsPerStrip);
407                } else {
408                    strips[i] = T4AndT6Compression.compressT4_1D(strips[i],
409                            width, strips[i].length / ((width + 7) / 8),
410                            hasFillBitsBeforeEOL);
411                }
412            }
413        } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_4) {
414            final Integer t6Parameter = params.getT6Options();
415            if (t6Parameter != null) {
416                t6Options = t6Parameter.intValue();
417            }
418            t6Options &= 0x4;
419            final boolean usesUncompressedMode = (t6Options & TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE) != 0;
420            if (usesUncompressedMode) {
421                throw new ImageWriteException(
422                        "T.6 compression with the uncompressed mode extension is not yet supported");
423            }
424            for (int i = 0; i < strips.length; i++) {
425                strips[i] = T4AndT6Compression.compressT6(strips[i], width,
426                        strips[i].length / ((width + 7) / 8));
427            }
428        } else if (compression == TIFF_COMPRESSION_PACKBITS) {
429            for (int i = 0; i < strips.length; i++) {
430                strips[i] = new PackBits().compress(strips[i]);
431            }
432        } else if (compression == TIFF_COMPRESSION_LZW) {
433            predictor =  TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING;
434            for (int i = 0; i < strips.length; i++) {
435                final byte[] uncompressed = strips[i];
436                this.applyPredictor(width, samplesPerPixel, strips[i]);
437
438                final int LZW_MINIMUM_CODE_SIZE = 8;
439                final MyLzwCompressor compressor = new MyLzwCompressor(
440                        LZW_MINIMUM_CODE_SIZE, ByteOrder.BIG_ENDIAN, true);
441                final byte[] compressed = compressor.compress(uncompressed);
442                strips[i] = compressed;
443            }
444        } else if (compression == TIFF_COMPRESSION_DEFLATE_ADOBE) {
445            predictor = TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING;
446            for (int i = 0; i < strips.length; i++) {
447                this.applyPredictor(width, samplesPerPixel, strips[i]);
448                strips[i] = ZlibDeflate.compress(strips[i]);
449            }
450        } else if (compression == TIFF_COMPRESSION_UNCOMPRESSED) {
451            // do nothing.
452        } else {
453            throw new ImageWriteException(
454                    "Invalid compression parameter (Only CCITT 1D/Group 3/Group 4, LZW, Packbits, Zlib Deflate and uncompressed supported).");
455        }
456
457        final TiffElement.DataElement[] imageData = new TiffElement.DataElement[strips.length];
458        for (int i = 0; i < strips.length; i++) {
459            imageData[i] = new TiffImageData.Data(0, strips[i].length, strips[i]);
460        }
461
462        final TiffOutputSet outputSet = new TiffOutputSet(byteOrder);
463        final TiffOutputDirectory directory = outputSet.addRootDirectory();
464
465        // WriteField stripOffsetsField;
466
467        {
468
469            directory.add(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, width);
470            directory.add(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, height);
471            directory.add(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION,
472                    (short) photometricInterpretation);
473            directory.add(TiffTagConstants.TIFF_TAG_COMPRESSION,
474                    (short) compression);
475            directory.add(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL,
476                    (short) samplesPerPixel);
477
478            switch (samplesPerPixel) {
479            case 3:
480                directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE,
481                        (short) bitsPerSample, (short) bitsPerSample,
482                        (short) bitsPerSample);
483                break;
484            case 4:
485                directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE,
486                        (short) bitsPerSample, (short) bitsPerSample,
487                        (short) bitsPerSample, (short) bitsPerSample);
488                directory.add(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES,
489                    (short)TiffTagConstants.EXTRA_SAMPLE_UNASSOCIATED_ALPHA);
490                break;
491            case 1:
492                directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE,
493                        (short) bitsPerSample);
494                break;
495            default:
496                break;
497            }
498            // {
499            // stripOffsetsField = new WriteField(TIFF_TAG_STRIP_OFFSETS,
500            // FIELD_TYPE_LONG, stripOffsets.length, FIELD_TYPE_LONG
501            // .writeData(stripOffsets, byteOrder));
502            // directory.add(stripOffsetsField);
503            // }
504            // {
505            // WriteField field = new WriteField(TIFF_TAG_STRIP_BYTE_COUNTS,
506            // FIELD_TYPE_LONG, stripByteCounts.length,
507            // FIELD_TYPE_LONG.writeData(stripByteCounts,
508            // WRITE_BYTE_ORDER));
509            // directory.add(field);
510            // }
511            directory.add(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP,
512                    rowsPerStrip);
513            if (pixelDensity.isUnitless()) {
514                directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT,
515                        (short) 0);
516                directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION,
517                        RationalNumber.valueOf(pixelDensity.getRawHorizontalDensity()));
518                directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION,
519                        RationalNumber.valueOf(pixelDensity.getRawVerticalDensity()));
520            } else if (pixelDensity.isInInches()) {
521                directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT,
522                        (short) 2);
523                directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION,
524                        RationalNumber.valueOf(pixelDensity.horizontalDensityInches()));
525                directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION,
526                        RationalNumber.valueOf(pixelDensity.verticalDensityInches()));
527            } else {
528                directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT,
529                        (short) 1);
530                directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION,
531                        RationalNumber.valueOf(pixelDensity.horizontalDensityCentimetres()));
532                directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION,
533                        RationalNumber.valueOf(pixelDensity.verticalDensityCentimetres()));
534            }
535            if (t4Options != 0) {
536                directory.add(TiffTagConstants.TIFF_TAG_T4_OPTIONS, t4Options);
537            }
538            if (t6Options != 0) {
539                directory.add(TiffTagConstants.TIFF_TAG_T6_OPTIONS, t6Options);
540            }
541
542            if (null != xmpXml) {
543                final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8);
544                directory.add(TiffTagConstants.TIFF_TAG_XMP, xmpXmlBytes);
545            }
546
547            if(predictor==TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING){
548                directory.add(TiffTagConstants.TIFF_TAG_PREDICTOR, predictor);
549            }
550
551        }
552
553        final TiffImageData tiffImageData = new TiffImageData.Strips(imageData,
554                rowsPerStrip);
555        directory.setTiffImageData(tiffImageData);
556
557        if (userExif != null) {
558            combineUserExifIntoFinalExif(userExif, outputSet);
559        }
560
561        write(os, outputSet);
562    }
563
564    private void combineUserExifIntoFinalExif(final TiffOutputSet userExif,
565            final TiffOutputSet outputSet) throws ImageWriteException {
566        final List<TiffOutputDirectory> outputDirectories = outputSet.getDirectories();
567        outputDirectories.sort(TiffOutputDirectory.COMPARATOR);
568        for (final TiffOutputDirectory userDirectory : userExif.getDirectories()) {
569            final int location = Collections.binarySearch(outputDirectories,
570                    userDirectory, TiffOutputDirectory.COMPARATOR);
571            if (location < 0) {
572                outputSet.addDirectory(userDirectory);
573            } else {
574                final TiffOutputDirectory outputDirectory = outputDirectories.get(location);
575                for (final TiffOutputField userField : userDirectory.getFields()) {
576                    if (outputDirectory.findField(userField.tagInfo) == null) {
577                        outputDirectory.add(userField);
578                    }
579                }
580            }
581        }
582    }
583
584    private byte[][] getStrips(final BufferedImage src, final int samplesPerPixel,
585            final int bitsPerSample, final int rowsPerStrip) {
586        final int width = src.getWidth();
587        final int height = src.getHeight();
588
589        final int stripCount = (height + rowsPerStrip - 1) / rowsPerStrip;
590
591        byte[][] result;
592        { // Write Strips
593            result = new byte[stripCount][];
594
595            int remainingRows = height;
596
597            for (int i = 0; i < stripCount; i++) {
598                final int rowsInStrip = Math.min(rowsPerStrip, remainingRows);
599                remainingRows -= rowsInStrip;
600
601                final int bitsInRow = bitsPerSample * samplesPerPixel * width;
602                final int bytesPerRow = (bitsInRow + 7) / 8;
603                final int bytesInStrip = rowsInStrip * bytesPerRow;
604
605                final byte[] uncompressed = new byte[bytesInStrip];
606
607                int counter = 0;
608                int y = i * rowsPerStrip;
609                final int stop = i * rowsPerStrip + rowsPerStrip;
610
611                for (; (y < height) && (y < stop); y++) {
612                    int bitCache = 0;
613                    int bitsInCache = 0;
614                    for (int x = 0; x < width; x++) {
615                        final int rgb = src.getRGB(x, y);
616                        final int red = 0xff & (rgb >> 16);
617                        final int green = 0xff & (rgb >> 8);
618                        final int blue = 0xff & (rgb >> 0);
619
620                        if (bitsPerSample == 1) {
621                            int sample = (red + green + blue) / 3;
622                            if (sample > 127) {
623                                sample = 0;
624                            } else {
625                                sample = 1;
626                            }
627                            bitCache <<= 1;
628                            bitCache |= sample;
629                            bitsInCache++;
630                            if (bitsInCache == 8) {
631                                uncompressed[counter++] = (byte) bitCache;
632                                bitCache = 0;
633                                bitsInCache = 0;
634                            }
635                        } else if(samplesPerPixel==4){
636                            uncompressed[counter++] = (byte) red;
637                            uncompressed[counter++] = (byte) green;
638                            uncompressed[counter++] = (byte) blue;
639                            uncompressed[counter++] = (byte) (rgb>>24);
640                        }else {
641                            // samples per pixel is 3
642                            uncompressed[counter++] = (byte) red;
643                            uncompressed[counter++] = (byte) green;
644                            uncompressed[counter++] = (byte) blue;
645                        }
646                    }
647                    if (bitsInCache > 0) {
648                        bitCache <<= (8 - bitsInCache);
649                        uncompressed[counter++] = (byte) bitCache;
650                    }
651                }
652
653                result[i] = uncompressed;
654            }
655
656        }
657
658        return result;
659    }
660
661    protected void writeImageFileHeader(final BinaryOutputStream bos)
662            throws IOException {
663        writeImageFileHeader(bos, TIFF_HEADER_SIZE);
664    }
665
666    protected void writeImageFileHeader(final BinaryOutputStream bos,
667            final long offsetToFirstIFD) throws IOException {
668        if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
669            bos.write('I');
670            bos.write('I');
671        } else {
672            bos.write('M');
673            bos.write('M');
674        }
675
676        bos.write2Bytes(42); // tiffVersion
677
678        bos.write4Bytes((int) offsetToFirstIFD);
679    }
680
681}