< prev index next >

src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java

Print this page


   1 /*
   2  * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any


 241      * and tile height.
 242      *
 243      * <p> If {@code tileHeight < 0}, the results of this method
 244      * are undefined.  If {@code tileHeight == 0}, an
 245      * {@code ArithmeticException} will be thrown.
 246      *
 247      * @throws ArithmeticException  If {@code tileHeight == 0}.
 248      */
 249     public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
 250         y -= tileGridYOffset;
 251         if (y < 0) {
 252             y += 1 - tileHeight;         // force round to -infinity (ceiling)
 253         }
 254         return y/tileHeight;
 255     }
 256 
 257     public TIFFImageWriter(ImageWriterSpi originatingProvider) {
 258         super(originatingProvider);
 259     }
 260 

 261     public ImageWriteParam getDefaultWriteParam() {
 262         return new TIFFImageWriteParam(getLocale());
 263     }
 264 

 265     public void setOutput(Object output) {
 266         if (output != null) {
 267             if (!(output instanceof ImageOutputStream)) {
 268                 throw new IllegalArgumentException
 269                     ("output not an ImageOutputStream!");
 270             }
 271 
 272             // reset() must precede setOutput() as it sets output to null
 273             reset();
 274 
 275             this.stream = (ImageOutputStream)output;
 276 
 277             //
 278             // The output is expected to be positioned at a TIFF header
 279             // or at some arbitrary location which may or may not be
 280             // the EOF. In the former case the writer should be able
 281             // either to overwrite the existing sequence or append to it.
 282             //
 283 
 284             // Set the position of the header and the next available space.


 299                     } else {
 300                         // Neither TIFF header nor EOF: overwrite.
 301                         this.nextSpace = headerPosition;
 302                     }
 303                 } catch(IOException io) { // thrown by readFully()
 304                     // At EOF or not at a TIFF header.
 305                     this.nextSpace = headerPosition;
 306                 }
 307                 stream.seek(headerPosition);
 308             } catch(IOException ioe) { // thrown by getStreamPosition()
 309                 // Assume it's at zero.
 310                 this.nextSpace = headerPosition = 0L;
 311             }
 312         } else {
 313             this.stream = null;
 314         }
 315 
 316         super.setOutput(output);
 317     }
 318 

 319     public IIOMetadata
 320         getDefaultStreamMetadata(ImageWriteParam param) {
 321         return new TIFFStreamMetadata();
 322     }
 323 

 324     public IIOMetadata
 325         getDefaultImageMetadata(ImageTypeSpecifier imageType,
 326                                 ImageWriteParam param) {
 327 
 328         List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
 329         tagSets.add(BaselineTIFFTagSet.getInstance());
 330         TIFFImageMetadata imageMetadata = new TIFFImageMetadata(tagSets);
 331 
 332         if(imageType != null) {
 333             TIFFImageMetadata im =
 334                 (TIFFImageMetadata)convertImageMetadata(imageMetadata,
 335                                                         imageType,
 336                                                         param);
 337             if(im != null) {
 338                 imageMetadata = im;
 339             }
 340         }
 341 
 342         return imageMetadata;
 343     }
 344 

 345     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
 346                                              ImageWriteParam param) {
 347         // Check arguments.
 348         if(inData == null) {
 349             throw new NullPointerException("inData == null!");
 350         }
 351 
 352         // Note: param is irrelevant as it does not contain byte order.
 353 
 354         TIFFStreamMetadata outData = null;
 355         if(inData instanceof TIFFStreamMetadata) {
 356             outData = new TIFFStreamMetadata();
 357             outData.byteOrder = ((TIFFStreamMetadata)inData).byteOrder;
 358             return outData;
 359         } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(
 360                       TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME)) {
 361             outData = new TIFFStreamMetadata();
 362             String format = TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME;
 363             try {
 364                 outData.mergeTree(format, inData.getAsTree(format));
 365             } catch(IIOInvalidTreeException e) {
 366                 return null;
 367             }
 368         }
 369 
 370         return outData;
 371     }
 372 

 373     public IIOMetadata
 374         convertImageMetadata(IIOMetadata inData,
 375                              ImageTypeSpecifier imageType,
 376                              ImageWriteParam param) {
 377         // Check arguments.
 378         if(inData == null) {
 379             throw new NullPointerException("inData == null!");
 380         }
 381         if(imageType == null) {
 382             throw new NullPointerException("imageType == null!");
 383         }
 384 
 385         TIFFImageMetadata outData = null;
 386 
 387         // Obtain a TIFFImageMetadata object.
 388         if(inData instanceof TIFFImageMetadata) {
 389             // Create a new metadata object from a clone of the input IFD.
 390             TIFFIFD inIFD = ((TIFFImageMetadata)inData).getRootIFD();
 391             outData = new TIFFImageMetadata(inIFD.getShallowClone());
 392         } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(


2288             // Divide scaling table into high and low bytes
2289             scaleh = new byte[numBands][];
2290             scalel = new byte[numBands][];
2291 
2292             for (int b = 0; b < numBands; b++) {
2293                 int maxInSample = (1 << sampleSize[b]) - 1;
2294                 int halfMaxInSample = maxInSample/2;
2295                 scaleh[b] = new byte[maxInSample + 1];
2296                 scalel[b] = new byte[maxInSample + 1];
2297                 for (int s = 0; s <= maxInSample; s++) {
2298                     int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
2299                     scaleh[b][s] = (byte)(val >> 8);
2300                     scalel[b][s] = (byte)(val & 0xff);
2301                 }
2302             }
2303             scale = null;
2304             scale0 = null;
2305         }
2306     }
2307 

