Skip to content

Commit

Permalink
chore: Prepare odiff for the image io redistribution
Browse files Browse the repository at this point in the history
Also did good old profiling and fixed some of the inefficienceis in the
main loop
  • Loading branch information
dmtrKovalenko committed Sep 14, 2024
1 parent 5808f32 commit a5c400b
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 89 deletions.
Binary file removed .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.merlin
.Ds_Store
node_modules/
_build
_esy
Expand Down
6 changes: 4 additions & 2 deletions bin/Main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ let main img1Path img2Path diffPath threshold outputDiffMask failOnLayoutChange
Gc.set
{
(Gc.get ()) with
(* 128 MB *)
major_heap_increment = 128 * 1024 * 1024;
(* 16MB is a reasonable value for minor heap size *)
minor_heap_size = 2 * 1024 * 1024;
(* Double the minor heap *)
major_heap_increment = 2 * 1024 * 1024;
(* Reasonable high value to reduce major GC frequency *)
space_overhead = 500;
(* Disable compaction *)
Expand Down
Binary file modified images/out.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion io/bmp/Bmp.ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ module IO : Odiff.ImageIO.ImageIO = struct
let width, height, data = ReadBmp.load filename in
{ width; height; image = data }

let readDirectPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) =
let readRawPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) =
let image : data = img.image in
Array1.unsafe_get image ((y * img.width) + x)
[@@inline]

let readRawPixelAtOffset offset (img : t Odiff.ImageIO.img) =
Array1.unsafe_get img.image offset
[@@inline]

let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) =
let image : data = img.image in
Array1.unsafe_set image ((y * img.width) + x) color
Expand Down
9 changes: 7 additions & 2 deletions io/jpg/Jpg.ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ module IO = struct
let width, height, data = ReadJpg.read_jpeg_image filename in
{ width; height; image = { data } }

let readDirectPixel ~x ~y (img : t Odiff.ImageIO.img) =
Array1.unsafe_get img.image.data ((y * img.width) + x)
let readRawPixel ~x ~y (img : t Odiff.ImageIO.img) =
(Array1.unsafe_get img.image.data ((y * img.width) + x) [@inline.always])
[@@inline]

let readRawPixelAtOffset offset (img : t Odiff.ImageIO.img) =
Array1.unsafe_get img.image.data offset
[@@inline]

let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) =
Array1.unsafe_set img.image.data ((y * img.width) + x) color
Expand Down
7 changes: 6 additions & 1 deletion io/png/Png.ml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ type data = (int32, int32_elt, c_layout) Array1.t
module IO : Odiff.ImageIO.ImageIO = struct
type t = data

let readDirectPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) =
let readRawPixelAtOffset offset (img : t Odiff.ImageIO.img) =
Array1.unsafe_get img.image offset
[@@inline always]

let readRawPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) =
let image : data = img.image in
Array1.unsafe_get image ((y * img.width) + x)
[@@inline always]

let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) =
let image : data = img.image in
Expand Down
10 changes: 7 additions & 3 deletions io/tiff/Tiff.ml
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
open Bigarray
open Odiff.ImageIO

type data = (int32, int32_elt, c_layout) Array1.t

module IO : Odiff.ImageIO.ImageIO = struct
module IO : ImageIO = struct
type buffer
type t = { data : data }

let loadImage filename : t Odiff.ImageIO.img =
let width, height, data = ReadTiff.load filename in
{ width; height; image = { data } }

let readDirectPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) =
Array1.unsafe_get img.image.data ((y * img.width) + x)
let readRawPixel ~x ~y img =
(Array1.unsafe_get img.image.data ((y * img.width) + x) [@inline.always])

let readRawPixelAtOffset offset img = Array1.unsafe_get img.image.data offset
[@@inline.always]

let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) =
Array1.unsafe_set img.image.data ((y * img.width) + x) color
Expand Down
18 changes: 6 additions & 12 deletions src/Antialiasing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,11 @@ module MakeAntialiasing (IO1 : ImageIO.ImageIO) (IO2 : ImageIO.ImageIO) = struct
| false -> 0)
in

