~josejuan-sanchez/+junk/original-jhv-experimental-version

« back to all changes in this revision

Viewing changes to src/jhv/src/org/helioviewer/jhv/internal_plugins/filter/sharpen/SharpenFilter.java

  • Committer: José Juan Sánchez Hernández
  • Date: 2013-02-05 13:32:08 UTC
  • Revision ID: josejuan.sanchez@gmail.com-20130205133208-dfz1sh1uge5pjkny
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package org.helioviewer.jhv.internal_plugins.filter.sharpen;
 
2
 
 
3
import org.helioviewer.viewmodel.filter.AbstractFilter;
 
4
import org.helioviewer.viewmodel.filter.StandardFilter;
 
5
import org.helioviewer.viewmodel.imagedata.ARGBInt32ImageData;
 
6
import org.helioviewer.viewmodel.imagedata.ImageData;
 
7
import org.helioviewer.viewmodel.imagedata.SingleChannelByte8ImageData;
 
8
import org.helioviewer.viewmodel.imagedata.SingleChannelShortImageData;
 
9
import org.helioviewer.viewmodel.imagetransport.Byte8ImageTransport;
 
10
import org.helioviewer.viewmodel.imagetransport.Int32ImageTransport;
 
11
import org.helioviewer.viewmodel.imagetransport.Short16ImageTransport;
 
12
 
 
13
/**
 
14
 * Filter for sharpen an image.
 
15
 * 
 
16
 * <p>
 
17
 * This filter sharpens the image by applying the unsharp mask algorithm. It
 
18
 * uses the following formula:
 
19
 * 
 
20
 * <p>
 
21
 * p_res(x,y) = (1 + a) * p_in(x,y) - a * p_low(x,y)
 
22
 * 
 
23
 * <p>
 
24
 * Here, p_res means the resulting pixel, p_in means the original input pixel
 
25
 * and p_low the pixel of the lowpassed filtered original. As applying the
 
26
 * lowpass, the image is convoluted with the 3x3 Gauss-kernel:
 
27
 * 
 
28
 * <p>
 
29
 * 1/16 * {{1, 2, 1}, {2, 4, 2}, {1, 2, 1}}
 
30
 * 
 
31
 * <p>
 
32
 * If the weighting is zero, the input data stays untouched.
 
33
 * 
 
34
 * <p>
 
35
 * The output of the filter always has the same image format as the input.
 
36
 * 
 
37
 * <p>
 
38
 * This filter supports only software rendering, but there is an OpenGL
 
39
 * implementation in {@link SharpenGLFilter}. This is because the OpenGL
 
40
 * implementation may be invalid due to graphics card restrictions.
 
41
 * 
 
42
 * @author Markus Langenberg
 
43
 * 
 
44
 */
 
