├── 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="Options: $@" 12 | else 13 | options="" 14 | fi 15 | 16 | echo "\ 17 | 18 | 21 | 23 | $options 24 | 25 | 28 | 29 |
19 | \"input\"\"output\"
\"difference\" 27 |
" 30 | } 31 | 32 | print () { 33 | echo "\ 34 | 36 | 37 | 38 | 39 | Purple fringe removal examples 40 | 63 | 64 | 65 |

Purple fringe removal examples

66 |

These images were processed with 67 | unpurple, 68 | a command-line program that tries to remove 69 | purple fringing 70 | from digital photos. 71 |

72 |

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 | [Before]() 25 | 26 | After: 27 | 28 | [After]() 29 | 30 | Difference: 31 | 32 | [Difference]() 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 " Intensity of purple fringe (default: %g)" !intensity; 206 | 207 | "-m", Arg.Set_float min_brightness, 208 | sprintf " Minimum brightness (default: %g)" !min_brightness; 209 | 210 | "-r", Arg.Set_float radius, 211 | sprintf " Blur radius (default: %g pixels)" !radius; 212 | 213 | "-minred", Arg.Set_float min_red_to_blue_ratio, 214 | sprintf " Minimum red:blue ratio in the fringe (default: %g)" 215 | !min_red_to_blue_ratio; 216 | 217 | "-maxred", Arg.Set_float max_red_to_blue_ratio, 218 | sprintf " Maximum red:blue ratio in the fringe (default: %g)" 219 | !max_red_to_blue_ratio; 220 | 221 | "-gentle", Arg.Unit (fun () -> 222 | min_brightness := 0.8; 223 | min_red_to_blue_ratio := 0.15), 224 | " Same as -m 0.8 -minred 0.15"; 225 | 226 | "-diff", Arg.Unit (fun () -> mode := Diff), 227 | "Output purple mask that would be substracted to the original image"; 228 | 229 | "-blur", Arg.Unit (fun () -> mode := Blur), 230 | "Output blur used to simulate lack of focus of the purple light"; 231 | ] 232 | in 233 | let anon_fun s = 234 | files := s :: !files 235 | in 236 | let usage_msg = 237 | sprintf "\ 238 | Usage: %s [options] 239 | This program attempts to remove purple fringing from photos (JPEG format). 240 | " Sys.argv.(0) 241 | in 242 | Arg.parse options anon_fun usage_msg; 243 | let infile, outfile = 244 | match List.rev !files with 245 | [ infile; outfile ] -> infile, outfile 246 | | _ -> failwith "needs one input file and one output file; try -help" 247 | in 248 | let param = { 249 | radius = !radius; 250 | intensity = !intensity; 251 | min_brightness = !min_brightness; 252 | min_red_to_blue_ratio = !min_red_to_blue_ratio; 253 | max_red_to_blue_ratio = !max_red_to_blue_ratio; 254 | mode = !mode; 255 | } 256 | in 257 | run param infile outfile 258 | 259 | let () = main () 260 | --------------------------------------------------------------------------------