2308     public void write(IIOMetadata sm,
2309                       IIOImage iioimage,
2310                       ImageWriteParam p) throws IOException {
2311         if (stream == null) {
2312             throw new IllegalStateException("output == null!");
2313         }
2314         markPositions();
2315         write(sm, iioimage, p, true, true);
2316         if (abortRequested()) {
2317             resetPositions();
2318         }
2319     }
2320 
2321     private void writeHeader() throws IOException {
2322         if (streamMetadata != null) {
2323             this.byteOrder = streamMetadata.byteOrder;
2324         } else {
2325             this.byteOrder = ByteOrder.BIG_ENDIAN;
2326         }
2327 


2601                     stream.writeInt(byteCount);
2602                     stripOrTileByteCountsPosition += 4;
2603                     stream.reset();
2604 
2605                     pixelsDone += tileRect.width*tileRect.height;
2606                     processImageProgress(100.0F*pixelsDone/totalPixels);
2607                     if (abortRequested()) {
2608                         processWriteAborted();
2609                         return;
2610                     }
2611                 } catch (IOException e) {
2612                     throw new IIOException("I/O error writing TIFF file!", e);
2613                 }
2614             }
2615         }
2616 
2617         processImageComplete();
2618         currentImage++;
2619     }
2620 

2621     public boolean canWriteSequence() {
2622         return true;
2623     }
2624 

2625     public void prepareWriteSequence(IIOMetadata streamMetadata)
2626         throws IOException {
2627         if (getOutput() == null) {
2628             throw new IllegalStateException("getOutput() == null!");
2629         }
2630 
2631         // Set up stream metadata.
2632         if (streamMetadata != null) {
2633             streamMetadata = convertStreamMetadata(streamMetadata, null);
2634         }
2635         if(streamMetadata == null) {
2636             streamMetadata = getDefaultStreamMetadata(null);
2637         }
2638         this.streamMetadata = (TIFFStreamMetadata)streamMetadata;
2639 
2640         // Write the header.
2641         writeHeader();
2642 
2643         // Set the sequence flag.
2644         this.isWritingSequence = true;
2645     }
2646 

2647     public void writeToSequence(IIOImage image, ImageWriteParam param)
2648         throws IOException {
2649         // Check sequence flag.
2650         if(!this.isWritingSequence) {
2651             throw new IllegalStateException
2652                 ("prepareWriteSequence() has not been called!");
2653         }
2654 
2655         // Append image.
2656         writeInsert(-1, image, param);
2657     }
2658 

2659     public void endWriteSequence() throws IOException {
2660         // Check output.
2661         if (getOutput() == null) {
2662             throw new IllegalStateException("getOutput() == null!");
2663         }
2664 
2665         // Check sequence flag.
2666         if(!isWritingSequence) {
2667             throw new IllegalStateException
2668                 ("prepareWriteSequence() has not been called!");
2669         }
2670 
2671         // Unset sequence flag.
2672         this.isWritingSequence = false;
2673 
2674         // Position the stream at the end, not at the next IFD pointer position.
2675         long streamLength = this.stream.length();
2676         if (streamLength != -1) {
2677             stream.seek(streamLength);
2678         }
2679     }
2680 

2681     public boolean canInsertImage(int imageIndex) throws IOException {
2682         if (getOutput() == null) {
2683             throw new IllegalStateException("getOutput() == null!");
2684         }
2685 
2686         // Mark position as locateIFD() will seek to IFD at imageIndex.
2687         stream.mark();
2688 
2689         // locateIFD() will throw an IndexOutOfBoundsException if
2690         // imageIndex is < -1 or is too big thereby satisfying the spec.
2691         long[] ifdpos = new long[1];
2692         long[] ifd = new long[1];
2693         locateIFD(imageIndex, ifdpos, ifd);
2694 
2695         // Reset to position before locateIFD().
2696         stream.reset();
2697 
2698         return true;
2699     }
2700 


2747                 ifd[0] = 0;
2748                 return;
2749             }
2750 
2751             stream.skipBytes(12*numFields);
2752 
2753             ifdpos[0] = stream.getStreamPosition();
2754             ifd[0] = stream.readUnsignedInt();
2755             if (ifd[0] == 0) {
2756                 if (imageIndex != -1 && i < imageIndex - 1) {
2757                     stream.seek(startPos);
2758                     throw new IndexOutOfBoundsException(
2759                     "imageIndex is greater than the largest available index!");
2760                 }
2761                 break;
2762             }
2763             stream.seek(ifd[0]);
2764         }
2765     }
2766 

2767     public void writeInsert(int imageIndex,
2768                             IIOImage image,
2769                             ImageWriteParam param) throws IOException {
2770         int currentImageCached = currentImage;
2771         try {
2772             insert(imageIndex, image, param, true);
2773         } catch (Exception e) {
2774             throw e;
2775         } finally {
2776             currentImage = currentImageCached;
2777         }
2778     }
2779 
2780     private void insert(int imageIndex,
2781                         IIOImage image,
2782                         ImageWriteParam param,
2783                         boolean writeData) throws IOException {
2784         if (stream == null) {
2785             throw new IllegalStateException("Output not set!");
2786         }


2827         // Seek to the position containing the pointer in the new IFD.
2828         stream.seek(nextIFDPointerPos);
2829 
2830         // Update the new IFD to point to the old IFD.
2831         stream.writeInt((int)ifd[0]);
2832         // Don't need to update nextSpace here as already done in write().
2833 
2834         if (abortRequested()) {
2835             stream.seek(ifdpos[0]);
2836             stream.writeInt((int)prevPointerValue);
2837             resetPositions();
2838         }
2839     }
2840 
2841     // ----- BEGIN insert/writeEmpty methods -----
2842 
2843     private boolean isEncodingEmpty() {
2844         return isInsertingEmpty || isWritingEmpty;
2845     }
2846 

2847     public boolean canInsertEmpty(int imageIndex) throws IOException {
2848         return canInsertImage(imageIndex);
2849     }
2850 

2851     public boolean canWriteEmpty() throws IOException {
2852         if (getOutput() == null) {
2853             throw new IllegalStateException("getOutput() == null!");
2854         }
2855         return true;
2856     }
2857 
2858     // Check state and parameters for writing or inserting empty images.
2859     private void checkParamsEmpty(ImageTypeSpecifier imageType,
2860                                   int width,
2861                                   int height,
2862                                   List<? extends BufferedImage> thumbnails) {
2863         if (getOutput() == null) {
2864             throw new IllegalStateException("getOutput() == null!");
2865         }
2866 
2867         if(imageType == null) {
2868             throw new IllegalArgumentException("imageType == null!");
2869         }
2870 


2877             for(int i = 0; i < numThumbs; i++) {
2878                 Object thumb = thumbnails.get(i);
2879                 if(thumb == null || !(thumb instanceof BufferedImage)) {
2880                     throw new IllegalArgumentException
2881                         ("thumbnails contains null references or objects other than BufferedImages!");
2882                 }
2883             }
2884         }
2885 
2886         if(this.isInsertingEmpty) {
2887             throw new IllegalStateException
2888                 ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
2889         }
2890 
2891         if(this.isWritingEmpty) {
2892             throw new IllegalStateException
2893                 ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2894         }
2895     }
2896 

2897     public void prepareInsertEmpty(int imageIndex,
2898                                    ImageTypeSpecifier imageType,
2899                                    int width,
2900                                    int height,
2901                                    IIOMetadata imageMetadata,
2902                                    List<? extends BufferedImage> thumbnails,
2903                                    ImageWriteParam param) throws IOException {
2904         checkParamsEmpty(imageType, width, height, thumbnails);
2905 
2906         this.isInsertingEmpty = true;
2907 
2908         SampleModel emptySM = imageType.getSampleModel();
2909         RenderedImage emptyImage =
2910             new EmptyImage(0, 0, width, height,
2911                            0, 0, emptySM.getWidth(), emptySM.getHeight(),
2912                            emptySM, imageType.getColorModel());
2913 
2914         insert(imageIndex, new IIOImage(emptyImage, null, imageMetadata),
2915                param, false);
2916     }
2917 

2918     public void prepareWriteEmpty(IIOMetadata streamMetadata,
2919                                   ImageTypeSpecifier imageType,
2920                                   int width,
2921                                   int height,
2922                                   IIOMetadata imageMetadata,
2923                                   List<? extends BufferedImage> thumbnails,
2924                                   ImageWriteParam param) throws IOException {
2925         if (stream == null) {
2926             throw new IllegalStateException("output == null!");
2927         }
2928 
2929         checkParamsEmpty(imageType, width, height, thumbnails);
2930 
2931         this.isWritingEmpty = true;
2932 
2933         SampleModel emptySM = imageType.getSampleModel();
2934         RenderedImage emptyImage =
2935             new EmptyImage(0, 0, width, height,
2936                            0, 0, emptySM.getWidth(), emptySM.getHeight(),
2937                            emptySM, imageType.getColorModel());
2938 
2939         markPositions();
2940         write(streamMetadata, new IIOImage(emptyImage, null, imageMetadata),
2941               param, true, false);
2942         if (abortRequested()) {
2943             resetPositions();
2944         }
2945     }
2946 

2947     public void endInsertEmpty() throws IOException {
2948         if (getOutput() == null) {
2949             throw new IllegalStateException("getOutput() == null!");
2950         }
2951 
2952         if(!this.isInsertingEmpty) {
2953             throw new IllegalStateException
2954                 ("No previous call to prepareInsertEmpty()!");
2955         }
2956 
2957         if(this.isWritingEmpty) {
2958             throw new IllegalStateException
2959                 ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2960         }
2961 
2962         if (inReplacePixelsNest) {
2963             throw new IllegalStateException
2964                 ("In nested call to prepareReplacePixels!");
2965         }
2966 
2967         this.isInsertingEmpty = false;
2968     }
2969 

2970     public void endWriteEmpty() throws IOException {
2971         if (getOutput() == null) {
2972             throw new IllegalStateException("getOutput() == null!");
2973         }
2974 
2975         if(!this.isWritingEmpty) {
2976             throw new IllegalStateException
2977                 ("No previous call to prepareWriteEmpty()!");
2978         }
2979 
2980         if(this.isInsertingEmpty) {
2981             throw new IllegalStateException
2982                 ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
2983         }
2984 
2985         if (inReplacePixelsNest) {
2986             throw new IllegalStateException
2987                 ("In nested call to prepareReplacePixels!");
2988         }
2989 


3004 
3005         stream.mark();
3006         long[] ifdpos = new long[1];
3007         long[] ifd = new long[1];
3008         locateIFD(imageIndex, ifdpos, ifd);
3009         if (ifd[0] == 0) {
3010             stream.reset();
3011             throw new IndexOutOfBoundsException
3012                 ("imageIndex out of bounds!");
3013         }
3014 
3015         List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
3016         tagSets.add(BaselineTIFFTagSet.getInstance());
3017         TIFFIFD rootIFD = new TIFFIFD(tagSets);
3018         rootIFD.initialize(stream, true, false, false);
3019         stream.reset();
3020 
3021         return rootIFD;
3022     }
3023 

