Package: Java2DUtils
Java2DUtils
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Java2DUtils() |
|
|
|
|
|
||||||||||||||||||||
convertByteDataBuffer(BufferedImage, DataBufferByte, PixelInterleavedSampleModel, int[], int[]) |
|
|
|
|
|
||||||||||||||||||||
convertToSinglePixelPackedSampleModel(BufferedImage) |
|
|
|
|
|
||||||||||||||||||||
convertUShortDataBuffer(BufferedImage, DataBufferUShort, PixelInterleavedSampleModel, int[], int[]) |
|
|
|
|
|
||||||||||||||||||||
createOptimizedImage(BufferedImage, double, double, Quality) |
|
|
|
|
|
||||||||||||||||||||
createOptimizedImage(int, int) |
|
|
|
|
|
||||||||||||||||||||
createSimilarImage(BufferedImage, int, int) |
|
|
|
|
|
||||||||||||||||||||
findAffineTransformInterpolation(Quality) |
|
|
|
|
|
||||||||||||||||||||
findRenderingHintsInterpolation(Quality) |
|
|
|
|
|
||||||||||||||||||||
getAveragingKernel(int) |
|
|
|
|
|
||||||||||||||||||||
getEvenAveragingKernel(int) |
|
|
|
|
|
||||||||||||||||||||
getGraphicsConfiguration() |
|
|
|
|
|
||||||||||||||||||||
getICCProfileName(RenderedImage) |
|
|
|
|
|
||||||||||||||||||||
getOddAveragingKernel(int) |
|
|
|
|
|
||||||||||||||||||||
getProperties(BufferedImage) |
|
|
|
|
|
||||||||||||||||||||
logImage(Logger, String, RenderedImage) |
|
|
|
|
|
||||||||||||||||||||
rotateWithDrawImage(BufferedImage, double, Quality) |
|
|
|
|
|
||||||||||||||||||||
scaleWithAffineTransform(BufferedImage, double, double, Quality) |
|
|
|
|
|
||||||||||||||||||||
scaleWithDrawImage(BufferedImage, double, double, Quality) |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
||||||||||||||||||||
toString(ColorSpace) |
|
|
|
|
|
||||||||||||||||||||
toString(ICC_ColorSpace) |
|
|
|
|
|
||||||||||||||||||||
toString(PixelInterleavedSampleModel) |
|
|
|
|
|
||||||||||||||||||||
toString(SampleModel) |
|
|
|
|
|
||||||||||||||||||||
toString(SinglePixelPackedSampleModel) |
|
|
|
|
|
||||||||||||||||||||
toString(int[], int) |
|
|
|
|
|
Coverage
1: /*
2: * *********************************************************************************************************************
3: *
4: * Mistral: open source imaging engine
5: * http://tidalwave.it/projects/mistral
6: *
7: * Copyright (C) 2003 - 2023 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *********************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
12: * the License. You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/mistral-src
23: * git clone https://github.com/tidalwave-it/mistral-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.image.java2d;
28:
29: import java.util.Arrays;
30: import java.util.Map;
31: import java.util.Properties;
32: import java.awt.Graphics2D;
33: import java.awt.GraphicsConfiguration;
34: import java.awt.GraphicsEnvironment;
35: import java.awt.RenderingHints;
36: import java.awt.color.ColorSpace;
37: import java.awt.color.ICC_ColorSpace;
38: import java.awt.color.ICC_Profile;
39: import java.awt.geom.AffineTransform;
40: import java.awt.image.AffineTransformOp;
41: import java.awt.image.BufferedImage;
42: import java.awt.image.DataBuffer;
43: import java.awt.image.DataBufferByte;
44: import java.awt.image.DataBufferInt;
45: import java.awt.image.DataBufferUShort;
46: import java.awt.image.DirectColorModel;
47: import java.awt.image.Kernel;
48: import java.awt.image.PixelInterleavedSampleModel;
49: import java.awt.image.Raster;
50: import java.awt.image.RenderedImage;
51: import java.awt.image.SampleModel;
52: import java.awt.image.SinglePixelPackedSampleModel;
53: import org.slf4j.Logger;
54: import it.tidalwave.image.ImageUtils;
55: import it.tidalwave.image.Kernel2;
56: import it.tidalwave.image.Quality;
57: import lombok.extern.slf4j.Slf4j;
58:
59: /***********************************************************************************************************************
60: *
61: * @author Fabrizio Giudici
62: *
63: **********************************************************************************************************************/
64: @Slf4j
65: public class Java2DUtils
66: {
67: public static final Float ZERO = (float)0;
68: private static final Map<Quality, Object> renderingHintsQualityMap =
69: Map.of(Quality.FASTEST,
70: RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
71: Quality.INTERMEDIATE,
72: RenderingHints.VALUE_INTERPOLATION_BILINEAR,
73: Quality.BEST,
74: RenderingHints.VALUE_INTERPOLATION_BICUBIC);
75:
76: private static final Map<Quality, Integer> affineTransformQualityMap =
77: Map.of(Quality.FASTEST,
78: AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
79: Quality.INTERMEDIATE,
80: AffineTransformOp.TYPE_BILINEAR,
81: Quality.BEST,
82: AffineTransformOp.TYPE_BICUBIC);
83:
84: /*******************************************************************************************************************
85: *
86: *
87: ******************************************************************************************************************/
88: public static Properties getProperties (final BufferedImage image)
89: {
90: final var properties = new Properties();
91: final var propertyNames = image.getPropertyNames();
92:
93:• if (propertyNames != null)
94: {
95:• for (final var propertyName : propertyNames)
96: {
97: final var propertyValue = image.getProperty(propertyName);
98: properties.setProperty(propertyName, propertyValue.toString());
99: }
100: }
101:
102: return properties;
103: }
104:
105: /*******************************************************************************************************************
106: *
107: * @param width
108: * @param height
109: * @return
110: *
111: ******************************************************************************************************************/
112: public static BufferedImage createOptimizedImage (final int width, final int height)
113: {
114: final var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
115: final var gs = ge.getDefaultScreenDevice();
116: final var gc = gs.getDefaultConfiguration();
117:
118: return gc.createCompatibleImage(width, height);
119: }
120:
121: /*******************************************************************************************************************
122: *
123: *
124: ******************************************************************************************************************/
125: public static GraphicsConfiguration getGraphicsConfiguration()
126: {
127: final var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
128: final var gs = ge.getScreenDevices();
129: final var gd = gs[0]; // FIXME
130:
131: return gd.getDefaultConfiguration();
132: }
133:
134: /*******************************************************************************************************************
135: *
136: *
137: ******************************************************************************************************************/
138: public static BufferedImage createSimilarImage (final BufferedImage bufferedImage,
139: final int newWidth,
140: final int newHeight)
141: {
142: final var sampleModel = bufferedImage.getSampleModel().createCompatibleSampleModel(newWidth, newHeight);
143: final var newRaster = Raster.createWritableRaster(sampleModel, null);
144: final var colorModel = bufferedImage.getColorModel();
145:
146: return new BufferedImage(colorModel, newRaster, false, Java2DUtils.getProperties(bufferedImage));
147: }
148:
149: /*******************************************************************************************************************
150: *
151: *
152: ******************************************************************************************************************/
153: public static String getICCProfileName (final RenderedImage image)
154: {
155: final var iccProfile = ImageUtils.getICCProfile(image);
156:
157:• if (iccProfile != null)
158: {
159: return ImageUtils.getICCProfileName(iccProfile);
160: }
161:
162: else
163: {
164: return null;
165: }
166: }
167:
168: /*******************************************************************************************************************
169: *
170: * It seems that SinglePixelPackedSampleModel is the only fast mode when a
171: * color profile is converted. This is probably a bug (that has nothing to do
172: * with bugs 4886071 and 4705399).
173: * Note that grayscale images (TYPE_GRAY) are not converted.
174: *
175: ******************************************************************************************************************/
176: public static BufferedImage convertToSinglePixelPackedSampleModel (BufferedImage image)
177: {
178: log.debug("convertToSinglePixelPackedSampleModel(image: " + image + ")");
179: Java2DUtils.logImage(log, ">>>> source bufferedImage", image);
180:
181: var time = System.currentTimeMillis();
182:
183: final var sourceRaster = image.getRaster();
184: var colorModel = image.getColorModel();
185: var colorSpace = (ICC_ColorSpace)colorModel.getColorSpace();
186: final var ssmd = sourceRaster.getSampleModel();
187:
188:• if (colorSpace.getType() == ColorSpace.TYPE_GRAY)
189: {
190: log.debug(">>>> TYPE_GRAY, not converting");
191: }
192:
193:• else if (!(ssmd instanceof PixelInterleavedSampleModel))
194: {
195: log.debug(">>>> sourceSampleModel is " + ssmd.getClass() + ", not converting");
196: }
197:
198: else
199: {
200: final var sourceSampleModel = (PixelInterleavedSampleModel)ssmd;
201: final var bitMasks = new int[]{0x00ff0000, 0x0000ff00, 0x000000ff};
202:
203: final var sampleModel =
204: new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, image.getWidth(),
205: image.getHeight(), bitMasks);
206:
207: final var destRaster = Raster.createWritableRaster(sampleModel, null);
208: final var destDataBuffer = (DataBufferInt)destRaster.getDataBuffer();
209: final var destBuffer = destDataBuffer.getData();
210: final var bandOffsets = sourceSampleModel.getBandOffsets();
211:
212:• for (var i = 0; i < bandOffsets.length; i++)
213: {
214: bandOffsets[i] += ((-sourceRaster.getSampleModelTranslateX() * sourceSampleModel.getPixelStride()) -
215: (sourceRaster.getSampleModelTranslateY() * sourceSampleModel.getScanlineStride()));
216: }
217:
218: final var sourceDataBuffer = sourceRaster.getDataBuffer();
219:
220:• if (sourceDataBuffer instanceof DataBufferUShort)
221: {
222: convertUShortDataBuffer(image,
223: (DataBufferUShort)sourceDataBuffer,
224: sourceSampleModel,
225: bandOffsets,
226: destBuffer);
227: }
228:
229:• else if (sourceDataBuffer instanceof DataBufferByte)
230: {
231: convertByteDataBuffer(image,
232: (DataBufferByte)sourceDataBuffer,
233: sourceSampleModel,
234: bandOffsets,
235: destBuffer);
236: }
237:
238: else
239: {
240: throw new IllegalArgumentException("Cannot deal with " + sourceDataBuffer.getClass());
241: }
242:
243: final var sourceProfileName = ImageUtils.getICCProfileName(colorSpace.getProfile());
244:
245:• if (sourceProfileName.equals("Nikon sRGB 4.0.0.3001"))
246: {
247: log.warn(">>>> Workaround #1094403: using sRGB instead of " + sourceProfileName);
248: colorSpace = new ICC_ColorSpace(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
249: }
250:
251: colorModel = new DirectColorModel(colorSpace,
252: 24,
253: bitMasks[0],
254: bitMasks[1],
255: bitMasks[2],
256: 0,
257: false,
258: DataBuffer.TYPE_INT);
259: image = new BufferedImage(colorModel, destRaster, false, null);
260: }
261:
262: time = System.currentTimeMillis() - time;
263: Java2DUtils.logImage(log, ">>>> convertToSinglePixelPackedSampleModel() returning", image);
264: log.debug(">>>> convertToSinglePixelPackedSampleModel() completed ok in " + time + " msec");
265:
266: return image;
267: }
268:
269: /*******************************************************************************************************************
270: *
271: * Scales a <code>BufferedImage</code> by filtering with an
272: * <code>AffineTransform</code>.
273: *
274: * @param bufferedImage the image to scale
275: * @param xScale the horizontal scale
276: * @param yScale the vertical scale
277: * @param quality the quality
278: *
279: ******************************************************************************************************************/
280: public static BufferedImage scaleWithAffineTransform (final BufferedImage bufferedImage,
281: final double xScale,
282: final double yScale,
283: final Quality quality)
284: throws IllegalArgumentException
285: {
286: log.debug("scaleWithAffineTransform(" + xScale + ", " + yScale + ", " + quality);
287:
288: final var transform = AffineTransform.getScaleInstance(xScale, yScale);
289: final var interpolation = findAffineTransformInterpolation(quality);
290: log.debug(">>>> AffineTransformOp(" + transform + ", " + interpolation + ")");
291:
292: final var op = new AffineTransformOp(transform, interpolation);
293:
294: return op.filter(bufferedImage, null);
295: }
296:
297: /*******************************************************************************************************************
298: *
299: * Scales a <code>BufferedImage</code> by redrawing it on a new bitmap.
300: *
301: * @param bufferedImage the image to scale
302: * @param xScale the horizontal scale
303: * @param yScale the vertical scale
304: * @param quality the quality
305: *
306: ******************************************************************************************************************/
307: public static BufferedImage scaleWithDrawImage (final BufferedImage bufferedImage,
308: final double xScale,
309: final double yScale,
310: final Quality quality)
311: throws IllegalArgumentException
312: {
313: log.debug("scaleWithDrawImage(" + xScale + ", " + yScale + ", " + quality);
314:
315: final var newWidth = (int)Math.round(bufferedImage.getWidth() * xScale);
316: final var newHeight = (int)Math.round(bufferedImage.getHeight() * yScale);
317: final var result = createSimilarImage(bufferedImage, newWidth, newHeight);
318:
319: final var g2d = (Graphics2D)result.getGraphics();
320: final var interpolation = findRenderingHintsInterpolation(quality);
321: g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation);
322:
323: try
324: {
325: g2d.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
326: }
327: finally
328: {
329: g2d.dispose();
330: }
331:
332: return result;
333: }
334:
335: /*******************************************************************************************************************
336: *
337: *
338: ******************************************************************************************************************/
339: public static BufferedImage rotateWithDrawImage (final BufferedImage bufferedImage,
340: final double degrees,
341: final Quality quality)
342: {
343: final var radians = Math.toRadians(degrees);
344: final var cos = Math.cos(radians);
345: final var sin = Math.sin(radians);
346: final var width = bufferedImage.getWidth();
347: final var height = bufferedImage.getHeight();
348: final var newWidth = (int)Math.round(Math.abs(width * cos) + Math.abs(height * sin));
349: final var newHeight = (int)Math.round(Math.abs(width * sin) + Math.abs(height * cos));
350: final var result = createSimilarImage(bufferedImage, newWidth, newHeight);
351: final var g2d = (Graphics2D)result.getGraphics();
352: final var interpolation = findRenderingHintsInterpolation(quality);
353: g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation);
354:
355: try
356: {
357: g2d.transform(AffineTransform.getTranslateInstance((newWidth - width) / 2, (newHeight - height) / 2));
358: g2d.transform(AffineTransform.getRotateInstance(-radians, width / 2, height / 2));
359: g2d.drawImage(bufferedImage, 0, 0, null);
360: }
361:
362: finally
363: {
364: g2d.dispose();
365: }
366:
367: return result;
368: }
369:
370: /*******************************************************************************************************************
371: *
372: *
373: ******************************************************************************************************************/
374: protected static String toString (final int[] array, final int radix)
375: {
376: final var buffer = new StringBuilder();
377:
378:• for (var i = 0; i < array.length; i++)
379: {
380: buffer.append(Integer.toString(array[i], radix));
381:
382:• if (i < (array.length - 1))
383: {
384: buffer.append(",");
385: }
386: }
387:
388: return buffer.toString();
389: }
390:
391: /**
392: * @param image
393: */
394:
395: /*******************************************************************************************************************
396: *
397: *
398: ******************************************************************************************************************/
399: public static void logImage (final Logger log, final String prefix, final RenderedImage image)
400: {
401:• if (Java2DUtils.log.isDebugEnabled())
402: {
403:• if (image == null)
404: {
405: Java2DUtils.log.debug(prefix + "null image");
406: }
407:
408: else
409: {
410: // image.getData(); THIS IS SLOW SLOW SLOW!!
411: final var colorModel = image.getColorModel();
412: Java2DUtils.log.debug(prefix + ".size: " + image.getWidth() + ", " + image.getHeight());
413: Java2DUtils.log.debug(prefix + ".tiles: " + image.getNumXTiles() + " " + image.getNumYTiles());
414: Java2DUtils.log.debug(prefix + ".class: " + image.getClass().getName());
415: Java2DUtils.log.debug(prefix + ".sampleModel: " + toString(image.getSampleModel()));
416:
417:• if (colorModel != null)
418: {
419: Java2DUtils.log.debug(
420: prefix + ".colorModel: " + colorModel.getClass().getName() + " : " + colorModel);
421: Java2DUtils.log.debug(prefix + ".colorSpace: " + toString(colorModel.getColorSpace()));
422: }
423: }
424:
425: // log.debug(">>>> iccProfile is now: " + getICCProfileName(bufferedImage));
426: }
427: }
428:
429: /*******************************************************************************************************************
430: *
431: *
432: ******************************************************************************************************************/
433: private static String toString (final SampleModel sampleModel)
434: {
435:• if (sampleModel instanceof SinglePixelPackedSampleModel)
436: {
437: return toString((SinglePixelPackedSampleModel)sampleModel);
438: }
439:
440:• else if (sampleModel instanceof PixelInterleavedSampleModel)
441: {
442: return toString((PixelInterleavedSampleModel)sampleModel);
443: }
444:
445: else
446: {
447: return sampleModel.toString();
448: }
449: }
450:
451: /*******************************************************************************************************************
452: *
453: *
454: ******************************************************************************************************************/
455: private static String toString (final ColorSpace colorSpace)
456: {
457:• if (colorSpace instanceof ICC_ColorSpace)
458: {
459: return toString((ICC_ColorSpace)colorSpace);
460: }
461:
462: else
463: {
464: return colorSpace.toString();
465: }
466: }
467:
468: /*******************************************************************************************************************
469: *
470: *
471: ******************************************************************************************************************/
472: private static String toString (final ICC_ColorSpace colorSpace)
473: {
474: final var buffer = new StringBuilder();
475: buffer.append(colorSpace.getClass().getName());
476: buffer.append("[type: ");
477: buffer.append(colorSpace.getType());
478: buffer.append(", profile name: ");
479: buffer.append(ImageUtils.getICCProfileName(colorSpace.getProfile()));
480: buffer.append("]");
481:
482: return buffer.toString();
483: }
484:
485: /*******************************************************************************************************************
486: *
487: *
488: ******************************************************************************************************************/
489: private static String toString (final SinglePixelPackedSampleModel sampleModel)
490: {
491: final var buffer = new StringBuilder();
492: buffer.append(sampleModel.getClass().getName());
493: buffer.append("[width: ");
494: buffer.append(sampleModel.getWidth());
495: buffer.append(", height: ");
496: buffer.append(sampleModel.getHeight());
497: buffer.append(", numBands: ");
498: buffer.append(sampleModel.getNumBands());
499: buffer.append(", dataType: ");
500: buffer.append(sampleModel.getDataType());
501: buffer.append(", scanlineStride: ");
502: buffer.append(sampleModel.getScanlineStride());
503: buffer.append(", transferType: ");
504: buffer.append(sampleModel.getTransferType());
505: buffer.append(", numDataElements: ");
506: buffer.append(sampleModel.getNumDataElements());
507: buffer.append(", bitMasks: ");
508: buffer.append(toString(sampleModel.getBitMasks(), 16));
509: buffer.append(", bitOffsets: ");
510: buffer.append(toString(sampleModel.getBitOffsets(), 10));
511: buffer.append("]");
512:
513: return buffer.toString();
514: }
515:
516: /*******************************************************************************************************************
517: *
518: *
519: ******************************************************************************************************************/
520: private static String toString (final PixelInterleavedSampleModel sampleModel)
521: {
522: final var buffer = new StringBuilder();
523: buffer.append(sampleModel.getClass().getName());
524: buffer.append("[width: ");
525: buffer.append(sampleModel.getWidth());
526: buffer.append(", height: ");
527: buffer.append(sampleModel.getHeight());
528: buffer.append(", numBands: ");
529: buffer.append(sampleModel.getNumBands());
530: buffer.append(", dataType: ");
531: buffer.append(sampleModel.getDataType());
532: buffer.append(", scanlineStride: ");
533: buffer.append(sampleModel.getScanlineStride());
534: buffer.append(", transferType: ");
535: buffer.append(sampleModel.getTransferType());
536: buffer.append(", numDataElements: ");
537: buffer.append(sampleModel.getNumDataElements());
538: buffer.append(", bandOffsets: ");
539: buffer.append(toString(sampleModel.getBandOffsets(), 10));
540: buffer.append(", bankIndices: ");
541: buffer.append(toString(sampleModel.getBankIndices(), 10));
542: buffer.append("]");
543:
544: return buffer.toString();
545: }
546:
547: /*******************************************************************************************************************
548: *
549: * @param image
550: * @param sourceDataBuffer
551: * @param sourceSampleModel
552: * @param bandOffsets
553: * @param destBuffer
554: *
555: ******************************************************************************************************************/
556: private static void convertUShortDataBuffer (final BufferedImage image,
557: final DataBufferUShort sourceDataBuffer,
558: final PixelInterleavedSampleModel sourceSampleModel,
559: final int[] bandOffsets,
560: final int[] destBuffer)
561: {
562: var base = 0;
563: var i = 0;
564: final var sourceBuffer = sourceDataBuffer.getData();
565:
566:• for (var y = 0; y < image.getHeight(); y++)
567: {
568: var j = base;
569:
570:• for (var x = 0; x < image.getWidth(); x++)
571: {
572: final var r = (sourceBuffer[j + bandOffsets[0]] & 0xffff) >> 8;
573: final var g = (sourceBuffer[j + bandOffsets[1]] & 0xffff) >> 8;
574: final var b = (sourceBuffer[j + bandOffsets[2]] & 0xffff) >> 8;
575:
576: destBuffer[i++] = (r << 16) | (g << 8) | b;
577: j += 3;
578: }
579:
580: base += sourceSampleModel.getScanlineStride();
581: }
582: }
583:
584: /**
585: * @param image
586: * @param sourceDataBuffer
587: * @param sourceSampleModel
588: * @param bandOffsets
589: * @param destBuffer
590: */
591: private static void convertByteDataBuffer (final BufferedImage image,
592: final DataBufferByte sourceDataBuffer,
593: final PixelInterleavedSampleModel sourceSampleModel,
594: final int[] bandOffsets,
595: final int[] destBuffer)
596: {
597: var base = 0;
598: var i = 0;
599: final var sourceBuffer = sourceDataBuffer.getData();
600: final var pixelStride = sourceSampleModel.getPixelStride();
601:
602:• for (var y = 0; y < image.getHeight(); y++)
603: {
604: var j = base;
605:
606:• for (var x = 0; x < image.getWidth(); x++)
607: {
608: final var r = (sourceBuffer[j + bandOffsets[0]] & 0xff);
609: final var g = (sourceBuffer[j + bandOffsets[1]] & 0xff);
610: final var b = (sourceBuffer[j + bandOffsets[2]] & 0xff);
611:
612: destBuffer[i++] = (r << 16) | (g << 8) | b;
613: j += pixelStride;
614: }
615:
616: base += sourceSampleModel.getScanlineStride();
617: }
618: }
619:
620: /*******************************************************************************************************************
621: *
622: * @param xScale
623: * @param yScale
624: * @param quality
625: * @return
626: *
627: ******************************************************************************************************************/
628: public static BufferedImage createOptimizedImage (final BufferedImage bufferedImage,
629: final double xScale,
630: final double yScale,
631: final Quality quality)
632: {
633: log.debug("createOptimizedImage(" + xScale + ", " + yScale + ", " + quality + ")");
634:
635: final var iw = (int)Math.round(xScale * bufferedImage.getWidth());
636: final var ih = (int)Math.round(yScale * bufferedImage.getHeight());
637: final var image2 = createOptimizedImage(iw, ih);
638: final var g = (Graphics2D)image2.getGraphics();
639:
640: try
641: {
642: final var interpolation = findRenderingHintsInterpolation(quality);
643:
644: // Workaround for bugs #4886071 and #4705399
645: // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4886071
646: // and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4705399
647: /*
648: * FIXME: looks like we can avoid this now.
649: if (!bufferedImage.getColorModel().getColorSpace().isCS_sRGB())
650: {
651: ICC_Profile iccProfile = ImageUtils.getICCProfile(bufferedImage);
652: String iccProfileName = ImageUtils.getICCProfileName(iccProfile);
653: if (ICC_PROFILES_WORKAROUND.contains(iccProfileName))
654: {
655: logger.info(">>>> applying workaround for bugs #4886071 and #4705399 - profile " + iccProfileName);
656:
657: BufferedImage tempImage = createOptimizedImage(bufferedImage.getWidth(), bufferedImage.getHeight());
658: long time = System.currentTimeMillis();
659:
660: //BufferedImage image2 = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage
661: * .TYPE_INT_RGB);
662: Graphics g3 = tempImage.getGraphics();
663: g3.drawImage(bufferedImage, 0, 0, null);
664: g3.dispose();
665: bufferedImage = tempImage;
666: logger.info(">>>> workaround applied in " + (System.currentTimeMillis() - time) + " msec.");
667: }
668: }
669: */
670: log.debug(">>>> applying AffineTransform.getScaleInstance() with RenderingHint: " + interpolation);
671:
672: final var renderingHintSave = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
673: g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation);
674:
675: final var transform =
676:• ((xScale == 1.0) && (yScale == 1.0)) ? null : AffineTransform.getScaleInstance(xScale, yScale);
677: g.drawRenderedImage(bufferedImage, transform);
678:
679:• if (renderingHintSave != null)
680: {
681: g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, renderingHintSave);
682: }
683:
684: log.debug(">>>>>>>> iccProfile is now: " + Java2DUtils.getICCProfileName(image2));
685: }
686:
687: finally
688: {
689: g.dispose();
690: }
691:
692: return image2;
693: }
694:
695: /*******************************************************************************************************************
696: *
697: *
698: ******************************************************************************************************************/
699: public static Object findRenderingHintsInterpolation (final Quality quality)
700: throws IllegalArgumentException
701: {
702:• if (!renderingHintsQualityMap.containsKey(quality))
703: {
704: throw new IllegalArgumentException(quality.toString());
705: }
706:
707: return renderingHintsQualityMap.get(quality);
708: }
709:
710: /*******************************************************************************************************************
711: *
712: *
713: ******************************************************************************************************************/
714: public static int findAffineTransformInterpolation (final Quality quality)
715: throws IllegalArgumentException
716: {
717:• if (!affineTransformQualityMap.containsKey(quality))
718: {
719: throw new IllegalArgumentException(quality.toString());
720: }
721:
722: return affineTransformQualityMap.get(quality);
723: }
724:
725: /*******************************************************************************************************************
726: *
727: * @param n
728: * @return
729: *
730: ******************************************************************************************************************/
731: public static Kernel getAveragingKernel (final int n)
732: {
733: Kernel kernel = null;
734:
735:• if ((n & 1) == 1)
736: {
737: kernel = getOddAveragingKernel(n);
738: }
739:
740: else
741: {
742: kernel = getEvenAveragingKernel(n);
743: }
744:
745: // log.debug(">>>>>>>> Kernel: " + kernel);
746: return kernel;
747: }
748:
749: /*******************************************************************************************************************
750: *
751: * @param n
752: * @return
753: *
754: ******************************************************************************************************************/
755: private static Kernel getEvenAveragingKernel (final int n)
756: {
757: final var r = n + 1;
758: final var totalCount = r * r;
759: final var coreCount = (r - 2) * (r - 2);
760: final var extCount = totalCount - coreCount;
761: final var a = 1.0f / ((coreCount * 2) + extCount); // core count double
762: final var coreValue = a * 2;
763: final var result = new float[totalCount];
764:
765: var j = 0;
766:
767:• for (var i = 0; i < r; i++)
768: {
769: result[j++] = a;
770: }
771:
772:• for (var k = 0; k < (r - 2); k++)
773: {
774: result[j++] = a;
775:
776:• for (var h = 0; h < (r - 2); h++)
777: {
778: result[j++] = coreValue;
779: }
780:
781: result[j++] = a;
782: }
783:
784:• for (var i = 0; i < r; i++)
785: {
786: result[j++] = a;
787: }
788:• assert j == totalCount;
789:
790: return new Kernel2(r, r, result);
791: }
792:
793: /*******************************************************************************************************************
794: *
795: * @param n
796: * @return
797: *
798: ******************************************************************************************************************/
799: private static Kernel getOddAveragingKernel (final int n)
800: {
801: final var totalCount = n * n;
802: final var v = 1.0f / (totalCount);
803: final var result = new float[totalCount];
804: Arrays.fill(result, v);
805:
806: return new Kernel2(n, n, result);
807: }
808: }