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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * @author Oleg V. Khaschansky
21 package org.apache.harmony.awt.gl.color;
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;
32 import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
33 import org.apache.harmony.awt.internal.nls.Messages;
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.
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).
46 class NativeImageFormat {
47 //////////////////////////////////////////////
49 private static final int PT_ANY = 0; // Don't check colorspace
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 ///////////////////////////////////////////////
56 // Conversion of predefined BufferedImage formats to LCMS formats
57 private static final int INT_RGB_LCMS_FMT =
65 private static final int INT_ARGB_LCMS_FMT = INT_RGB_LCMS_FMT;
67 private static final int INT_BGR_LCMS_FMT =
73 private static final int THREE_BYTE_BGR_LCMS_FMT =
79 private static final int FOUR_BYTE_ABGR_LCMS_FMT =
86 private static final int BYTE_GRAY_LCMS_FMT =
87 colorspaceSh(PT_GRAY)|
91 private static final int USHORT_GRAY_LCMS_FMT =
92 colorspaceSh(PT_GRAY)|
96 // LCMS format packed into 32 bit value. For description
97 // of this format refer to LCMS documentation.
98 private int cmmFormat = 0;
101 private int rows = 0;
102 private int cols = 0;
104 // Scanline may contain some padding in the end
105 private int scanlineStride = -1;
107 private Object imageData;
108 // It's possible to have offset from the beginning of the array
109 private int dataOffset;
111 // Has the image alpha channel? If has - here its band band offset goes
112 private int alphaOffset = -1;
114 // initializes proper field IDs
115 private static native void initIDs();
122 ////////////////////////////////////
123 // LCMS image format encoders
124 ////////////////////////////////////
125 private static int colorspaceSh(int s) {
129 private static int swapfirstSh(int s) {
133 private static int flavorSh(int s) {
137 private static int planarSh(int s) {
141 private static int endianSh(int s) {
145 private static int doswapSh(int s) {
149 private static int extraSh(int s) {
153 private static int channelsSh(int s) {
157 private static int bytesSh(int s) {
160 ////////////////////////////////////
161 // End of LCMS image format encoders
162 ////////////////////////////////////
165 Object getChannelData() {
178 public NativeImageFormat() {
182 * Simple image layout for common case with
183 * not optimized workflow.
185 * For hifi colorspaces with 5+ color channels imgData
186 * should be <code>byte</code> array.
188 * For common colorspaces with up to 4 color channels it
189 * should be <code>short</code> array.
191 * Alpha channel is handled by caller, not by CMS.
193 * Color channels are in their natural order (not BGR but RGB).
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
200 public NativeImageFormat(Object imgData, int nChannels, int nRows, int nCols) {
201 if (imgData instanceof short[]) {
202 cmmFormat |= bytesSh(2);
204 else if (imgData instanceof byte[]) {
205 cmmFormat |= bytesSh(1);
208 // awt.47=First argument should be byte or short array
209 throw new IllegalArgumentException(Messages.getString("awt.47")); //$NON-NLS-1$
211 cmmFormat |= channelsSh(nChannels);
222 * Deduces image format from the buffered image type
223 * or color and sample models.
225 * @return image format object
227 public static NativeImageFormat createNativeImageFormat(BufferedImage bi) {
228 NativeImageFormat fmt = new NativeImageFormat();
230 switch (bi.getType()) {
231 case BufferedImage.TYPE_INT_RGB: {
232 fmt.cmmFormat = INT_RGB_LCMS_FMT;
236 case BufferedImage.TYPE_INT_ARGB:
237 case BufferedImage.TYPE_INT_ARGB_PRE: {
238 fmt.cmmFormat = INT_ARGB_LCMS_FMT;
243 case BufferedImage.TYPE_INT_BGR: {
244 fmt.cmmFormat = INT_BGR_LCMS_FMT;
248 case BufferedImage.TYPE_3BYTE_BGR: {
249 fmt.cmmFormat = THREE_BYTE_BGR_LCMS_FMT;
253 case BufferedImage.TYPE_4BYTE_ABGR_PRE:
254 case BufferedImage.TYPE_4BYTE_ABGR: {
255 fmt.cmmFormat = FOUR_BYTE_ABGR_LCMS_FMT;
260 case BufferedImage.TYPE_BYTE_GRAY: {
261 fmt.cmmFormat = BYTE_GRAY_LCMS_FMT;
265 case BufferedImage.TYPE_USHORT_GRAY: {
266 fmt.cmmFormat = USHORT_GRAY_LCMS_FMT;
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
279 break; // Try to look at sample model and color model
283 if (fmt.cmmFormat == 0) {
284 ColorModel cm = bi.getColorModel();
285 SampleModel sm = bi.getSampleModel();
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());
298 fmt.alphaOffset = calculateAlphaOffset(sm, bi.getRaster());
301 if (fmt.cmmFormat == 0)
304 if (!fmt.setImageData(bi.getRaster().getDataBuffer())) {
308 fmt.rows = bi.getHeight();
309 fmt.cols = bi.getWidth();
311 fmt.dataOffset = bi.getRaster().getDataBuffer().getOffset();
317 * Deduces image format from the raster sample model.
319 * @return image format object
321 public static NativeImageFormat createNativeImageFormat(Raster r) {
322 NativeImageFormat fmt = new NativeImageFormat();
323 SampleModel sm = r.getSampleModel();
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);
336 if (fmt.cmmFormat == 0)
339 fmt.cols = r.getWidth();
340 fmt.rows = r.getHeight();
341 fmt.dataOffset = r.getDataBuffer().getOffset();
343 if (!fmt.setImageData(r.getDataBuffer()))
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
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) {
364 int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands();
365 int extra = hasAlpha ? 1 : 0;
367 switch (sm.getDataType()) {
368 case DataBuffer.TYPE_BYTE:
370 case DataBuffer.TYPE_SHORT:
371 case DataBuffer.TYPE_USHORT:
373 case DataBuffer.TYPE_INT:
375 case DataBuffer.TYPE_DOUBLE:
378 return 0; // Unsupported data type
383 boolean knownFormat = false;
388 for (i=0; i < sm.getNumBands(); i++) {
389 if (sm.getBandOffsets()[i] != i) break;
391 if (i == sm.getNumBands()) { // Ok, it is it
399 for (i=0; i < sm.getNumBands()-1; i++) {
400 if (sm.getBandOffsets()[i] != i+1) break;
402 if (sm.getBandOffsets()[i] == 0) i++;
403 if (i == sm.getNumBands()) { // Ok, it is it
412 for (i=0; i < sm.getNumBands()-1; i++) {
413 if (sm.getBandOffsets()[i] != sm.getNumBands() - 2 - i) break;
415 if (sm.getBandOffsets()[i] == sm.getNumBands()-1) i++;
416 if (i == sm.getNumBands()) { // Ok, it is it
425 for (i=0; i < sm.getNumBands(); i++) {
426 if (sm.getBandOffsets()[i] != sm.getNumBands() - 1 - i) break;
428 if (i == sm.getNumBands()) { // Ok, it is it
435 // XXX - Planar formats are not supported yet
440 channelsSh(channels) |
444 swapfirstSh(swapFirst);
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
453 private static int getFormatFromSPPSampleModel(SinglePixelPackedSampleModel sm,
455 // Can we extract bytes?
456 int mask = sm.getBitMasks()[0] >>> sm.getBitOffsets()[0];
457 if (!(mask == 0xFF || mask == 0xFFFF || mask == 0xFFFFFFFF))
460 // All masks are same?
461 for (int i = 1; i < sm.getNumBands(); i++) {
462 if ((sm.getBitMasks()[i] >>> sm.getBitOffsets()[i]) != mask)
467 // Check if data type is supported
468 if (sm.getDataType() == DataBuffer.TYPE_USHORT)
470 else if (sm.getDataType() == DataBuffer.TYPE_INT)
491 int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands();
492 int extra = hasAlpha ? 1 : 0;
493 extra += pixelSize/bytes - sm.getNumBands(); // Unused bytes?
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)));
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));
507 int offsets[] = new int[pixelSize/bytes];
508 for (int i=0; i<offsetsLst.size(); i++) {
509 offsets[i] = offsetsLst.get(i).intValue();
514 boolean knownFormat = false;
519 for (i=0; i < pixelSize; i++) {
520 if (offsets[i] != i) break;
522 if (i == pixelSize) { // Ok, it is it
530 for (i=0; i < pixelSize-1; i++) {
531 if (offsets[i] != i+1) break;
533 if (offsets[i] == 0) i++;
534 if (i == pixelSize) { // Ok, it is it
543 for (i=0; i < pixelSize-1; i++) {
544 if (offsets[i] != pixelSize - 2 - i) break;
546 if (offsets[i] == pixelSize-1) i++;
547 if (i == pixelSize) { // Ok, it is it
556 for (i=0; i < pixelSize; i++) {
557 if (offsets[i] != pixelSize - 1 - i) break;
559 if (i == pixelSize) { // Ok, it is it
566 // XXX - Planar formats are not supported yet
571 channelsSh(channels) |
575 swapfirstSh(swapFirst);
579 * Obtains data array from the DataBuffer object
580 * @param db - data buffer
581 * @return - true if successful
583 private boolean setImageData(DataBuffer db) {
584 AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance();
586 imageData = dbAccess.getData(db);
587 } catch (IllegalArgumentException e) {
588 return false; // Unknown data buffer type
595 * Calculates scanline stride in bytes
596 * @param csm - component sample model
598 * @return scanline stride in bytes
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;
609 * Calculates scanline stride in bytes
610 * @param sppsm - sample model
612 * @return scanline stride in bytes
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;
623 * Calculates byte offset of the alpha channel from the beginning of the pixel data
624 * @param sm - sample model
626 * @return byte offset of the alpha channel
628 private static int calculateAlphaOffset(SampleModel sm, Raster r) {
629 if (sm instanceof ComponentSampleModel) {
630 ComponentSampleModel csm = (ComponentSampleModel) sm;
632 DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8;
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;
639 return -1; // No offset, don't copy alpha