001    /*
002     * BufferedRGB24Image
003     * 
004     * Copyright (c) 2002 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.gui.awt;
009    
010    import java.awt.image.BufferedImage;
011    import net.sourceforge.jiu.data.PixelImage;
012    import net.sourceforge.jiu.data.RGB24Image;
013    
014    /**
015     * A bridge class to use {@link java.awt.image.BufferedImage} objects (class defined
016     * in the standard runtime library, package <code>java.awt.image</code>) as 
017     * {@link net.sourceforge.jiu.data.RGB24Image} objects within JIU.
018     * This class encapsulates a single {@link java.awt.image.BufferedImage} object.
019     * It enables reusing existing BufferedImage objects as input or
020     * output of JIU operations, 
021     * removing the necessity for the conversion step from <code>java.awt.Image</code> 
022     * to <code>net.sourceforge.jiu.data.PixelImage</code> (or vice versa) 
023     * and thus reducing memory consumption.
024     * The name of this class is a combination of BufferedImage (the class of the object
025     * that is encapsulated) and RGB24Image (the JIU image data interface).
026     * <p>
027     * Internally, this class uses {@link java.awt.image.BufferedImage}'s getRGB and 
028     * setRGB methods to access image data.
029     * This approach is slower than working directly on the BufferedImage's data
030     * buffers.
031     * However, using getRGB and setRGB, this class will work with all types of BufferedImage objects.
032     * <p>
033     * Note that while the abstract <code>java.awt.Image</code> class existed from the very
034     * beginning (version 1.0) of the Java runtime library, <code>java.awt.image.BufferedImage</code>
035     * has not been added until version 1.2.
036     * <h3>Usage example</h3>
037     * This code snippet demonstrates to how combine functionality from Java's runtime
038     * library with JIU by using this class.
039     * Requires Java 1.4 or higher.
040     * Obviously, BufferedRGB24Image objects can only be used with operations that
041     * work on classes implementing RGB24Image.
042     * <pre>
043     *  import java.awt.image.BufferedImage;
044     *  import java.io.File;
045     *  import javax.imageio.ImageIO;
046     *  import net.sourceforge.jiu.color.Invert;
047     *  import net.sourceforge.jiu.data.PixelImage;
048     *  import net.sourceforge.jiu.gui.awt.BufferedRGB24Image;
049     *  ...
050     *  BufferedImage bufferedImage = ImageIO.read(new File("image.jpg"));
051     *  BufferedRGB24Image image = new BufferedRGB24Image(bufferedImage);
052     *  Invert invert = new Invert();
053     *  invert.setInputImage(image);
054     *  invert.process();
055     *  PixelImage outputImage = invert.getOutputImage();
056     * </pre>
057     * If you can be sure that an image object can be input and output
058     * image at the same time (as is the case with some operations), you 
059     * can even work with only one BufferedRGB24Image object.
060     * Invert is one of these operations, so the following would work:
061     * <pre>
062     *  Invert invert = new Invert();
063     *  invert.setInputImage(image);
064     *  invert.setOutputImage(image);
065     *  invert.process();
066     *  // image now is inverted
067     * </pre>
068     *
069     * @author Marco Schmidt
070     * @since 0.10.0
071     */
072    public class BufferedRGB24Image implements RGB24Image
073    {
074            private static final int RED_SHIFT = 16;
075            private static final int GREEN_SHIFT = 8;
076            private static final int BLUE_SHIFT = 0;
077    
078            /** 
079             * Masks for the three RGB channels.
080             * RGB_CLEAR[i] is an int with all bits on, except for those occupied by the channel i.
081             * RGB_CLEAR[i] can thus be used to bitwise AND an ARGB value so that the sample for channel i will be cleared.
082             */
083            private static final int[] RGB_CLEAR = new int[3];
084            private static final int[] RGB_SHIFT = new int[3];
085    
086            static
087            {
088                    RGB_SHIFT[INDEX_RED] = RED_SHIFT;
089                    RGB_SHIFT[INDEX_GREEN] = GREEN_SHIFT;
090                    RGB_SHIFT[INDEX_BLUE] = BLUE_SHIFT;
091                    RGB_CLEAR[INDEX_RED] = 0xff00ffff;
092                    RGB_CLEAR[INDEX_GREEN] = 0xffff00ff;
093                    RGB_CLEAR[INDEX_BLUE] = 0xffffff00;
094            }
095    
096            private final int HEIGHT;
097            private final BufferedImage image;
098            private final int WIDTH;
099    
100            /**
101             * Creates a new BufferedRGB24Image object, storing the argument
102             * BufferedImage object internally.
103             * All image data access will be delegated to that BufferedImage object's methods.
104             * @param bufferedImage the underlying BufferedImage object for this BufferedRGB24Image object
105             */
106            public BufferedRGB24Image(BufferedImage bufferedImage)
107            {
108                    image = bufferedImage;
109                    if (image == null)
110                    {
111                            throw new IllegalArgumentException("Argument image object must not be null.");
112                    }
113                    WIDTH = image.getWidth();
114                    HEIGHT = image.getHeight();
115            }
116    
117            /**
118             * Sets all the RGB samples in this image to the argument, keeping
119             * the alpha value.
120             * @param newValue all samples in the image will be set to this value
121             */
122            public void clear(byte newValue)
123            {
124                    final int RGB = (newValue & 0xff) | (newValue & 0xff) << 8 | (newValue & 0xff) << 16;
125                    for (int y = 0; y < getHeight(); y++)
126                    {
127                            for (int x = 0; x < getWidth(); x++)
128                            {
129                                    int rgba = image.getRGB(x, y);
130                                    rgba = (rgba & 0xff000000) | RGB;
131                                    image.setRGB(x, y, rgba);
132                            }
133                    }
134            }
135    
136            public void clear(int newValue)
137            {
138                    clear((byte)newValue);
139            }
140    
141            public void clear(int channelIndex, byte newValue)
142            {
143                    final int MASK = RGB_CLEAR[channelIndex];
144                    final int SAMPLE = (newValue & 0xff) << RGB_SHIFT[channelIndex];
145                    for (int y = 0; y < getHeight(); y++)
146                    {
147                            for (int x = 0; x < getWidth(); x++)
148                            {
149                                    int rgba = image.getRGB(x, y);
150                                    rgba = (rgba & MASK) | SAMPLE;
151                                    image.setRGB(x, y, rgba);
152                            }
153                    }
154            }
155    
156            public void clear(int channelIndex, int newValue)
157            {
158                    clear(channelIndex, (byte)newValue);
159            }
160    
161            public PixelImage createCompatibleImage(int width, int height)
162            {
163                    BufferedImage newBufferedImage = new BufferedImage(width, height, image.getType());
164                    return new BufferedRGB24Image(newBufferedImage);
165            }
166    
167            public PixelImage createCopy()
168            {
169                    BufferedImage newBufferedImage = new BufferedImage(getWidth(), getHeight(), image.getType());
170                    image.copyData(newBufferedImage.getRaster());
171                    return new BufferedRGB24Image(newBufferedImage);
172            }
173    
174            public long getAllocatedMemory()
175            {
176                    /* actually, number of pixels times 4 is just a guess,
177                       BufferedImage allows for all kinds of data buffers;
178                       for a more accurate approximation these data buffers
179                       must be examined */
180                    return 4L * (long)getWidth() * (long)getHeight();
181            }
182    
183            public int getBitsPerPixel()
184            {
185                    return 24;
186            }
187    
188            public byte getByteSample(int x, int y)
189            {
190                    return getByteSample(0, x, y);
191            }
192    
193            public byte getByteSample(int channelIndex, int x, int y)
194            {
195                    return (byte)((image.getRGB(x, y) >> RGB_SHIFT[channelIndex]) & 0xff);
196            }
197    
198            public void getByteSamples(int channelIndex, int x, int y, int w, int h, byte[] dest, int destOffset)
199            {
200                    final int SHIFT = RGB_SHIFT[channelIndex];
201                    int[] row = new int[w];
202                    while (h-- > 0)
203                    {
204                            image.getRGB(x, y++, w, 1, row, 0, w);
205                            int columns = w;
206                            int rowIndex = 0;
207                            while (columns-- > 0)
208                            {
209                                    dest[destOffset++] = (byte)((row[rowIndex++] >> SHIFT) & 0xff);
210                            }
211                    }
212            }
213    
214            public void getByteSamples(int x, int y, int w, int h, byte[] dest, int destOffset)
215            {
216                    getByteSamples(0, x, y, w, h, dest, destOffset);
217            }
218    
219            public int getHeight()
220            {
221                    return HEIGHT;
222            }
223    
224            public Class getImageType()
225            {
226                    return RGB24Image.class;
227            }
228    
229            public int getMaxSample(int channel)
230            {
231                    if (channel == INDEX_BLUE || channel == INDEX_RED || channel == INDEX_GREEN)
232                    {
233                            return 255;
234                    }
235                    else
236                    {
237                            throw new IllegalArgumentException("Not a valid channel index: "  + channel);
238                    }
239            }
240    
241            public int getNumChannels()
242            {
243                    return 3;
244            }
245    
246            public int getSample(int x, int y)
247            {
248                    return getSample(0, x, y);
249            }
250    
251            public int getSample(int channelIndex, int x, int y)
252            {
253                    return (image.getRGB(x, y) >> RGB_SHIFT[channelIndex]) & 0xff;
254            }
255    
256            public void getSamples(int x, int y, int w, int h, int[] dest, int destOffs)
257            {
258                    getSamples(0, x, y, w, h, dest, destOffs);
259            }
260    
261            public void getSamples(int channelIndex, int x, int y, int w, int h, int[] dest, int destOffs)
262            {
263                    final int SHIFT = RGB_SHIFT[channelIndex];
264                    int[] row = new int[w];
265                    while (h-- > 0)
266                    {
267                            image.getRGB(x, y++, w, 1, row, 0, w);
268                            int columns = w;
269                            int rowIndex = 0;
270                            while (columns-- > 0)
271                            {
272                                    dest[destOffs++] = (row[rowIndex++] >> SHIFT) & 0xff;
273                            }
274                    }
275            }
276    
277            public int getWidth()
278            {
279                    return WIDTH;
280            }
281    
282            public void putByteSample(int channelIndex, int x, int y, byte newValue)
283            {
284                    int argb = image.getRGB(x, y) & RGB_CLEAR[channelIndex];
285                    image.setRGB(x, y, argb | ((newValue & 0xff) << RGB_SHIFT[channelIndex]));
286            }
287    
288            public void putByteSample(int x, int y, byte newValue)
289            {
290                    putByteSample(0, x, y, newValue);
291            }
292    
293            public void putByteSamples(int channelIndex, int x, int y, int w, int h, byte[] src, int srcOffset)
294            {
295                    final int SHIFT = RGB_SHIFT[channelIndex];
296                    final int MASK = RGB_CLEAR[channelIndex];
297                    int[] row = new int[w];
298                    while (h-- > 0)
299                    {
300                            image.getRGB(x, y, w, 1, row, 0, w);
301                            int columns = w;
302                            int rowIndex = 0;
303                            while (columns-- > 0)
304                            {
305                                    int argb = row[rowIndex] & MASK;
306                                    row[rowIndex++] = argb | ((src[srcOffset++] & 0xff) << SHIFT);
307                            }
308                            image.setRGB(x, y++, w, 1, row, 0, w);
309                    }
310            }
311    
312            public void putByteSamples(int x, int y, int w, int h, byte[] src, int srcOffset)
313            {
314                    putByteSamples(0, x, y, w, h, src, srcOffset);
315            }
316    
317            public void putSample(int x, int y, int newValue)
318            {
319                    putSample(0, x, y, newValue);
320            }
321    
322            public void putSample(int channelIndex, int x, int y, int newValue)
323            {
324                    int argb = image.getRGB(x, y) & RGB_CLEAR[channelIndex];
325                    image.setRGB(x, y, argb | (newValue << RGB_SHIFT[channelIndex]));
326            }
327    
328            public void putSamples(int channelIndex, int x, int y, int w, int h, int[] src, int srcOffset)
329            {
330                    final int SHIFT = RGB_SHIFT[channelIndex];
331                    final int MASK = RGB_CLEAR[channelIndex];
332                    int[] row = new int[w];
333                    while (h-- > 0)
334                    {
335                            image.getRGB(x, y, w, 1, row, 0, w);
336                            int columns = w;
337                            int rowIndex = 0;
338                            while (columns-- > 0)
339                            {
340                                    int argb = row[rowIndex] & MASK;
341                                    row[rowIndex++] = argb | (src[srcOffset++] << SHIFT);
342                            }
343                            image.setRGB(x, y++, w, 1, row, 0, w);
344                    }
345            }
346    }