let baseColor = baseImg |> IO1.readDirectPixel ~x ~y in
let baseColor = baseImg |> IO1.readRawPixel ~x ~y in
for adj_y = y0 to y1 do
for adj_x = x0 to x1 do
if !zeroes < 3 && (x <> adj_x || y <> adj_y) then
let adjacentColor =
baseImg |> IO1.readDirectPixel ~x:adj_x ~y:adj_y
in
let adjacentColor = baseImg |> IO1.readRawPixel ~x:adj_x ~y:adj_y in
if baseColor = adjacentColor then incr zeroes
else
let delta =
Expand Down Expand Up @@ -75,15 +73,11 @@ module MakeAntialiasing (IO1 : ImageIO.ImageIO) (IO2 : ImageIO.ImageIO) = struct
let minX, minY = !minSiblingDeltaCoord in
let maxX, maxY = !maxSiblingDeltaCoord in
(hasManySiblingsWithSameColor ~x:minX ~y:minY ~width:baseImg.width
~height:baseImg.height
~readColor:(IO1.readDirectPixel baseImg)
~height:baseImg.height ~readColor:(IO1.readRawPixel baseImg)
|| hasManySiblingsWithSameColor ~x:maxX ~y:maxY ~width:baseImg.width
~height:baseImg.height
~readColor:(IO1.readDirectPixel baseImg))
~height:baseImg.height ~readColor:(IO1.readRawPixel baseImg))
&& (hasManySiblingsWithSameColor ~x:minX ~y:minY ~width:compImg.width
~height:compImg.height
~readColor:(IO2.readDirectPixel compImg)
~height:compImg.height ~readColor:(IO2.readRawPixel compImg)
|| hasManySiblingsWithSameColor ~x:maxX ~y:maxY ~width:compImg.width
~height:compImg.height
~readColor:(IO2.readDirectPixel compImg))
~height:compImg.height ~readColor:(IO2.readRawPixel compImg))
end
58 changes: 34 additions & 24 deletions src/ColorDelta.ml
Original file line number Diff line number Diff line change
@@ -1,43 +1,53 @@
open Int32

type pixel = { r : float; g : float; b : float; a : float }

let white_pixel : pixel = { r = 255.; g = 255.; b = 255.; a = 0. }
let blend_channel_white color alpha = 255. +. ((color -. 255.) *. alpha)
let white_pixel = (255., 255., 255., 0.)

let blendSemiTransparentColor = function
| r, g, b, 0. -> white_pixel
| r, g, b, 255. -> (r, g, b, 1.)
| r, g, b, alpha when alpha < 255. ->
let normalizedAlpha = alpha /. 255. in
let blendSemiTransparentPixel = function
| { r; g; b; a } when a = 0. -> white_pixel
| { r; g; b; a } when a = 255. -> { r; g; b; a = 1. }
| { r; g; b; a } when a < 255. ->
let normalizedAlpha = a /. 255. in
let r, g, b, a =
( blend_channel_white r normalizedAlpha,
blend_channel_white g normalizedAlpha,
blend_channel_white b normalizedAlpha,
normalizedAlpha )
in
(r, g, b, a)

{ r; g; b; a }
| _ ->
failwith
"Found pixel with alpha value greater than uint8 max value. Aborting."

let convertPixelToFloat pixel =
let pixel = pixel |> Int32.to_int in
let a = (pixel lsr 24) land 255 in
let b = (pixel lsr 16) land 255 in
let g = (pixel lsr 8) land 255 in
let r = pixel land 255 in

(Float.of_int r, Float.of_int g, Float.of_int b, Float.of_int a)

let rgb2y (r, g, b, a) =
let decodeRawPixel pixel =
let a = logand (shift_right_logical pixel 24) 255l in
let b = logand (shift_right_logical pixel 16) 255l in
let g = logand (shift_right_logical pixel 8) 255l in
let r = logand pixel 255l in

{
r = Int32.to_float r;
g = Int32.to_float g;
b = Int32.to_float b;
a = Int32.to_float a;
}
[@@inline]

let rgb2y { r; g; b; a } =
(r *. 0.29889531) +. (g *. 0.58662247) +. (b *. 0.11448223)

let rgb2i (r, g, b, a) =
let rgb2i { r; g; b; a } =
(r *. 0.59597799) -. (g *. 0.27417610) -. (b *. 0.32180189)

let rgb2q (r, g, b, a) =
let rgb2q { r; g; b; a } =
(r *. 0.21147017) -. (g *. 0.52261711) +. (b *. 0.31114694)

let calculatePixelColorDelta _pixelA _pixelB =
let pixelA = _pixelA |> convertPixelToFloat |> blendSemiTransparentColor in
let pixelB = _pixelB |> convertPixelToFloat |> blendSemiTransparentColor in
let calculatePixelColorDelta pixelA pixelB =
let pixelA = pixelA |> decodeRawPixel |> blendSemiTransparentPixel in
let pixelB = pixelB |> decodeRawPixel |> blendSemiTransparentPixel in

let y = rgb2y pixelA -. rgb2y pixelB in
let i = rgb2i pixelA -. rgb2i pixelB in
Expand All @@ -47,6 +57,6 @@ let calculatePixelColorDelta _pixelA _pixelB =
delta

let calculatePixelBrightnessDelta pixelA pixelB =
let pixelA = pixelA |> convertPixelToFloat |> blendSemiTransparentColor in
let pixelB = pixelB |> convertPixelToFloat |> blendSemiTransparentColor in
let pixelA = pixelA |> decodeRawPixel |> blendSemiTransparentPixel in
let pixelB = pixelB |> decodeRawPixel |> blendSemiTransparentPixel in
rgb2y pixelA -. rgb2y pixelB
69 changes: 45 additions & 24 deletions src/Diff.ml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
open Int32

(* Decimal representation of the RGBA in32 pixel red pixel *)
let redPixel = Int32.of_int 4278190335

Expand All @@ -6,22 +8,28 @@ let maxYIQPossibleDelta = 35215.

type 'a diffVariant = Layout | Pixel of ('a * int * float * int Stack.t)

let computeIgnoreRegionOffsets width =
List.map (fun ((x1, y1), (x2, y2)) ->
let p1 = (y1 * width) + x1 in
let p2 = (y2 * width) + x2 in
(p1, p2))
let unrollIgnoreRegions width list =
list
|> Option.map
(List.map (fun ((x1, y1), (x2, y2)) ->
let p1 = (y1 * width) + x1 in
let p2 = (y2 * width) + x2 in
(p1, p2)))

let isInIgnoreRegion offset =
List.exists (fun ((p1 : int), (p2 : int)) -> offset >= p1 && offset <= p2)
let isInIgnoreRegion offset list =
list
|> Option.map
(List.exists (fun ((p1 : int), (p2 : int)) ->
offset >= p1 && offset <= p2))
|> Option.value ~default:false

module MakeDiff (IO1 : ImageIO.ImageIO) (IO2 : ImageIO.ImageIO) = struct
module BaseAA = Antialiasing.MakeAntialiasing (IO1) (IO2)
module CompAA = Antialiasing.MakeAntialiasing (IO2) (IO1)

let compare (base : IO1.t ImageIO.img) (comp : IO2.t ImageIO.img)
?(antialiasing = false) ?(outputDiffMask = false) ?(diffLines = false)
?diffPixel ?(threshold = 0.1) ?(ignoreRegions = []) () =
?diffPixel ?(threshold = 0.1) ?ignoreRegions () =
let maxDelta = maxYIQPossibleDelta *. (threshold ** 2.) in
let diffPixel = match diffPixel with Some x -> x | None -> redPixel in
let diffOutput =
Expand All @@ -42,30 +50,43 @@ module MakeDiff (IO1 : ImageIO.ImageIO) (IO2 : ImageIO.ImageIO) = struct
then diffLinesStack |> Stack.push y
in

