001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2013 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.image; 040 041import java.awt.color.ColorSpace; 042import java.awt.image.BufferedImage; 043import java.awt.image.ColorModel; 044import java.awt.image.DataBuffer; 045import java.awt.image.DataBufferByte; 046import java.awt.image.DataBufferInt; 047import java.awt.image.DataBufferUShort; 048import java.awt.image.DirectColorModel; 049import java.awt.image.Raster; 050import java.awt.image.SampleModel; 051import java.awt.image.WritableRaster; 052 053import org.dcm4che3.data.Tag; 054import org.dcm4che3.data.Attributes; 055 056/** 057 * @author Gunter Zeilinger <gunterze@gmail.com> 058 * 059 */ 060public class PaletteColorModel extends ColorModel { 061 062 private static final int[] opaqueBits = {8, 8, 8}; 063 064 private final LUT lut; 065 066 public PaletteColorModel(int bits, int dataType, ColorSpace cs, 067 Attributes ds) { 068 super(bits, opaqueBits, cs, false, false, OPAQUE, dataType); 069 int[] rDesc = lutDescriptor(ds, 070 Tag.RedPaletteColorLookupTableDescriptor); 071 int[] gDesc = lutDescriptor(ds, 072 Tag.GreenPaletteColorLookupTableDescriptor); 073 int[] bDesc = lutDescriptor(ds, 074 Tag.BluePaletteColorLookupTableDescriptor); 075 byte[] r = lutData(ds, rDesc, 076 Tag.RedPaletteColorLookupTableData, 077 Tag.SegmentedRedPaletteColorLookupTableData); 078 byte[] g = lutData(ds, gDesc, 079 Tag.GreenPaletteColorLookupTableData, 080 Tag.SegmentedGreenPaletteColorLookupTableData); 081 byte[] b = lutData(ds, bDesc, 082 Tag.BluePaletteColorLookupTableData, 083 Tag.SegmentedBluePaletteColorLookupTableData); 084 lut = LUT.create(bits, r, g, b, rDesc[1], gDesc[1], bDesc[1]); 085 } 086 087 private int[] lutDescriptor(Attributes ds, int descTag) { 088 int[] desc = ds.getInts(descTag); 089 if (desc == null) { 090 throw new IllegalArgumentException("Missing LUT Descriptor!"); 091 } 092 if (desc.length != 3) { 093 throw new IllegalArgumentException( 094 "Illegal number of LUT Descriptor values: " + desc.length); 095 } 096 if (desc[0] < 0) 097 throw new IllegalArgumentException( 098 "Illegal LUT Descriptor: len=" + desc[0]); 099 int bits = desc[2]; 100 if (bits != 8 && bits != 16) 101 throw new IllegalArgumentException( 102 "Illegal LUT Descriptor: bits=" + bits); 103 return desc; 104 } 105 106 private byte[] lutData(Attributes ds, int[] desc, int dataTag, int segmTag) { 107 int len = desc[0] == 0 ? 0x10000 : desc[0]; 108 int bits = desc[2]; 109 byte[] data = ds.getSafeBytes(dataTag); 110 if (data == null) { 111 int[] segm = ds.getInts(segmTag); 112 if (segm == null) { 113 throw new IllegalArgumentException("Missing LUT Data!"); 114 } 115 if (bits == 8) { 116 throw new IllegalArgumentException( 117 "Segmented LUT Data with LUT Descriptor: bits=8"); 118 } 119 data = new byte[len]; 120 inflateSegmentedLut(segm, data); 121 } else if (bits == 16 || data.length != len) { 122 if (data.length != len << 1) 123 lutLengthMismatch(data.length, len); 124 int hilo = ds.bigEndian() ? 0 : 1; 125 if (bits == 8) 126 hilo = 1 - hilo; // padded high bits -> use low bits 127 data = LookupTableFactory.halfLength(data, hilo); 128 } 129 return data; 130 } 131 132 private void inflateSegmentedLut(int[] in, byte[] out) { 133 int x = 0; 134 try { 135 for (int i = 0; i < in.length;) { 136 int op = in[i++]; 137 int n = in[i++]; 138 switch (op) { 139 case 0: 140 while (n-- > 0) 141 out[x++] = (byte) in[i++]; 142 break; 143 case 1: 144 x = linearSegment(in[i++], out, x, n); 145 break; 146 case 2: { 147 int i2 = (in[i++] & 0xffff) | (in[i++] << 16); 148 while (n-- > 0) { 149 int op2 = in[i2++]; 150 int n2 = in[i2++] & 0xffff; 151 switch (op2) { 152 case 0: 153 while (n2-- > 0) 154 out[x++] = (byte) in[i2++]; 155 break; 156 case 1: 157 x = linearSegment(in[i2++], out, x, n); 158 break; 159 default: 160 illegalOpcode(op, i2-2); 161 } 162 } 163 } 164 default: 165 illegalOpcode(op, i-2); 166 } 167 } 168 } catch (IndexOutOfBoundsException e) { 169 if (x > out.length) 170 exceedsLutLength(out.length); 171 else 172 endOfSegmentedLut(); 173 } 174 if (x < out.length) 175 lutLengthMismatch(x, out.length); 176 } 177 178 private static void endOfSegmentedLut() { 179 throw new IllegalArgumentException( 180 "Running out of data inflating segmented LUT"); 181 } 182 183 private static int linearSegment(int y1, byte[] out, int x, int n) { 184 if (x == 0) 185 throw new IllegalArgumentException( 186 "Linear segment cannot be the first segment"); 187 188 try { 189 int y0 = out[x-1]; 190 int dy = y1-y0; 191 for (int j = 1; j <= n; j++) 192 out[x++] = (byte)((y0 + dy*j/n)>>8); 193 } catch (IndexOutOfBoundsException e) { 194 exceedsLutLength(out.length); 195 } 196 return x; 197 } 198 199 private static void exceedsLutLength(int descLen) { 200 throw new IllegalArgumentException( 201 "Number of entries in inflated segmented LUT exceeds specified value: " 202 + descLen + " in LUT Descriptor"); 203 } 204 205 private static void lutLengthMismatch(int lutLen, int descLen) { 206 throw new IllegalArgumentException("Number of actual LUT entries: " 207 + lutLen + " mismatch specified value: " 208 + descLen + " in LUT Descriptor"); 209 } 210 211 private static void illegalOpcode(int op, int i) { 212 throw new IllegalArgumentException("illegal op code:" + op 213 + ", index:" + i); 214 } 215 216 @Override 217 public boolean isCompatibleRaster(Raster raster) { 218 return isCompatibleSampleModel(raster.getSampleModel()); 219 } 220 221 @Override 222 public boolean isCompatibleSampleModel(SampleModel sm) { 223 return sm.getTransferType() == transferType 224 && sm.getNumBands() == 1; 225 } 226 227 @Override 228 public int getRed(int pixel) { 229 return lut.getRed(pixel); 230 } 231 232 @Override 233 public int getGreen(int pixel) { 234 return lut.getGreen(pixel); 235 } 236 237 @Override 238 public int getBlue(int pixel) { 239 return lut.getBlue(pixel); 240 } 241 242 @Override 243 public int getAlpha(int pixel) { 244 return lut.getAlpha(pixel); 245 } 246 247 @Override 248 public int getRGB(int pixel) { 249 return lut.getRGB(pixel); 250 } 251 252 @Override 253 public WritableRaster createCompatibleWritableRaster(int w, int h) { 254 return Raster.createInterleavedRaster( 255 pixel_bits <= 8 256 ? DataBuffer.TYPE_BYTE 257 : DataBuffer.TYPE_USHORT, 258 w, h, 1, null); 259 } 260 261 public BufferedImage convertToIntDiscrete(Raster raster) { 262 if (!isCompatibleRaster(raster)) 263 throw new IllegalArgumentException( 264 "This raster is not compatible with this PaletteColorModel."); 265 266 ColorModel cm = new DirectColorModel(getColorSpace(), 24, 267 0xff0000, 0x00ff00, 0x0000ff, 0, false, DataBuffer.TYPE_INT); 268 269 int w = raster.getWidth(); 270 int h = raster.getHeight(); 271 WritableRaster discreteRaster = cm.createCompatibleWritableRaster(w, h); 272 int[] discretData = ((DataBufferInt) discreteRaster.getDataBuffer()).getData(); 273 DataBuffer data = raster.getDataBuffer(); 274 if (data instanceof DataBufferByte) { 275 byte[] pixels = ((DataBufferByte) data).getData(); 276 for (int i = 0; i < pixels.length; i++) 277 discretData[i] = getRGB(pixels[i]); 278 } else { 279 short[] pixels = ((DataBufferUShort) data).getData(); 280 for (int i = 0; i < pixels.length; i++) 281 discretData[i] = getRGB(pixels[i]); 282 } 283 return new BufferedImage(cm, discreteRaster, false, null); 284 } 285 286 private static abstract class LUT { 287 288 final int mask; 289 290 LUT(int bits) { 291 mask = (1 << bits) - 1; 292 } 293 294 public static LUT create(int bits, byte[] r, byte[] g, byte[] b, 295 int rOffset, int gOffset, int bOffset) { 296 297 return r.length == g.length && g.length == b.length 298 && rOffset == gOffset && gOffset == bOffset 299 ? new Packed(bits, r, g, b, rOffset) 300 : new PerColor(bits, r, g, b, rOffset, gOffset, bOffset); 301 } 302 303 int index(int pixel, int offset, int length) { 304 return Math.min(Math.max(0, (pixel & mask) - offset), length-1); 305 } 306 307 abstract int getRed(int pixel); 308 309 abstract int getGreen(int pixel); 310 311 abstract int getBlue(int pixel); 312 313 abstract int getAlpha(int pixel); 314 315 abstract int getRGB(int pixel); 316 317 static class Packed extends LUT { 318 319 final int offset; 320 final int[] rgb; 321 322 Packed(int bits, byte[] r, byte[] g, byte[] b, int offset) { 323 super(bits); 324 int length = r.length; 325 this.offset = offset; 326 rgb = new int[length]; 327 for (int i = 0; i < r.length; i++) 328 rgb[i] = 0xff000000 329 | ((r[i] & 0xff) << 16) 330 | ((g[i] & 0xff) << 8) 331 | (b[i] & 0xff); 332 } 333 334 @Override 335 public int getAlpha(int pixel) { 336 return (rgb[index(pixel, offset, rgb.length)] >> 24) & 0xff; 337 } 338 339 @Override 340 public int getRed(int pixel) { 341 return (rgb[index(pixel, offset, rgb.length)] >> 16) & 0xff; 342 } 343 344 @Override 345 public int getGreen(int pixel) { 346 return (rgb[index(pixel, offset, rgb.length)] >> 8) & 0xff; 347 } 348 349 @Override 350 public int getBlue(int pixel) { 351 return rgb[index(pixel, offset, rgb.length)] & 0xff; 352 } 353 354 @Override 355 public int getRGB(int pixel) { 356 return rgb[index(pixel, offset, rgb.length)]; 357 } 358 } 359 360 static class PerColor extends LUT { 361 362 final byte[] r; 363 final byte[] g; 364 final byte[] b; 365 final int rOffset; 366 final int gOffset; 367 final int bOffset; 368 369 PerColor(int bits, byte[] r, byte[] g, byte[] b, int rOffset, 370 int gbOffset, int bOffset) { 371 super(bits); 372 this.r = r; 373 this.g = g; 374 this.b = b; 375 this.rOffset = rOffset; 376 this.gOffset = gbOffset; 377 this.bOffset = bOffset; 378 } 379 380 @Override 381 public int getAlpha(int pixel) { 382 return 0xff; 383 } 384 385 @Override 386 public int getRed(int pixel) { 387 return value(pixel, rOffset, r); 388 } 389 390 @Override 391 public int getGreen(int pixel) { 392 return value(pixel, gOffset, g); 393 } 394 395 @Override 396 public int getBlue(int pixel) { 397 return value(pixel, bOffset, b); 398 } 399 400 @Override 401 public int getRGB(int pixel) { 402 return 0xff000000 403 | (value(pixel, rOffset, r) << 16) 404 | (value(pixel, gOffset, g) << 8) 405 | (value(pixel, bOffset, b)); 406 } 407 408 int value(int pixel, int offset, byte[] lut) { 409 return lut[index(pixel, offset, lut.length)] & 0xff; 410 } 411 } 412 413 } 414 415}