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;
|