let ignoreRegions =
ignoreRegions |> computeIgnoreRegionOffsets base.width
in
let ignoreRegions = unrollIgnoreRegions base.width ignoreRegions in
let hasIgnoreRegions = ignoreRegions |> Option.is_some in

let size = (base.height * base.width) - 1 in
let x = ref 0 in
let y = ref 0 in

let layoutDifference =
base.width <> comp.width || base.height <> comp.height
in

for offset = 0 to size do
(* if images are different we can't use offset *)
let baseColor =
if layoutDifference then IO1.readRawPixel ~x:!x ~y:!y base
else IO1.readRawPixelAtOffset offset base
in

(if !x >= comp.width || !y >= comp.height then (
let alpha =
(Int32.to_int (IO1.readDirectPixel ~x:!x ~y:!y base) lsr 24) land 255
in
if alpha <> 0 then countDifference !x !y)
let alpha = logand (shift_right_logical baseColor 24) 255l in
if alpha <> Int32.zero then countDifference !x !y)
else
let baseColor = IO1.readDirectPixel ~x:!x ~y:!y base in
let compColor = IO2.readDirectPixel ~x:!x ~y:!y comp in
let compColor =
if layoutDifference then IO1.readRawPixel ~x:!x ~y:!y base
else IO2.readRawPixelAtOffset offset comp
in

if baseColor <> compColor then
let delta =
ColorDelta.calculatePixelColorDelta baseColor compColor
let isIgnored =
hasIgnoreRegions && isInIgnoreRegion offset ignoreRegions
in
if delta > maxDelta then
let isIgnored = isInIgnoreRegion offset ignoreRegions in
if not isIgnored then

if not isIgnored then
let delta =
ColorDelta.calculatePixelColorDelta baseColor compColor
in
if delta > maxDelta then
let isAntialiased =
if not antialiasing then false
else
Expand All @@ -88,15 +109,15 @@ module MakeDiff (IO1 : ImageIO.ImageIO) (IO2 : ImageIO.ImageIO) = struct

let diff (base : IO1.t ImageIO.img) (comp : IO2.t ImageIO.img) ~outputDiffMask
?(threshold = 0.1) ~diffPixel ?(failOnLayoutChange = true)
?(antialiasing = false) ?(diffLines = false) ?(ignoreRegions = []) () =
?(antialiasing = false) ?(diffLines = false) ?ignoreRegions () =
if
failOnLayoutChange = true
&& (base.width <> comp.width || base.height <> comp.height)
then Layout
else
let diffResult =
compare base comp ~threshold ~diffPixel ~outputDiffMask ~antialiasing
~diffLines ~ignoreRegions ()
~diffLines ?ignoreRegions ()
in

Pixel diffResult
Expand Down
3 changes: 2 additions & 1 deletion src/ImageIO.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ module type ImageIO = sig

val loadImage : string -> t img
val makeSameAsLayout : t img -> t img
val readDirectPixel : x:int -> y:int -> t img -> Int32.t
val readRawPixelAtOffset : int -> t img -> Int32.t [@@inline.always]
val readRawPixel : x:int -> y:int -> t img -> Int32.t [@@inline.always]
val setImgColor : x:int -> y:int -> Int32.t -> t img -> unit
val saveImage : t img -> string -> unit
val freeImage : t img -> unit
Expand Down
4 changes: 2 additions & 2 deletions src/dune
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
(env
(dev
(flags (:standard -w +42))
(ocamlopt_flags (:standard -S)))
(ocamlopt_flags (:standard -unsafe)))
(release
(ocamlopt_flags (:standard -O3 -rounds 5 -unbox-closures -inline 200 -inline-max-depth 7 -unbox-closures-factor 50))))
(ocamlopt_flags (:standard -unsafe -O3 -rounds 5 -unboxed-types -unbox-closures -inline 200 -inline-max-depth 7 -unbox-closures-factor 50))))


Loading

0 comments on commit a5c400b

Please sign in to comment.