1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.jai.utils; 4 5 import com.lightcrafts.mediax.jai.*; 6 import java.awt.image.*; 7 import java.awt.image.renderable.ParameterBlock; 8 import java.awt.*; 9 import java.awt.geom.AffineTransform; 10 import java.awt.color.ColorSpace; 11 import java.awt.color.ICC_Profile; 12 import java.io.IOException; 13 import java.text.NumberFormat; 14 import java.text.DecimalFormat; 15 16 import com.lightcrafts.jai.JAIContext; 17 import com.lightcrafts.jai.operator.LCMSColorConvertDescriptor; 18 import com.lightcrafts.utils.ColorProfileInfo; 19 import com.lightcrafts.model.ImageEditor.Rendering; 20 import com.lightcrafts.model.ImageEditor.ImageProcessor; 21 import com.lightcrafts.model.Operation; 22 import com.lightcrafts.media.jai.util.ImageUtil; 23 24 /** 25 * Created by IntelliJ IDEA. 26 * User: fabio 27 * Date: Apr 7, 2005 28 * Time: 8:00:25 AM 29 */ 30 public class Functions { 31 public static boolean DEBUG = false; 32 crop(RenderedImage image, float x, float y, float width, float height, RenderingHints hints)33 static public RenderedOp crop(RenderedImage image, float x, float y, float width, float height, RenderingHints hints) { 34 ParameterBlock pb = new ParameterBlock(); 35 pb.addSource(image); 36 pb.add(x); 37 pb.add(y); 38 pb.add(width); 39 pb.add(height); 40 return JAI.create("Crop", pb, hints); 41 } 42 scaledRendering(Rendering rendering, Operation op, float scale, boolean cheap)43 static public PlanarImage scaledRendering(Rendering rendering, Operation op, float scale, boolean cheap) { 44 Rendering newRendering = rendering.clone(); 45 float oldScale = rendering.getScaleFactor(); 46 newRendering.cheapScale = cheap; 47 newRendering.setScaleFactor(scale * oldScale); 48 return newRendering.getRendering(rendering.indexOf(op)); 49 } 50 gaussianBlur(RenderedImage image, Rendering rendering, Operation op, double radius)51 static public RenderedOp gaussianBlur(RenderedImage image, Rendering rendering, 52 Operation op, double radius) { 53 return gaussianBlur(image, rendering, op, null, radius); 54 } 55 gaussianBlur(RenderedImage image, Rendering rendering, Operation op, ImageProcessor processor, double radius)56 static public RenderedOp gaussianBlur(RenderedImage image, Rendering rendering, 57 Operation op, ImageProcessor processor, double radius) { 58 double newRadius = radius; 59 float rescale = 1; 60 61 final int size = Math.min(image.getWidth(), image.getHeight()); 62 final int tileSize = Math.max( JAIContext.TILE_WIDTH, JAIContext.TILE_HEIGHT); 63 64 if (size > tileSize) { 65 while (newRadius > 32) { 66 newRadius /= 2; 67 rescale /= 2; 68 } 69 } 70 71 RenderingHints extenderHints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, 72 BorderExtender.createInstance(BorderExtender.BORDER_COPY)); 73 74 Interpolation interp = Interpolation.getInstance(Interpolation.INTERP_BICUBIC); 75 76 RenderedImage scaleDown; 77 if (rescale != 1) { 78 scaleDown = scaledRendering(rendering, op, rescale, true); 79 if (processor != null) 80 scaleDown = processor.process(scaleDown); 81 } else 82 scaleDown = processor != null ? processor.process(image) : image; 83 84 KernelJAI kernel = Functions.getGaussKernel(newRadius); 85 ParameterBlock pb = new ParameterBlock(); 86 pb.addSource(scaleDown); 87 pb.add(kernel); 88 RenderedOp blur = JAI.create("LCSeparableConvolve", pb, extenderHints); 89 90 if (rescale != 1) { 91 pb = new ParameterBlock(); 92 pb.addSource(blur); 93 pb.add(AffineTransform.getScaleInstance(image.getWidth() / (double) blur.getWidth(), 94 image.getHeight() / (double) blur.getHeight())); 95 pb.add(interp); 96 RenderingHints sourceLayoutHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, 97 new ImageLayout(0, 0, 98 JAIContext.TILE_WIDTH, 99 JAIContext.TILE_HEIGHT, 100 null, null)); 101 sourceLayoutHints.add(extenderHints); 102 // sourceLayoutHints.add(JAIContext.noCacheHint); 103 return JAI.create("Affine", pb, sourceLayoutHints); 104 } else 105 return blur; 106 } 107 getImageLayout(RenderedImage image)108 public static ImageLayout getImageLayout(RenderedImage image) { 109 return getImageLayout(image.getSampleModel().getDataType(), 110 image.getColorModel().getColorSpace()); 111 } 112 getImageLayout(RenderedImage image, int tileWidth, int tileHeight)113 public static ImageLayout getImageLayout(RenderedImage image, int tileWidth, int tileHeight) { 114 return getImageLayout(image.getSampleModel().getDataType(), 115 image.getColorModel().getColorSpace(), 116 tileWidth, tileHeight); 117 } 118 getImageLayout(RenderedImage image, int dataType)119 public static ImageLayout getImageLayout(RenderedImage image, int dataType) { 120 return getImageLayout(dataType, 121 image.getColorModel().getColorSpace()); 122 } 123 getImageLayout(int dataType, ColorSpace cs)124 public static ImageLayout getImageLayout(int dataType, ColorSpace cs) { 125 return getImageLayout(dataType, cs, JAIContext.TILE_WIDTH, JAIContext.TILE_HEIGHT); 126 } 127 getImageLayout(int dataType, ColorSpace cs, int tileWidth, int tileHeight)128 public static ImageLayout getImageLayout(int dataType, ColorSpace cs, int tileWidth, int tileHeight) { 129 ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, dataType); 130 return new ImageLayout(0, 0, tileWidth, tileHeight, cm.createCompatibleSampleModel(tileWidth, tileHeight), cm); 131 } 132 fromLinearToCS(ColorSpace target, float color[])133 public static float[] fromLinearToCS(ColorSpace target, float color[]) { 134 synchronized (ColorSpace.class) { 135 return target.fromCIEXYZ(JAIContext.linearColorSpace.toCIEXYZ(color)); 136 } 137 } 138 fromLinearToCS(ColorSpace target, int color[])139 public static int[] fromLinearToCS(ColorSpace target, int color[]) { 140 float[] converted; 141 synchronized (ColorSpace.class) { 142 converted = target.fromCIEXYZ(JAIContext.linearColorSpace.toCIEXYZ( 143 new float[]{color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f}) 144 ); 145 } 146 return new int[] {(int) (255 * converted[0]), (int) (255 * converted[1]), (int) (255 * converted[2])}; 147 } 148 gauss(double x, double s)149 public static double gauss(double x, double s) { 150 return Math.exp(-x * x / (2 * s * s)); 151 } 152 LoG(double x, double y, double s)153 public static double LoG(double x, double y, double s) { 154 double exp = (x * x + y * y) / (2 * s * s); 155 return - Math.exp(-exp) * (1 - exp) /*/ (Math.PI * Math.pow(s, 4))*/; 156 } 157 LoG(double x, double s)158 public static double LoG(double x, double s) { 159 double exp = (x * x) / (2 * s * s); 160 return - Math.exp(-exp) * (1 - exp) /*/ (Math.PI * Math.pow(s, 4))*/; 161 } 162 163 /** 164 * Generates the kernel from the current theta and kernel size. 165 */ generateLoGKernel(double theta, int kernelSize)166 public static float[] generateLoGKernel(double theta, int kernelSize) { 167 float logKernel[] = new float[kernelSize * kernelSize]; 168 int k = 0; 169 double scale = 0; 170 for (int j = 0; j < kernelSize; ++j) { 171 for (int i = 0; i < kernelSize; ++i) { 172 int x = (-kernelSize / 2) + i; 173 int y = (-kernelSize / 2) + j; 174 double value = LoG(x, y, theta); 175 scale += value; 176 logKernel[k++] = (float) value; 177 } 178 } 179 for (int i = 0; i < logKernel.length; i++) 180 logKernel[i] /= scale; 181 return logKernel; 182 } 183 logScale(double value, double max)184 static public double logScale(double value, double max) { 185 assert value >= 0 && value <= 1.0; 186 return Math.pow(max + 1, value) - 1; 187 } 188 189 static NumberFormat fmt = DecimalFormat.getInstance(); 190 LoGSharpenKernel(double radius, double gain)191 static public KernelJAI LoGSharpenKernel(double radius, double gain) { 192 if (radius < 0.00001) 193 radius = 0.00001; 194 195 int size = 5; 196 197 float data[] = generateLoGKernel(radius, size); 198 199 if (DEBUG) System.out.println("kernel data: (" + radius + ") "); 200 for (int i = 0; i < size; i++) { 201 for (int j = 0; j < size; j++) { 202 data[i + size * j] *= gain; 203 if (i == size / 2 && j == size / 2) 204 data[i + size * j] += (1 - gain); 205 if (DEBUG) System.out.print(fmt.format(data[i + size * j]) + " "); 206 } 207 if (DEBUG) System.out.println(); 208 } 209 210 return new KernelJAI(size, size, data); 211 } 212 LoGSharpenKernel2(double radius, double gain)213 static public KernelJAI LoGSharpenKernel2(double radius, double gain) { 214 if (radius < 0.00001) 215 radius = 0.00001; 216 217 int size = 5; 218 219 float data[] = generateLoGKernel(radius, size); 220 221 if (DEBUG) System.out.println("kernel data: (" + radius + ") "); 222 for (int i = 0; i < size; i++) { 223 for (int j = 0; j < size; j++) { 224 if (i == size / 2 && j == size / 2) 225 data[i + size * j] = (float) (1 + gain * (1 - data[i + size * j])); 226 else 227 data[i + size * j] *= -gain; 228 if (DEBUG) System.out.print(fmt.format(data[i + size * j]) + " "); 229 } 230 if (DEBUG) System.out.println(); 231 } 232 233 return new KernelJAI(size, size, data).getRotatedKernel(); 234 } 235 getLoGKernel(double radius)236 static public KernelJAI getLoGKernel(double radius) { 237 // boolean DEBUG = true; 238 239 if (radius < 0.00001) 240 radius = 0.00001; 241 242 int size = (int) (6 * radius + 0.5); 243 size += 1 - size & 1; 244 245 if (size < 3) 246 size = 3; 247 float data[] = new float[size]; 248 if (DEBUG) System.out.print("Radius: " + radius + ", kernel size: " + size + ", kernel data: "); 249 float positive = 0; 250 float negative = 0; 251 // float scale = 0; 252 for (int x = -size/2, j = 0; x <= size/2; x++, j++) { 253 data[j] = (float) LoG(x, radius); 254 if (data[j] > 0) 255 positive += data[j]; 256 else 257 negative += data[j]; 258 // scale += data[j]; 259 if (DEBUG) System.out.print(", " + data[j]); 260 } 261 if (DEBUG) System.out.println(); 262 for (int i = 0; i < data.length; i++) { 263 if (data[i] > 0) 264 data[i] *= (-negative/positive); 265 } 266 return new KernelJAI(size, size, size/2, size/2, data, data); 267 } 268 getLoGKernel(double radius, double gain)269 static public KernelJAI getLoGKernel(double radius, double gain) { 270 // boolean DEBUG = true; 271 272 if (radius < 0.00001) 273 radius = 0.00001; 274 275 int size = (int) (8 * radius + 0.5); 276 size += 1 - size & 1; 277 278 if (size < 3) 279 size = 3; 280 float data[] = new float[size]; 281 if (DEBUG) System.out.print("Radius: " + radius + ", kernel size: " + size + ", kernel data: "); 282 float positive = 0; 283 float negative = 0; 284 float scale = 0; 285 for (int x = -size/2, j = 0; x <= size/2; x++, j++) { 286 data[j] = (float) LoG(x, radius); 287 if (data[j] > 0) 288 positive += data[j]; 289 else 290 negative += data[j]; 291 scale += data[j]; 292 if (DEBUG) System.out.print(", " + data[j]); 293 } 294 if (DEBUG) System.out.println(); 295 if (false) { 296 for (int i = 0; i < data.length; i++) { 297 if (data[i] > 0) 298 data[i] *= -negative/positive; 299 data[i] *= -gain; 300 if (i == size / 2) 301 data[i] += (1 + gain); 302 } 303 } else { 304 for (int i = 0; i < data.length; i++) 305 data[i] /= scale; 306 } 307 return new KernelJAI(size, size, size/2, size/2, data, data); 308 } 309 getGaussKernel(double sigma)310 static public KernelJAI getGaussKernel(double sigma) { 311 if (sigma < 0.001) 312 sigma = 0.001; 313 314 int size = 2 * (int) Math.ceil(sigma) + 1; 315 316 float data[] = new float[size]; 317 int j = 0; 318 float scale = 0; 319 320 for (int x = -size/2; x <= size/2; x++) { 321 data[j++] = (float) gauss(x, sigma); 322 scale += data[j - 1]; 323 } 324 325 for (int i = 0; i < data.length; i++) 326 data[i] /= scale; 327 328 return new KernelJAI(size, size, size/2, size/2, data, data); 329 } 330 getSincKernel(double sigma)331 static public KernelJAI getSincKernel(double sigma) { 332 // boolean DEBUG = true; 333 334 if (sigma < 0.00001) 335 sigma = 0.00001; 336 337 int size = 4 * (int) Math.round(sigma) + 1; 338 339 if (size < 3) 340 size = 3; 341 float data[] = new float[size]; 342 if (DEBUG) System.out.print("Radius: " + sigma + ", kernel size: " + size + ", kernel data: "); 343 int j = 0; 344 float scale = 0; 345 for (int x = -size/2; x <= size/2; x++) { 346 data[j++] = x == 0 ? 1 : (float) Math.sin(x * sigma) / x; 347 scale += data[j - 1]; 348 if (DEBUG) System.out.print(", " + data[j - 1]); 349 } 350 if (DEBUG) System.out.println(); 351 352 for (int i = 0; i < data.length; i++) 353 data[i] /= scale; 354 return new KernelJAI(size, size, size/2, size/2, data, data); 355 } 356 lanczos2(double x)357 static public double lanczos2(double x) { 358 if (x == 0) 359 return 1; 360 else if (x > -2 && x < 2) 361 return Math.sin(Math.PI * x) * Math.sin(Math.PI * x / 2) / (Math.PI * Math.PI * x * x / 2); 362 else 363 return 0; 364 } 365 lanczos3(double x)366 static public double lanczos3(double x) { 367 if (x == 0) 368 return 1; 369 else if (x > -3 && x < 3) 370 return Math.sin(Math.PI * x) * Math.sin(Math.PI * x / 3) / (Math.PI * Math.PI * x * x / 3); 371 else 372 return 0; 373 } 374 getLanczos2Kernel(int ratio)375 static public KernelJAI getLanczos2Kernel(int ratio) { 376 /* 377 * To decimate a signal we have to sample with a frequency 378 * of 1/ratio inside the support of the filter function. 379 * 380 * The lanczos2 has a support [-2, 2] so we need 4 * ratio + 1 381 * points for a zero phase filter. 382 * 383 */ 384 385 int samples = 4 * ratio + 1; 386 float data[] = new float[samples]; 387 float sum = 0; 388 for (int i = 0; i < samples; i++) 389 sum += data[i] = (float) lanczos2(i / (double) ratio - 2.); 390 for (int i = 0; i < samples; i++) 391 data[i] /= sum; 392 return new KernelJAI(samples, samples, samples/2, samples/2, data, data); 393 } 394 getHighPassKernel(double ratio)395 static public KernelJAI getHighPassKernel(double ratio) { 396 /* 397 * To decimate a signal we have to sample with a frequency 398 * of 1/ratio inside the support of the filter function. 399 * 400 * The lanczos2 has a support [-2, 2] so we need 4 * ratio + 1 401 * points for a zero phase filter. 402 * 403 */ 404 405 int samples = 4 * (int) (ratio+0.5) + 1; 406 float data[] = new float[samples]; 407 float sum = 0; 408 for (int i = 0; i < samples; i++) 409 sum += data[i] = - (float) lanczos2(i / ratio - 2.); 410 for (int i = 0; i < samples; i++) 411 data[i] /= sum; 412 data[samples/2] += 1; 413 return new KernelJAI(samples, samples, samples/2, samples/2, data, data); 414 } 415 416 /* 417 Build an ImageLayout that works well with the underlaying OS X Core Graphics engine based on RGB buffers. 418 For some reason Java uses BGR buffers by default that require expensive translations at draw time on the Mac. 419 */ 420 getDirectImageLayout(int width, int height, ColorSpace cs)421 public static ImageLayout getDirectImageLayout(int width, int height, ColorSpace cs) { 422 ImageLayout layout = new ImageLayout(); 423 ColorModel cm = new DirectColorModel(cs, 424 32, 425 0x00ff0000, // Red 426 0x0000ff00, // Green 427 0x000000ff, // Blue 428 0x00000000, // Alpha 429 false, 430 DataBuffer.TYPE_INT); 431 432 layout.setColorModel(cm); 433 layout.setSampleModel(cm.createCompatibleSampleModel(width, height)); 434 layout.setTileWidth(JAIContext.TILE_WIDTH); 435 layout.setTileHeight(JAIContext.TILE_HEIGHT); 436 return layout; 437 } 438 439 public static class sRGBWrapper extends PlanarImage { 440 final RenderedImage source; 441 patchColorModel(ImageLayout layout, ColorModel cm)442 static ImageLayout patchColorModel(ImageLayout layout, ColorModel cm) { 443 layout.setColorModel(cm); 444 return layout; 445 } 446 sRGBWrapper(RenderedImage source)447 public sRGBWrapper(RenderedImage source) { 448 super(patchColorModel(new ImageLayout(source), 449 new ComponentColorModel(source.getSampleModel().getNumBands() == 3 450 ? JAIContext.sRGBColorSpace 451 : source.getSampleModel().getNumBands() == 4 452 ? JAIContext.CMYKColorSpace 453 : JAIContext.gray22ColorSpace, 454 false, false, 455 Transparency.OPAQUE, DataBuffer.TYPE_BYTE)), null, null); 456 this.source = source; 457 } 458 getTile(int tileX, int tileY)459 public Raster getTile(int tileX, int tileY) { 460 return source.getTile(tileX, tileY); 461 } 462 } 463 464 public static class CSWrapper extends PlanarImage { 465 final RenderedImage source; 466 patchColorModel(ImageLayout layout, ColorModel cm)467 static ImageLayout patchColorModel(ImageLayout layout, ColorModel cm) { 468 layout.setColorModel(cm); 469 return layout; 470 } 471 CSWrapper(RenderedImage source, ColorSpace cs)472 public CSWrapper(RenderedImage source, ColorSpace cs) { 473 super(patchColorModel(new ImageLayout(source), 474 new ComponentColorModel(cs, false, false, 475 Transparency.OPAQUE, 476 source.getColorModel().getTransferType())), null, null); 477 this.source = source; 478 } 479 getTile(int tileX, int tileY)480 public Raster getTile(int tileX, int tileY) { 481 return source.getTile(tileX, tileY); 482 } 483 } 484 485 private static final ColorModel sRGBColorModel = new ComponentColorModel( 486 JAIContext.sRGBColorSpace, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 487 toFastBufferedImage(RenderedImage image)488 public static BufferedImage toFastBufferedImage(RenderedImage image) { 489 // Note: we could use opImage.getAsBufferedImage(), but images thus produced 490 // are awfully inefficient for drawing which would be bad thing for thumbs 491 if (!(image instanceof BufferedImage) 492 || ((BufferedImage) image).getType() != BufferedImage.TYPE_INT_RGB) { 493 BufferedImage goodImage = new BufferedImage(image.getWidth(), image.getHeight(), 494 image.getSampleModel().getNumBands() == 1 495 ? BufferedImage.TYPE_BYTE_GRAY 496 : BufferedImage.TYPE_INT_RGB); 497 Graphics2D big = (Graphics2D) goodImage.getGraphics(); 498 if (image instanceof PlanarImage) { 499 PlanarImage opImage = image.getSampleModel().getNumBands() == 3 500 ? new sRGBWrapper(image) 501 : PlanarImage.wrapRenderedImage(image); 502 big.drawRenderedImage(opImage, AffineTransform.getTranslateInstance(-image.getMinX(), -image.getMinY())); 503 // opImage.copyData(goodImage.getRaster()); 504 opImage.dispose(); 505 } else if (image instanceof BufferedImage) { 506 BufferedImage srgbImage = new BufferedImage(sRGBColorModel, 507 ((BufferedImage) image).getRaster(), false, null); 508 big.drawRenderedImage(srgbImage, new AffineTransform()); 509 // Functions.copyData(goodImage.getRaster(), ((BufferedImage) image).getRaster()); 510 } 511 big.dispose(); 512 return goodImage; 513 } 514 return (BufferedImage) image; 515 } 516 fromByteToUShort(RenderedImage source, RenderingHints hints)517 public static RenderedOp fromByteToUShort(RenderedImage source, RenderingHints hints) { 518 // NOTE: Specifying the ImageLayout forces rescale to also perform the Format operation 519 520 ComponentColorModel cm = new ComponentColorModel(source.getColorModel().getColorSpace(), false, false, 521 Transparency.OPAQUE, DataBuffer.TYPE_USHORT); 522 523 RenderingHints formatHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, 524 new ImageLayout(0, 0, JAIContext.TILE_WIDTH, JAIContext.TILE_HEIGHT, 525 cm.createCompatibleSampleModel(source.getWidth(), 526 source.getHeight()), 527 cm)); 528 529 if (hints != null) 530 formatHints.add(hints); 531 532 final double C0 = 0; 533 final double C1 = 256.0; 534 535 ParameterBlock pb = new ParameterBlock(); 536 pb.addSource(source); 537 pb.add(new double[]{C1}); 538 pb.add(new double[]{C0}); 539 return JAI.create("Rescale", pb, formatHints); 540 } 541 fromShortToUShort(RenderedImage source, RenderingHints hints)542 public static RenderedOp fromShortToUShort(RenderedImage source, RenderingHints hints) { 543 // NOTE: Specifying the ImageLayout forces rescale to also perform the Format operation 544 545 ComponentColorModel cm = new ComponentColorModel(source.getColorModel().getColorSpace(), false, false, 546 Transparency.OPAQUE, DataBuffer.TYPE_USHORT); 547 548 RenderingHints formatHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, 549 new ImageLayout(0, 0, JAIContext.TILE_WIDTH, JAIContext.TILE_HEIGHT, 550 cm.createCompatibleSampleModel(source.getWidth(), 551 source.getHeight()), 552 cm)); 553 554 if (hints != null) 555 formatHints.add(hints); 556 557 final double C0 = 0; 558 final double C1 = 1; 559 560 ParameterBlock pb = new ParameterBlock(); 561 pb.addSource(source); 562 pb.add(new double[]{C1}); 563 pb.add(new double[]{C0}); 564 return JAI.create("Rescale", pb, formatHints); 565 } 566 fromUShortToByte(RenderedImage source, RenderingHints hints)567 public static RenderedOp fromUShortToByte(RenderedImage source, RenderingHints hints) { 568 // NOTE: Specifying the ImageLayout forces rescale to also perform the Format operation 569 570 ComponentColorModel cm = new ComponentColorModel(source.getColorModel().getColorSpace(), false, false, 571 Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 572 573 RenderingHints formatHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, 574 new ImageLayout(0, 0, JAIContext.TILE_WIDTH, JAIContext.TILE_HEIGHT, 575 cm.createCompatibleSampleModel(source.getWidth(), 576 source.getHeight()), 577 cm)); 578 579 if (hints != null) 580 formatHints.add(hints); 581 582 final double C0 = 0; 583 final double C1 = 1.0/256.0; 584 585 ParameterBlock pb = new ParameterBlock(); 586 pb.addSource(source); 587 pb.add(new double[]{C1}); 588 pb.add(new double[]{C0}); 589 return JAI.create("Rescale", pb, formatHints); 590 } 591 toColorSpace(RenderedImage source, ColorSpace cs, ICC_Profile proof, LCMSColorConvertDescriptor.RenderingIntent intent, LCMSColorConvertDescriptor.RenderingIntent proofIntent, RenderingHints hints)592 public static PlanarImage toColorSpace(RenderedImage source, ColorSpace cs, ICC_Profile proof, 593 LCMSColorConvertDescriptor.RenderingIntent intent, 594 LCMSColorConvertDescriptor.RenderingIntent proofIntent, 595 RenderingHints hints) { 596 if (source.getColorModel().getColorSpace().equals(cs)) 597 return PlanarImage.wrapRenderedImage(source); 598 599 // NOTE: specifying the ColorModel alone is not sufficient since 600 // the new image might have a different number of components 601 602 ColorModel cm = new ComponentColorModel(cs, false, false, Transparency.OPAQUE, 603 source.getColorModel().getTransferType()); 604 605 RenderingHints formatHints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, 606 new ImageLayout(0, 0, JAIContext.TILE_WIDTH, JAIContext.TILE_HEIGHT, 607 cm.createCompatibleSampleModel(source.getWidth(), 608 source.getHeight()), 609 cm)); 610 611 if (hints != null) 612 formatHints.add(hints); 613 614 ParameterBlock pb = new ParameterBlock(); 615 pb.addSource(source); 616 pb.add(cm); 617 if (intent != null) 618 pb.add(intent); 619 else 620 pb.add(LCMSColorConvertDescriptor.PERCEPTUAL); 621 if (proof != null) { 622 pb.add(proof); 623 if (proofIntent != null) 624 pb.add(proofIntent); 625 } 626 return JAI.create("LCMSColorConvert", pb, formatHints); 627 } 628 toColorSpace(RenderedImage source, ColorSpace cs, LCMSColorConvertDescriptor.RenderingIntent intent, RenderingHints hints)629 public static PlanarImage toColorSpace(RenderedImage source, ColorSpace cs, 630 LCMSColorConvertDescriptor.RenderingIntent intent, 631 RenderingHints hints) { 632 return toColorSpace(source, cs, null, intent, null, hints); 633 } 634 toColorSpace(RenderedImage source, ColorSpace cs, RenderingHints hints)635 public static PlanarImage toColorSpace(RenderedImage source, ColorSpace cs, RenderingHints hints) { 636 return toColorSpace(source, cs, null, null, null, hints); 637 } 638 toUShortLinear(PlanarImage image, RenderingHints hints)639 public static PlanarImage toUShortLinear(PlanarImage image, RenderingHints hints) { 640 // int numComponents = image.getColorModel().getNumComponents(); 641 642 ColorSpace linearCS = /* numComponents == 1 ? 643 JAIContext.linearGrayColorSpace : */ 644 JAIContext.linearColorSpace; 645 646 if (image.getColorModel().getColorSpace().equals(linearCS) 647 && image.getSampleModel().getDataType() == DataBuffer.TYPE_USHORT) 648 return image; 649 650 if (image.getColorModel().getColorSpace() == linearCS) 651 return image; 652 653 if (image.getSampleModel().getDataType() == DataBuffer.TYPE_BYTE) 654 return Functions.toColorSpace(Functions.fromByteToUShort(image, JAIContext.noCacheHint), linearCS, hints); 655 else 656 return Functions.toColorSpace(image, linearCS, hints); 657 } 658 intToBigEndian(int value, byte[] array, int index)659 public static void intToBigEndian(int value, byte[] array, int index) { 660 array[index] = (byte) (value >> 24); 661 array[index+1] = (byte) (value >> 16); 662 array[index+2] = (byte) (value >> 8); 663 array[index+3] = (byte) (value); 664 } 665 666 // Hack to extract a color profile and write it into a file as an output profile extractProfile(ICC_Profile profile, String path)667 public static void extractProfile(ICC_Profile profile, String path) { 668 if (profile.getProfileClass() != ICC_Profile.CLASS_OUTPUT) { 669 byte[] theHeader = profile.getData(ICC_Profile.icSigHead); 670 intToBigEndian (ICC_Profile.icSigOutputClass, theHeader, ICC_Profile.icHdrDeviceClass); 671 profile.setData (ICC_Profile.icSigHead, theHeader); 672 } 673 674 String profileName = ColorProfileInfo.getNameOf(profile); 675 676 try { 677 profile.write(path + profileName + ".icc"); 678 } catch (IOException e) { 679 e.printStackTrace(); 680 } 681 } 682 copyData(WritableRaster raster, Raster source)683 public static WritableRaster copyData(WritableRaster raster, Raster source) { 684 Rectangle region; // the region to be copied 685 if (raster == null) { // copy the entire image 686 region = source.getBounds(); 687 688 SampleModel sm = source.getSampleModel(); 689 if(sm.getWidth() != region.width || 690 sm.getHeight() != region.height) { 691 sm = sm.createCompatibleSampleModel(region.width, 692 region.height); 693 } 694 raster = Raster.createWritableRaster(sm, region.getLocation()); 695 } else { 696 region = raster.getBounds().intersection(source.getBounds()); 697 698 if (region.isEmpty()) { // Raster is outside of image's boundary 699 return raster; 700 } 701 } 702 703 SampleModel[] sampleModels = { source.getSampleModel() }; 704 int tagID = RasterAccessor.findCompatibleTag(sampleModels, 705 raster.getSampleModel()); 706 707 RasterFormatTag srcTag = new RasterFormatTag(source.getSampleModel(),tagID); 708 RasterFormatTag dstTag = 709 new RasterFormatTag(raster.getSampleModel(),tagID); 710 711 Rectangle subRegion = region.intersection(source.getBounds()); 712 713 RasterAccessor s = new RasterAccessor(source, subRegion, 714 srcTag, null); 715 RasterAccessor d = new RasterAccessor(raster, subRegion, 716 dstTag, null); 717 718 if (source.getSampleModel() instanceof ComponentSampleModel && 719 raster.getSampleModel() instanceof ComponentSampleModel) { 720 ComponentSampleModel ssm = (ComponentSampleModel) source.getSampleModel(); 721 722 if (ssm.getPixelStride() == ssm.getNumBands() && 723 source.getSampleModel().getNumBands() == raster.getSampleModel().getNumBands()) 724 fastCopyRaster(s, d); 725 else 726 ImageUtil.copyRaster(s, d); 727 } else 728 ImageUtil.copyRaster(s, d); 729 730 return raster; 731 } 732 fastCopyRaster(RasterAccessor src, RasterAccessor dst)733 private static void fastCopyRaster(RasterAccessor src, 734 RasterAccessor dst) { 735 int srcLineStride = src.getScanlineStride(); 736 int[] srcBandOffsets = src.getBandOffsets(); 737 738 int dstPixelStride = dst.getPixelStride(); 739 int dstLineStride = dst.getScanlineStride(); 740 int[] dstBandOffsets = dst.getBandOffsets(); 741 742 int width = dst.getWidth() * dstPixelStride; 743 int height = dst.getHeight() * dstLineStride; 744 745 int dataType = src.getDataType(); 746 747 final Object s, d; 748 749 if (dataType == DataBuffer.TYPE_BYTE) { 750 s = src.getByteDataArray(0); 751 d = dst.getByteDataArray(0); 752 } else if (dataType == DataBuffer.TYPE_SHORT || 753 dataType == DataBuffer.TYPE_USHORT) { 754 s = src.getShortDataArray(0); 755 d = dst.getShortDataArray(0); 756 } else if (dataType == DataBuffer.TYPE_INT) { 757 s = src.getIntDataArray(0); 758 d = dst.getIntDataArray(0); 759 } else if (dataType == DataBuffer.TYPE_FLOAT) { 760 s = src.getFloatDataArray(0); 761 d = dst.getFloatDataArray(0); 762 } else if (dataType == DataBuffer.TYPE_DOUBLE) { 763 s = src.getDoubleDataArray(0); 764 d = dst.getDoubleDataArray(0); 765 } else 766 throw new IllegalArgumentException(); 767 768 int srcOffset = Integer.MAX_VALUE; 769 for (int offset : srcBandOffsets) 770 if (offset < srcOffset) 771 srcOffset = offset; 772 int dstOffset = Integer.MAX_VALUE; 773 for (int offset : dstBandOffsets) 774 if (offset < dstOffset) 775 dstOffset = offset; 776 777 int heightEnd = dstOffset + height; 778 779 for (int dstLineOffset = dstOffset, 780 srcLineOffset = srcOffset; 781 dstLineOffset < heightEnd; 782 dstLineOffset += dstLineStride, 783 srcLineOffset += srcLineStride) { 784 785 System.arraycopy(s, srcLineOffset, d, dstLineOffset, width); 786 } 787 } 788 } 789