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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
package org.helioviewer.jhv.internal_plugins.filter.sharpen;

import org.helioviewer.viewmodel.filter.AbstractFilter;
import org.helioviewer.viewmodel.filter.StandardFilter;
import org.helioviewer.viewmodel.imagedata.ARGBInt32ImageData;
import org.helioviewer.viewmodel.imagedata.ImageData;
import org.helioviewer.viewmodel.imagedata.SingleChannelByte8ImageData;
import org.helioviewer.viewmodel.imagedata.SingleChannelShortImageData;
import org.helioviewer.viewmodel.imagetransport.Byte8ImageTransport;
import org.helioviewer.viewmodel.imagetransport.Int32ImageTransport;
import org.helioviewer.viewmodel.imagetransport.Short16ImageTransport;

/**
 * Filter for sharpen an image.
 * 
 * <p>
 * This filter sharpens the image by applying the unsharp mask algorithm. It
 * uses the following formula:
 * 
 * <p>
 * p_res(x,y) = (1 + a) * p_in(x,y) - a * p_low(x,y)
 * 
 * <p>
 * Here, p_res means the resulting pixel, p_in means the original input pixel
 * and p_low the pixel of the lowpassed filtered original. As applying the
 * lowpass, the image is convoluted with the 3x3 Gauss-kernel:
 * 
 * <p>
 * 1/16 * {{1, 2, 1}, {2, 4, 2}, {1, 2, 1}}
 * 
 * <p>
 * If the weighting is zero, the input data stays untouched.
 * 
 * <p>
 * The output of the filter always has the same image format as the input.
 * 
 * <p>
 * This filter supports only software rendering, but there is an OpenGL
 * implementation in {@link SharpenGLFilter}. This is because the OpenGL
 * implementation may be invalid due to graphics card restrictions.
 * 
 * @author Markus Langenberg
 * 
 */
public class SharpenFilter extends AbstractFilter implements StandardFilter {

    // /////////////////////////
    // GENERAL //
    // /////////////////////////

    protected static final int span = 2;

    protected float weighting = 0.0f;

    private SharpenPanel panel;

    private int convolveX[] = null;
    private int convolveY[] = null;

    private ImageData lastImageData;

    private boolean forceRefilter = false;

    /**
     * Sets the corresponding sharpen panel.
     * 
     * @param panel
     *            Corresponding panel.
     */
    void setPanel(SharpenPanel panel) {
        this.panel = panel;
        panel.setValue(weighting);
    }

    /**
     * Sets the weighting of the sharpening.
     * 
     * @param newWeighting
     *            Weighting of the sharpening
     */
    void setWeighting(float newWeighting) {
        weighting = newWeighting;
        notifyAllListeners();
    }

    /**
     * {@inheritDoc}
     * 
     * <p>
     * This filter is not a major filter.
     */
    public boolean isMajorFilter() {
        return false;
    }

    // /////////////////////////
    // STANDARD //
    // /////////////////////////

    /**
     * Blurs a single channel image by applying a 3x3 Gauss lowpass filter.
     * 
     * Since a convolution with a Gauss kernel is separable, this function is
     * optimized by doing so.
     * 
     * <p>
     * If the image has more than one channel, this function has to be called
     * multiple times.
     * 
     * @param width
     *            Width of the image
     * @param height
     *            Height of the image
     * @param input
     *            Pixel data of the image, given as an integer
     * @return Blurred single channel image
     */
    private int[] blur(int width, int height, byte input[]) {
        if (convolveY == null || convolveY.length < width * height)
            convolveY = new int[width * height];

        for (int i = 0; i < width * height; i++) {
            convolveY[i] = input[i] & 0xFF;
        }

        return blur(width, height, convolveY);
    }

    /**
     * Blurs a single channel image by applying a 3x3 Gauss lowpass filter.
     * 
     * Since a convolution with a Gauss kernel is separable, this function is
     * optimized by doing so.
     * 
     * <p>
     * If the image has more than one channel, this function has to be called
     * multiple times.
     * 
     * @param width
     *            Width of the image
     * @param height
     *            Height of the image
     * @param input
     *            Pixel data of the image, given as an integer
     * @param mask
     *            to apply on the input data
     * @return Blurred single channel image
     */
    private int[] blur(int width, int height, short input[], int mask) {
        if (convolveY == null || convolveY.length < width * height)
            convolveY = new int[width * height];

        for (int i = 0; i < width * height; i++) {
            convolveY[i] = input[i] & mask;
        }

        return blur(width, height, convolveY);
    }