45
public class SharpenFilter extends AbstractFilter implements StandardFilter {
 
46
 
 
47
    // /////////////////////////
 
48
    // GENERAL //
 
49
    // /////////////////////////
 
50
 
 
51
    protected static final int span = 2;
 
52
 
 
53
    protected float weighting = 0.0f;
 
54
 
 
55
    private SharpenPanel panel;
 
56
 
 
57
    private int convolveX[] = null;
 
58
    private int convolveY[] = null;
 
59
 
 
60
    private ImageData lastImageData;
 
61
 
 
62
    private boolean forceRefilter = false;
 
63
 
 
64
    /**
 
65
     * Sets the corresponding sharpen panel.
 
66
     * 
 
67
     * @param panel
 
68
     *            Corresponding panel.
 
69
     */
 
70
    void setPanel(SharpenPanel panel) {
 
71
        this.panel = panel;
 
72
        panel.setValue(weighting);
 
73
    }
 
74
 
 
75
    /**
 
76
     * Sets the weighting of the sharpening.
 
77
     * 
 
78
     * @param newWeighting
 
79
     *            Weighting of the sharpening
 
80
     */
 
81
    void setWeighting(float newWeighting) {
 
82
        weighting = newWeighting;
 
83
        notifyAllListeners();
 
84
    }
 
85
 
 
86
    /**
 
87
     * {@inheritDoc}
 
88
     * 
 
89
     * <p>
 
90
     * This filter is not a major filter.
 
91
     */
 
92
    public boolean isMajorFilter() {
 
93
        return false;
 
94
    }
 
95
 
 
96
    // /////////////////////////
 
97
    // STANDARD //
 
98
    // /////////////////////////
 
99
 
 
100
    /**
 
101
     * Blurs a single channel image by applying a 3x3 Gauss lowpass filter.
 
102
     * 
 
103
     * Since a convolution with a Gauss kernel is separable, this function is
 
104
     * optimized by doing so.
 
105
     * 
 
106
     * <p>
 
107
     * If the image has more than one channel, this function has to be called
 
108
     * multiple times.
 
109
     * 
 
110
     * @param width
 
111
     *            Width of the image
 
112
     * @param height
 
113
     *            Height of the image
 
114
     * @param input
 
115
     *            Pixel data of the image, given as an integer
 
116
     * @return Blurred single channel image
 
117
     */
 
118
    private int[] blur(int width, int height, byte input[]) {
 
119
        if (convolveY == null || convolveY.length < width * height)
 
120
            convolveY = new int[width * height];
 
121
 
 
122
        for (int i = 0; i < width * height; i++) {
 
123
            convolveY[i] = input[i] & 0xFF;
 
124
        }
 
125
 
 
126
        return blur(width, height, convolveY);
 
127
    }
 
128
 
 
129
    /**
 
130
     * Blurs a single channel image by applying a 3x3 Gauss lowpass filter.
 
131
     * 
 
132
     * Since a convolution with a Gauss kernel is separable, this function is
 
133
     * optimized by doing so.
 
134
     * 
 
135
     * <p>
 
136
     * If the image has more than one channel, this function has to be called
 
137
     * multiple times.
 
138
     * 
 
139
     * @param width
 
140
     *            Width of the image
 
141
     * @param height
 
142
     *            Height of the image
 
143
     * @param input
 
144
     *            Pixel data of the image, given as an integer
 
145
     * @param mask
 
146
     *            to apply on the input data
 
147
     * @return Blurred single channel image
 
148
     */
 
149
    private int[] blur(int width, int height, short input[], int mask) {
 
150
        if (convolveY == null || convolveY.length < width * height)
 
151
            convolveY = new int[width * height];
 
152
 
 
153
        for (int i = 0; i < width * height; i++) {
 
154
            convolveY[i] = input[i] & mask;
 
155
        }
 
156
 
 
157
        return blur(width, height, convolveY);
 
158
    }
 
159
 
 
160
    /**
 
161
     * Blurs a single channel image by applying a 3x3 Gauss lowpass filter.
 
162
     * 
 
163
     * Since a convolution with a Gauss kernel is separable, this function is
 
164
     * optimized by doing so.
 
165
     * 
 
166
     * <p>
 
167
     * If the image has more than one channel, this function has to be called
 
168
     * multiple times.
 
169
     * 
 
170
     * @param width
 
171
     *            Width of the image
 
172
     * @param height
 
173
     *            Height of the image
 
174
     * @param input
 
175
     *            Pixel data of the image, given as an integer
 
176
     * @return Blurred single channel image
 
177
     */
 
178
    private int[] blur(int width, int height, int[] input) {
 
179
        if (width < 2 * span || height < 2 * span) {
 
180
            return input;
 
181
        }
 
182
        if (convolveX == null || convolveX.length < width * height)
 
183
            convolveX = new int[width * height];
 
184
        if (convolveY == null || convolveY.length < width * height)
 
185
            convolveY = new int[width * height];
 
186
 
 
187
        int tmpIndex;
 
188
 
 
189
        // convolve borders in x direction
 
190
        for (int i = 0; i < height; i++) {
 
191
            for (int j = 0; j < span; j++) {
 
192
                tmpIndex = i * width + j;
 
193
 
 
194
                convolveX[tmpIndex] = ((input[tmpIndex - j] + (input[tmpIndex] << 1) + input[tmpIndex + span]) >> 2);
 
195
 
 
196
                tmpIndex = (i + 1) * width - 1 - j;
 
197
                convolveX[tmpIndex] = ((input[tmpIndex + j] + (input[tmpIndex] << 1) + input[tmpIndex - span]) >> 2);
 
198
            }
 
199
        }
 
200
 
 
201
        // convolve inner region in x direction
 
202
        for (int i = 0; i < height; i++) {
 
203
            for (int j = span; j < width - span; j++) {
 
204
                tmpIndex = i * width + j;
 
205
                convolveX[tmpIndex] = ((input[tmpIndex - span] + (input[tmpIndex] << 1) + input[tmpIndex + span]) >> 2);
 
206
            }
 
207
        }
 
208
 
 
209
        int spanTimesWidth = span * width;
 
210
 
 
211
        // convolve borders in y direction
 
212
        for (int i = 0; i < span; i++) {
 
213
            for (int j = 0; j < width; j++) {
 
214
                tmpIndex = i * width + j;
 
215
                convolveY[tmpIndex] = ((convolveX[tmpIndex - i * width] + (convolveX[tmpIndex] << 1) + convolveX[tmpIndex + spanTimesWidth]) >> 2);
 
216
 
 
217
                tmpIndex = (height - i) * width - 1 - j;
 
218
                convolveY[tmpIndex] = ((convolveX[tmpIndex + i * width] + (convolveX[tmpIndex] << 1) + convolveX[tmpIndex - spanTimesWidth]) >> 2);
 
219
            }
 
220
        }
 
221
 
 
222
        // convolve inner region in y direction
 
223
        for (int i = span; i < height - span; i++) {
 
224
            for (int j = 0; j < width; j++) {
 
225
                tmpIndex = i * width + j;
 
226
                convolveY[tmpIndex] = ((convolveX[tmpIndex - spanTimesWidth] + (convolveX[tmpIndex] << 1) + convolveX[tmpIndex + spanTimesWidth]) >> 2);
 
227
            }
 
228
        }
 
229
 
 
230
        return convolveY;
 
231
    }
 
232
 
 
233
    /**
 
234
     * {@inheritDoc}
 
235
     */
 
236
    public ImageData apply(ImageData data) {
 
237
        if (data == null) {
 
238
            return null;
 
239
        }
 
240
 
 
241
        if (weighting <= 0.01f) {
 
242
            return data;
 
243
        }
 
244
 
 
245
        // Single channel byte image
 
246
        try {
 
247
            if (data.getImageTransport() instanceof Byte8ImageTransport) {
 
248
                byte[] pixelData = ((Byte8ImageTransport) data.getImageTransport()).getByte8PixelData();
 
249
 
 
250
                // lowpass
 
251
                if (forceRefilter || lastImageData != data) {
 
252
                    blur(data.getWidth(), data.getHeight(), pixelData);
 
253
                }
 
254
 
 
255
                // unsharp masking
 
256
                byte[] resultPixelData = new byte[pixelData.length];
 
257
                for (int i = 0; i < pixelData.length; i++) {
 
258
                    resultPixelData[i] = (byte) Math.min(Math.max((1.0f + weighting) * (pixelData[i] & 0xFF) - weighting * convolveY[i], 0), 0xFF);
 
259
                }
 
260
 
 
261
                lastImageData = data;
 
262
 
 
263
                return new SingleChannelByte8ImageData(data, resultPixelData);
 
264
 
 
265
                // Single channel short image
 
266
            } else if (data.getImageTransport() instanceof Short16ImageTransport) {
 
267
                short[] pixelData = ((Short16ImageTransport) data.getImageTransport()).getShort16PixelData();
 
268
 
 
269
                // calculate mask
 
270
                int mask = (1 << data.getImageTransport().getNumBitsPerPixel()) - 1;
 
271
 
 
272
                // lowpass
 
273
                if (forceRefilter || lastImageData != data) {
 
274
                    blur(data.getWidth(), data.getHeight(), pixelData, mask);
 
275
                }
 
276
 
 
277
                // unsharp masking
 
278
                short[] resultPixelData = new short[pixelData.length];
 
279
                for (int i = 0; i < pixelData.length; i++) {
 
280
                    resultPixelData[i] = (short) Math.min(Math.max((1.0f + weighting) * (pixelData[i] & mask) - weighting * convolveY[i], 0), 0xFFFF);
 
281
                }
 
282
 
 
283
                lastImageData = data;
 
284
 
 
285
                return new SingleChannelShortImageData(data, resultPixelData);
 
286
 
 
287
                // (A)RGB image: Filter each channel separate
 
288
            } else if (data.getImageTransport() instanceof Int32ImageTransport) {
 
289
 
 
290
                int[] pixelData = ((Int32ImageTransport) data.getImageTransport()).getInt32PixelData();
 
291
                int[] resultPixelData = new int[pixelData.length];
 
292
 
 
293
                int[] channel = new int[pixelData.length];
 
294
 
 
295
                // copy alpha channel unfiltered
 
296
                for (int i = 0; i < pixelData.length; i++) {
 
297
                    resultPixelData[i] = pixelData[i] & 0xFF000000;
 
298
                }
 
299
 
 
300
                // perform for each color channel
 
301
                for (int c = 0; c < 3; c++) {
 
302
                    for (int i = 0; i < pixelData.length; i++) {
 
303
                        channel[i] = (pixelData[i] >>> c * 8) & 0xFF;
 
304
                    }
 
305
 
 
306
                    // blur
 
307
                    blur(data.getWidth(), data.getHeight(), channel);
 
308
 
 
309
                    // unsharp masking
 
310
                    for (int i = 0; i < pixelData.length; i++) {
 
311
                        resultPixelData[i] |= (((int) Math.min(Math.max((1.0f + weighting) * (channel[i] & 0xFF) - weighting * convolveY[i], 0), 0xFF)) << (c * 8));
 
312
                    }
 
313
                }
 
314
 
 
315
                lastImageData = data;
 
316
 
 
317
                return new ARGBInt32ImageData(data, resultPixelData);
 
318
 
 
319
            }
 
320
        } finally {
 
321
            forceRefilter = false;
 
322
        }
 
323
 
 
324
        return null;
 
325
    }
 
326
 
 
327
    /**
 
328
     * {@inheritDoc}
 
329
     */
 
330
    public void forceRefilter() {
 
331
        forceRefilter = true;
 
332
 
 
333
    }
 
334
 
 
335
    /**
 
336
     * {@inheritDoc}
 
337
     */
 
338
    public void setState(String state) {
 
339
        setWeighting(Float.parseFloat(state));
 
340
        panel.setValue(weighting);
 
341
    }
 
342
 
 
343
    /**
 
344
     * {@inheritDoc}
 
345
     */
 
346
    public String getState() {
 
347
        return Float.toString(weighting);
 
348
    }
 
349
}