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