├── examples ├── purple-sky-gentle.jpg ├── eye.jpg ├── snake.jpg ├── tree.jpg ├── purple0.jpg ├── purple2.jpg ├── purple3.jpg ├── butterfly.jpg ├── difficult.jpg ├── eye-diff.jpg ├── eye-fixed.jpg ├── purple-sky.jpg ├── snake-diff.jpg ├── tree-diff.jpg ├── tree-fixed.jpg ├── worst-case.jpg ├── worst-case.xcf ├── purple0-diff.jpg ├── purple2-diff.jpg ├── purple3-diff.jpg ├── snake-fixed.jpg ├── butterfly-diff.jpg ├── butterfly-fixed.jpg ├── difficult-diff.jpg ├── difficult-fixed.jpg ├── purple-sky-diff.jpg ├── purple0-fixed.jpg ├── purple2-fixed.jpg ├── purple3-fixed.jpg ├── turkey-vulture.jpg ├── worst-case-diff.jpg ├── purple-sky-fixed.jpg ├── wikipedia-horsie.jpg ├── worst-case-fixed.jpg ├── turkey-vulture-diff.jpg ├── turkey-vulture-fixed.jpg ├── purple-sky-gentle-diff.jpg ├── wikipedia-horsie-diff.jpg ├── wikipedia-horsie-fixed.jpg ├── purple-sky-gentle-fixed.jpg ├── Makefile └── make-examples.sh ├── src ├── Makefile └── unpurple.ml ├── Makefile ├── INSTALL ├── LICENSE └── README.md /examples/purple-sky-gentle.jpg: -------------------------------------------------------------------------------- 1 | purple-sky.jpg -------------------------------------------------------------------------------- /examples/eye.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/eye.jpg -------------------------------------------------------------------------------- /examples/snake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/snake.jpg -------------------------------------------------------------------------------- /examples/tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/tree.jpg -------------------------------------------------------------------------------- /examples/purple0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple0.jpg -------------------------------------------------------------------------------- /examples/purple2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple2.jpg -------------------------------------------------------------------------------- /examples/purple3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple3.jpg -------------------------------------------------------------------------------- /examples/butterfly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/butterfly.jpg -------------------------------------------------------------------------------- /examples/difficult.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/difficult.jpg -------------------------------------------------------------------------------- /examples/eye-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/eye-diff.jpg -------------------------------------------------------------------------------- /examples/eye-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/eye-fixed.jpg -------------------------------------------------------------------------------- /examples/purple-sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple-sky.jpg -------------------------------------------------------------------------------- /examples/snake-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/snake-diff.jpg -------------------------------------------------------------------------------- /examples/tree-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/tree-diff.jpg -------------------------------------------------------------------------------- /examples/tree-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/tree-fixed.jpg -------------------------------------------------------------------------------- /examples/worst-case.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/worst-case.jpg -------------------------------------------------------------------------------- /examples/worst-case.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/worst-case.xcf -------------------------------------------------------------------------------- /examples/purple0-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple0-diff.jpg -------------------------------------------------------------------------------- /examples/purple2-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple2-diff.jpg -------------------------------------------------------------------------------- /examples/purple3-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple3-diff.jpg -------------------------------------------------------------------------------- /examples/snake-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/snake-fixed.jpg -------------------------------------------------------------------------------- /examples/butterfly-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/butterfly-diff.jpg -------------------------------------------------------------------------------- /examples/butterfly-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/butterfly-fixed.jpg -------------------------------------------------------------------------------- /examples/difficult-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/difficult-diff.jpg -------------------------------------------------------------------------------- /examples/difficult-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/difficult-fixed.jpg -------------------------------------------------------------------------------- /examples/purple-sky-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple-sky-diff.jpg -------------------------------------------------------------------------------- /examples/purple0-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple0-fixed.jpg -------------------------------------------------------------------------------- /examples/purple2-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple2-fixed.jpg -------------------------------------------------------------------------------- /examples/purple3-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple3-fixed.jpg -------------------------------------------------------------------------------- /examples/turkey-vulture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/turkey-vulture.jpg -------------------------------------------------------------------------------- /examples/worst-case-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/worst-case-diff.jpg -------------------------------------------------------------------------------- /examples/purple-sky-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple-sky-fixed.jpg -------------------------------------------------------------------------------- /examples/wikipedia-horsie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/wikipedia-horsie.jpg -------------------------------------------------------------------------------- /examples/worst-case-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/worst-case-fixed.jpg -------------------------------------------------------------------------------- /examples/turkey-vulture-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/turkey-vulture-diff.jpg -------------------------------------------------------------------------------- /examples/turkey-vulture-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/turkey-vulture-fixed.jpg -------------------------------------------------------------------------------- /examples/purple-sky-gentle-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple-sky-gentle-diff.jpg -------------------------------------------------------------------------------- /examples/wikipedia-horsie-diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/wikipedia-horsie-diff.jpg -------------------------------------------------------------------------------- /examples/wikipedia-horsie-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/wikipedia-horsie-fixed.jpg -------------------------------------------------------------------------------- /examples/purple-sky-gentle-fixed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjambon/purple-fringe/HEAD/examples/purple-sky-gentle-fixed.jpg -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | unpurple: unpurple.ml 2 | ocamlfind ocamlopt -o unpurple unpurple.ml \ 3 | -package camlimages.jpeg -linkpkg 4 | .PHONY: clean 5 | clean: 6 | rm -f *~ *.cmi *.cmx *.o unpurple 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default 2 | default: 3 | cd src; $(MAKE) 4 | 5 | .PHONY: examples 6 | examples: default 7 | cd examples; $(MAKE) 8 | 9 | .PHONY: clean 10 | clean: 11 | rm -f *~ 12 | cd src; $(MAKE) clean 13 | cd examples; $(MAKE) clean 14 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | This describes the installation of the standalone prototype 2 | implemented in OCaml. 3 | 4 | Build-time requirements 5 | ----------------------- 6 | 7 | - GNU make, bash and other Unix utilities 8 | - OCaml 9 | - Camlimages built with JPEG support 10 | 11 | Compilation 12 | ----------- 13 | 14 | $ make 15 | 16 | Installation 17 | ------------ 18 | 19 | $ cp src/unpurple ~/bin/ # or wherever you want 20 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: examples 2 | EXAMPLES = \ 3 | wikipedia-horsie \ 4 | butterfly \ 5 | turkey-vulture \ 6 | dragonfly \ 7 | eye \ 8 | tree \ 9 | snake \ 10 | purple-sky \ 11 | purple-sky-gentle \ 12 | purple0 \ 13 | purple2 \ 14 | purple3 \ 15 | difficult \ 16 | worst-case 17 | 18 | EXAMPLES_IN = $(addsuffix .jpg, $(EXAMPLES)) 19 | EXAMPLES_OUT = $(addsuffix -fixed.jpg, $(EXAMPLES)) 20 | EXAMPLES_DIFF = $(addsuffix -diff.jpg, $(EXAMPLES)) 21 | 22 | examples: examples.html 23 | examples.html: $(EXAMPLES_IN) Makefile make-examples.sh ../src/unpurple 24 | ./make-examples.sh 25 | 26 | .PHONY: install 27 | install: 28 | mkdir -p $$WWW/purple-fringe 29 | cp examples.html \ 30 | $(EXAMPLES_IN) $(EXAMPLES_OUT) $(EXAMPLES_DIFF) \ 31 | $$WWW/purple-fringe 32 | 33 | .PHONY: clean 34 | clean: 35 | rm -f *~ 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Source code 2 | =========== 3 | 4 | All source code in this repository is distributed under the following 5 | conditions: 6 | 7 | 8 | Copyright (c) 2012 Martin Jambon 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions 13 | are met: 14 | 1. Redistributions of source code must retain the above copyright 15 | notice, this list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 3. The name of the author may not be used to endorse or promote products 20 | derived from this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 23 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | 34 | 35 | Images 36 | ====== 37 | 38 | All photos included in this repository have been placed under the 39 | public domain by their respective authors. 40 | 41 | Horse photo: http://en.wikipedia.org/wiki/File:Purple_fringing.jpg 42 | 43 | All the other photos were taken by Martin Jambon. 44 | -------------------------------------------------------------------------------- /examples/make-examples.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | 3 | sample () { 4 | x="$1" 5 | shift 6 | 7 | ../src/unpurple $x.jpg $x-fixed.jpg "$@" 8 | ../src/unpurple $x.jpg $x-diff.jpg -diff "$@" 9 | 10 | if [ -n "$*" ]; then 11 | options="
$@
19 | ![]() |
21 | ![]() |
| 25 | |
27 | |
28 |
These images were processed with
67 | unpurple,
68 | a command-line program that tries to remove
69 | purple fringing
70 | from digital photos.
71 |
73 | In each example the default options are used unless otherwise indicated. 74 |
75 | " 76 | sample wikipedia-horsie 77 | sample butterfly -minred 0.15 78 | sample dragonfly 79 | sample turkey-vulture 80 | sample eye 81 | sample tree 82 | sample snake 83 | sample purple-sky 84 | sample purple-sky-gentle -minred 0.15 85 | sample purple0 86 | sample purple2 87 | sample purple3 88 | sample difficult -m 0.8 -i 2.0 89 | sample worst-case -m 0.5 -r 1.0 90 | 91 | echo "\ 92 | 93 | 94 | 95 | " 96 | } 97 | 98 | print $* > examples.html 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Unpurple 2 | ======== 3 | 4 | Unpurple is a tool for removing purple fringes (axial chromatic aberration) 5 | from digital photos using a heuristic of my own. 6 | 7 | This is the original OCaml implementation which produces a standalone 8 | executable with a simple command-line interface. 9 | It is fast enough (~ one second per megapixel) but doesn't 10 | preserve Exif data and the JPEG compression factor is fixed at 75%. 11 | 12 | Unpurple was 13 | [ported](https://github.com/dtschump/gmic-community/blob/master/include/stanislav_paskalev.gmic) 14 | to [G'MIC](https://gmic.eu/) 15 | by [Stanislav Paskalev](https://github.com/solarsea), allowing its use from 16 | [GIMP](https://www.gimp.org/) and other image-manipulation software. 17 | 🎉 18 | 19 | Example 20 | ------- 21 | 22 | Before: 23 | 24 | [
]()
25 |
26 | After:
27 |
28 | [
]()
29 |
30 | Difference:
31 |
32 | [
]()
33 |
34 | [More examples](https://mjambon.github.io/mjambon2016/purple-fringe/examples.html)
35 |
36 | Algorithm outline
37 | -----------------
38 |
39 | 1. Produce a blurred mask from the blue component in the original image.
40 | 2. Subtract from the original image some amount of blue and red based on the intensities found in the blurred mask, using the following constraints:
41 | * Blue level may not drop below green level.
42 | * Red level may not drop below green level.
43 | * Red:blue ratio may not drop below some constant.
44 |
45 | Please refer to the implementation for details and default parameters.
46 |
47 | Intuition and future prospects
48 | ------------------------------
49 |
50 | The original motivation for this work was to remove purple fringing
51 | from my own photos that I took with a relatively cheap lens. Coming up
52 | with an algorithm required:
53 |
54 | 1. Notions about optics, and in particular understanding why chromatic
55 | aberration happens.
56 | 2. Notions about image manipulation and RGB color encoding.
57 | 3. Notions about the visible spectrum (rainbow) and how wavelengths
58 | map to certain perceived colors.
59 | 4. Notions about the perception of colors in humans and why computers
60 | encode colors as 3 components (excluding brightness or transparency) and
61 | not another number.
62 |
63 | It is useful, or perhaps critical, to understand why mixing blue and
64 | red dots produces a perception of purple, even though none of the
65 | light emitted from blue and red dots needs to have the wavelength of
66 | the purple band of the rainbow. School and Wikipedia are your friends.
67 |
68 | Here's my mental model of purple fringing, which may be inacurrate but
69 | turned out to be good enough. It may not work as well or at all for
70 | other types of chromatic aberration:
71 |
72 | A camera "lens" is made of several simple lenses, which help not
73 | only with zooming in, but also correcting for certain problems. My
74 | understanding is that they allow multiple regions of the visible
75 | spectrum to remain in focus, but this has the opposite effect on the
76 | short wavelengths (purple and ultraviolet) which end up very blurred out.
77 | It is also possible that UV light is captured as purple by the sensor
78 | of a digital camera. As a result, black objects on a white background
79 | will exhibit a purple fringe all around. Note that this is different
80 | then some other types of chromatic aberration, where some objects have a red
81 | margin on one side and a blue margin on the other side.
82 |
83 | Now for the method. The problem of removing the purple fringe is
84 | tackled by observing that the bright parts of the image still contain
85 | most of the purple that they should contain. We don't need to take the
86 | purple from the purple fringes and put it back into the bright
87 | areas. They're still plenty bright and their colors look natural. We
88 | assume that they still contain most of the indigo-purple color
89 | corresponding to the short wavelengths, in the correct location. So
90 | instead of trying to locate a purple fringe in the hope of removing
91 | it, we create a purple fringe from the image which already has a
92 | purple fringe.
93 | This involves selecting the blue-purple component of the image, which
94 | is a combination of blue and red within some acceptable ratio and
95 | blurring it. The result is a mask that if added to the original image
96 | would produce a purple fringe roughly like the one we want to remove.
97 | What remains to do is somehow subtract this approximate artificial
98 | purple fringe mask from the original. The first problem is that our
99 | artificial fringe is most likely wider and more intense that the
100 | actual fringe we want to remove, and we don't want to remove too much
101 | so as to not introduce new colors. The second problem is that we don't
102 | want to remove the blue-purple component from the bright areas of the
103 | image. These problems are solved by:
104 |
105 | 1. Subtracting the purple mask only where it's brighter than the
106 | original purple component.
107 | 2. Subtracting purple only from regions that are somewhat purple, and
108 | at most until they look grey. For example, from
109 | a dark purplish pixel like (red = 0.3, green = 0.1, blue = 0.3), we
110 | may consider a purple fringe mask of (0.25, 0, 0.25). If we
111 | subtracted this mask directly, we would get (0.05, 0.1, 0.05) and
112 | now the pixel would be greenish! We avoid this by ensuring that at
113 | worst we turn a pixel grey. In this case, our resulting pixel would
114 | be (0.1, 0.1, 0.1), which is a dark grey rather than an undesirable
115 | dark green.
116 |
117 | This is what the current transformations try to do, and it works
118 | often well, which is sometimes surprising. Some areas where a purple
119 | fringe was removed look greyer than they probably should, but the grey
120 | color has the advantage of being discreet.
121 |
122 | Some people have asked about removing green fringing. The current
123 | algorithm won't remove it. However, it's possible that a similar
124 | algorithm would work. I estimate it would take at least a couple of
125 | days to obtain a proof of concept, if it is feasible. I hope the
126 | explanations above may help whoever is interested in adding support
127 | for green-fringe removal.
128 |
--------------------------------------------------------------------------------
/src/unpurple.ml:
--------------------------------------------------------------------------------
1 | open Printf
2 | open Color
3 |
4 | type mode = Normal | Diff | Blur
5 |
6 | type param = {
7 | radius : float; (* pixels *)
8 | intensity : float; (* scalar more or less around 1.0 *)
9 | min_brightness : float; (* positive scalar smaller 1.0 *)
10 | min_red_to_blue_ratio : float;
11 | max_red_to_blue_ratio : float;
12 | mode : mode;
13 | }
14 |
15 | let default_radius = 5.
16 | let default_intensity = 1.
17 | let default_min_brightness = 0.
18 | let default_min_red_to_blue_ratio = 0.
19 | let default_max_red_to_blue_ratio = 0.33
20 |
21 | let dims m =
22 | let n1 = Array.length m in
23 | if n1 = 0 then 0, 0
24 | else n1, Array.length m.(0)
25 |
26 | let init_acc radius a =
27 | let n = Array.length a in
28 | if n = 0 then 0.
29 | else
30 | let acc = ref 0. in
31 | acc := float (radius+2) *. a.(0);
32 | for i = 1 to radius - 1 do
33 | acc := !acc +. a.(min (n-1) i)
34 | done;
35 | !acc
36 |
37 | let init_acc_dim2 radius m j =
38 | let n = Array.length m in
39 | if n = 0 then 0.
40 | else
41 | let acc = ref 0. in
42 | acc := float (radius+2) *. m.(0).(j);
43 | for i = 1 to radius - 1 do
44 | acc := !acc +. m.(min (n-1) i).(j)
45 | done;
46 | !acc
47 |
48 | let motion_blur_dim1 radius m =
49 | let n1, n2 = dims m in
50 | let w = float (2 * radius + 1) in
51 | for i = 0 to n1 - 1 do
52 | let a = m.(i) in
53 | let b = Array.make n2 0. in
54 | let acc = ref (init_acc radius a) in
55 | for j = 0 to n2 - 1 do
56 | acc := !acc
57 | -. a.(max 0 (j-1 - radius))
58 | +. a.(min (n2-1) (j + radius));
59 | b.(j) <- !acc /. w;
60 | done;
61 | m.(i) <- b
62 | done
63 |
64 | let motion_blur_dim2 radius m =
65 | let n1, n2 = dims m in
66 | let w = float (2 * radius + 1) in
67 | for j = 0 to n2 - 1 do
68 | let b = Array.make n1 0. in
69 | let acc = ref (init_acc_dim2 radius m j) in
70 | for i = 0 to n1 - 1 do
71 | acc := !acc
72 | -. m.(max 0 (i-1 - radius)).(j)
73 | +. m.(min (n1-1) (i + radius)).(j);
74 | b.(i) <- !acc /. w;
75 | done;
76 | for i = 0 to n1 - 1 do
77 | m.(i).(j) <- b.(i)
78 | done
79 | done
80 |
81 | let box_blur radius m =
82 | motion_blur_dim1 radius m;
83 | motion_blur_dim2 radius m
84 |
85 | (*
86 | 0/2 -> 0
87 | 1/2 -> 1
88 | 2/2 -> 1
89 | 3/2 -> 2
90 | ...
91 | *)
92 | let div_up a b =
93 | let r = a / b in
94 | if a mod b = 0 then
95 | r
96 | else
97 | r + 1
98 |
99 | let tent_blur radius m =
100 | box_blur (div_up radius 2) m;
101 | box_blur (div_up radius 2) m
102 |
103 | let make_purple_blur param w h m =
104 | (* Use the blue component to define the intensity of the light
105 | subject to unfocusing *)
106 | let blur = Array.make_matrix w h 0. in
107 | for i = 0 to w - 1 do
108 | for j = 0 to h - 1 do
109 | let b = float (Rgb24.get m i j).b /. 255. in
110 | let p =
111 | let thresh = param.min_brightness in
112 | let grey_level = (max 0. (b -. thresh)) *. 1. /. (1.-.thresh) in
113 | param.intensity *. grey_level
114 | in
115 | blur.(i).(j) <- p
116 | done
117 | done;
118 | (* "Unfocus" using a blur algorithm. It doesn't have to be a perfect circular
119 | blur. The radius specified is usually larger than needed anyway. *)
120 | let radius = truncate (ceil param.radius) in
121 | tent_blur radius blur;
122 | blur
123 |
124 | let remove_purple_blur param w h m purple_blur =
125 | let m2 = Rgb24.copy m in
126 | for i = 0 to w - 1 do
127 | for j = 0 to h - 1 do
128 | let { r; g; b } = Rgb24.get m i j in
129 | let bl = min 255. (255. *. purple_blur.(i).(j)) in
130 |
131 | (* amount of blue and red that would produce a grey if removed *)
132 | let db = max (float b -. float g) 0. in
133 | let dr = max (float r -. float g) 0. in
134 |
135 | (* maximum amount of blue that we accept to remove, ignoring red level *)
136 | let mb = min bl db in
137 |
138 | (* amount of red that we will remove, honoring max red:blue *)
139 | let r_diff = min dr (mb *. param.max_red_to_blue_ratio) in
140 |
141 | (* amount of blue that we will remove, honoring min red:blue *)
142 | let b_diff =
143 | if param.min_red_to_blue_ratio > 0. then
144 | min mb (r_diff /. param.min_red_to_blue_ratio)
145 | else
146 | mb
147 | in
148 |
149 | let bl = truncate bl in
150 | let r_diff = truncate r_diff in
151 | let b_diff = truncate b_diff in
152 |
153 | let pixel =
154 | match param.mode with
155 | Normal ->
156 | {
157 | r = r - r_diff;
158 | g = g;
159 | b = b - b_diff
160 | }
161 | | Diff ->
162 | {
163 | r = r_diff;
164 | g = 0;
165 | b = b_diff
166 | }
167 | | Blur ->
168 | {
169 | r = bl;
170 | g = bl;
171 | b = bl;
172 | }
173 | in
174 | Rgb24.set m2 i j pixel
175 | done
176 | done;
177 | m2
178 |
179 | let remove_purple_fringe param img =
180 | let m =
181 | match img with
182 | Images.Rgb24 x -> x
183 | | _ -> failwith "Not an RGB image"
184 | in
185 | let w, h = Images.size img in
186 | let mask = make_purple_blur param w h m in
187 | let m2 = remove_purple_blur param w h m mask in
188 | Images.Rgb24 m2
189 |
190 | let run param infile outfile =
191 | let img = Images.load infile [] in
192 | let img2 = remove_purple_fringe param img in
193 | Images.save outfile None [] img2
194 |
195 | let main () =
196 | let intensity = ref default_intensity in
197 | let radius = ref default_radius in
198 | let min_brightness = ref default_min_brightness in
199 | let min_red_to_blue_ratio = ref default_min_red_to_blue_ratio in
200 | let max_red_to_blue_ratio = ref default_max_red_to_blue_ratio in
201 | let mode = ref Normal in
202 | let files = ref [] in
203 | let options = [
204 | "-i", Arg.Set_float intensity,
205 | sprintf "