1 /*
   2  * Copyright (c) 2007, 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
  23  * questions.
  24  */
  25 package sun.java2d.cmm.lcms;
  26 
  27 import java.awt.image.BufferedImage;
  28 import java.awt.image.ComponentColorModel;
  29 import java.awt.image.ComponentSampleModel;
  30 import java.awt.image.DataBuffer;
  31 import java.awt.image.ColorModel;
  32 import java.awt.image.Raster;
  33 import java.awt.image.SampleModel;
  34 import sun.awt.image.ByteComponentRaster;
  35 import sun.awt.image.ShortComponentRaster;
  36 import sun.awt.image.IntegerComponentRaster;
  37 
  38 class LCMSImageLayout {
  39 
  40     public static int BYTES_SH(int x) {
  41         return x;
  42     }
  43 
  44     public static int EXTRA_SH(int x) {
  45         return x << 7;
  46     }
  47 
  48     public static int CHANNELS_SH(int x) {
  49         return x << 3;
  50     }
  51     public static final int SWAPFIRST = 1 << 14;
  52     public static final int DOSWAP = 1 << 10;
  53     public static final int PT_RGB_8 =
  54             CHANNELS_SH(3) | BYTES_SH(1);
  55     public static final int PT_GRAY_8 =
  56             CHANNELS_SH(1) | BYTES_SH(1);
  57     public static final int PT_GRAY_16 =
  58             CHANNELS_SH(1) | BYTES_SH(2);
  59     public static final int PT_RGBA_8 =
  60             EXTRA_SH(1) | CHANNELS_SH(3) | BYTES_SH(1);
  61     public static final int PT_ARGB_8 =
  62             EXTRA_SH(1) | CHANNELS_SH(3) | BYTES_SH(1) | SWAPFIRST;
  63     public static final int PT_BGR_8 =
  64             DOSWAP | CHANNELS_SH(3) | BYTES_SH(1);
  65     public static final int PT_ABGR_8 =
  66             DOSWAP | EXTRA_SH(1) | CHANNELS_SH(3) | BYTES_SH(1);
  67     public static final int PT_BGRA_8 = EXTRA_SH(1) | CHANNELS_SH(3)
  68             | BYTES_SH(1) | DOSWAP | SWAPFIRST;
  69     public static final int DT_BYTE = 0;
  70     public static final int DT_SHORT = 1;
  71     public static final int DT_INT = 2;
  72     public static final int DT_DOUBLE = 3;
  73     boolean isIntPacked = false;
  74     int pixelType;
  75     int dataType;
  76     int width;
  77     int height;
  78     int nextRowOffset;
  79     private int nextPixelOffset;
  80     int offset;
  81 
  82     /* This flag indicates whether the image can be processed
  83      * at once by doTransfrom() native call. Otherwise, the
  84      * image is processed scan by scan.
  85      */
  86     private boolean imageAtOnce = false;
  87     Object dataArray;
  88 
  89     private int dataArrayLength; /* in bytes */
  90 
  91     private LCMSImageLayout(int np, int pixelType, int pixelSize)
  92             throws ImageLayoutException
  93     {
  94         this.pixelType = pixelType;
  95         width = np;
  96         height = 1;
  97         nextPixelOffset = pixelSize;
  98         nextRowOffset = safeMult(pixelSize, np);
  99         offset = 0;
 100     }
 101 
 102     private LCMSImageLayout(int width, int height, int pixelType,
 103                             int pixelSize)
 104             throws ImageLayoutException
 105     {
 106         this.pixelType = pixelType;
 107         this.width = width;
 108         this.height = height;
 109         nextPixelOffset = pixelSize;
 110         nextRowOffset = safeMult(pixelSize, width);
 111         offset = 0;
 112     }
 113 
 114 
 115     public LCMSImageLayout(byte[] data, int np, int pixelType, int pixelSize)
 116             throws ImageLayoutException
 117     {
 118         this(np, pixelType, pixelSize);
 119         dataType = DT_BYTE;
 120         dataArray = data;
 121         dataArrayLength = data.length;
 122 
 123         verify();
 124     }
 125 
 126     public LCMSImageLayout(short[] data, int np, int pixelType, int pixelSize)
 127             throws ImageLayoutException
 128     {
 129         this(np, pixelType, pixelSize);
 130         dataType = DT_SHORT;
 131         dataArray = data;
 132         dataArrayLength = 2 * data.length;
 133 
 134         verify();
 135     }
 136 
 137     public LCMSImageLayout(int[] data, int np, int pixelType, int pixelSize)
 138             throws ImageLayoutException
 139     {
 140         this(np, pixelType, pixelSize);
 141         dataType = DT_INT;
 142         dataArray = data;
 143         dataArrayLength = 4 * data.length;
 144 
 145         verify();
 146     }
 147 
 148     public LCMSImageLayout(double[] data, int np, int pixelType, int pixelSize)
 149             throws ImageLayoutException
 150     {
 151         this(np, pixelType, pixelSize);
 152         dataType = DT_DOUBLE;
 153         dataArray = data;
 154         dataArrayLength = 8 * data.length;
 155 
 156         verify();
 157     }
 158 
 159     private LCMSImageLayout() {
 160     }
 161 
 162     /* This method creates a layout object for given image.
 163      * Returns null if the image is not supported by current implementation.
 164      */
 165     public static LCMSImageLayout createImageLayout(BufferedImage image) throws ImageLayoutException {
 166         LCMSImageLayout l = new LCMSImageLayout();
 167 
 168         switch (image.getType()) {
 169             case BufferedImage.TYPE_INT_RGB:
 170                 l.pixelType = PT_ARGB_8;
 171                 l.isIntPacked = true;
 172                 break;
 173             case BufferedImage.TYPE_INT_ARGB:
 174                 l.pixelType = PT_ARGB_8;
 175                 l.isIntPacked = true;
 176                 break;
 177             case BufferedImage.TYPE_INT_BGR:
 178                 l.pixelType = PT_ABGR_8;
 179                 l.isIntPacked = true;
 180                 break;
 181             case BufferedImage.TYPE_3BYTE_BGR:
 182                 l.pixelType = PT_BGR_8;
 183                 break;
 184             case BufferedImage.TYPE_4BYTE_ABGR:
 185                 l.pixelType = PT_ABGR_8;
 186                 break;
 187             case BufferedImage.TYPE_BYTE_GRAY:
 188                 l.pixelType = PT_GRAY_8;
 189                 break;
 190             case BufferedImage.TYPE_USHORT_GRAY:
 191                 l.pixelType = PT_GRAY_16;
 192                 break;
 193             default:
 194                 /* ColorConvertOp creates component images as
 195                  * default destination, so this kind of images
 196                  * has to be supported.
 197                  */
 198                 ColorModel cm = image.getColorModel();
 199                 if (cm instanceof ComponentColorModel) {
 200                     ComponentColorModel ccm = (ComponentColorModel) cm;
 201 
 202                     // verify whether the component size is fine
 203                     int[] cs = ccm.getComponentSize();
 204                     for (int s : cs) {
 205                         if (s != 8) {
 206                             return null;
 207                         }
 208                     }
 209 
 210                     return createImageLayout(image.getRaster());
 211 
 212                 }
 213                 return null;
 214         }
 215 
 216         l.width = image.getWidth();
 217         l.height = image.getHeight();
 218 
 219         switch (image.getType()) {
 220             case BufferedImage.TYPE_INT_RGB:
 221             case BufferedImage.TYPE_INT_ARGB:
 222             case BufferedImage.TYPE_INT_BGR:
 223                 do {
 224                     IntegerComponentRaster intRaster = (IntegerComponentRaster)
 225                             image.getRaster();
 226                     l.nextRowOffset = safeMult(4, intRaster.getScanlineStride());
 227                     l.nextPixelOffset = safeMult(4, intRaster.getPixelStride());
 228                     l.offset = safeMult(4, intRaster.getDataOffset(0));
 229                     l.dataArray = intRaster.getDataStorage();
 230                     l.dataArrayLength = 4 * intRaster.getDataStorage().length;
 231                     l.dataType = DT_INT;
 232 
 233                     if (l.nextRowOffset == l.width * 4 * intRaster.getPixelStride()) {
 234                         l.imageAtOnce = true;
 235                     }
 236                 } while (false);
 237                 break;
 238 
 239             case BufferedImage.TYPE_3BYTE_BGR:
 240             case BufferedImage.TYPE_4BYTE_ABGR:
 241                 do {
 242                     ByteComponentRaster byteRaster = (ByteComponentRaster)
 243                             image.getRaster();
 244                     l.nextRowOffset = byteRaster.getScanlineStride();
 245                     l.nextPixelOffset = byteRaster.getPixelStride();
 246 
 247                     int firstBand = image.getSampleModel().getNumBands() - 1;
 248                     l.offset = byteRaster.getDataOffset(firstBand);
 249                     l.dataArray = byteRaster.getDataStorage();
 250                     l.dataArrayLength = byteRaster.getDataStorage().length;
 251                     l.dataType = DT_BYTE;
 252                     if (l.nextRowOffset == l.width * byteRaster.getPixelStride()) {
 253                         l.imageAtOnce = true;
 254                     }
 255                 } while (false);
 256                 break;
 257 
 258             case BufferedImage.TYPE_BYTE_GRAY:
 259                 do {
 260                     ByteComponentRaster byteRaster = (ByteComponentRaster)
 261                             image.getRaster();
 262                     l.nextRowOffset = byteRaster.getScanlineStride();
 263                     l.nextPixelOffset = byteRaster.getPixelStride();
 264 
 265                     l.dataArrayLength = byteRaster.getDataStorage().length;
 266                     l.offset = byteRaster.getDataOffset(0);
 267                     l.dataArray = byteRaster.getDataStorage();
 268                     l.dataType = DT_BYTE;
 269 
 270                     if (l.nextRowOffset == l.width * byteRaster.getPixelStride()) {
 271                         l.imageAtOnce = true;
 272                     }
 273                 } while (false);
 274                 break;
 275 
 276             case BufferedImage.TYPE_USHORT_GRAY:
 277                 do {
 278                     ShortComponentRaster shortRaster = (ShortComponentRaster)
 279                             image.getRaster();
 280                     l.nextRowOffset = safeMult(2, shortRaster.getScanlineStride());
 281                     l.nextPixelOffset = safeMult(2, shortRaster.getPixelStride());
 282 
 283                     l.offset = safeMult(2, shortRaster.getDataOffset(0));
 284                     l.dataArray = shortRaster.getDataStorage();
 285                     l.dataArrayLength = 2 * shortRaster.getDataStorage().length;
 286                     l.dataType = DT_SHORT;
 287 
 288                     if (l.nextRowOffset == l.width * 2 * shortRaster.getPixelStride()) {
 289                         l.imageAtOnce = true;
 290                     }
 291                 } while (false);
 292                 break;
 293             default:
 294                 return null;
 295         }
 296         l.verify();
 297         return l;
 298     }
 299 
 300     private static enum BandOrder {
 301         DIRECT,
 302         INVERTED,
 303         ARBITRARY,
 304         UNKNOWN;
 305 
 306         public static BandOrder getBandOrder(int[] bandOffsets) {
 307             BandOrder order = UNKNOWN;
 308 
 309             int numBands = bandOffsets.length;
 310 
 311             for (int i = 0; (order != ARBITRARY) && (i < bandOffsets.length); i++) {
 312                 switch (order) {
 313                     case UNKNOWN:
 314                         if (bandOffsets[i] == i) {
 315                             order = DIRECT;
 316                         } else if (bandOffsets[i] == (numBands - 1 - i)) {
 317                             order = INVERTED;
 318                         } else {
 319                             order = ARBITRARY;
 320                         }
 321                         break;
 322                     case DIRECT:
 323                         if (bandOffsets[i] != i) {
 324                             order = ARBITRARY;
 325                         }
 326                         break;
 327                     case INVERTED:
 328                         if (bandOffsets[i] != (numBands - 1 - i)) {
 329                             order = ARBITRARY;
 330                         }
 331                         break;
 332                 }
 333             }
 334             return order;
 335         }
 336     }
 337 
 338     private void verify() throws ImageLayoutException {
 339 
 340         if (offset < 0 || offset >= dataArrayLength) {
 341             throw new ImageLayoutException("Invalid image layout");
 342         }
 343 
 344         if (nextPixelOffset != getBytesPerPixel(pixelType)) {
 345             throw new ImageLayoutException("Invalid image layout");
 346         }
 347 
 348         int lastScanOffset = safeMult(nextRowOffset, (height - 1));
 349 
 350         int lastPixelOffset = safeMult(nextPixelOffset, (width -1 ));
 351 
 352         lastPixelOffset = safeAdd(lastPixelOffset, lastScanOffset);
 353 
 354         int off = safeAdd(offset, lastPixelOffset);
 355 
 356         if (off < 0 || off >= dataArrayLength) {
 357             throw new ImageLayoutException("Invalid image layout");
 358         }
 359     }
 360 
 361     static int safeAdd(int a, int b) throws ImageLayoutException {
 362         long res = a;
 363         res += b;
 364         if (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE) {
 365             throw new ImageLayoutException("Invalid image layout");
 366         }
 367         return (int)res;
 368     }
 369 
 370     static int safeMult(int a, int b) throws ImageLayoutException {
 371         long res = a;
 372         res *= b;
 373         if (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE) {
 374             throw new ImageLayoutException("Invalid image layout");
 375         }
 376         return (int)res;
 377     }
 378 
 379     public static class ImageLayoutException extends Exception {
 380         public ImageLayoutException(String message) {
 381             super(message);
 382         }
 383     }
 384     public static LCMSImageLayout createImageLayout(Raster r) {
 385         LCMSImageLayout l = new LCMSImageLayout();
 386         if (r instanceof ByteComponentRaster) {
 387             ByteComponentRaster br = (ByteComponentRaster)r;
 388 
 389             ComponentSampleModel csm = (ComponentSampleModel)r.getSampleModel();
 390 
 391             l.pixelType = CHANNELS_SH(br.getNumBands()) | BYTES_SH(1);
 392 
 393             int[] bandOffsets = csm.getBandOffsets();
 394             BandOrder order = BandOrder.getBandOrder(bandOffsets);
 395 
 396             int firstBand = 0;
 397             switch (order) {
 398                 case INVERTED:
 399                     l.pixelType |= DOSWAP;
 400                     firstBand  = csm.getNumBands() - 1;
 401                     break;
 402                 case DIRECT:
 403                     // do nothing
 404                     break;
 405                 default:
 406                     // unable to create the image layout;
 407                     return null;
 408             }
 409 
 410             l.nextRowOffset = br.getScanlineStride();
 411             l.nextPixelOffset = br.getPixelStride();
 412 
 413             l.offset = br.getDataOffset(firstBand);
 414             l.dataArray = br.getDataStorage();
 415             l.dataType = DT_BYTE;
 416 
 417             l.width = br.getWidth();
 418             l.height = br.getHeight();
 419 
 420             if (l.nextRowOffset == l.width * br.getPixelStride()) {
 421                 l.imageAtOnce = true;
 422             }
 423             return l;
 424         }
 425         return null;
 426     }
 427 
 428     /**
 429      * Derives number of bytes per pixel from the pixel format.
 430      * Following bit fields are used here:
 431      *  [0..2] - bytes per sample
 432      *  [3..6] - number of color samples per pixel
 433      *  [7..9] - number of non-color samples per pixel
 434      *
 435      * A complete description of the pixel format can be found
 436      * here: lcms2.h, lines 651 - 667.
 437      *
 438      * @param pixelType pixel format in lcms2 notation.
 439      * @return number of bytes per pixel for given pixel format.
 440      */
 441     private static int getBytesPerPixel(int pixelType) {
 442         int bytesPerSample = (0x7 & pixelType);
 443         int colorSamplesPerPixel = 0xF & (pixelType >> 3);
 444         int extraSamplesPerPixel = 0x7 & (pixelType >> 7);
 445 
 446         return bytesPerSample * (colorSamplesPerPixel + extraSamplesPerPixel);
 447     }
 448 }