3024     public boolean canReplacePixels(int imageIndex) throws IOException {
3025         if (getOutput() == null) {
3026             throw new IllegalStateException("getOutput() == null!");
3027         }
3028 
3029         TIFFIFD rootIFD = readIFD(imageIndex);
3030         TIFFField f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
3031         int compression = f.getAsInt(0);
3032 
3033         return compression == BaselineTIFFTagSet.COMPRESSION_NONE;
3034     }
3035 
3036     private Object replacePixelsLock = new Object();
3037 
3038     private int replacePixelsIndex = -1;
3039     private TIFFImageMetadata replacePixelsMetadata = null;
3040     private long[] replacePixelsTileOffsets = null;
3041     private long[] replacePixelsByteCounts = null;
3042     private long replacePixelsOffsetsPosition = 0L;
3043     private long replacePixelsByteCountsPosition = 0L;
3044     private Rectangle replacePixelsRegion = null;
3045     private boolean inReplacePixelsNest = false;
3046 
3047     private TIFFImageReader reader = null;
3048 

3049     public void prepareReplacePixels(int imageIndex,
3050                                      Rectangle region) throws IOException {
3051         synchronized(replacePixelsLock) {
3052             // Check state and parameters vis-a-vis ImageWriter specification.
3053             if (stream == null) {
3054                 throw new IllegalStateException("Output not set!");
3055             }
3056             if (region == null) {
3057                 throw new IllegalArgumentException("region == null!");
3058             }
3059             if (region.getWidth() < 1) {
3060                 throw new IllegalArgumentException("region.getWidth() < 1!");
3061             }
3062             if (region.getHeight() < 1) {
3063                 throw new IllegalArgumentException("region.getHeight() < 1!");
3064             }
3065             if (inReplacePixelsNest) {
3066                 throw new IllegalStateException
3067                     ("In nested call to prepareReplacePixels!");
3068             }


3212             for(int k = 0; k < b; k++) {
3213                 int outY = outMinY;
3214                 for(int j = inMinY; j < jMax; j += subPeriodY) {
3215                     raster.getSamples(inMinX, j, inWidth, 1, k, samples);
3216                     int s = 0;
3217                     for(int i = 0; i < inWidth; i += subPeriodX) {
3218                         subsamples[s++] = samples[i];
3219                     }
3220                     wr.setSamples(outMinX, outY++, outWidth, 1, k,
3221                                   subsamples);
3222                 }
3223             }
3224         }
3225 
3226         return wr.createChild(outMinX, outMinY,
3227                               target.width, target.height,
3228                               target.x, target.y,
3229                               sourceBands);
3230     }
3231 

3232     public void replacePixels(RenderedImage image, ImageWriteParam param)
3233         throws IOException {
3234 
3235         synchronized(replacePixelsLock) {
3236             // Check state and parameters vis-a-vis ImageWriter specification.
3237             if (stream == null) {
3238                 throw new IllegalStateException("stream == null!");
3239             }
3240 
3241             if (image == null) {
3242                 throw new IllegalArgumentException("image == null!");
3243             }
3244 
3245             if (!inReplacePixelsNest) {
3246                 throw new IllegalStateException
3247                     ("No previous call to prepareReplacePixels!");
3248             }
3249 
3250             // Subsampling values.
3251             int stepX = 1, stepY = 1, gridX = 0, gridY = 0;


3551                             stream.writeInt((int)nextSpace);
3552                             stream.seek(replacePixelsByteCountsPosition +
3553                                         4*tileIndex);
3554                             stream.writeInt(numBytes);
3555                             stream.reset();
3556 
3557                             // Increment location of next available space.
3558                             nextSpace += numBytes;
3559                         }
3560                     }
3561                 }
3562 
3563             } catch(IOException e) {
3564                 throw e;
3565             } finally {
3566                 stream.reset();
3567             }
3568         }
3569     }
3570 

3571     public void replacePixels(Raster raster, ImageWriteParam param)
3572         throws IOException {
3573         if (raster == null) {
3574             throw new NullPointerException("raster == null!");
3575         }
3576 
3577         replacePixels(new SingleTileRenderedImage(raster,
3578                                                   image.getColorModel()),
3579                       param);
3580     }
3581 

3582     public void endReplacePixels() throws IOException {
3583         synchronized(replacePixelsLock) {
3584             if(!this.inReplacePixelsNest) {
3585                 throw new IllegalStateException
3586                     ("No previous call to prepareReplacePixels()!");
3587             }
3588             replacePixelsIndex = -1;
3589             replacePixelsMetadata = null;
3590             replacePixelsTileOffsets = null;
3591             replacePixelsByteCounts = null;
3592             replacePixelsOffsetsPosition = 0L;
3593             replacePixelsByteCountsPosition = 0L;
3594             replacePixelsRegion = null;
3595             inReplacePixelsNest = false;
3596         }
3597     }
3598 
3599     // ----- END replacePixels methods -----
3600 
3601     // Save stream positions for use when aborted.
3602     private void markPositions() throws IOException {
3603         prevStreamPosition = stream.getStreamPosition();
3604         prevHeaderPosition = headerPosition;
3605         prevNextSpace = nextSpace;
3606     }
3607 
3608     // Reset to positions saved by markPositions().
3609     private void resetPositions() throws IOException {
3610         stream.seek(prevStreamPosition);
3611         headerPosition = prevHeaderPosition;
3612         nextSpace = prevNextSpace;
3613     }
3614 

