• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 /**
18  * @author Oleg V. Khaschansky
19  * @version $Revision$
20  */
21 package org.apache.harmony.awt.gl.color;
22 
23 import java.awt.image.BufferedImage;
24 import java.awt.image.ColorModel;
25 import java.awt.image.ComponentSampleModel;
26 import java.awt.image.DataBuffer;
27 import java.awt.image.Raster;
28 import java.awt.image.SampleModel;
29 import java.awt.image.SinglePixelPackedSampleModel;
30 import java.util.ArrayList;
31 
32 import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
33 import org.apache.harmony.awt.internal.nls.Messages;
34 
35 
36 /**
37  * This class converts java color/sample models to the LCMS pixel formats.
38  * It also encapsulates all the information about the image format, which native CMM
39  * needs to have in order to read/write data.
40  *
41  * At present planar formats (multiple bands) are not supported
42  * and they are handled as a common (custom) case.
43  * Samples other than 1 - 7 bytes and multiple of 8 bits are
44  * also handled as custom (and won't be supported in the nearest future).
45  */
46 class NativeImageFormat {
47     //////////////////////////////////////////////
48     //  LCMS Pixel types
49     private static final int PT_ANY = 0;    // Don't check colorspace
50     // 1 & 2 are reserved
51     private static final int PT_GRAY     = 3;
52     private static final int PT_RGB      = 4;
53     // Skipping other since we don't use them here
54     ///////////////////////////////////////////////
55 
56     // Conversion of predefined BufferedImage formats to LCMS formats
57     private static final int INT_RGB_LCMS_FMT =
58         colorspaceSh(PT_RGB)|
59         extraSh(1)|
60         channelsSh(3)|
61         bytesSh(1)|
62         doswapSh(1)|
63         swapfirstSh(1);
64 
65     private static final int INT_ARGB_LCMS_FMT = INT_RGB_LCMS_FMT;
66 
67     private static final int INT_BGR_LCMS_FMT =
68         colorspaceSh(PT_RGB)|
69         extraSh(1)|
70         channelsSh(3)|
71         bytesSh(1);
72 
73     private static final int THREE_BYTE_BGR_LCMS_FMT =
74         colorspaceSh(PT_RGB)|
75         channelsSh(3)|
76         bytesSh(1)|
77         doswapSh(1);
78 
79     private static final int FOUR_BYTE_ABGR_LCMS_FMT =
80         colorspaceSh(PT_RGB)|
81         extraSh(1)|
82         channelsSh(3)|
83         bytesSh(1)|
84         doswapSh(1);
85 
86     private static final int BYTE_GRAY_LCMS_FMT =
87         colorspaceSh(PT_GRAY)|
88         channelsSh(1)|
89         bytesSh(1);
90 
91     private static final int USHORT_GRAY_LCMS_FMT =
92         colorspaceSh(PT_GRAY)|
93         channelsSh(1)|
94         bytesSh(2);
95 
96     // LCMS format packed into 32 bit value. For description
97     // of this format refer to LCMS documentation.
98     private int cmmFormat = 0;
99 
100     // Dimensions
101     private int rows = 0;
102     private int cols = 0;
103 
104     //  Scanline may contain some padding in the end
105     private int scanlineStride = -1;
106 
107     private Object imageData;
108     // It's possible to have offset from the beginning of the array
109     private int dataOffset;
110 
111     // Has the image alpha channel? If has - here its band band offset goes
112     private int alphaOffset = -1;
113 
114     // initializes proper field IDs
initIDs()115     private static native void initIDs();
116 
117     static {
NativeCMM.loadCMM()118         NativeCMM.loadCMM();
initIDs()119         initIDs();
120     }
121 
122     ////////////////////////////////////
123     // LCMS image format encoders
124     ////////////////////////////////////
colorspaceSh(int s)125     private static int colorspaceSh(int s) {
126         return (s << 16);
127     }
128 
swapfirstSh(int s)129     private static int swapfirstSh(int s) {
130         return (s << 14);
131     }
132 
flavorSh(int s)133     private static int flavorSh(int s) {
134         return (s << 13);
135     }
136 
planarSh(int s)137     private static int planarSh(int s) {
138         return (s << 12);
139     }
140 
endianSh(int s)141     private static int endianSh(int s) {
142         return (s << 11);
143     }
144 
doswapSh(int s)145     private static int doswapSh(int s) {
146         return (s << 10);
147     }
148 
extraSh(int s)149     private static int extraSh(int s) {
150         return (s << 7);
151     }
152 
channelsSh(int s)153     private static int channelsSh(int s) {
154         return (s << 3);
155     }
156 
bytesSh(int s)157     private static int bytesSh(int s) {
158         return s;
159     }
160     ////////////////////////////////////
161     // End of LCMS image format encoders
162     ////////////////////////////////////
163 
164     // Accessors
getChannelData()165     Object getChannelData() {
166         return imageData;
167     }
168 
getNumCols()169     int getNumCols() {
170         return cols;
171     }
172 
getNumRows()173     int getNumRows() {
174         return rows;
175     }
176 
177     // Constructors
NativeImageFormat()178     public NativeImageFormat() {
179     }
180 
181     /**
182      * Simple image layout for common case with
183      * not optimized workflow.
184      *
185      * For hifi colorspaces with 5+ color channels imgData
186      * should be <code>byte</code> array.
187      *
188      * For common colorspaces with up to 4 color channels it
189      * should be <code>short</code> array.
190      *
191      * Alpha channel is handled by caller, not by CMS.
192      *
193      * Color channels are in their natural order (not BGR but RGB).
194      *
195      * @param imgData - array of <code>byte</code> or <code>short</code>
196      * @param nChannels - number of channels
197      * @param nRows - number of scanlines in the image
198      * @param nCols - number of pixels in one row of the image
199      */
NativeImageFormat(Object imgData, int nChannels, int nRows, int nCols)200     public NativeImageFormat(Object imgData, int nChannels, int nRows, int nCols) {
201         if (imgData instanceof short[]) {
202             cmmFormat |= bytesSh(2);
203         }
204         else if (imgData instanceof byte[]) {
205             cmmFormat |= bytesSh(1);
206         }
207         else
208             // awt.47=First argument should be byte or short array
209             throw new IllegalArgumentException(Messages.getString("awt.47")); //$NON-NLS-1$
210 
211         cmmFormat |= channelsSh(nChannels);
212 
213         rows = nRows;
214         cols = nCols;
215 
216         imageData = imgData;
217 
218         dataOffset = 0;
219     }
220 
221     /**
222      * Deduces image format from the buffered image type
223      * or color and sample models.
224      * @param bi - image
225      * @return image format object
226      */
createNativeImageFormat(BufferedImage bi)227     public static NativeImageFormat createNativeImageFormat(BufferedImage bi) {
228         NativeImageFormat fmt = new NativeImageFormat();
229 
230         switch (bi.getType()) {
231             case BufferedImage.TYPE_INT_RGB: {
232                 fmt.cmmFormat = INT_RGB_LCMS_FMT;
233                 break;
234             }
235 
236             case BufferedImage.TYPE_INT_ARGB:
237             case BufferedImage.TYPE_INT_ARGB_PRE: {
238                 fmt.cmmFormat = INT_ARGB_LCMS_FMT;
239                 fmt.alphaOffset = 3;
240                 break;
241             }
242 
243             case BufferedImage.TYPE_INT_BGR: {
244                 fmt.cmmFormat = INT_BGR_LCMS_FMT;
245                 break;
246             }
247 
248             case BufferedImage.TYPE_3BYTE_BGR: {
249                 fmt.cmmFormat = THREE_BYTE_BGR_LCMS_FMT;
250                 break;
251             }
252 
253             case BufferedImage.TYPE_4BYTE_ABGR_PRE:
254             case BufferedImage.TYPE_4BYTE_ABGR: {
255                 fmt.cmmFormat = FOUR_BYTE_ABGR_LCMS_FMT;
256                 fmt.alphaOffset = 0;
257                 break;
258             }
259 
260             case BufferedImage.TYPE_BYTE_GRAY: {
261                 fmt.cmmFormat = BYTE_GRAY_LCMS_FMT;
262                 break;
263             }
264 
265             case BufferedImage.TYPE_USHORT_GRAY: {
266                 fmt.cmmFormat = USHORT_GRAY_LCMS_FMT;
267                 break;
268             }
269 
270             case BufferedImage.TYPE_BYTE_BINARY:
271             case BufferedImage.TYPE_USHORT_565_RGB:
272             case BufferedImage.TYPE_USHORT_555_RGB:
273             case BufferedImage.TYPE_BYTE_INDEXED: {
274                 // A bunch of unsupported formats
275                 return null;
276             }
277 
278             default:
279                 break; // Try to look at sample model and color model
280         }
281 
282 
283         if (fmt.cmmFormat == 0) {
284             ColorModel cm = bi.getColorModel();
285             SampleModel sm = bi.getSampleModel();
286 
287             if (sm instanceof ComponentSampleModel) {
288                 ComponentSampleModel csm = (ComponentSampleModel) sm;
289                 fmt.cmmFormat = getFormatFromComponentModel(csm, cm.hasAlpha());
290                 fmt.scanlineStride = calculateScanlineStrideCSM(csm, bi.getRaster());
291             } else if (sm instanceof SinglePixelPackedSampleModel) {
292                 SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm;
293                 fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, cm.hasAlpha());
294                 fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, bi.getRaster());
295             }
296 
297             if (cm.hasAlpha())
298                 fmt.alphaOffset = calculateAlphaOffset(sm, bi.getRaster());
299         }
300 
301         if (fmt.cmmFormat == 0)
302             return null;
303 
304         if (!fmt.setImageData(bi.getRaster().getDataBuffer())) {
305             return null;
306         }
307 
308         fmt.rows = bi.getHeight();
309         fmt.cols = bi.getWidth();
310 
311         fmt.dataOffset = bi.getRaster().getDataBuffer().getOffset();
312 
313         return fmt;
314     }
315 
316     /**
317      * Deduces image format from the raster sample model.
318      * @param r - raster
319      * @return image format object
320      */
createNativeImageFormat(Raster r)321     public static NativeImageFormat createNativeImageFormat(Raster r) {
322         NativeImageFormat fmt = new NativeImageFormat();
323         SampleModel sm = r.getSampleModel();
324 
325         // Assume that there's no alpha
326         if (sm instanceof ComponentSampleModel) {
327             ComponentSampleModel csm = (ComponentSampleModel) sm;
328             fmt.cmmFormat = getFormatFromComponentModel(csm, false);
329             fmt.scanlineStride = calculateScanlineStrideCSM(csm, r);
330         } else if (sm instanceof SinglePixelPackedSampleModel) {
331             SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm;
332             fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, false);
333             fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, r);
334         }
335 
336         if (fmt.cmmFormat == 0)
337             return null;
338 
339         fmt.cols = r.getWidth();
340         fmt.rows = r.getHeight();
341         fmt.dataOffset = r.getDataBuffer().getOffset();
342 
343         if (!fmt.setImageData(r.getDataBuffer()))
344             return null;
345 
346         return fmt;
347     }
348 
349     /**
350      * Obtains LCMS format from the component sample model
351      * @param sm - sample model
352      * @param hasAlpha - true if there's an alpha channel
353      * @return LCMS format
354      */
getFormatFromComponentModel(ComponentSampleModel sm, boolean hasAlpha)355     private static int getFormatFromComponentModel(ComponentSampleModel sm, boolean hasAlpha) {
356         // Multiple data arrays (banks) not supported
357         int bankIndex = sm.getBankIndices()[0];
358         for (int i=1; i < sm.getNumBands(); i++) {
359             if (sm.getBankIndices()[i] != bankIndex) {
360                 return 0;
361             }
362         }
363 
364         int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands();
365         int extra = hasAlpha ? 1 : 0;
366         int bytes = 1;
367         switch (sm.getDataType()) {
368             case DataBuffer.TYPE_BYTE:
369                 bytes = 1; break;
370             case DataBuffer.TYPE_SHORT:
371             case DataBuffer.TYPE_USHORT:
372                 bytes = 2; break;
373             case DataBuffer.TYPE_INT:
374                 bytes = 4; break;
375             case DataBuffer.TYPE_DOUBLE:
376                 bytes = 0; break;
377             default:
378                 return 0; // Unsupported data type
379         }
380 
381         int doSwap = 0;
382         int swapFirst = 0;
383         boolean knownFormat = false;
384 
385         int i;
386 
387         // "RGBA"
388         for (i=0; i < sm.getNumBands(); i++) {
389             if (sm.getBandOffsets()[i] != i) break;
390         }
391         if (i == sm.getNumBands()) { // Ok, it is it
392             doSwap = 0;
393             swapFirst = 0;
394             knownFormat = true;
395         }
396 
397         // "ARGB"
398         if (!knownFormat) {
399             for (i=0; i < sm.getNumBands()-1; i++) {
400                 if (sm.getBandOffsets()[i] != i+1) break;
401             }
402             if (sm.getBandOffsets()[i] == 0) i++;
403             if (i == sm.getNumBands()) { // Ok, it is it
404                 doSwap = 0;
405                 swapFirst = 1;
406                 knownFormat = true;
407             }
408         }
409 
410         // "BGRA"
411         if (!knownFormat) {
412             for (i=0; i < sm.getNumBands()-1; i++) {
413                 if (sm.getBandOffsets()[i] != sm.getNumBands() - 2 - i) break;
414             }
415             if (sm.getBandOffsets()[i] == sm.getNumBands()-1) i++;
416             if (i == sm.getNumBands()) { // Ok, it is it
417                 doSwap = 1;
418                 swapFirst = 1;
419                 knownFormat = true;
420             }
421         }
422 
423         // "ABGR"
424         if (!knownFormat) {
425             for (i=0; i < sm.getNumBands(); i++) {
426                 if (sm.getBandOffsets()[i] != sm.getNumBands() - 1 - i) break;
427             }
428             if (i == sm.getNumBands()) { // Ok, it is it
429                 doSwap = 1;
430                 swapFirst = 0;
431                 knownFormat = true;
432             }
433         }
434 
435         // XXX - Planar formats are not supported yet
436         if (!knownFormat)
437             return 0;
438 
439         return
440             channelsSh(channels) |
441             bytesSh(bytes) |
442             extraSh(extra) |
443             doswapSh(doSwap) |
444             swapfirstSh(swapFirst);
445     }
446 
447     /**
448      * Obtains LCMS format from the single pixel packed sample model
449      * @param sm - sample model
450      * @param hasAlpha - true if there's an alpha channel
451      * @return LCMS format
452      */
getFormatFromSPPSampleModel(SinglePixelPackedSampleModel sm, boolean hasAlpha)453     private static int getFormatFromSPPSampleModel(SinglePixelPackedSampleModel sm,
454             boolean hasAlpha) {
455         // Can we extract bytes?
456         int mask = sm.getBitMasks()[0] >>> sm.getBitOffsets()[0];
457         if (!(mask == 0xFF || mask == 0xFFFF || mask == 0xFFFFFFFF))
458             return 0;
459 
460         // All masks are same?
461         for (int i = 1; i < sm.getNumBands(); i++) {
462             if ((sm.getBitMasks()[i] >>> sm.getBitOffsets()[i]) != mask)
463                 return 0;
464         }
465 
466         int pixelSize = 0;
467         // Check if data type is supported
468         if (sm.getDataType() == DataBuffer.TYPE_USHORT)
469             pixelSize = 2;
470         else if (sm.getDataType() == DataBuffer.TYPE_INT)
471             pixelSize = 4;
472         else
473             return 0;
474 
475 
476         int bytes = 0;
477         switch (mask) {
478             case 0xFF:
479                 bytes = 1;
480                 break;
481             case 0xFFFF:
482                 bytes = 2;
483                 break;
484             case 0xFFFFFFFF:
485                 bytes = 4;
486                 break;
487             default: return 0;
488         }
489 
490 
491         int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands();
492         int extra = hasAlpha ? 1 : 0;
493         extra +=  pixelSize/bytes - sm.getNumBands(); // Unused bytes?
494 
495         // Form an ArrayList containing offset for each band
496         ArrayList<Integer> offsetsLst = new ArrayList<Integer>();
497         for (int k=0; k < sm.getNumBands(); k++) {
498             offsetsLst.add(new Integer(sm.getBitOffsets()[k]/(bytes*8)));
499         }
500 
501         // Add offsets for unused space
502         for (int i=0; i<pixelSize/bytes; i++) {
503             if (offsetsLst.indexOf(new Integer(i)) < 0)
504                 offsetsLst.add(new Integer(i));
505         }
506 
507         int offsets[] = new int[pixelSize/bytes];
508         for (int i=0; i<offsetsLst.size(); i++) {
509             offsets[i] = offsetsLst.get(i).intValue();
510         }
511 
512         int doSwap = 0;
513         int swapFirst = 0;
514         boolean knownFormat = false;
515 
516         int i;
517 
518         // "RGBA"
519         for (i=0; i < pixelSize; i++) {
520             if (offsets[i] != i) break;
521         }
522         if (i == pixelSize) { // Ok, it is it
523             doSwap = 0;
524             swapFirst = 0;
525             knownFormat = true;
526         }
527 
528         // "ARGB"
529         if (!knownFormat) {
530             for (i=0; i < pixelSize-1; i++) {
531                 if (offsets[i] != i+1) break;
532             }
533             if (offsets[i] == 0) i++;
534             if (i == pixelSize) { // Ok, it is it
535                 doSwap = 0;
536                 swapFirst = 1;
537                 knownFormat = true;
538             }
539         }
540 
541         // "BGRA"
542         if (!knownFormat) {
543             for (i=0; i < pixelSize-1; i++) {
544                 if (offsets[i] != pixelSize - 2 - i) break;
545             }
546             if (offsets[i] == pixelSize-1) i++;
547             if (i == pixelSize) { // Ok, it is it
548                 doSwap = 1;
549                 swapFirst = 1;
550                 knownFormat = true;
551             }
552         }
553 
554         // "ABGR"
555         if (!knownFormat) {
556             for (i=0; i < pixelSize; i++) {
557                 if (offsets[i] != pixelSize - 1 - i) break;
558             }
559             if (i == pixelSize) { // Ok, it is it
560                 doSwap = 1;
561                 swapFirst = 0;
562                 knownFormat = true;
563             }
564         }
565 
566         // XXX - Planar formats are not supported yet
567         if (!knownFormat)
568             return 0;
569 
570         return
571             channelsSh(channels) |
572             bytesSh(bytes) |
573             extraSh(extra) |
574             doswapSh(doSwap) |
575             swapfirstSh(swapFirst);
576     }
577 
578     /**
579      * Obtains data array from the DataBuffer object
580      * @param db - data buffer
581      * @return - true if successful
582      */
setImageData(DataBuffer db)583     private boolean setImageData(DataBuffer db) {
584         AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance();
585         try {
586             imageData = dbAccess.getData(db);
587         } catch (IllegalArgumentException e) {
588             return false; // Unknown data buffer type
589         }
590 
591         return true;
592     }
593 
594     /**
595      * Calculates scanline stride in bytes
596      * @param csm - component sample model
597      * @param r - raster
598      * @return scanline stride in bytes
599      */
calculateScanlineStrideCSM(ComponentSampleModel csm, Raster r)600     private static int calculateScanlineStrideCSM(ComponentSampleModel csm, Raster r) {
601         if (csm.getScanlineStride() != csm.getPixelStride()*csm.getWidth()) {
602             int dataTypeSize = DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8;
603             return csm.getScanlineStride()*dataTypeSize;
604         }
605         return -1;
606     }
607 
608     /**
609      * Calculates scanline stride in bytes
610      * @param sppsm - sample model
611      * @param r - raster
612      * @return scanline stride in bytes
613      */
calculateScanlineStrideSPPSM(SinglePixelPackedSampleModel sppsm, Raster r)614     private static int calculateScanlineStrideSPPSM(SinglePixelPackedSampleModel sppsm, Raster r) {
615         if (sppsm.getScanlineStride() != sppsm.getWidth()) {
616             int dataTypeSize = DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8;
617             return sppsm.getScanlineStride()*dataTypeSize;
618         }
619         return -1;
620     }
621 
622     /**
623      * Calculates byte offset of the alpha channel from the beginning of the pixel data
624      * @param sm - sample model
625      * @param r - raster
626      * @return byte offset of the alpha channel
627      */
calculateAlphaOffset(SampleModel sm, Raster r)628     private static int calculateAlphaOffset(SampleModel sm, Raster r) {
629         if (sm instanceof ComponentSampleModel) {
630             ComponentSampleModel csm = (ComponentSampleModel) sm;
631             int dataTypeSize =
632                 DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8;
633             return
634                 csm.getBandOffsets()[csm.getBandOffsets().length - 1] * dataTypeSize;
635         } else if (sm instanceof SinglePixelPackedSampleModel) {
636             SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm;
637             return sppsm.getBitOffsets()[sppsm.getBitOffsets().length - 1] / 8;
638         } else {
639             return -1; // No offset, don't copy alpha
640         }
641     }
642 }
643