    /**
     * Blurs a single channel image by applying a 3x3 Gauss lowpass filter.
     * 
     * Since a convolution with a Gauss kernel is separable, this function is
     * optimized by doing so.
     * 
     * <p>
     * If the image has more than one channel, this function has to be called
     * multiple times.
     * 
     * @param width
     *            Width of the image
     * @param height
     *            Height of the image
     * @param input
     *            Pixel data of the image, given as an integer
     * @return Blurred single channel image
     */
    private int[] blur(int width, int height, int[] input) {
        if (width < 2 * span || height < 2 * span) {
            return input;
        }
        if (convolveX == null || convolveX.length < width * height)
            convolveX = new int[width * height];
        if (convolveY == null || convolveY.length < width * height)
            convolveY = new int[width * height];

        int tmpIndex;

        // convolve borders in x direction
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < span; j++) {
                tmpIndex = i * width + j;

                convolveX[tmpIndex] = ((input[tmpIndex - j] + (input[tmpIndex] << 1) + input[tmpIndex + span]) >> 2);

                tmpIndex = (i + 1) * width - 1 - j;
                convolveX[tmpIndex] = ((input[tmpIndex + j] + (input[tmpIndex] << 1) + input[tmpIndex - span]) >> 2);
            }
        }

        // convolve inner region in x direction
        for (int i = 0; i < height; i++) {
            for (int j = span; j < width - span; j++) {
                tmpIndex = i * width + j;
                convolveX[tmpIndex] = ((input[tmpIndex - span] + (input[tmpIndex] << 1) + input[tmpIndex + span]) >> 2);
            }
        }

        int spanTimesWidth = span * width;

        // convolve borders in y direction
        for (int i = 0; i < span; i++) {
            for (int j = 0; j < width; j++) {
                tmpIndex = i * width + j;
                convolveY[tmpIndex] = ((convolveX[tmpIndex - i * width] + (convolveX[tmpIndex] << 1) + convolveX[tmpIndex + spanTimesWidth]) >> 2);

                tmpIndex = (height - i) * width - 1 - j;
                convolveY[tmpIndex] = ((convolveX[tmpIndex + i * width] + (convolveX[tmpIndex] << 1) + convolveX[tmpIndex - spanTimesWidth]) >> 2);
            }
        }

        // convolve inner region in y direction
        for (int i = span; i < height - span; i++) {
            for (int j = 0; j < width; j++) {
                tmpIndex = i * width + j;
                convolveY[tmpIndex] = ((convolveX[tmpIndex - spanTimesWidth] + (convolveX[tmpIndex] << 1) + convolveX[tmpIndex + spanTimesWidth]) >> 2);
            }
        }

        return convolveY;
    }

    /**
     * {@inheritDoc}
     */
    public ImageData apply(ImageData data) {
        if (data == null) {
            return null;
        }

        if (weighting <= 0.01f) {
            return data;
        }

        // Single channel byte image
        try {
            if (data.getImageTransport() instanceof Byte8ImageTransport) {
                byte[] pixelData = ((Byte8ImageTransport) data.getImageTransport()).getByte8PixelData();

                // lowpass
                if (forceRefilter || lastImageData != data) {
                    blur(data.getWidth(), data.getHeight(), pixelData);
                }

                // unsharp masking
                byte[] resultPixelData = new byte[pixelData.length];
                for (int i = 0; i < pixelData.length; i++) {
                    resultPixelData[i] = (byte) Math.min(Math.max((1.0f + weighting) * (pixelData[i] & 0xFF) - weighting * convolveY[i], 0), 0xFF);
                }

                lastImageData = data;

                return new SingleChannelByte8ImageData(data, resultPixelData);

                // Single channel short image
            } else if (data.getImageTransport() instanceof Short16ImageTransport) {
                short[] pixelData = ((Short16ImageTransport) data.getImageTransport()).getShort16PixelData();

                // calculate mask
                int mask = (1 << data.getImageTransport().getNumBitsPerPixel()) - 1;

                // lowpass
                if (forceRefilter || lastImageData != data) {
                    blur(data.getWidth(), data.getHeight(), pixelData, mask);
                }

                // unsharp masking
                short[] resultPixelData = new short[pixelData.length];
                for (int i = 0; i < pixelData.length; i++) {
                    resultPixelData[i] = (short) Math.min(Math.max((1.0f + weighting) * (pixelData[i] & mask) - weighting * convolveY[i], 0), 0xFFFF);
                }

                lastImageData = data;

                return new SingleChannelShortImageData(data, resultPixelData);

                // (A)RGB image: Filter each channel separate
            } else if (data.getImageTransport() instanceof Int32ImageTransport) {

                int[] pixelData = ((Int32ImageTransport) data.getImageTransport()).getInt32PixelData();
                int[] resultPixelData = new int[pixelData.length];

                int[] channel = new int[pixelData.length];

                // copy alpha channel unfiltered
                for (int i = 0; i < pixelData.length; i++) {
                    resultPixelData[i] = pixelData[i] & 0xFF000000;
                }

                // perform for each color channel
                for (int c = 0; c < 3; c++) {
                    for (int i = 0; i < pixelData.length; i++) {
                        channel[i] = (pixelData[i] >>> c * 8) & 0xFF;
                    }

                    // blur
                    blur(data.getWidth(), data.getHeight(), channel);

                    // unsharp masking
                    for (int i = 0; i < pixelData.length; i++) {
                        resultPixelData[i] |= (((int) Math.min(Math.max((1.0f + weighting) * (channel[i] & 0xFF) - weighting * convolveY[i], 0), 0xFF)) << (c * 8));
                    }
                }

                lastImageData = data;

                return new ARGBInt32ImageData(data, resultPixelData);

            }
        } finally {
            forceRefilter = false;
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public void forceRefilter() {
        forceRefilter = true;

    }

    /**
     * {@inheritDoc}
     */
    public void setState(String state) {
        setWeighting(Float.parseFloat(state));
        panel.setValue(weighting);
    }

    /**
     * {@inheritDoc}
     */
    public String getState() {
        return Float.toString(weighting);
    }
}