| 1 | /* |
| 2 | * $RCSfile: ThresholdBlurOpImage.java $ |
| 3 | * |
| 4 | * Copyright (c) 2007 Jeremiah Spaulding |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU General Public License |
| 17 | * along with this program; if not, write to the Free Software |
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 19 | * |
| 20 | * $Revision: 1.0 $ |
| 21 | * $Date: 2007/07/15 00:00:00 $ |
| 22 | */ |
| 23 | package net.sf.jomic.tools; |
| 24 | |
| 25 | import java.awt.Rectangle; |
| 26 | import java.awt.image.DataBuffer; |
| 27 | import java.awt.image.Raster; |
| 28 | import java.awt.image.RenderedImage; |
| 29 | import java.awt.image.WritableRaster; |
| 30 | import javax.media.jai.AreaOpImage; |
| 31 | import javax.media.jai.BorderExtender; |
| 32 | import javax.media.jai.ImageLayout; |
| 33 | import javax.media.jai.KernelJAI; |
| 34 | import javax.media.jai.RasterAccessor; |
| 35 | import javax.media.jai.RasterFormatTag; |
| 36 | import java.util.Map; |
| 37 | |
| 38 | /** |
| 39 | * An OpImage class to perform threshold bounded gaussian blur on a source image. |
| 40 | * |
| 41 | * @see Convolution |
| 42 | */ |
| 43 | final class ThresholdBlurOpImage extends AreaOpImage |
| 44 | { |
| 45 | |
| 46 | /** |
| 47 | * The kernel with which to do the operation. |
| 48 | */ |
| 49 | protected KernelJAI kernel; |
| 50 | |
| 51 | /** |
| 52 | * Kernel variables. |
| 53 | */ |
| 54 | private int kw, kh; |
| 55 | |
| 56 | private int threshold; |
| 57 | |
| 58 | /** |
| 59 | * Creates a ThresholdBlurOpImage given a ParameterBlock containing the image source and |
| 60 | * pre-rotated convolution kernel. The image dimensions are derived from the source image. The |
| 61 | * tile grid layout, SampleModel, and ColorModel may optionally be specified by an ImageLayout |
| 62 | * object. |
| 63 | * |
| 64 | * @param source a RenderedImage. |
| 65 | * @param extender a BorderExtender, or null. |
| 66 | * @param config Description of the parameter |
| 67 | * @param layout an ImageLayout optionally containing the tile grid layout, SampleModel, |
| 68 | * and ColorModel, or null. |
| 69 | * @param kernel the pre-rotated convolution KernelJAI. |
| 70 | * @param threshold the user-set threshold. |
| 71 | */ |
| 72 | public ThresholdBlurOpImage(RenderedImage source, |
| 73 | BorderExtender extender, |
| 74 | Map config, |
| 75 | ImageLayout layout, |
| 76 | KernelJAI kernel, |
| 77 | int threshold) { |
| 78 | super(source, |
| 79 | layout, |
| 80 | config, |
| 81 | true, |
| 82 | extender, |
| 83 | kernel.getLeftPadding(), |
| 84 | kernel.getRightPadding(), |
| 85 | kernel.getTopPadding(), |
| 86 | kernel.getBottomPadding()); |
| 87 | |
| 88 | this.kernel = kernel; |
| 89 | kw = kernel.getWidth(); |
| 90 | kh = kernel.getHeight(); |
| 91 | this.threshold = threshold; |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Performs threshold gaussian blur on a specified rectangle. The sources are cobbled. When |
| 96 | * blurring the target pixel, only other pixels under the kernel whose difference from the |
| 97 | * target pixel is within THRESHOLD are considered. |
| 98 | * |
| 99 | * @param sources an array of source Rasters, guaranteed to provide all necessary source data |
| 100 | * for computing the output. |
| 101 | * @param dest a WritableRaster tile containing the area to be computed. |
| 102 | * @param destRect the rectangle within dest to be processed. |
| 103 | */ |
| 104 | protected void computeRect(Raster[] sources, |
| 105 | WritableRaster dest, |
| 106 | Rectangle destRect) { |
| 107 | // Retrieve format tags. |
| 108 | RasterFormatTag[] formatTags = getFormatTags(); |
| 109 | |
| 110 | Raster source = sources[0]; |
| 111 | Rectangle srcRect = mapDestRect(destRect, 0); |
| 112 | |
| 113 | RasterAccessor srcAccessor = |
| 114 | new RasterAccessor(source, srcRect, |
| 115 | formatTags[0], getSourceImage(0).getColorModel()); |
| 116 | RasterAccessor dstAccessor = |
| 117 | new RasterAccessor(dest, destRect, |
| 118 | formatTags[1], getColorModel()); |
| 119 | |
| 120 | switch (dstAccessor.getDataType()) { |
| 121 | case DataBuffer.TYPE_BYTE: |
| 122 | byteLoop(srcAccessor, dstAccessor); |
| 123 | break; |
| 124 | default: |
| 125 | assert false : "Byte Type Only!"; |
| 126 | } |
| 127 | |
| 128 | // If the RasterAccessor object set up a temporary buffer for the |
| 129 | // op to write to, tell the RasterAccessor to write that data |
| 130 | // to the raster now that we're done with it. |
| 131 | if (dstAccessor.isDataCopy()) { |
| 132 | dstAccessor.clampDataArrays(); |
| 133 | dstAccessor.copyDataToRaster(); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | private void byteLoop(RasterAccessor src, RasterAccessor dst) { |
| 138 | int dwidth = dst.getWidth(); |
| 139 | int dheight = dst.getHeight(); |
| 140 | int dnumBands = dst.getNumBands(); |
| 141 | |
| 142 | float[] kdata = kernel.getKernelData(); |
| 143 | |
| 144 | byte dstDataArrays[][] = dst.getByteDataArrays(); |
| 145 | int dstBandOffsets[] = dst.getBandOffsets(); |
| 146 | int dstPixelStride = dst.getPixelStride(); |
| 147 | int dstScanlineStride = dst.getScanlineStride(); |
| 148 | |
| 149 | byte srcDataArrays[][] = src.getByteDataArrays(); |
| 150 | int srcBandOffsets[] = src.getBandOffsets(); |
| 151 | int srcPixelStride = src.getPixelStride(); |
| 152 | int srcScanlineStride = src.getScanlineStride(); |
| 153 | int toMiddle = (kw / 2 * srcPixelStride) + (kh / 2 * srcScanlineStride); |
| 154 | |
| 155 | for (int k = 0; k < dnumBands; k++) { |
| 156 | byte dstData[] = dstDataArrays[k]; |
| 157 | byte srcData[] = srcDataArrays[k]; |
| 158 | int srcScanlineOffset = srcBandOffsets[k]; |
| 159 | int dstScanlineOffset = dstBandOffsets[k]; |
| 160 | |
| 161 | for (int j = 0; j < dheight; j++) { |
| 162 | int srcPixelOffset = srcScanlineOffset; |
| 163 | int dstPixelOffset = dstScanlineOffset; |
| 164 | |
| 165 | for (int i = 0; i < dwidth; i++) { |
| 166 | byte origPixel = srcData[srcPixelOffset + toMiddle]; |
| 167 | float f = 0; |
| 168 | float weight = 0; |
| 169 | int kernelVerticalOffset = 0; |
| 170 | int imageVerticalOffset = srcPixelOffset; |
| 171 | int imageOffset = imageVerticalOffset; |
| 172 | |
| 173 | for (int u = 0; u < kh; u++) { |
| 174 | imageOffset = imageVerticalOffset; |
| 175 | for (int v = 0; v < kw; v++) { |
| 176 | if ((origPixel >= srcData[imageOffset] && (origPixel - srcData[imageOffset]) < threshold) || |
| 177 | (origPixel < srcData[imageOffset] && (srcData[imageOffset] - origPixel) < threshold)) { |
| 178 | f += (srcData[imageOffset] & 0xff) * kdata[kernelVerticalOffset + v]; |
| 179 | weight += kdata[kernelVerticalOffset + v]; |
| 180 | } |
| 181 | imageOffset += srcPixelStride; |
| 182 | } |
| 183 | kernelVerticalOffset += kw; |
| 184 | imageVerticalOffset += srcScanlineStride; |
| 185 | } |
| 186 | |
| 187 | int val; |
| 188 | |
| 189 | if (weight == 0) { |
| 190 | val = origPixel; |
| 191 | } else { |
| 192 | val = (int) (f / weight); |
| 193 | } |
| 194 | |
| 195 | if (val < 0) { |
| 196 | val = 0; |
| 197 | } else if (val > 255) { |
| 198 | val = 255; |
| 199 | } |
| 200 | dstData[dstPixelOffset] = (byte) val; |
| 201 | srcPixelOffset += srcPixelStride; |
| 202 | dstPixelOffset += dstPixelStride; |
| 203 | } |
| 204 | srcScanlineOffset += srcScanlineStride; |
| 205 | dstScanlineOffset += dstScanlineStride; |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | } |