1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.jai.opimage; 4 5 import com.lightcrafts.mediax.jai.PointOpImage; 6 import com.lightcrafts.mediax.jai.ImageLayout; 7 import com.lightcrafts.mediax.jai.RasterAccessor; 8 import com.lightcrafts.mediax.jai.RasterFormatTag; 9 import com.lightcrafts.jai.JAIContext; 10 11 import java.awt.image.DataBuffer; 12 import java.awt.image.RenderedImage; 13 import java.awt.image.Raster; 14 import java.awt.image.WritableRaster; 15 import java.awt.*; 16 import java.awt.color.ICC_ProfileRGB; 17 import java.awt.color.ICC_Profile; 18 import java.awt.color.ColorSpace; 19 import java.util.Map; 20 21 import Jama.Matrix; 22 23 /** 24 * Copyright (C) Light Crafts, Inc. 25 * User: fabio 26 * Date: Mar 20, 2007 27 * Time: 4:32:46 PM 28 */ 29 public class VibranceOpImage extends PointOpImage { 30 private final float transform[][]; 31 private final boolean saturationIncrease; 32 private final float[][] toLinearsRGB; 33 VibranceOpImage(RenderedImage source, float transform[][], Map config)34 public VibranceOpImage(RenderedImage source, float transform[][], Map config) { 35 super(source, new ImageLayout(source), config, true); 36 permitInPlaceOperation(); 37 this.transform = transform; 38 39 saturationIncrease = transform[0][0] > 1; 40 41 ICC_ProfileRGB linRGB = (ICC_ProfileRGB) ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB); 42 toLinearsRGB = new Matrix(linRGB.getMatrix()).inverse().times(new Matrix(((ICC_ProfileRGB) JAIContext.linearProfile).getMatrix())).getArrayFloat(); 43 } 44 computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect)45 protected void computeRect(Raster[] sources, 46 WritableRaster dest, 47 Rectangle destRect) { 48 // Retrieve format tags. 49 RasterFormatTag[] formatTags = getFormatTags(); 50 51 RasterAccessor src = new RasterAccessor(sources[0], destRect, formatTags[0], 52 getSourceImage(0).getColorModel()); 53 RasterAccessor dst = new RasterAccessor(dest, destRect, formatTags[1], getColorModel()); 54 55 switch (dst.getDataType()) { 56 case DataBuffer.TYPE_USHORT: 57 ushortLoop(src, dst); 58 break; 59 default: 60 throw new UnsupportedOperationException("Unsupported data type: " + dst.getDataType()); 61 } 62 } 63 64 /* 65 * faster float arctan2 implementation. 66 * see: http://www.dspguru.com/comp.dsp/tricks/alg/fxdatan2.htm 67 */ 68 arctan2(float y, float x)69 static float arctan2(float y, float x) { 70 final float coeff_1 = (float) Math.PI / 4; 71 final float coeff_2 = 3 * coeff_1; 72 final float abs_y = Math.abs(y) + 1e-10f; // kludge to prevent 0/0 condition 73 float angle; 74 75 if (x >= 0) { 76 float r = (x - abs_y) / (x + abs_y); 77 angle = coeff_1 - coeff_1 * r; 78 } else { 79 float r = (x + abs_y) / (abs_y - x); 80 angle = coeff_2 - coeff_1 * r; 81 } 82 83 return y < 0 ? -angle : angle; 84 } 85 ushortLoop(RasterAccessor src, RasterAccessor dst)86 protected void ushortLoop(RasterAccessor src, RasterAccessor dst) { 87 int width = src.getWidth(); 88 int height = src.getHeight(); 89 90 short dstData[] = dst.getShortDataArray(0); 91 int dstBandOffsets[] = dst.getBandOffsets(); 92 int dstLineStride = dst.getScanlineStride(); 93 int dstPixelStride = dst.getPixelStride(); 94 95 short srcData[] = src.getShortDataArray(0); 96 int srcBandOffsets[] = src.getBandOffsets(); 97 int srcLineStride = src.getScanlineStride(); 98 int srcPixelStride = src.getPixelStride(); 99 100 int srcROffset = srcBandOffsets[0]; 101 int srcGOffset = srcBandOffsets[1]; 102 int srcBOffset = srcBandOffsets[2]; 103 104 int dstROffset = dstBandOffsets[0]; 105 int dstGOffset = dstBandOffsets[1]; 106 int dstBOffset = dstBandOffsets[2]; 107 108 for (int row = 0; row < height; row++) { 109 for (int col = 0; col < width; col++) { 110 int srcPixOffset = srcPixelStride * col + row * srcLineStride; 111 int r = 0xffff & srcData[srcPixOffset + srcROffset]; 112 int g = 0xffff & srcData[srcPixOffset + srcGOffset]; 113 int b = 0xffff & srcData[srcPixOffset + srcBOffset]; 114 115 float lr = toLinearsRGB[0][0] * r + toLinearsRGB[0][1] * g + toLinearsRGB[0][2] * b; 116 float lg = toLinearsRGB[1][0] * r + toLinearsRGB[1][1] * g + toLinearsRGB[1][2] * b; 117 float lb = toLinearsRGB[2][0] * r + toLinearsRGB[2][1] * g + toLinearsRGB[2][2] * b; 118 119 float x = lr - 0.5f*(lg+lb); 120 float y = 0.866f*(lg-lb); 121 122 float hue = (float) (arctan2(x, y) + Math.PI); 123 124 if (hue < 0) 125 hue += 2 * Math.PI; 126 127 if (hue > 4 * Math.PI / 3) 128 hue -= 4 * Math.PI / 3; 129 else if (hue > 2 * Math.PI / 3) 130 hue -= 2 * Math.PI / 3; 131 132 float mask = (float) (0.5 + 0.5 * (1 - Math.abs(Math.PI / 6 - hue) / (Math.PI / 3))); 133 134 if (saturationIncrease) { 135 int min = Math.min(r, Math.min(g, b)); 136 int max = Math.max(r, Math.max(g, b)); 137 138 float saturation = max != 0 ? 1 - min / (float) max : 0; 139 140 mask *= (1 - saturation * saturation); 141 } 142 143 float rr = transform[0][0] * r + transform[0][1] * g + transform[0][2] * b; 144 float gg = transform[1][0] * r + transform[1][1] * g + transform[1][2] * b; 145 float bb = transform[2][0] * r + transform[2][1] * g + transform[2][2] * b; 146 147 rr = (1 - mask) * r + rr * mask; 148 gg = (1 - mask) * g + gg * mask; 149 bb = (1 - mask) * b + bb * mask; 150 151 int dstPixOffset = dstPixelStride * col + row * dstLineStride; 152 dstData[dstPixOffset + dstROffset] = (short) (rr < 0 ? 0 : rr > 0xffff ? 0xffff : rr); 153 dstData[dstPixOffset + dstGOffset] = (short) (gg < 0 ? 0 : gg > 0xffff ? 0xffff : gg); 154 dstData[dstPixOffset + dstBOffset] = (short) (bb < 0 ? 0 : bb > 0xffff ? 0xffff : bb); 155 } 156 } 157 } 158 } 159