001/*
002 * $RCSfile: CLibImageWriter.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.6 $
042 * $Date: 2007/02/06 22:14:59 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.clib;
046
047import java.awt.Point;
048import java.awt.Rectangle;
049import java.awt.color.ColorSpace;
050import java.awt.geom.AffineTransform;
051import java.awt.image.AffineTransformOp;
052import java.awt.image.ColorModel;
053import java.awt.image.ComponentSampleModel;
054import java.awt.image.DataBuffer;
055import java.awt.image.DataBufferByte;
056import java.awt.image.DataBufferUShort;
057import java.awt.image.IndexColorModel;
058import java.awt.image.MultiPixelPackedSampleModel;
059import java.awt.image.PixelInterleavedSampleModel;
060import java.awt.image.Raster;
061import java.awt.image.RenderedImage;
062import java.awt.image.SampleModel;
063import java.awt.image.SinglePixelPackedSampleModel;
064import java.awt.image.WritableRaster;
065import java.io.IOException;
066import javax.imageio.IIOImage;
067import javax.imageio.ImageWriter;
068import javax.imageio.ImageWriteParam;
069import javax.imageio.ImageTypeSpecifier;
070import javax.imageio.metadata.IIOMetadata;
071import javax.imageio.spi.ImageWriterSpi;
072
073public abstract class CLibImageWriter extends ImageWriter {
074    /**
075     * Returns the data array from the <code>DataBuffer</code>.
076     */
077    private static final Object getDataBufferData(DataBuffer db) {
078        Object data;
079
080        int dType = db.getDataType();
081        switch (dType) {
082        case DataBuffer.TYPE_BYTE:
083            data = ((DataBufferByte)db).getData();
084            break;
085        case DataBuffer.TYPE_USHORT:
086            data = ((DataBufferUShort)db).getData();
087            break;
088        default:
089            throw new IllegalArgumentException
090                (I18N.getString("Generic0")+" "+dType);
091        }
092
093        return data;
094    }
095
096    /**
097     * Returns a contiguous <code>Raster</code> of data over the specified
098     * <code>Rectangle</code>. If the region is a sub-region of a single
099     * tile, then a child of that tile will be returned. If the region
100     * overlaps more than one tile and has 8 bits per sample, then a
101     * pixel interleaved Raster having band offsets 0,1,... will be returned.
102     * Otherwise the Raster returned by <code>im.copyData(null)</code> will
103     * be returned.
104     */
105    private static final Raster getContiguousData(RenderedImage im,
106                                                  Rectangle region) {
107        if(im == null) {
108            throw new IllegalArgumentException("im == null");
109        } else if(region == null) {
110            throw new IllegalArgumentException("region == null");
111        }
112
113        Raster raster;
114        if(im.getNumXTiles() == 1 && im.getNumYTiles() == 1) {
115            // Image is not tiled so just get a reference to the tile.
116            raster = im.getTile(im.getMinTileX(), im.getMinTileY());
117
118            // Ensure result has requested coverage.
119            Rectangle bounds = raster.getBounds();
120            if (!bounds.equals(region)) {
121                raster = raster.createChild(region.x, region.y,
122                                            region.width, region.height,
123                                            region.x, region.y,
124                                            null);
125            }
126        } else {
127            // Image is tiled.
128
129            // Create an interleaved raster for copying for 8-bit case.
130            // This ensures that for RGB data the band offsets are {0,1,2}.
131            SampleModel sampleModel = im.getSampleModel();
132            WritableRaster target = sampleModel.getSampleSize(0) == 8 ?
133                Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
134                                               im.getWidth(),
135                                               im.getHeight(),
136                                               sampleModel.getNumBands(),
137                                               new Point(im.getMinX(),
138                                                         im.getMinY())) :
139                null;
140
141            // Copy the data.
142            raster = im.copyData(target);
143        }
144
145        return raster;
146    }
147
148    /**
149     * Subsamples and sub-bands the input <code>Raster</code> over a
150     * sub-region and stores the result in a <code>WritableRaster</code>.
151     *
152     * @param src The source <code>Raster</code>
153     * @param sourceBands The source bands to use; may be <code>null</code>
154     * @param subsampleX The subsampling factor along the horizontal axis.
155     * @param subsampleY The subsampling factor along the vertical axis.
156     * in which case all bands will be used.
157     * @param dst The destination <code>WritableRaster</code>.
158     * @throws IllegalArgumentException if <code>source</code> is
159     * <code>null</code> or empty, <code>dst</code> is <code>null</code>,
160     * <code>sourceBands.length</code> exceeds the number of bands in
161     * <code>source</code>, or <code>sourcBands</code> contains an element
162     * which is negative or greater than or equal to the number of bands
163     * in <code>source</code>.
164     */
165    private static void reformat(Raster source,
166                                 int[] sourceBands,
167                                 int subsampleX,
168                                 int subsampleY,
169                                 WritableRaster dst) {
170        // Check for nulls.
171        if(source == null) {
172            throw new IllegalArgumentException("source == null!");
173        } else if(dst == null) {
174            throw new IllegalArgumentException("dst == null!");
175        }
176
177        // Validate the source bounds. XXX is this needed?
178        Rectangle sourceBounds = source.getBounds();
179        if(sourceBounds.isEmpty()) {
180            throw new IllegalArgumentException
181                ("source.getBounds().isEmpty()!");
182        }
183
184        // Check sub-banding.
185        boolean isSubBanding = false;
186        int numSourceBands = source.getSampleModel().getNumBands();
187        if(sourceBands != null) {
188            if(sourceBands.length > numSourceBands) {
189                throw new IllegalArgumentException
190                    ("sourceBands.length > numSourceBands!");
191            }
192
193            boolean isRamp = sourceBands.length == numSourceBands;
194            for(int i = 0; i < sourceBands.length; i++) {
195                if(sourceBands[i] < 0 || sourceBands[i] >= numSourceBands) {
196                    throw new IllegalArgumentException
197                        ("sourceBands[i] < 0 || sourceBands[i] >= numSourceBands!");
198                } else if(sourceBands[i] != i) {
199                    isRamp = false;
200                }
201            }
202
203            isSubBanding = !isRamp;
204        }
205
206        // Allocate buffer for a single source row.
207        int sourceWidth = sourceBounds.width;
208        int[] pixels = new int[sourceWidth*numSourceBands];
209
210        // Initialize variables used in loop.
211        int sourceX = sourceBounds.x;
212        int sourceY = sourceBounds.y;
213        int numBands = sourceBands != null ?
214            sourceBands.length : numSourceBands;
215        int dstWidth = dst.getWidth();
216        int dstYMax = dst.getHeight() - 1;
217        int copyFromIncrement = numSourceBands*subsampleX;
218
219        // Loop over source rows, subsample each, and store in destination.
220        for(int dstY = 0; dstY <= dstYMax; dstY++) {
221            // Read one row.
222            source.getPixels(sourceX, sourceY, sourceWidth, 1, pixels);
223
224            // Copy within the same buffer by left shifting.
225            if(isSubBanding) {
226                int copyFrom = 0;
227                int copyTo = 0;
228                for(int i = 0; i < dstWidth; i++) {
229                    for(int j = 0; j < numBands; j++) {
230                        pixels[copyTo++] = pixels[copyFrom + sourceBands[j]];
231                    }
232                    copyFrom += copyFromIncrement;
233                }
234            } else {
235                int copyFrom = copyFromIncrement;
236                int copyTo = numSourceBands;
237                // Start from index 1 as no need to copy the first pixel.
238                for(int i = 1; i < dstWidth; i++) {
239                    int k = copyFrom;
240                    for(int j = 0; j < numSourceBands; j++) {
241                        pixels[copyTo++] = pixels[k++];
242                    }
243                    copyFrom += copyFromIncrement;
244                }
245            }
246
247            // Set the destionation row.
248            dst.setPixels(0, dstY, dstWidth, 1, pixels);
249
250            // Increment the source row.
251            sourceY += subsampleY;
252        }
253    }
254
255    protected CLibImageWriter(ImageWriterSpi originatingProvider) {
256        super(originatingProvider);
257    }
258
259    public IIOMetadata convertImageMetadata(IIOMetadata inData,
260                                            ImageTypeSpecifier imageType,
261                                            ImageWriteParam param) {
262        return null;
263    }
264
265    public IIOMetadata convertStreamMetadata(IIOMetadata inData,
266                                             ImageWriteParam param) {
267        return null;
268    }
269
270    public IIOMetadata
271        getDefaultImageMetadata(ImageTypeSpecifier imageType,
272                                ImageWriteParam param) {
273        return null;
274    }
275
276    public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
277        return null;
278    }
279
280    /* XXX
281    protected int getSignificantBits(RenderedImage image) {
282        SampleModel sampleModel = image.getSampleModel();
283        int numBands = sampleModel.getNumBands();
284        int[] sampleSize = sampleModel.getSampleSize();
285        int significantBits = sampleSize[0];
286        for(int i = 1; i < numBands; i++) {
287            significantBits = Math.max(significantBits, sampleSize[i]);
288        }
289
290        return significantBits;
291    }
292    */
293
294    // Code copied from ImageReader.java with ImageReadParam replaced
295    // by ImageWriteParam.
296    private static final Rectangle getSourceRegion(ImageWriteParam param,
297                                                   int sourceMinX,
298                                                   int sourceMinY,
299                                                   int srcWidth,
300                                                   int srcHeight) {
301        Rectangle sourceRegion =
302            new Rectangle(sourceMinX, sourceMinY, srcWidth, srcHeight);
303        if (param != null) {
304            Rectangle region = param.getSourceRegion();
305            if (region != null) {
306                sourceRegion = sourceRegion.intersection(region);
307            }
308
309            int subsampleXOffset = param.getSubsamplingXOffset();
310            int subsampleYOffset = param.getSubsamplingYOffset();
311            sourceRegion.x += subsampleXOffset;
312            sourceRegion.y += subsampleYOffset;
313            sourceRegion.width -= subsampleXOffset;
314            sourceRegion.height -= subsampleYOffset;
315        }
316
317        return sourceRegion;
318    }
319}