3615     public void reset() {
3616         super.reset();
3617 
3618         stream = null;
3619         image = null;
3620         imageType = null;
3621         byteOrder = null;
3622         param = null;
3623         compressor = null;
3624         colorConverter = null;
3625         streamMetadata = null;
3626         imageMetadata = null;
3627 
3628         isRescaling = false;
3629 
3630         isWritingSequence = false;
3631         isWritingEmpty = false;
3632         isInsertingEmpty = false;
3633 
3634         replacePixelsIndex = -1;


   1 /*
   2  * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any


 241      * and tile height.
 242      *
 243      * <p> If {@code tileHeight < 0}, the results of this method
 244      * are undefined.  If {@code tileHeight == 0}, an
 245      * {@code ArithmeticException} will be thrown.
 246      *
 247      * @throws ArithmeticException  If {@code tileHeight == 0}.
 248      */
 249     public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
 250         y -= tileGridYOffset;
 251         if (y < 0) {
 252             y += 1 - tileHeight;         // force round to -infinity (ceiling)
 253         }
 254         return y/tileHeight;
 255     }
 256 
 257     public TIFFImageWriter(ImageWriterSpi originatingProvider) {
 258         super(originatingProvider);
 259     }
 260 
 261     @Override
 262     public ImageWriteParam getDefaultWriteParam() {
 263         return new TIFFImageWriteParam(getLocale());
 264     }
 265 
 266     @Override
 267     public void setOutput(Object output) {
 268         if (output != null) {
 269             if (!(output instanceof ImageOutputStream)) {
 270                 throw new IllegalArgumentException
 271                     ("output not an ImageOutputStream!");
 272             }
 273 
 274             // reset() must precede setOutput() as it sets output to null
 275             reset();
 276 
 277             this.stream = (ImageOutputStream)output;
 278 
 279             //
 280             // The output is expected to be positioned at a TIFF header
 281             // or at some arbitrary location which may or may not be
 282             // the EOF. In the former case the writer should be able
 283             // either to overwrite the existing sequence or append to it.
 284             //
 285 
 286             // Set the position of the header and the next available space.


 301                     } else {
 302                         // Neither TIFF header nor EOF: overwrite.
 303                         this.nextSpace = headerPosition;
 304                     }
 305                 } catch(IOException io) { // thrown by readFully()
 306                     // At EOF or not at a TIFF header.
 307                     this.nextSpace = headerPosition;
 308                 }
 309                 stream.seek(headerPosition);
 310             } catch(IOException ioe) { // thrown by getStreamPosition()
 311                 // Assume it's at zero.
 312                 this.nextSpace = headerPosition = 0L;
 313             }
 314         } else {
 315             this.stream = null;
 316         }
 317 
 318         super.setOutput(output);
 319     }
 320 
 321     @Override
 322     public IIOMetadata
 323         getDefaultStreamMetadata(ImageWriteParam param) {
 324         return new TIFFStreamMetadata();
 325     }
 326 
 327     @Override
 328     public IIOMetadata
 329         getDefaultImageMetadata(ImageTypeSpecifier imageType,
 330                                 ImageWriteParam param) {
 331 
 332         List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
 333         tagSets.add(BaselineTIFFTagSet.getInstance());
 334         TIFFImageMetadata imageMetadata = new TIFFImageMetadata(tagSets);
 335 
 336         if(imageType != null) {
 337             TIFFImageMetadata im =
 338                 (TIFFImageMetadata)convertImageMetadata(imageMetadata,
 339                                                         imageType,
 340                                                         param);
 341             if(im != null) {
 342                 imageMetadata = im;
 343             }
 344         }
 345 
 346         return imageMetadata;
 347     }
 348 
 349     @Override
 350     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
 351                                              ImageWriteParam param) {
 352         // Check arguments.
 353         if(inData == null) {
 354             throw new NullPointerException("inData == null!");
 355         }
 356 
 357         // Note: param is irrelevant as it does not contain byte order.
 358 
 359         TIFFStreamMetadata outData = null;
 360         if(inData instanceof TIFFStreamMetadata) {
 361             outData = new TIFFStreamMetadata();
 362             outData.byteOrder = ((TIFFStreamMetadata)inData).byteOrder;
 363             return outData;
 364         } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(
 365                       TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME)) {
 366             outData = new TIFFStreamMetadata();
 367             String format = TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME;
 368             try {
 369                 outData.mergeTree(format, inData.getAsTree(format));
 370             } catch(IIOInvalidTreeException e) {
 371                 return null;
 372             }
 373         }
 374 
 375         return outData;
 376     }
 377 
 378     @Override
 379     public IIOMetadata
 380         convertImageMetadata(IIOMetadata inData,
 381                              ImageTypeSpecifier imageType,
 382                              ImageWriteParam param) {
 383         // Check arguments.
 384         if(inData == null) {
 385             throw new NullPointerException("inData == null!");
 386         }
 387         if(imageType == null) {
 388             throw new NullPointerException("imageType == null!");
 389         }
 390 
 391         TIFFImageMetadata outData = null;
 392 
 393         // Obtain a TIFFImageMetadata object.
 394         if(inData instanceof TIFFImageMetadata) {
 395             // Create a new metadata object from a clone of the input IFD.
 396             TIFFIFD inIFD = ((TIFFImageMetadata)inData).getRootIFD();
 397             outData = new TIFFImageMetadata(inIFD.getShallowClone());
 398         } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(


2294             // Divide scaling table into high and low bytes
2295             scaleh = new byte[numBands][];
2296             scalel = new byte[numBands][];
2297 
2298             for (int b = 0; b < numBands; b++) {
2299                 int maxInSample = (1 << sampleSize[b]) - 1;
2300                 int halfMaxInSample = maxInSample/2;
2301                 scaleh[b] = new byte[maxInSample + 1];
2302                 scalel[b] = new byte[maxInSample + 1];
2303                 for (int s = 0; s <= maxInSample; s++) {
2304                     int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
2305                     scaleh[b][s] = (byte)(val >> 8);
2306                     scalel[b][s] = (byte)(val & 0xff);
2307                 }
2308             }
2309             scale = null;
2310             scale0 = null;
2311         }
2312     }
2313 
2314     @Override
2315     public void write(IIOMetadata sm,
2316                       IIOImage iioimage,
2317                       ImageWriteParam p) throws IOException {
2318         if (stream == null) {
2319             throw new IllegalStateException("output == null!");
2320         }
2321         markPositions();
2322         write(sm, iioimage, p, true, true);
2323         if (abortRequested()) {
2324             resetPositions();
2325         }
2326     }
2327 
2328     private void writeHeader() throws IOException {
2329         if (streamMetadata != null) {
2330             this.byteOrder = streamMetadata.byteOrder;
2331         } else {
2332             this.byteOrder = ByteOrder.BIG_ENDIAN;
2333         }
2334 


2608                     stream.writeInt(byteCount);
2609                     stripOrTileByteCountsPosition += 4;
2610                     stream.reset();
2611 
2612                     pixelsDone += tileRect.width*tileRect.height;
2613                     processImageProgress(100.0F*pixelsDone/totalPixels);
2614                     if (abortRequested()) {
2615                         processWriteAborted();
2616                         return;
2617                     }
2618                 } catch (IOException e) {
2619                     throw new IIOException("I/O error writing TIFF file!", e);
2620                 }
2621             }
2622         }
2623 
2624         processImageComplete();
2625         currentImage++;
2626     }
2627 
2628     @Override
2629     public boolean canWriteSequence() {
2630         return true;
2631     }
2632 
2633     @Override
2634     public void prepareWriteSequence(IIOMetadata streamMetadata)
2635         throws IOException {
2636         if (getOutput() == null) {
2637             throw new IllegalStateException("getOutput() == null!");
2638         }
2639 
2640         // Set up stream metadata.
2641         if (streamMetadata != null) {
2642             streamMetadata = convertStreamMetadata(streamMetadata, null);
2643         }
2644         if(streamMetadata == null) {
2645             streamMetadata = getDefaultStreamMetadata(null);
2646         }
2647         this.streamMetadata = (TIFFStreamMetadata)streamMetadata;
2648 
2649         // Write the header.
2650         writeHeader();
2651 
2652         // Set the sequence flag.
2653         this.isWritingSequence = true;
2654     }
2655 
2656     @Override
2657     public void writeToSequence(IIOImage image, ImageWriteParam param)
2658         throws IOException {
2659         // Check sequence flag.
2660         if(!this.isWritingSequence) {
2661             throw new IllegalStateException
2662                 ("prepareWriteSequence() has not been called!");
2663         }
2664 
2665         // Append image.
2666         writeInsert(-1, image, param);
2667     }
2668 
2669     @Override
2670     public void endWriteSequence() throws IOException {
2671         // Check output.
2672         if (getOutput() == null) {
2673             throw new IllegalStateException("getOutput() == null!");
2674         }
2675 
2676         // Check sequence flag.
2677         if(!isWritingSequence) {
2678             throw new IllegalStateException
2679                 ("prepareWriteSequence() has not been called!");
2680         }
2681 
2682         // Unset sequence flag.
2683         this.isWritingSequence = false;
2684 
2685         // Position the stream at the end, not at the next IFD pointer position.
2686         long streamLength = this.stream.length();
2687         if (streamLength != -1) {
2688             stream.seek(streamLength);
2689         }
2690     }
2691 
2692     @Override
2693     public boolean canInsertImage(int imageIndex) throws IOException {
2694         if (getOutput() == null) {
2695             throw new IllegalStateException("getOutput() == null!");
2696         }
2697 
2698         // Mark position as locateIFD() will seek to IFD at imageIndex.
2699         stream.mark();
2700 
2701         // locateIFD() will throw an IndexOutOfBoundsException if
2702         // imageIndex is < -1 or is too big thereby satisfying the spec.
2703         long[] ifdpos = new long[1];
2704         long[] ifd = new long[1];
2705         locateIFD(imageIndex, ifdpos, ifd);
2706 
2707         // Reset to position before locateIFD().
2708         stream.reset();
2709 
2710         return true;
2711     }
2712 


2759                 ifd[0] = 0;
2760                 return;
2761             }
2762 
2763             stream.skipBytes(12*numFields);
2764 
2765             ifdpos[0] = stream.getStreamPosition();
2766             ifd[0] = stream.readUnsignedInt();
2767             if (ifd[0] == 0) {
2768                 if (imageIndex != -1 && i < imageIndex - 1) {
2769                     stream.seek(startPos);
2770                     throw new IndexOutOfBoundsException(
2771                     "imageIndex is greater than the largest available index!");
2772                 }
2773                 break;
2774             }
2775             stream.seek(ifd[0]);
2776         }
2777     }
2778 
2779     @Override
2780     public void writeInsert(int imageIndex,
2781                             IIOImage image,
2782                             ImageWriteParam param) throws IOException {
2783         int currentImageCached = currentImage;
2784         try {
2785             insert(imageIndex, image, param, true);
2786         } catch (Exception e) {
2787             throw e;
2788         } finally {
2789             currentImage = currentImageCached;
2790         }
2791     }
2792 
2793     private void insert(int imageIndex,
2794                         IIOImage image,
2795                         ImageWriteParam param,
2796                         boolean writeData) throws IOException {
2797         if (stream == null) {
2798             throw new IllegalStateException("Output not set!");
2799         }


2840         // Seek to the position containing the pointer in the new IFD.
2841         stream.seek(nextIFDPointerPos);
2842 
2843         // Update the new IFD to point to the old IFD.
2844         stream.writeInt((int)ifd[0]);
2845         // Don't need to update nextSpace here as already done in write().
2846 
2847         if (abortRequested()) {
2848             stream.seek(ifdpos[0]);
2849             stream.writeInt((int)prevPointerValue);
2850             resetPositions();
2851         }
2852     }
2853 
2854     // ----- BEGIN insert/writeEmpty methods -----
2855 
2856     private boolean isEncodingEmpty() {
2857         return isInsertingEmpty || isWritingEmpty;
2858     }
2859 
2860     @Override
2861     public boolean canInsertEmpty(int imageIndex) throws IOException {
2862         return canInsertImage(imageIndex);
2863     }
2864 
2865     @Override
2866     public boolean canWriteEmpty() throws IOException {
2867         if (getOutput() == null) {
2868             throw new IllegalStateException("getOutput() == null!");
2869         }
2870         return true;
2871     }
2872 
2873     // Check state and parameters for writing or inserting empty images.
2874     private void checkParamsEmpty(ImageTypeSpecifier imageType,
2875                                   int width,
2876                                   int height,
2877                                   List<? extends BufferedImage> thumbnails) {
2878         if (getOutput() == null) {
2879             throw new IllegalStateException("getOutput() == null!");
2880         }
2881 
2882         if(imageType == null) {
2883             throw new IllegalArgumentException("imageType == null!");
2884         }
2885 


2892             for(int i = 0; i < numThumbs; i++) {
2893                 Object thumb = thumbnails.get(i);
2894                 if(thumb == null || !(thumb instanceof BufferedImage)) {
2895                     throw new IllegalArgumentException
2896                         ("thumbnails contains null references or objects other than BufferedImages!");
2897                 }
2898             }
2899         }
2900 
2901         if(this.isInsertingEmpty) {
2902             throw new IllegalStateException
2903                 ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
2904         }
2905 
2906         if(this.isWritingEmpty) {
2907             throw new IllegalStateException
2908                 ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2909         }
2910     }
2911 
2912     @Override
2913     public void prepareInsertEmpty(int imageIndex,
2914                                    ImageTypeSpecifier imageType,
2915                                    int width,
2916                                    int height,
2917                                    IIOMetadata imageMetadata,
2918                                    List<? extends BufferedImage> thumbnails,
2919                                    ImageWriteParam param) throws IOException {
2920         checkParamsEmpty(imageType, width, height, thumbnails);
2921 
2922         this.isInsertingEmpty = true;
2923 
2924         SampleModel emptySM = imageType.getSampleModel();
2925         RenderedImage emptyImage =
2926             new EmptyImage(0, 0, width, height,
2927                            0, 0, emptySM.getWidth(), emptySM.getHeight(),
2928                            emptySM, imageType.getColorModel());
2929 
2930         insert(imageIndex, new IIOImage(emptyImage, null, imageMetadata),
2931                param, false);
2932     }
2933 
2934     @Override
2935     public void prepareWriteEmpty(IIOMetadata streamMetadata,
2936                                   ImageTypeSpecifier imageType,
2937                                   int width,
2938                                   int height,
2939                                   IIOMetadata imageMetadata,
2940                                   List<? extends BufferedImage> thumbnails,
2941                                   ImageWriteParam param) throws IOException {
2942         if (stream == null) {
2943             throw new IllegalStateException("output == null!");
2944         }
2945 
2946         checkParamsEmpty(imageType, width, height, thumbnails);
2947 
2948         this.isWritingEmpty = true;
2949 
2950         SampleModel emptySM = imageType.getSampleModel();
2951         RenderedImage emptyImage =
2952             new EmptyImage(0, 0, width, height,
2953                            0, 0, emptySM.getWidth(), emptySM.getHeight(),
2954                            emptySM, imageType.getColorModel());
2955 
2956         markPositions();
2957         write(streamMetadata, new IIOImage(emptyImage, null, imageMetadata),
2958               param, true, false);
2959         if (abortRequested()) {
2960             resetPositions();
2961         }
2962     }
2963 
2964     @Override
2965     public void endInsertEmpty() throws IOException {
2966         if (getOutput() == null) {
2967             throw new IllegalStateException("getOutput() == null!");
2968         }
2969 
2970         if(!this.isInsertingEmpty) {
2971             throw new IllegalStateException
2972                 ("No previous call to prepareInsertEmpty()!");
2973         }
2974 
2975         if(this.isWritingEmpty) {
2976             throw new IllegalStateException
2977                 ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2978         }
2979 
2980         if (inReplacePixelsNest) {
2981             throw new IllegalStateException
2982                 ("In nested call to prepareReplacePixels!");
2983         }
2984 
2985         this.isInsertingEmpty = false;
2986     }
2987 
2988     @Override
2989     public void endWriteEmpty() throws IOException {
2990         if (getOutput() == null) {
2991             throw new IllegalStateException("getOutput() == null!");
2992         }
2993 
2994         if(!this.isWritingEmpty) {
2995             throw new IllegalStateException
2996                 ("No previous call to prepareWriteEmpty()!");
2997         }
2998 
2999         if(this.isInsertingEmpty) {
3000             throw new IllegalStateException
3001                 ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
3002         }
3003 
3004         if (inReplacePixelsNest) {
3005             throw new IllegalStateException
3006                 ("In nested call to prepareReplacePixels!");
3007         }
3008 


3023 
3024         stream.mark();
3025         long[] ifdpos = new long[1];
3026         long[] ifd = new long[1];
3027         locateIFD(imageIndex, ifdpos, ifd);
3028         if (ifd[0] == 0) {
3029             stream.reset();
3030             throw new IndexOutOfBoundsException
3031                 ("imageIndex out of bounds!");
3032         }
3033 
3034         List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
3035         tagSets.add(BaselineTIFFTagSet.getInstance());
3036         TIFFIFD rootIFD = new TIFFIFD(tagSets);
3037         rootIFD.initialize(stream, true, false, false);
3038         stream.reset();
3039 
3040         return rootIFD;
3041     }
3042 
3043     @Override
3044     public boolean canReplacePixels(int imageIndex) throws IOException {
3045         if (getOutput() == null) {
3046             throw new IllegalStateException("getOutput() == null!");
3047         }
3048 
3049         TIFFIFD rootIFD = readIFD(imageIndex);
3050         TIFFField f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
3051         int compression = f.getAsInt(0);
3052 
3053         return compression == BaselineTIFFTagSet.COMPRESSION_NONE;
3054     }
3055 
3056     private Object replacePixelsLock = new Object();
3057 
3058     private int replacePixelsIndex = -1;
3059     private TIFFImageMetadata replacePixelsMetadata = null;
3060     private long[] replacePixelsTileOffsets = null;
3061     private long[] replacePixelsByteCounts = null;
3062     private long replacePixelsOffsetsPosition = 0L;
3063     private long replacePixelsByteCountsPosition = 0L;
3064     private Rectangle replacePixelsRegion = null;
3065     private boolean inReplacePixelsNest = false;
3066 
3067     private TIFFImageReader reader = null;
3068 
3069     @Override
3070     public void prepareReplacePixels(int imageIndex,
3071                                      Rectangle region) throws IOException {
3072         synchronized(replacePixelsLock) {
3073             // Check state and parameters vis-a-vis ImageWriter specification.
3074             if (stream == null) {
3075                 throw new IllegalStateException("Output not set!");
3076             }
3077             if (region == null) {
3078                 throw new IllegalArgumentException("region == null!");
3079             }
3080             if (region.getWidth() < 1) {
3081                 throw new IllegalArgumentException("region.getWidth() < 1!");
3082             }
3083             if (region.getHeight() < 1) {
3084                 throw new IllegalArgumentException("region.getHeight() < 1!");
3085             }
3086             if (inReplacePixelsNest) {
3087                 throw new IllegalStateException
3088                     ("In nested call to prepareReplacePixels!");
3089             }


3233             for(int k = 0; k < b; k++) {
3234                 int outY = outMinY;
3235                 for(int j = inMinY; j < jMax; j += subPeriodY) {
3236                     raster.getSamples(inMinX, j, inWidth, 1, k, samples);
3237                     int s = 0;
3238                     for(int i = 0; i < inWidth; i += subPeriodX) {
3239                         subsamples[s++] = samples[i];
3240                     }
3241                     wr.setSamples(outMinX, outY++, outWidth, 1, k,
3242                                   subsamples);
3243                 }
3244             }
3245         }
3246 
3247         return wr.createChild(outMinX, outMinY,
3248                               target.width, target.height,
3249                               target.x, target.y,
3250                               sourceBands);
3251     }
3252 
3253     @Override
3254     public void replacePixels(RenderedImage image, ImageWriteParam param)
3255         throws IOException {
3256 
3257         synchronized(replacePixelsLock) {
3258             // Check state and parameters vis-a-vis ImageWriter specification.
3259             if (stream == null) {
3260                 throw new IllegalStateException("stream == null!");
3261             }
3262 
3263             if (image == null) {
3264                 throw new IllegalArgumentException("image == null!");
3265             }
3266 
3267             if (!inReplacePixelsNest) {
3268                 throw new IllegalStateException
3269                     ("No previous call to prepareReplacePixels!");
3270             }
3271 
3272             // Subsampling values.
3273             int stepX = 1, stepY = 1, gridX = 0, gridY = 0;


3573                             stream.writeInt((int)nextSpace);
3574                             stream.seek(replacePixelsByteCountsPosition +
3575                                         4*tileIndex);
3576                             stream.writeInt(numBytes);
3577                             stream.reset();
3578 
3579                             // Increment location of next available space.
3580                             nextSpace += numBytes;
3581                         }
3582                     }
3583                 }
3584 
3585             } catch(IOException e) {
3586                 throw e;
3587             } finally {
3588                 stream.reset();
3589             }
3590         }
3591     }
3592 
3593     @Override
3594     public void replacePixels(Raster raster, ImageWriteParam param)
3595         throws IOException {
3596         if (raster == null) {
3597             throw new NullPointerException("raster == null!");
3598         }
3599 
3600         replacePixels(new SingleTileRenderedImage(raster,
3601                                                   image.getColorModel()),
3602                       param);
3603     }
3604 
3605     @Override
3606     public void endReplacePixels() throws IOException {
3607         synchronized(replacePixelsLock) {
3608             if(!this.inReplacePixelsNest) {
3609                 throw new IllegalStateException
3610                     ("No previous call to prepareReplacePixels()!");
3611             }
3612             replacePixelsIndex = -1;
3613             replacePixelsMetadata = null;
3614             replacePixelsTileOffsets = null;
3615             replacePixelsByteCounts = null;
3616             replacePixelsOffsetsPosition = 0L;
3617             replacePixelsByteCountsPosition = 0L;
3618             replacePixelsRegion = null;
3619             inReplacePixelsNest = false;
3620         }
3621     }
3622 
3623     // ----- END replacePixels methods -----
3624 
3625     // Save stream positions for use when aborted.
3626     private void markPositions() throws IOException {
3627         prevStreamPosition = stream.getStreamPosition();
3628         prevHeaderPosition = headerPosition;
3629         prevNextSpace = nextSpace;
3630     }
3631 
3632     // Reset to positions saved by markPositions().
3633     private void resetPositions() throws IOException {
3634         stream.seek(prevStreamPosition);
3635         headerPosition = prevHeaderPosition;
3636         nextSpace = prevNextSpace;
3637     }
3638 
3639     @Override
3640     public void reset() {
3641         super.reset();
3642 
3643         stream = null;
3644         image = null;
3645         imageType = null;
3646         byteOrder = null;
3647         param = null;
3648         compressor = null;
3649         colorConverter = null;
3650         streamMetadata = null;
3651         imageMetadata = null;
3652 
3653         isRescaling = false;
3654 
3655         isWritingSequence = false;
3656         isWritingEmpty = false;
3657         isInsertingEmpty = false;
3658 
3659         replacePixelsIndex = -1;


< prev index next >